mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 20:52:12 -03:00
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:
@@ -61,10 +61,8 @@ export class CustomShapeMenu {
|
||||
featureContainer.id = 'shape-mask-feature-container';
|
||||
featureContainer.className = 'feature-container';
|
||||
// Add main auto-apply checkbox to the new container
|
||||
const checkboxContainer = this._createCheckbox(() => `${this.canvas.autoApplyShapeMask ? "☑" : "☐"} Auto-apply shape mask`, () => {
|
||||
// Always hide any active shape preview lines first to prevent them from getting stuck
|
||||
this.canvas.maskTool.hideShapePreview();
|
||||
this.canvas.autoApplyShapeMask = !this.canvas.autoApplyShapeMask;
|
||||
const checkboxContainer = this._createCheckbox('auto-apply-checkbox', () => this.canvas.autoApplyShapeMask, 'Auto-apply shape mask', (e) => {
|
||||
this.canvas.autoApplyShapeMask = e.target.checked;
|
||||
if (this.canvas.autoApplyShapeMask) {
|
||||
this.canvas.maskTool.applyShapeMask();
|
||||
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.");
|
||||
featureContainer.appendChild(checkboxContainer);
|
||||
// Add expansion checkbox
|
||||
const expansionContainer = this._createCheckbox(() => `${this.canvas.shapeMaskExpansion ? "☑" : "☐"} Expand/Contract mask`, () => {
|
||||
this.canvas.shapeMaskExpansion = !this.canvas.shapeMaskExpansion;
|
||||
const expansionContainer = this._createCheckbox('expansion-checkbox', () => this.canvas.shapeMaskExpansion, 'Expand/Contract mask', (e) => {
|
||||
this.canvas.shapeMaskExpansion = e.target.checked;
|
||||
this._updateUI();
|
||||
if (this.canvas.autoApplyShapeMask) {
|
||||
this.canvas.maskTool.hideShapePreview();
|
||||
@@ -89,7 +87,6 @@ export class CustomShapeMenu {
|
||||
this.canvas.render();
|
||||
}
|
||||
}, "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);
|
||||
// Add expansion slider container
|
||||
const expansionSliderContainer = document.createElement('div');
|
||||
@@ -154,8 +151,8 @@ export class CustomShapeMenu {
|
||||
expansionSliderContainer.appendChild(expansionValueDisplay);
|
||||
featureContainer.appendChild(expansionSliderContainer);
|
||||
// Add feather checkbox
|
||||
const featherContainer = this._createCheckbox(() => `${this.canvas.shapeMaskFeather ? "☑" : "☐"} Feather edges`, () => {
|
||||
this.canvas.shapeMaskFeather = !this.canvas.shapeMaskFeather;
|
||||
const featherContainer = this._createCheckbox('feather-checkbox', () => this.canvas.shapeMaskFeather, 'Feather edges', (e) => {
|
||||
this.canvas.shapeMaskFeather = e.target.checked;
|
||||
this._updateUI();
|
||||
if (this.canvas.autoApplyShapeMask) {
|
||||
this.canvas.maskTool.hideShapePreview();
|
||||
@@ -163,7 +160,6 @@ export class CustomShapeMenu {
|
||||
this.canvas.render();
|
||||
}
|
||||
}, "Softens the edges of the shape mask by creating a gradual transition from opaque to transparent.");
|
||||
featherContainer.id = 'feather-checkbox';
|
||||
featureContainer.appendChild(featherContainer);
|
||||
// Add feather slider container
|
||||
const featherSliderContainer = document.createElement('div');
|
||||
@@ -219,30 +215,19 @@ export class CustomShapeMenu {
|
||||
extensionContainer.id = 'output-area-extension-container';
|
||||
extensionContainer.className = 'feature-container';
|
||||
// Add main extension checkbox
|
||||
const extensionCheckboxContainer = this._createCheckbox(() => `${this.canvas.outputAreaExtensionEnabled ? "☑" : "☐"} Extend output area`, () => {
|
||||
this.canvas.outputAreaExtensionEnabled = !this.canvas.outputAreaExtensionEnabled;
|
||||
const extensionCheckboxContainer = this._createCheckbox('extension-checkbox', () => this.canvas.outputAreaExtensionEnabled, 'Extend output area', (e) => {
|
||||
this.canvas.outputAreaExtensionEnabled = e.target.checked;
|
||||
if (this.canvas.outputAreaExtensionEnabled) {
|
||||
// When enabling, capture current canvas size as the baseline
|
||||
this.canvas.originalCanvasSize = {
|
||||
width: this.canvas.width,
|
||||
height: this.canvas.height
|
||||
};
|
||||
// Restore last saved extensions instead of starting from zero
|
||||
this.canvas.originalCanvasSize = { width: this.canvas.width, height: this.canvas.height };
|
||||
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 {
|
||||
// Save current extensions before disabling
|
||||
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 };
|
||||
log.info(`Saved extensions for later:`, this.canvas.lastOutputAreaExtensions);
|
||||
}
|
||||
this._updateExtensionUI();
|
||||
this._updateCanvasSize(); // Update canvas size when toggling
|
||||
this._updateCanvasSize();
|
||||
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.");
|
||||
extensionContainer.appendChild(extensionCheckboxContainer);
|
||||
// Create sliders container
|
||||
@@ -333,26 +318,28 @@ export class CustomShapeMenu {
|
||||
// Add viewport change listener to update shape preview when zooming/panning
|
||||
this._addViewportChangeListener();
|
||||
}
|
||||
_createCheckbox(textFn, clickHandler, tooltipText) {
|
||||
const container = document.createElement('div');
|
||||
_createCheckbox(id, getChecked, text, clickHandler, tooltipText) {
|
||||
const container = document.createElement('label');
|
||||
container.className = 'checkbox-container';
|
||||
container.onmouseover = () => {
|
||||
container.style.backgroundColor = '#555';
|
||||
};
|
||||
container.onmouseout = () => {
|
||||
container.style.backgroundColor = 'transparent';
|
||||
};
|
||||
const updateText = () => {
|
||||
container.textContent = textFn();
|
||||
};
|
||||
updateText();
|
||||
container.htmlFor = id;
|
||||
const input = document.createElement('input');
|
||||
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) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
clickHandler();
|
||||
updateText();
|
||||
};
|
||||
// Add tooltip if provided
|
||||
input.onchange = (e) => {
|
||||
clickHandler(e);
|
||||
};
|
||||
if (tooltipText) {
|
||||
this._addTooltip(container, tooltipText);
|
||||
}
|
||||
@@ -361,16 +348,23 @@ export class CustomShapeMenu {
|
||||
_updateUI() {
|
||||
if (!this.element)
|
||||
return;
|
||||
// Toggle visibility of sub-options based on the main checkbox state
|
||||
const expansionCheckbox = this.element.querySelector('#expansion-checkbox');
|
||||
const setChecked = (id, checked) => {
|
||||
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) {
|
||||
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) {
|
||||
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');
|
||||
if (expansionSliderContainer) {
|
||||
expansionSliderContainer.style.display = (this.canvas.autoApplyShapeMask && this.canvas.shapeMaskExpansion) ? 'block' : 'none';
|
||||
@@ -379,22 +373,6 @@ export class CustomShapeMenu {
|
||||
if (featherSliderContainer) {
|
||||
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() {
|
||||
if (!this.element)
|
||||
|
||||
@@ -2,92 +2,165 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 8px 15px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
|
||||
background-color: #2f2f2f;
|
||||
color: #e0e0e0;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-family: sans-serif;
|
||||
font-size: 12px;
|
||||
gap: 10px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font-size: 13px;
|
||||
z-index: 1001;
|
||||
border: 1px solid #555;
|
||||
border: 1px solid #202020;
|
||||
user-select: none;
|
||||
min-width: 200px;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
#layerforge-custom-shape-menu .menu-line {
|
||||
margin: 2px 0;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
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 {
|
||||
background-color: #282828;
|
||||
background-color: #3a3a3a;
|
||||
border-radius: 6px;
|
||||
margin-top: 6px;
|
||||
padding: 4px 0;
|
||||
border: 1px solid #444;
|
||||
padding: 10px;
|
||||
border: 1px solid #4a4a4a;
|
||||
}
|
||||
|
||||
#layerforge-custom-shape-menu .slider-container {
|
||||
margin: 0 8px 6px 8px;
|
||||
padding: 4px 8px;
|
||||
margin-top: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#layerforge-custom-shape-menu .slider-label {
|
||||
font-size: 11px;
|
||||
margin-bottom: 4px;
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
margin-bottom: 6px;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
#layerforge-custom-shape-menu input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: #555;
|
||||
outline: none;
|
||||
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 {
|
||||
font-size: 10px;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
margin-top: 2px;
|
||||
color: #aaa;
|
||||
margin-top: 4px;
|
||||
color: #bbb;
|
||||
min-height: 14px;
|
||||
}
|
||||
|
||||
#layerforge-custom-shape-menu .extension-slider-container {
|
||||
margin: 6px 0;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#layerforge-custom-shape-menu .checkbox-container {
|
||||
margin: 6px 0 2px 0;
|
||||
padding: 4px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
line-height: 18px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#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 {
|
||||
position: fixed;
|
||||
background-color: #1a1a1a;
|
||||
color: #ffffff;
|
||||
background-color: #2f2f2f;
|
||||
color: #e0e0e0;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
line-height: 1.4;
|
||||
max-width: 250px;
|
||||
word-wrap: break-word;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.6);
|
||||
border: 1px solid #444;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
||||
border: 1px solid #202020;
|
||||
z-index: 10000;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
|
||||
Reference in New Issue
Block a user