Skip to content

Commit af3e910

Browse files
fix(ui): fix layer arrangement
1 parent af25d00 commit af3e910

File tree

2 files changed

+41
-36
lines changed

2 files changed

+41
-36
lines changed

invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
layerTranslated,
1616
selectRegionalPromptsSlice,
1717
} from 'features/regionalPrompts/store/regionalPromptsSlice';
18-
import { debouncedRenderers, renderers } from 'features/regionalPrompts/util/renderers';
18+
import { debouncedRenderers, renderers as normalRenderers } from 'features/regionalPrompts/util/renderers';
1919
import Konva from 'konva';
2020
import type { IRect } from 'konva/lib/types';
2121
import type { MutableRefObject } from 'react';
@@ -52,20 +52,8 @@ const useStageRenderer = (
5252
const lastMouseDownPos = useStore($lastMouseDownPos);
5353
const isMouseOver = useStore($isMouseOver);
5454
const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor);
55-
56-
const renderLayers = useMemo(
57-
() => (asPreview ? debouncedRenderers.renderLayers : renderers.renderLayers),
58-
[asPreview]
59-
);
60-
const renderToolPreview = useMemo(
61-
() => (asPreview ? debouncedRenderers.renderToolPreview : renderers.renderToolPreview),
62-
[asPreview]
63-
);
64-
const renderBbox = useMemo(() => (asPreview ? debouncedRenderers.renderBbox : renderers.renderBbox), [asPreview]);
65-
const renderBackground = useMemo(
66-
() => (asPreview ? debouncedRenderers.renderBackground : renderers.renderBackground),
67-
[asPreview]
68-
);
55+
const layerIds = useMemo(() => state.layers.map((l) => l.id), [state.layers]);
56+
const renderers = useMemo(() => (asPreview ? debouncedRenderers : normalRenderers), [asPreview]);
6957

7058
const onLayerPosChanged = useCallback(
7159
(layerId: string, x: number, y: number) => {
@@ -152,11 +140,12 @@ const useStageRenderer = (
152140
}, [stageRef, width, height, wrapper]);
153141

154142
useLayoutEffect(() => {
155-
log.trace('Rendering brush preview');
143+
log.trace('Rendering tool preview');
156144
if (asPreview) {
145+
// Preview should not display tool
157146
return;
158147
}
159-
renderToolPreview(
148+
renderers.renderToolPreview(
160149
stageRef.current,
161150
tool,
162151
selectedLayerIdColor,
@@ -176,29 +165,36 @@ const useStageRenderer = (
176165
lastMouseDownPos,
177166
isMouseOver,
178167
state.brushSize,
179-
renderToolPreview,
168+
renderers,
180169
]);
181170

182171
useLayoutEffect(() => {
183172
log.trace('Rendering layers');
184-
renderLayers(stageRef.current, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged);
185-
}, [stageRef, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged, renderLayers]);
173+
renderers.renderLayers(stageRef.current, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged);
174+
}, [stageRef, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged, renderers]);
186175

187176
useLayoutEffect(() => {
188177
log.trace('Rendering bbox');
189178
if (asPreview) {
179+
// Preview should not display bboxes
190180
return;
191181
}
192-
renderBbox(stageRef.current, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown);
193-
}, [stageRef, asPreview, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown, renderBbox]);
182+
renderers.renderBbox(stageRef.current, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown);
183+
}, [stageRef, asPreview, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown, renderers]);
194184

195185
useLayoutEffect(() => {
196186
log.trace('Rendering background');
197187
if (asPreview) {
188+
// The preview should not have a background
198189
return;
199190
}
200-
renderBackground(stageRef.current, width, height);
201-
}, [stageRef, asPreview, width, height, renderBackground]);
191+
renderers.renderBackground(stageRef.current, width, height);
192+
}, [stageRef, asPreview, width, height, renderers]);
193+
194+
useLayoutEffect(() => {
195+
log.trace('Arranging layers');
196+
renderers.arrangeLayers(stageRef.current, layerIds);
197+
}, [stageRef, layerIds, renderers]);
202198
};
203199

