modernized the "Custom Shape Menu"

I updated src/css/custom_shape_menu.css with new styles for a modern look and feel, and I refactored src/CustomShapeMenu.ts to use a new HTML structure for the checkboxes that works with the updated CSS. The menu should now be fully functional with the new design.
This commit is contained in:
Dariusz L
2025-07-28 17:05:57 +02:00
parent d2b7b396aa
commit 7f39cfc8ed
4 changed files with 323 additions and 209 deletions

View File

@@ -61,10 +61,8 @@ export class CustomShapeMenu {
featureContainer.id = 'shape-mask-feature-container'; featureContainer.id = 'shape-mask-feature-container';
featureContainer.className = 'feature-container'; featureContainer.className = 'feature-container';
// Add main auto-apply checkbox to the new container // Add main auto-apply checkbox to the new container
const checkboxContainer = this._createCheckbox(() => `${this.canvas.autoApplyShapeMask ? "☑" : "☐"} Auto-apply shape mask`, () => { const checkboxContainer = this._createCheckbox('auto-apply-checkbox', () => this.canvas.autoApplyShapeMask, 'Auto-apply shape mask', (e) => {
// Always hide any active shape preview lines first to prevent them from getting stuck this.canvas.autoApplyShapeMask = e.target.checked;
this.canvas.maskTool.hideShapePreview();
this.canvas.autoApplyShapeMask = !this.canvas.autoApplyShapeMask;
if (this.canvas.autoApplyShapeMask) { if (this.canvas.autoApplyShapeMask) {
this.canvas.maskTool.applyShapeMask(); this.canvas.maskTool.applyShapeMask();
log.info("Auto-apply shape mask enabled - mask applied automatically"); log.info("Auto-apply shape mask enabled - mask applied automatically");
@@ -80,8 +78,8 @@ export class CustomShapeMenu {
}, "Automatically applies a mask based on the custom output area shape. When enabled, the mask will be applied to all layers within the shape boundary."); }, "Automatically applies a mask based on the custom output area shape. When enabled, the mask will be applied to all layers within the shape boundary.");
featureContainer.appendChild(checkboxContainer); featureContainer.appendChild(checkboxContainer);
// Add expansion checkbox // Add expansion checkbox
const expansionContainer = this._createCheckbox(() => `${this.canvas.shapeMaskExpansion ? "☑" : "☐"} Expand/Contract mask`, () => { const expansionContainer = this._createCheckbox('expansion-checkbox', () => this.canvas.shapeMaskExpansion, 'Expand/Contract mask', (e) => {
this.canvas.shapeMaskExpansion = !this.canvas.shapeMaskExpansion; this.canvas.shapeMaskExpansion = e.target.checked;
this._updateUI(); this._updateUI();
if (this.canvas.autoApplyShapeMask) { if (this.canvas.autoApplyShapeMask) {
this.canvas.maskTool.hideShapePreview(); this.canvas.maskTool.hideShapePreview();
@@ -89,7 +87,6 @@ export class CustomShapeMenu {
this.canvas.render(); this.canvas.render();
} }
}, "Dilate (expand) or erode (contract) the shape mask. Positive values expand the mask outward, negative values shrink it inward."); }, "Dilate (expand) or erode (contract) the shape mask. Positive values expand the mask outward, negative values shrink it inward.");
expansionContainer.id = 'expansion-checkbox';
featureContainer.appendChild(expansionContainer); featureContainer.appendChild(expansionContainer);
// Add expansion slider container // Add expansion slider container
const expansionSliderContainer = document.createElement('div'); const expansionSliderContainer = document.createElement('div');
@@ -154,8 +151,8 @@ export class CustomShapeMenu {
expansionSliderContainer.appendChild(expansionValueDisplay); expansionSliderContainer.appendChild(expansionValueDisplay);
featureContainer.appendChild(expansionSliderContainer); featureContainer.appendChild(expansionSliderContainer);
// Add feather checkbox // Add feather checkbox
const featherContainer = this._createCheckbox(() => `${this.canvas.shapeMaskFeather ? "☑" : "☐"} Feather edges`, () => { const featherContainer = this._createCheckbox('feather-checkbox', () => this.canvas.shapeMaskFeather, 'Feather edges', (e) => {
this.canvas.shapeMaskFeather = !this.canvas.shapeMaskFeather; this.canvas.shapeMaskFeather = e.target.checked;
this._updateUI(); this._updateUI();
if (this.canvas.autoApplyShapeMask) { if (this.canvas.autoApplyShapeMask) {
this.canvas.maskTool.hideShapePreview(); this.canvas.maskTool.hideShapePreview();
@@ -163,7 +160,6 @@ export class CustomShapeMenu {
this.canvas.render(); this.canvas.render();
} }
}, "Softens the edges of the shape mask by creating a gradual transition from opaque to transparent."); }, "Softens the edges of the shape mask by creating a gradual transition from opaque to transparent.");
featherContainer.id = 'feather-checkbox';
featureContainer.appendChild(featherContainer); featureContainer.appendChild(featherContainer);
// Add feather slider container // Add feather slider container
const featherSliderContainer = document.createElement('div'); const featherSliderContainer = document.createElement('div');
@@ -219,30 +215,19 @@ export class CustomShapeMenu {
extensionContainer.id = 'output-area-extension-container'; extensionContainer.id = 'output-area-extension-container';
extensionContainer.className = 'feature-container'; extensionContainer.className = 'feature-container';
// Add main extension checkbox // Add main extension checkbox
const extensionCheckboxContainer = this._createCheckbox(() => `${this.canvas.outputAreaExtensionEnabled ? "☑" : "☐"} Extend output area`, () => { const extensionCheckboxContainer = this._createCheckbox('extension-checkbox', () => this.canvas.outputAreaExtensionEnabled, 'Extend output area', (e) => {
this.canvas.outputAreaExtensionEnabled = !this.canvas.outputAreaExtensionEnabled; this.canvas.outputAreaExtensionEnabled = e.target.checked;
if (this.canvas.outputAreaExtensionEnabled) { if (this.canvas.outputAreaExtensionEnabled) {
// When enabling, capture current canvas size as the baseline this.canvas.originalCanvasSize = { width: this.canvas.width, height: this.canvas.height };
this.canvas.originalCanvasSize = {
width: this.canvas.width,
height: this.canvas.height
};
// Restore last saved extensions instead of starting from zero
this.canvas.outputAreaExtensions = { ...this.canvas.lastOutputAreaExtensions }; this.canvas.outputAreaExtensions = { ...this.canvas.lastOutputAreaExtensions };
log.info(`Captured current canvas size as baseline: ${this.canvas.width}x${this.canvas.height}`);
log.info(`Restored last extensions:`, this.canvas.outputAreaExtensions);
} }
else { else {
// Save current extensions before disabling
this.canvas.lastOutputAreaExtensions = { ...this.canvas.outputAreaExtensions }; this.canvas.lastOutputAreaExtensions = { ...this.canvas.outputAreaExtensions };
// Reset current extensions when disabled (but keep the saved ones)
this.canvas.outputAreaExtensions = { top: 0, bottom: 0, left: 0, right: 0 }; this.canvas.outputAreaExtensions = { top: 0, bottom: 0, left: 0, right: 0 };
log.info(`Saved extensions for later:`, this.canvas.lastOutputAreaExtensions);
} }
this._updateExtensionUI(); this._updateExtensionUI();
this._updateCanvasSize(); // Update canvas size when toggling this._updateCanvasSize();
this.canvas.render(); this.canvas.render();
log.info(`Output area extension ${this.canvas.outputAreaExtensionEnabled ? 'enabled' : 'disabled'}`);
}, "Allows extending the output area boundaries in all directions without changing the custom shape."); }, "Allows extending the output area boundaries in all directions without changing the custom shape.");
extensionContainer.appendChild(extensionCheckboxContainer); extensionContainer.appendChild(extensionCheckboxContainer);
// Create sliders container // Create sliders container
@@ -333,26 +318,28 @@ export class CustomShapeMenu {
// Add viewport change listener to update shape preview when zooming/panning // Add viewport change listener to update shape preview when zooming/panning
this._addViewportChangeListener(); this._addViewportChangeListener();
} }
_createCheckbox(textFn, clickHandler, tooltipText) { _createCheckbox(id, getChecked, text, clickHandler, tooltipText) {
const container = document.createElement('div'); const container = document.createElement('label');
container.className = 'checkbox-container'; container.className = 'checkbox-container';
container.onmouseover = () => { container.htmlFor = id;
container.style.backgroundColor = '#555'; const input = document.createElement('input');
}; input.type = 'checkbox';
container.onmouseout = () => { input.id = id;
container.style.backgroundColor = 'transparent'; input.checked = getChecked();
}; const customCheckbox = document.createElement('div');
const updateText = () => { customCheckbox.className = 'custom-checkbox';
container.textContent = textFn(); const labelText = document.createElement('span');
}; labelText.textContent = text;
updateText(); container.appendChild(input);
container.appendChild(customCheckbox);
container.appendChild(labelText);
// Stop propagation to prevent menu from closing, but allow default checkbox behavior
container.onclick = (e) => { container.onclick = (e) => {
e.preventDefault();
e.stopPropagation(); e.stopPropagation();
clickHandler();
updateText();
}; };
// Add tooltip if provided input.onchange = (e) => {
clickHandler(e);
};
if (tooltipText) { if (tooltipText) {
this._addTooltip(container, tooltipText); this._addTooltip(container, tooltipText);
} }
@@ -361,16 +348,23 @@ export class CustomShapeMenu {
_updateUI() { _updateUI() {
if (!this.element) if (!this.element)
return; return;
// Toggle visibility of sub-options based on the main checkbox state const setChecked = (id, checked) => {
const expansionCheckbox = this.element.querySelector('#expansion-checkbox'); const input = this.element.querySelector(`#${id}`);
if (input)
input.checked = checked;
};
setChecked('auto-apply-checkbox', this.canvas.autoApplyShapeMask);
setChecked('expansion-checkbox', this.canvas.shapeMaskExpansion);
setChecked('feather-checkbox', this.canvas.shapeMaskFeather);
setChecked('extension-checkbox', this.canvas.outputAreaExtensionEnabled);
const expansionCheckbox = this.element.querySelector('#expansion-checkbox')?.parentElement;
if (expansionCheckbox) { if (expansionCheckbox) {
expansionCheckbox.style.display = this.canvas.autoApplyShapeMask ? 'block' : 'none'; expansionCheckbox.style.display = this.canvas.autoApplyShapeMask ? 'flex' : 'none';
} }
const featherCheckbox = this.element.querySelector('#feather-checkbox'); const featherCheckbox = this.element.querySelector('#feather-checkbox')?.parentElement;
if (featherCheckbox) { if (featherCheckbox) {
featherCheckbox.style.display = this.canvas.autoApplyShapeMask ? 'block' : 'none'; featherCheckbox.style.display = this.canvas.autoApplyShapeMask ? 'flex' : 'none';
} }
// Update sliders visibility based on their respective checkboxes
const expansionSliderContainer = this.element.querySelector('#expansion-slider-container'); const expansionSliderContainer = this.element.querySelector('#expansion-slider-container');
if (expansionSliderContainer) { if (expansionSliderContainer) {
expansionSliderContainer.style.display = (this.canvas.autoApplyShapeMask && this.canvas.shapeMaskExpansion) ? 'block' : 'none'; expansionSliderContainer.style.display = (this.canvas.autoApplyShapeMask && this.canvas.shapeMaskExpansion) ? 'block' : 'none';
@@ -379,22 +373,6 @@ export class CustomShapeMenu {
if (featherSliderContainer) { if (featherSliderContainer) {
featherSliderContainer.style.display = (this.canvas.autoApplyShapeMask && this.canvas.shapeMaskFeather) ? 'block' : 'none'; featherSliderContainer.style.display = (this.canvas.autoApplyShapeMask && this.canvas.shapeMaskFeather) ? 'block' : 'none';
} }
// Update checkbox texts
const checkboxes = this.element.querySelectorAll('div[style*="cursor: pointer"]');
checkboxes.forEach((checkbox, index) => {
if (index === 0) { // Main checkbox
checkbox.textContent = `${this.canvas.autoApplyShapeMask ? "☑" : "☐"} Auto-apply shape mask`;
}
else if (index === 1) { // Expansion checkbox
checkbox.textContent = `${this.canvas.shapeMaskExpansion ? "☑" : "☐"} Dilate/Erode mask`;
}
else if (index === 2) { // Feather checkbox
checkbox.textContent = `${this.canvas.shapeMaskFeather ? "☑" : "☐"} Feather edges`;
}
else if (index === 3) { // Extension checkbox
checkbox.textContent = `${this.canvas.outputAreaExtensionEnabled ? "☑" : "☐"} Extend output area`;
}
});
} }
_updateExtensionUI() { _updateExtensionUI() {
if (!this.element) if (!this.element)

View File

@@ -2,92 +2,165 @@
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background-color: #333; background-color: #2f2f2f;
color: white; color: #e0e0e0;
padding: 8px 15px; padding: 12px;
border-radius: 10px; border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.5); box-shadow: 0 5px 15px rgba(0,0,0,0.3);
display: none; display: none;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 10px;
font-family: sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 12px; font-size: 13px;
z-index: 1001; z-index: 1001;
border: 1px solid #555; border: 1px solid #202020;
user-select: none; user-select: none;
min-width: 200px; min-width: 220px;
} }
#layerforge-custom-shape-menu .menu-line { #layerforge-custom-shape-menu .menu-line {
margin: 2px 0; font-weight: 600;
line-height: 18px; color: #4a90e2;
padding-bottom: 5px;
border-bottom: 1px solid #444;
margin-bottom: 5px;
display: flex;
align-items: center;
gap: 8px;
} }
#layerforge-custom-shape-menu .feature-container { #layerforge-custom-shape-menu .feature-container {
background-color: #282828; background-color: #3a3a3a;
border-radius: 6px; border-radius: 6px;
margin-top: 6px; padding: 10px;
padding: 4px 0; border: 1px solid #4a4a4a;
border: 1px solid #444;
} }
#layerforge-custom-shape-menu .slider-container { #layerforge-custom-shape-menu .slider-container {
margin: 0 8px 6px 8px; margin-top: 10px;
padding: 4px 8px;
display: none; display: none;
} }
#layerforge-custom-shape-menu .slider-label { #layerforge-custom-shape-menu .slider-label {
font-size: 11px; font-size: 12px;
margin-bottom: 4px; margin-bottom: 6px;
color: #ccc; color: #e0e0e0;
} }
#layerforge-custom-shape-menu input[type="range"] { #layerforge-custom-shape-menu input[type="range"] {
-webkit-appearance: none;
width: 100%; width: 100%;
height: 4px; height: 4px;
background: #555; background: #555;
outline: none;
border-radius: 2px; border-radius: 2px;
outline: none;
padding: 0;
margin: 0;
}
#layerforge-custom-shape-menu input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
background: #e0e0e0;
border-radius: 50%;
cursor: pointer;
border: 2px solid #555;
transition: background 0.2s;
}
#layerforge-custom-shape-menu input[type="range"]::-webkit-slider-thumb:hover {
background: #fff;
}
#layerforge-custom-shape-menu input[type="range"]::-moz-range-thumb {
width: 14px;
height: 14px;
background: #e0e0e0;
border-radius: 50%;
cursor: pointer;
border: 2px solid #555;
} }
#layerforge-custom-shape-menu .slider-value-display { #layerforge-custom-shape-menu .slider-value-display {
font-size: 10px; font-size: 11px;
text-align: center; text-align: center;
margin-top: 2px; margin-top: 4px;
color: #aaa; color: #bbb;
min-height: 14px;
} }
#layerforge-custom-shape-menu .extension-slider-container { #layerforge-custom-shape-menu .extension-slider-container {
margin: 6px 0; margin: 10px 0;
} }
#layerforge-custom-shape-menu .checkbox-container { #layerforge-custom-shape-menu .checkbox-container {
margin: 6px 0 2px 0; display: flex;
padding: 4px 8px; align-items: center;
gap: 10px;
padding: 6px;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; transition: background-color 0.2s;
line-height: 18px; position: relative;
} }
#layerforge-custom-shape-menu .checkbox-container:hover { #layerforge-custom-shape-menu .checkbox-container:hover {
background-color: #555; background-color: #4a4a4a;
}
#layerforge-custom-shape-menu .checkbox-container input[type="checkbox"] {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
#layerforge-custom-shape-menu .checkbox-container .custom-checkbox {
height: 16px;
width: 16px;
background-color: #2a2a2a;
border: 1px solid #666;
border-radius: 3px;
transition: all 0.2s;
position: relative;
flex-shrink: 0;
}
#layerforge-custom-shape-menu .checkbox-container input:checked ~ .custom-checkbox {
background-color: #3a76d6;
border-color: #3a76d6;
}
#layerforge-custom-shape-menu .checkbox-container .custom-checkbox::after {
content: "";
position: absolute;
display: none;
left: 5px;
top: 1px;
width: 4px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
#layerforge-custom-shape-menu .checkbox-container input:checked ~ .custom-checkbox::after {
display: block;
} }
.layerforge-tooltip { .layerforge-tooltip {
position: fixed; position: fixed;
background-color: #1a1a1a; background-color: #2f2f2f;
color: #ffffff; color: #e0e0e0;
padding: 8px 12px; padding: 8px 12px;
border-radius: 6px; border-radius: 6px;
font-size: 12px; font-size: 12px;
font-family: sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.4; line-height: 1.4;
max-width: 250px; max-width: 250px;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0 4px 12px rgba(0,0,0,0.6); box-shadow: 0 4px 12px rgba(0,0,0,0.4);
border: 1px solid #444; border: 1px solid #202020;
z-index: 10000; z-index: 10000;
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;

View File

@@ -86,13 +86,11 @@ export class CustomShapeMenu {
// Add main auto-apply checkbox to the new container // Add main auto-apply checkbox to the new container
const checkboxContainer = this._createCheckbox( const checkboxContainer = this._createCheckbox(
() => `${this.canvas.autoApplyShapeMask ? "☑" : "☐"} Auto-apply shape mask`, 'auto-apply-checkbox',
() => { () => this.canvas.autoApplyShapeMask,
// Always hide any active shape preview lines first to prevent them from getting stuck 'Auto-apply shape mask',
this.canvas.maskTool.hideShapePreview(); (e) => {
this.canvas.autoApplyShapeMask = (e.target as HTMLInputElement).checked;
this.canvas.autoApplyShapeMask = !this.canvas.autoApplyShapeMask;
if (this.canvas.autoApplyShapeMask) { if (this.canvas.autoApplyShapeMask) {
this.canvas.maskTool.applyShapeMask(); this.canvas.maskTool.applyShapeMask();
log.info("Auto-apply shape mask enabled - mask applied automatically"); log.info("Auto-apply shape mask enabled - mask applied automatically");
@@ -102,7 +100,6 @@ export class CustomShapeMenu {
this.canvas.shapeMaskFeather = false; this.canvas.shapeMaskFeather = false;
log.info("Auto-apply shape mask disabled - mask area removed and sub-options reset."); log.info("Auto-apply shape mask disabled - mask area removed and sub-options reset.");
} }
this._updateUI(); this._updateUI();
this.canvas.render(); this.canvas.render();
}, },
@@ -112,11 +109,12 @@ export class CustomShapeMenu {
// Add expansion checkbox // Add expansion checkbox
const expansionContainer = this._createCheckbox( const expansionContainer = this._createCheckbox(
() => `${this.canvas.shapeMaskExpansion ? "☑" : "☐"} Expand/Contract mask`, 'expansion-checkbox',
() => { () => this.canvas.shapeMaskExpansion,
this.canvas.shapeMaskExpansion = !this.canvas.shapeMaskExpansion; 'Expand/Contract mask',
(e) => {
this.canvas.shapeMaskExpansion = (e.target as HTMLInputElement).checked;
this._updateUI(); this._updateUI();
if (this.canvas.autoApplyShapeMask) { if (this.canvas.autoApplyShapeMask) {
this.canvas.maskTool.hideShapePreview(); this.canvas.maskTool.hideShapePreview();
this.canvas.maskTool.applyShapeMask(); this.canvas.maskTool.applyShapeMask();
@@ -125,7 +123,6 @@ export class CustomShapeMenu {
}, },
"Dilate (expand) or erode (contract) the shape mask. Positive values expand the mask outward, negative values shrink it inward." "Dilate (expand) or erode (contract) the shape mask. Positive values expand the mask outward, negative values shrink it inward."
); );
expansionContainer.id = 'expansion-checkbox';
featureContainer.appendChild(expansionContainer); featureContainer.appendChild(expansionContainer);
// Add expansion slider container // Add expansion slider container
@@ -205,11 +202,12 @@ export class CustomShapeMenu {
// Add feather checkbox // Add feather checkbox
const featherContainer = this._createCheckbox( const featherContainer = this._createCheckbox(
() => `${this.canvas.shapeMaskFeather ? "☑" : "☐"} Feather edges`, 'feather-checkbox',
() => { () => this.canvas.shapeMaskFeather,
this.canvas.shapeMaskFeather = !this.canvas.shapeMaskFeather; 'Feather edges',
(e) => {
this.canvas.shapeMaskFeather = (e.target as HTMLInputElement).checked;
this._updateUI(); this._updateUI();
if (this.canvas.autoApplyShapeMask) { if (this.canvas.autoApplyShapeMask) {
this.canvas.maskTool.hideShapePreview(); this.canvas.maskTool.hideShapePreview();
this.canvas.maskTool.applyShapeMask(); this.canvas.maskTool.applyShapeMask();
@@ -218,7 +216,6 @@ export class CustomShapeMenu {
}, },
"Softens the edges of the shape mask by creating a gradual transition from opaque to transparent." "Softens the edges of the shape mask by creating a gradual transition from opaque to transparent."
); );
featherContainer.id = 'feather-checkbox';
featureContainer.appendChild(featherContainer); featureContainer.appendChild(featherContainer);
// Add feather slider container // Add feather slider container
@@ -288,32 +285,21 @@ export class CustomShapeMenu {
// Add main extension checkbox // Add main extension checkbox
const extensionCheckboxContainer = this._createCheckbox( const extensionCheckboxContainer = this._createCheckbox(
() => `${this.canvas.outputAreaExtensionEnabled ? "☑" : "☐"} Extend output area`, 'extension-checkbox',
() => { () => this.canvas.outputAreaExtensionEnabled,
this.canvas.outputAreaExtensionEnabled = !this.canvas.outputAreaExtensionEnabled; 'Extend output area',
(e) => {
if (this.canvas.outputAreaExtensionEnabled) { this.canvas.outputAreaExtensionEnabled = (e.target as HTMLInputElement).checked;
// When enabling, capture current canvas size as the baseline if (this.canvas.outputAreaExtensionEnabled) {
this.canvas.originalCanvasSize = { this.canvas.originalCanvasSize = { width: this.canvas.width, height: this.canvas.height };
width: this.canvas.width,
height: this.canvas.height
};
// Restore last saved extensions instead of starting from zero
this.canvas.outputAreaExtensions = { ...this.canvas.lastOutputAreaExtensions }; this.canvas.outputAreaExtensions = { ...this.canvas.lastOutputAreaExtensions };
log.info(`Captured current canvas size as baseline: ${this.canvas.width}x${this.canvas.height}`);
log.info(`Restored last extensions:`, this.canvas.outputAreaExtensions);
} else { } else {
// Save current extensions before disabling
this.canvas.lastOutputAreaExtensions = { ...this.canvas.outputAreaExtensions }; this.canvas.lastOutputAreaExtensions = { ...this.canvas.outputAreaExtensions };
// Reset current extensions when disabled (but keep the saved ones)
this.canvas.outputAreaExtensions = { top: 0, bottom: 0, left: 0, right: 0 }; this.canvas.outputAreaExtensions = { top: 0, bottom: 0, left: 0, right: 0 };
log.info(`Saved extensions for later:`, this.canvas.lastOutputAreaExtensions);
} }
this._updateExtensionUI(); this._updateExtensionUI();
this._updateCanvasSize(); // Update canvas size when toggling this._updateCanvasSize();
this.canvas.render(); this.canvas.render();
log.info(`Output area extension ${this.canvas.outputAreaExtensionEnabled ? 'enabled' : 'disabled'}`);
}, },
"Allows extending the output area boundaries in all directions without changing the custom shape." "Allows extending the output area boundaries in all directions without changing the custom shape."
); );
@@ -424,31 +410,41 @@ export class CustomShapeMenu {
this._addViewportChangeListener(); this._addViewportChangeListener();
} }
private _createCheckbox(textFn: () => string, clickHandler: () => void, tooltipText?: string): HTMLDivElement { private _createCheckbox(
const container = document.createElement('div'); id: string,
getChecked: () => boolean,
text: string,
clickHandler: (e: Event) => void,
tooltipText?: string
): HTMLLabelElement {
const container = document.createElement('label');
container.className = 'checkbox-container'; container.className = 'checkbox-container';
container.htmlFor = id;
container.onmouseover = () => { const input = document.createElement('input');
container.style.backgroundColor = '#555'; input.type = 'checkbox';
input.id = id;
input.checked = getChecked();
const customCheckbox = document.createElement('div');
customCheckbox.className = 'custom-checkbox';
const labelText = document.createElement('span');
labelText.textContent = text;
container.appendChild(input);
container.appendChild(customCheckbox);
container.appendChild(labelText);
// Stop propagation to prevent menu from closing, but allow default checkbox behavior
container.onclick = (e: MouseEvent) => {
e.stopPropagation();
}; };
container.onmouseout = () => { input.onchange = (e: Event) => {
container.style.backgroundColor = 'transparent'; clickHandler(e);
}; };
const updateText = () => {
container.textContent = textFn();
};
updateText();
container.onclick = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
clickHandler();
updateText();
};
// Add tooltip if provided
if (tooltipText) { if (tooltipText) {
this._addTooltip(container, tooltipText); this._addTooltip(container, tooltipText);
} }
@@ -458,19 +454,27 @@ export class CustomShapeMenu {
private _updateUI(): void { private _updateUI(): void {
if (!this.element) return; if (!this.element) return;
const setChecked = (id: string, checked: boolean) => {
const input = this.element!.querySelector(`#${id}`) as HTMLInputElement;
if (input) input.checked = checked;
};
// Toggle visibility of sub-options based on the main checkbox state setChecked('auto-apply-checkbox', this.canvas.autoApplyShapeMask);
const expansionCheckbox = this.element.querySelector('#expansion-checkbox') as HTMLElement; setChecked('expansion-checkbox', this.canvas.shapeMaskExpansion);
setChecked('feather-checkbox', this.canvas.shapeMaskFeather);
setChecked('extension-checkbox', this.canvas.outputAreaExtensionEnabled);
const expansionCheckbox = this.element.querySelector('#expansion-checkbox')?.parentElement as HTMLElement;
if (expansionCheckbox) { if (expansionCheckbox) {
expansionCheckbox.style.display = this.canvas.autoApplyShapeMask ? 'block' : 'none'; expansionCheckbox.style.display = this.canvas.autoApplyShapeMask ? 'flex' : 'none';
} }
const featherCheckbox = this.element.querySelector('#feather-checkbox') as HTMLElement; const featherCheckbox = this.element.querySelector('#feather-checkbox')?.parentElement as HTMLElement;
if (featherCheckbox) { if (featherCheckbox) {
featherCheckbox.style.display = this.canvas.autoApplyShapeMask ? 'block' : 'none'; featherCheckbox.style.display = this.canvas.autoApplyShapeMask ? 'flex' : 'none';
} }
// Update sliders visibility based on their respective checkboxes
const expansionSliderContainer = this.element.querySelector('#expansion-slider-container') as HTMLElement; const expansionSliderContainer = this.element.querySelector('#expansion-slider-container') as HTMLElement;
if (expansionSliderContainer) { if (expansionSliderContainer) {
expansionSliderContainer.style.display = (this.canvas.autoApplyShapeMask && this.canvas.shapeMaskExpansion) ? 'block' : 'none'; expansionSliderContainer.style.display = (this.canvas.autoApplyShapeMask && this.canvas.shapeMaskExpansion) ? 'block' : 'none';
@@ -480,20 +484,6 @@ export class CustomShapeMenu {
if (featherSliderContainer) { if (featherSliderContainer) {
featherSliderContainer.style.display = (this.canvas.autoApplyShapeMask && this.canvas.shapeMaskFeather) ? 'block' : 'none'; featherSliderContainer.style.display = (this.canvas.autoApplyShapeMask && this.canvas.shapeMaskFeather) ? 'block' : 'none';
} }
// Update checkbox texts
const checkboxes = this.element.querySelectorAll('div[style*="cursor: pointer"]');
checkboxes.forEach((checkbox, index) => {
if (index === 0) { // Main checkbox
checkbox.textContent = `${this.canvas.autoApplyShapeMask ? "☑" : "☐"} Auto-apply shape mask`;
} else if (index === 1) { // Expansion checkbox
checkbox.textContent = `${this.canvas.shapeMaskExpansion ? "☑" : "☐"} Dilate/Erode mask`;
} else if (index === 2) { // Feather checkbox
checkbox.textContent = `${this.canvas.shapeMaskFeather ? "☑" : "☐"} Feather edges`;
} else if (index === 3) { // Extension checkbox
checkbox.textContent = `${this.canvas.outputAreaExtensionEnabled ? "☑" : "☐"} Extend output area`;
}
});
} }
private _updateExtensionUI(): void { private _updateExtensionUI(): void {

View File

@@ -2,92 +2,165 @@
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background-color: #333; background-color: #2f2f2f;
color: white; color: #e0e0e0;
padding: 8px 15px; padding: 12px;
border-radius: 10px; border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.5); box-shadow: 0 5px 15px rgba(0,0,0,0.3);
display: none; display: none;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 10px;
font-family: sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 12px; font-size: 13px;
z-index: 1001; z-index: 1001;
border: 1px solid #555; border: 1px solid #202020;
user-select: none; user-select: none;
min-width: 200px; min-width: 220px;
} }
#layerforge-custom-shape-menu .menu-line { #layerforge-custom-shape-menu .menu-line {
margin: 2px 0; font-weight: 600;
line-height: 18px; color: #4a90e2;
padding-bottom: 5px;
border-bottom: 1px solid #444;
margin-bottom: 5px;
display: flex;
align-items: center;
gap: 8px;
} }
#layerforge-custom-shape-menu .feature-container { #layerforge-custom-shape-menu .feature-container {
background-color: #282828; background-color: #3a3a3a;
border-radius: 6px; border-radius: 6px;
margin-top: 6px; padding: 10px;
padding: 4px 0; border: 1px solid #4a4a4a;
border: 1px solid #444;
} }
#layerforge-custom-shape-menu .slider-container { #layerforge-custom-shape-menu .slider-container {
margin: 0 8px 6px 8px; margin-top: 10px;
padding: 4px 8px;
display: none; display: none;
} }
#layerforge-custom-shape-menu .slider-label { #layerforge-custom-shape-menu .slider-label {
font-size: 11px; font-size: 12px;
margin-bottom: 4px; margin-bottom: 6px;
color: #ccc; color: #e0e0e0;
} }
#layerforge-custom-shape-menu input[type="range"] { #layerforge-custom-shape-menu input[type="range"] {
-webkit-appearance: none;
width: 100%; width: 100%;
height: 4px; height: 4px;
background: #555; background: #555;
outline: none;
border-radius: 2px; border-radius: 2px;
outline: none;
padding: 0;
margin: 0;
}
#layerforge-custom-shape-menu input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
background: #e0e0e0;
border-radius: 50%;
cursor: pointer;
border: 2px solid #555;
transition: background 0.2s;
}
#layerforge-custom-shape-menu input[type="range"]::-webkit-slider-thumb:hover {
background: #fff;
}
#layerforge-custom-shape-menu input[type="range"]::-moz-range-thumb {
width: 14px;
height: 14px;
background: #e0e0e0;
border-radius: 50%;
cursor: pointer;
border: 2px solid #555;
} }
#layerforge-custom-shape-menu .slider-value-display { #layerforge-custom-shape-menu .slider-value-display {
font-size: 10px; font-size: 11px;
text-align: center; text-align: center;
margin-top: 2px; margin-top: 4px;
color: #aaa; color: #bbb;
min-height: 14px;
} }
#layerforge-custom-shape-menu .extension-slider-container { #layerforge-custom-shape-menu .extension-slider-container {
margin: 6px 0; margin: 10px 0;
} }
#layerforge-custom-shape-menu .checkbox-container { #layerforge-custom-shape-menu .checkbox-container {
margin: 6px 0 2px 0; display: flex;
padding: 4px 8px; align-items: center;
gap: 10px;
padding: 6px;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; transition: background-color 0.2s;
line-height: 18px; position: relative;
} }
#layerforge-custom-shape-menu .checkbox-container:hover { #layerforge-custom-shape-menu .checkbox-container:hover {
background-color: #555; background-color: #4a4a4a;
}
#layerforge-custom-shape-menu .checkbox-container input[type="checkbox"] {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
#layerforge-custom-shape-menu .checkbox-container .custom-checkbox {
height: 16px;
width: 16px;
background-color: #2a2a2a;
border: 1px solid #666;
border-radius: 3px;
transition: all 0.2s;
position: relative;
flex-shrink: 0;
}
#layerforge-custom-shape-menu .checkbox-container input:checked ~ .custom-checkbox {
background-color: #3a76d6;
border-color: #3a76d6;
}
#layerforge-custom-shape-menu .checkbox-container .custom-checkbox::after {
content: "";
position: absolute;
display: none;
left: 5px;
top: 1px;
width: 4px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
#layerforge-custom-shape-menu .checkbox-container input:checked ~ .custom-checkbox::after {
display: block;
} }
.layerforge-tooltip { .layerforge-tooltip {
position: fixed; position: fixed;
background-color: #1a1a1a; background-color: #2f2f2f;
color: #ffffff; color: #e0e0e0;
padding: 8px 12px; padding: 8px 12px;
border-radius: 6px; border-radius: 6px;
font-size: 12px; font-size: 12px;
font-family: sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.4; line-height: 1.4;
max-width: 250px; max-width: 250px;
word-wrap: break-word; word-wrap: break-word;
box-shadow: 0 4px 12px rgba(0,0,0,0.6); box-shadow: 0 4px 12px rgba(0,0,0,0.4);
border: 1px solid #444; border: 1px solid #202020;
z-index: 10000; z-index: 10000;
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;