mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 20:52:12 -03:00
Add shape mask preview system for expansion and feather sliders
Implements a real-time blue outline preview when adjusting the expansion and feather sliders in the custom shape mask UI. The preview updates dynamically while dragging, and applies the mask only on mouse release. Adds a viewport change listener to keep the preview in sync during zooming or panning. Refactors mask expansion/contraction to use fast morphological operations for sharp edges and updates mask drawing to handle holes correctly.
This commit is contained in:
@@ -141,8 +141,12 @@ export class CustomShapeMenu {
|
||||
this.canvas.shapeMaskExpansionValue = value;
|
||||
expansionValueDisplay.textContent = value > 0 ? `+${value}px` : `${value}px`;
|
||||
};
|
||||
// Add debouncing for expansion slider
|
||||
// Add preview system for expansion slider
|
||||
let expansionTimeout = null;
|
||||
let isExpansionDragging = false;
|
||||
expansionSlider.onmousedown = () => {
|
||||
isExpansionDragging = true;
|
||||
};
|
||||
expansionSlider.oninput = () => {
|
||||
updateExpansionSliderDisplay();
|
||||
if (this.canvas.autoApplyShapeMask) {
|
||||
@@ -150,13 +154,31 @@ export class CustomShapeMenu {
|
||||
if (expansionTimeout) {
|
||||
clearTimeout(expansionTimeout);
|
||||
}
|
||||
// Apply mask immediately for visual feedback (without saving state)
|
||||
this.canvas.maskTool.applyShapeMask(false); // false = don't save state
|
||||
if (isExpansionDragging) {
|
||||
// Show blue preview line while dragging - NO mask application
|
||||
const featherValue = this.canvas.shapeMaskFeather ? this.canvas.shapeMaskFeatherValue : 0;
|
||||
this.canvas.maskTool.showShapePreview(this.canvas.shapeMaskExpansionValue, featherValue);
|
||||
}
|
||||
else {
|
||||
// Apply mask immediately for programmatic changes (not user dragging)
|
||||
this.canvas.maskTool.hideShapePreview();
|
||||
this.canvas.maskTool.applyShapeMask(false);
|
||||
this.canvas.render();
|
||||
}
|
||||
// Clear any pending timeout - we only apply mask on mouseup now
|
||||
if (expansionTimeout) {
|
||||
clearTimeout(expansionTimeout);
|
||||
expansionTimeout = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
expansionSlider.onmouseup = () => {
|
||||
isExpansionDragging = false;
|
||||
if (this.canvas.autoApplyShapeMask) {
|
||||
// Apply final mask immediately when user releases slider
|
||||
this.canvas.maskTool.hideShapePreview();
|
||||
this.canvas.maskTool.applyShapeMask(true);
|
||||
this.canvas.render();
|
||||
// Save state after 500ms of no changes
|
||||
expansionTimeout = window.setTimeout(() => {
|
||||
this.canvas.canvasState.saveMaskState();
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
updateExpansionSliderDisplay();
|
||||
@@ -214,22 +236,35 @@ export class CustomShapeMenu {
|
||||
this.canvas.shapeMaskFeatherValue = value;
|
||||
featherValueDisplay.textContent = `${value}px`;
|
||||
};
|
||||
// Add debouncing for feather slider
|
||||
// Add preview system for feather slider (mirrors expansion slider)
|
||||
let featherTimeout = null;
|
||||
let isFeatherDragging = false;
|
||||
featherSlider.onmousedown = () => {
|
||||
isFeatherDragging = true;
|
||||
};
|
||||
featherSlider.oninput = () => {
|
||||
updateFeatherSliderDisplay();
|
||||
if (this.canvas.autoApplyShapeMask) {
|
||||
// Clear previous timeout
|
||||
if (featherTimeout) {
|
||||
clearTimeout(featherTimeout);
|
||||
if (isFeatherDragging) {
|
||||
// Show blue preview line while dragging
|
||||
const expansionValue = this.canvas.shapeMaskExpansion ? this.canvas.shapeMaskExpansionValue : 0;
|
||||
this.canvas.maskTool.showShapePreview(expansionValue, this.canvas.shapeMaskFeatherValue);
|
||||
}
|
||||
// Apply mask immediately for visual feedback (without saving state)
|
||||
this.canvas.maskTool.applyShapeMask(false); // false = don't save state
|
||||
else {
|
||||
// Apply immediately for programmatic changes
|
||||
this.canvas.maskTool.hideShapePreview();
|
||||
this.canvas.maskTool.applyShapeMask(false);
|
||||
this.canvas.render();
|
||||
}
|
||||
}
|
||||
};
|
||||
featherSlider.onmouseup = () => {
|
||||
isFeatherDragging = false;
|
||||
if (this.canvas.autoApplyShapeMask) {
|
||||
// Apply final mask when user releases slider
|
||||
this.canvas.maskTool.hideShapePreview();
|
||||
this.canvas.maskTool.applyShapeMask(true); // true = save state
|
||||
this.canvas.render();
|
||||
// Save state after 500ms of no changes
|
||||
featherTimeout = window.setTimeout(() => {
|
||||
this.canvas.canvasState.saveMaskState();
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
updateFeatherSliderDisplay();
|
||||
@@ -246,6 +281,8 @@ export class CustomShapeMenu {
|
||||
}
|
||||
this.uiInitialized = true;
|
||||
this._updateUI();
|
||||
// Add viewport change listener to update shape preview when zooming/panning
|
||||
this._addViewportChangeListener();
|
||||
}
|
||||
_createCheckbox(textFn, clickHandler) {
|
||||
const container = document.createElement('div');
|
||||
@@ -314,4 +351,42 @@ export class CustomShapeMenu {
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Add viewport change listener to update shape preview when zooming/panning
|
||||
*/
|
||||
_addViewportChangeListener() {
|
||||
// Store previous viewport state to detect changes
|
||||
let previousViewport = {
|
||||
x: this.canvas.viewport.x,
|
||||
y: this.canvas.viewport.y,
|
||||
zoom: this.canvas.viewport.zoom
|
||||
};
|
||||
// Check for viewport changes in render loop
|
||||
const checkViewportChange = () => {
|
||||
if (this.canvas.maskTool.shapePreviewVisible) {
|
||||
const current = this.canvas.viewport;
|
||||
// Check if viewport has changed
|
||||
if (current.x !== previousViewport.x ||
|
||||
current.y !== previousViewport.y ||
|
||||
current.zoom !== previousViewport.zoom) {
|
||||
// Update shape preview with current expansion/feather values
|
||||
const expansionValue = this.canvas.shapeMaskExpansionValue || 0;
|
||||
const featherValue = this.canvas.shapeMaskFeather ? (this.canvas.shapeMaskFeatherValue || 0) : 0;
|
||||
this.canvas.maskTool.showShapePreview(expansionValue, featherValue);
|
||||
// Update previous viewport state
|
||||
previousViewport = {
|
||||
x: current.x,
|
||||
y: current.y,
|
||||
zoom: current.zoom
|
||||
};
|
||||
}
|
||||
}
|
||||
// Continue checking if UI is still active
|
||||
if (this.uiInitialized) {
|
||||
requestAnimationFrame(checkViewportChange);
|
||||
}
|
||||
};
|
||||
// Start the viewport change detection
|
||||
requestAnimationFrame(checkViewportChange);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user