204200
type Props = {

invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,6 @@ const createVectorMaskLayer = (
267267

268268
stage.add(konvaLayer);
269269

270-
// When a layer is added, it ends up on top of the brush preview - we need to move the preview back to the top.
271-
stage.findOne<Konva.Layer>(`#${TOOL_PREVIEW_LAYER_ID}`)?.moveToTop();
272-
273270
return konvaLayer;
274271
};
275272

@@ -326,7 +323,6 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro
326323
const renderVectorMaskLayer = (
327324
stage: Konva.Stage,
328325
reduxLayer: VectorMaskLayer,
329-
reduxLayerIndex: number,
330326
globalMaskLayerOpacity: number,
331327
tool: Tool,
332328
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
@@ -339,10 +335,6 @@ const renderVectorMaskLayer = (
339335
listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events
340336
x: Math.floor(reduxLayer.x),
341337
y: Math.floor(reduxLayer.y),
342-
// We have a konva layer for each redux layer, plus a brush preview layer, which should always be on top. We can
343-
// therefore use the index of the redux layer as the zIndex for konva layers. If more layers are added to the
344-
// stage, this may no longer be work.
345-
zIndex: reduxLayerIndex,
346338
});
347339

348340
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
@@ -433,11 +425,9 @@ const renderLayers = (
433425
}
434426
}
435427

436-
for (let layerIndex = 0; layerIndex < reduxLayers.length; layerIndex++) {
437-
const reduxLayer = reduxLayers[layerIndex];
438-
assert(reduxLayer, `Layer at index ${layerIndex} is undefined`);
428+
for (const reduxLayer of reduxLayers) {
439429
if (isVectorMaskLayer(reduxLayer)) {
440-
renderVectorMaskLayer(stage, reduxLayer, layerIndex, globalMaskLayerOpacity, tool, onLayerPosChanged);
430+
renderVectorMaskLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged);
441431
}
442432
}
443433
};
@@ -593,11 +583,29 @@ const renderBackground = (stage: Konva.Stage, width: number, height: number) =>
593583
background.fillPatternOffset(stagePos);
594584
};
595585

586+
/**
587+
* Arranges all layers in the z-axis by updating their z-indices.
588+
* @param stage The konva stage
589+
* @param layerIds An array of redux layer ids, in their z-index order
590+
*/
591+
export const arrangeLayers = (stage: Konva.Stage, layerIds: string[]): void => {
592+
let nextZIndex = 0;
593+
// Background is the first layer
594+
stage.findOne<Konva.Layer>(`#${BACKGROUND_LAYER_ID}`)?.zIndex(nextZIndex++);
595+
// Then arrange the redux layers in order
596+
for (const layerId of layerIds) {
597+
stage.findOne<Konva.Layer>(`#${layerId}`)?.zIndex(nextZIndex++);
598+
}
599+
// Finally, the tool preview layer is always on top
600+
stage.findOne<Konva.Layer>(`#${TOOL_PREVIEW_LAYER_ID}`)?.zIndex(nextZIndex++);
601+
};
602+
596603
export const renderers = {
597604
renderToolPreview,
598605
renderLayers,
599606
renderBbox,
600607
renderBackground,
608+
arrangeLayers,
601609
};
602610

603611
const DEBOUNCE_MS = 300;
@@ -607,4 +615,5 @@ export const debouncedRenderers = {
607615
renderLayers: debounce(renderLayers, DEBOUNCE_MS),
608616
renderBbox: debounce(renderBbox, DEBOUNCE_MS),
609617
renderBackground: debounce(renderBackground, DEBOUNCE_MS),
618+
arrangeLayers: debounce(arrangeLayers, DEBOUNCE_MS),
610619
};

0 commit comments

Comments
 (0)