fix(ui): fix layer arrangement

This commit is contained in:
psychedelicious 2024-04-24 16:32:04 +10:00
parent af25d00964
commit af3e910ad3
2 changed files with 41 additions and 36 deletions

View File

@ -15,7 +15,7 @@ import {
layerTranslated, layerTranslated,
selectRegionalPromptsSlice, selectRegionalPromptsSlice,
} from 'features/regionalPrompts/store/regionalPromptsSlice'; } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { debouncedRenderers, renderers } from 'features/regionalPrompts/util/renderers'; import { debouncedRenderers, renderers as normalRenderers } from 'features/regionalPrompts/util/renderers';
import Konva from 'konva'; import Konva from 'konva';
import type { IRect } from 'konva/lib/types'; import type { IRect } from 'konva/lib/types';
import type { MutableRefObject } from 'react'; import type { MutableRefObject } from 'react';
@ -52,20 +52,8 @@ const useStageRenderer = (
const lastMouseDownPos = useStore($lastMouseDownPos); const lastMouseDownPos = useStore($lastMouseDownPos);
const isMouseOver = useStore($isMouseOver); const isMouseOver = useStore($isMouseOver);
const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor); const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor);
const layerIds = useMemo(() => state.layers.map((l) => l.id), [state.layers]);
const renderLayers = useMemo( const renderers = useMemo(() => (asPreview ? debouncedRenderers : normalRenderers), [asPreview]);
() => (asPreview ? debouncedRenderers.renderLayers : renderers.renderLayers),
[asPreview]
);
const renderToolPreview = useMemo(
() => (asPreview ? debouncedRenderers.renderToolPreview : renderers.renderToolPreview),
[asPreview]
);
const renderBbox = useMemo(() => (asPreview ? debouncedRenderers.renderBbox : renderers.renderBbox), [asPreview]);
const renderBackground = useMemo(
() => (asPreview ? debouncedRenderers.renderBackground : renderers.renderBackground),
[asPreview]
);
const onLayerPosChanged = useCallback( const onLayerPosChanged = useCallback(
(layerId: string, x: number, y: number) => { (layerId: string, x: number, y: number) => {
@ -152,11 +140,12 @@ const useStageRenderer = (
}, [stageRef, width, height, wrapper]); }, [stageRef, width, height, wrapper]);
useLayoutEffect(() => { useLayoutEffect(() => {
log.trace('Rendering brush preview'); log.trace('Rendering tool preview');
if (asPreview) { if (asPreview) {
// Preview should not display tool
return; return;
} }
renderToolPreview( renderers.renderToolPreview(
stageRef.current, stageRef.current,
tool, tool,
selectedLayerIdColor, selectedLayerIdColor,
@ -176,29 +165,36 @@ const useStageRenderer = (
lastMouseDownPos, lastMouseDownPos,
isMouseOver, isMouseOver,
state.brushSize, state.brushSize,
renderToolPreview, renderers,
]); ]);
useLayoutEffect(() => { useLayoutEffect(() => {
log.trace('Rendering layers'); log.trace('Rendering layers');
renderLayers(stageRef.current, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged); renderers.renderLayers(stageRef.current, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged);
}, [stageRef, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged, renderLayers]); }, [stageRef, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged, renderers]);
useLayoutEffect(() => { useLayoutEffect(() => {
log.trace('Rendering bbox'); log.trace('Rendering bbox');
if (asPreview) { if (asPreview) {
// Preview should not display bboxes
return; return;
} }
renderBbox(stageRef.current, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown); renderers.renderBbox(stageRef.current, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown);
}, [stageRef, asPreview, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown, renderBbox]); }, [stageRef, asPreview, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown, renderers]);
useLayoutEffect(() => { useLayoutEffect(() => {
log.trace('Rendering background'); log.trace('Rendering background');
if (asPreview) { if (asPreview) {
// The preview should not have a background
return; return;
} }
renderBackground(stageRef.current, width, height); renderers.renderBackground(stageRef.current, width, height);
}, [stageRef, asPreview, width, height, renderBackground]); }, [stageRef, asPreview, width, height, renderers]);
useLayoutEffect(() => {
log.trace('Arranging layers');
renderers.arrangeLayers(stageRef.current, layerIds);
}, [stageRef, layerIds, renderers]);
}; };
type Props = { type Props = {

View File

@ -267,9 +267,6 @@ const createVectorMaskLayer = (
stage.add(konvaLayer); stage.add(konvaLayer);
// When a layer is added, it ends up on top of the brush preview - we need to move the preview back to the top.
stage.findOne<Konva.Layer>(`#${TOOL_PREVIEW_LAYER_ID}`)?.moveToTop();
return konvaLayer; return konvaLayer;
}; };
@ -326,7 +323,6 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro
const renderVectorMaskLayer = ( const renderVectorMaskLayer = (
stage: Konva.Stage, stage: Konva.Stage,
reduxLayer: VectorMaskLayer, reduxLayer: VectorMaskLayer,
reduxLayerIndex: number,
globalMaskLayerOpacity: number, globalMaskLayerOpacity: number,
tool: Tool, tool: Tool,
onLayerPosChanged?: (layerId: string, x: number, y: number) => void onLayerPosChanged?: (layerId: string, x: number, y: number) => void
@ -339,10 +335,6 @@ const renderVectorMaskLayer = (
listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events
x: Math.floor(reduxLayer.x), x: Math.floor(reduxLayer.x),
y: Math.floor(reduxLayer.y), y: Math.floor(reduxLayer.y),
// We have a konva layer for each redux layer, plus a brush preview layer, which should always be on top. We can
// therefore use the index of the redux layer as the zIndex for konva layers. If more layers are added to the
// stage, this may no longer be work.
zIndex: reduxLayerIndex,
}); });
// Convert the color to a string, stripping the alpha - the object group will handle opacity. // Convert the color to a string, stripping the alpha - the object group will handle opacity.
@ -433,11 +425,9 @@ const renderLayers = (
} }
} }
for (let layerIndex = 0; layerIndex < reduxLayers.length; layerIndex++) { for (const reduxLayer of reduxLayers) {
const reduxLayer = reduxLayers[layerIndex];
assert(reduxLayer, `Layer at index ${layerIndex} is undefined`);
if (isVectorMaskLayer(reduxLayer)) { if (isVectorMaskLayer(reduxLayer)) {
renderVectorMaskLayer(stage, reduxLayer, layerIndex, globalMaskLayerOpacity, tool, onLayerPosChanged); renderVectorMaskLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged);
} }
} }
}; };
@ -593,11 +583,29 @@ const renderBackground = (stage: Konva.Stage, width: number, height: number) =>
background.fillPatternOffset(stagePos); background.fillPatternOffset(stagePos);
}; };
/**
* Arranges all layers in the z-axis by updating their z-indices.
* @param stage The konva stage
* @param layerIds An array of redux layer ids, in their z-index order
*/
export const arrangeLayers = (stage: Konva.Stage, layerIds: string[]): void => {
let nextZIndex = 0;
// Background is the first layer
stage.findOne<Konva.Layer>(`#${BACKGROUND_LAYER_ID}`)?.zIndex(nextZIndex++);
// Then arrange the redux layers in order
for (const layerId of layerIds) {
stage.findOne<Konva.Layer>(`#${layerId}`)?.zIndex(nextZIndex++);
}
// Finally, the tool preview layer is always on top
stage.findOne<Konva.Layer>(`#${TOOL_PREVIEW_LAYER_ID}`)?.zIndex(nextZIndex++);
};
export const renderers = { export const renderers = {
renderToolPreview, renderToolPreview,
renderLayers, renderLayers,
renderBbox, renderBbox,
renderBackground, renderBackground,
arrangeLayers,
}; };
const DEBOUNCE_MS = 300; const DEBOUNCE_MS = 300;
@ -607,4 +615,5 @@ export const debouncedRenderers = {
renderLayers: debounce(renderLayers, DEBOUNCE_MS), renderLayers: debounce(renderLayers, DEBOUNCE_MS),
renderBbox: debounce(renderBbox, DEBOUNCE_MS), renderBbox: debounce(renderBbox, DEBOUNCE_MS),
renderBackground: debounce(renderBackground, DEBOUNCE_MS), renderBackground: debounce(renderBackground, DEBOUNCE_MS),
arrangeLayers: debounce(arrangeLayers, DEBOUNCE_MS),
}; };