mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): abstract out bbox renderer
This commit is contained in:
parent
ede8826757
commit
8caa7bc2b1
@ -11,10 +11,11 @@ import {
|
|||||||
BRUSH_PREVIEW_BORDER_OUTER_ID,
|
BRUSH_PREVIEW_BORDER_OUTER_ID,
|
||||||
BRUSH_PREVIEW_FILL_ID,
|
BRUSH_PREVIEW_FILL_ID,
|
||||||
BRUSH_PREVIEW_LAYER_ID,
|
BRUSH_PREVIEW_LAYER_ID,
|
||||||
|
getPromptRegionLayerBboxId,
|
||||||
getPromptRegionLayerObjectGroupId,
|
getPromptRegionLayerObjectGroupId,
|
||||||
layerBboxChanged,
|
layerBboxChanged,
|
||||||
layerSelected,
|
|
||||||
layerTranslated,
|
layerTranslated,
|
||||||
|
REGIONAL_PROMPT_LAYER_BBOX_NAME,
|
||||||
REGIONAL_PROMPT_LAYER_NAME,
|
REGIONAL_PROMPT_LAYER_NAME,
|
||||||
REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME,
|
REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME,
|
||||||
selectRegionalPromptsSlice,
|
selectRegionalPromptsSlice,
|
||||||
@ -22,27 +23,23 @@ import {
|
|||||||
import { getKonvaLayerBbox } from 'features/regionalPrompts/util/bbox';
|
import { getKonvaLayerBbox } from 'features/regionalPrompts/util/bbox';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import type { KonvaEventObject, Node, NodeConfig } from 'konva/lib/Node';
|
import type { KonvaEventObject, Node, NodeConfig } from 'konva/lib/Node';
|
||||||
import type { Vector2d } from 'konva/lib/types';
|
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
import { useCallback, useLayoutEffect } from 'react';
|
import { useCallback, useLayoutEffect } from 'react';
|
||||||
import type { RgbColor } from 'react-colorful';
|
import type { RgbColor } from 'react-colorful';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { useMouseDown, useMouseEnter, useMouseLeave, useMouseMove, useMouseUp } from './mouseEventHooks';
|
import { useMouseDown, useMouseEnter, useMouseLeave, useMouseMove, useMouseUp } from './mouseEventHooks';
|
||||||
|
|
||||||
export const $stage = atom<Konva.Stage | null>(null);
|
export const $stage = atom<Konva.Stage | null>(null);
|
||||||
|
|
||||||
type Props = {
|
|
||||||
container: HTMLDivElement | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const selectPromptLayerObjectGroup = (item: Node<NodeConfig>) =>
|
export const selectPromptLayerObjectGroup = (item: Node<NodeConfig>) =>
|
||||||
item.name() !== REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME;
|
item.name() !== REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME;
|
||||||
|
|
||||||
const isKonvaLayer = (node: Node<NodeConfig>): node is Konva.Layer => node.nodeType === 'Layer';
|
type Props = {
|
||||||
const isKonvaLine = (node: Node<NodeConfig>): node is Konva.Line => node.nodeType === 'Line';
|
container: HTMLDivElement | null;
|
||||||
const isKonvaGroup = (node: Node<NodeConfig>): node is Konva.Group => node.nodeType === 'Group';
|
};
|
||||||
const isKonvaRect = (node: Node<NodeConfig>): node is Konva.Rect => node.nodeType === 'Rect';
|
|
||||||
|
|
||||||
const renderBrushPreview = (
|
const renderBrushPreview = (
|
||||||
stage: Konva.Stage,
|
stage: Konva.Stage,
|
||||||
@ -51,6 +48,9 @@ const renderBrushPreview = (
|
|||||||
cursorPos: Vector2d,
|
cursorPos: Vector2d,
|
||||||
brushSize: number
|
brushSize: number
|
||||||
) => {
|
) => {
|
||||||
|
// Update the stage's pointer style
|
||||||
|
stage.container().style.cursor = tool === 'move' ? 'default' : 'none';
|
||||||
|
|
||||||
// Create the layer if it doesn't exist
|
// Create the layer if it doesn't exist
|
||||||
let layer = stage.findOne<Konva.Layer>(`#${BRUSH_PREVIEW_LAYER_ID}`);
|
let layer = stage.findOne<Konva.Layer>(`#${BRUSH_PREVIEW_LAYER_ID}`);
|
||||||
if (!layer) {
|
if (!layer) {
|
||||||
@ -227,6 +227,47 @@ export const renderLayers = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderBbox = (
|
||||||
|
stage: Konva.Stage,
|
||||||
|
tool: Tool,
|
||||||
|
selectedLayerId: string | null,
|
||||||
|
onBboxChanged: (layerId: string, bbox: IRect) => void
|
||||||
|
) => {
|
||||||
|
// Hide all bounding boxes
|
||||||
|
for (const bboxRect of stage.find<Konva.Rect>(`.${REGIONAL_PROMPT_LAYER_BBOX_NAME}`)) {
|
||||||
|
bboxRect.visible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No selected layer or not using the move tool - nothing more to do here
|
||||||
|
if (!selectedLayerId || tool !== 'move') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const konvaLayer = stage.findOne<Konva.Layer>(`#${selectedLayerId}`);
|
||||||
|
assert(konvaLayer, `Selected layer ${selectedLayerId} not found in stage`);
|
||||||
|
|
||||||
|
const bbox = getKonvaLayerBbox(konvaLayer, selectPromptLayerObjectGroup);
|
||||||
|
onBboxChanged(selectedLayerId, bbox);
|
||||||
|
|
||||||
|
let rect = konvaLayer.findOne<Konva.Rect>(`.${REGIONAL_PROMPT_LAYER_BBOX_NAME}`);
|
||||||
|
if (!rect) {
|
||||||
|
rect = new Konva.Rect({
|
||||||
|
id: getPromptRegionLayerBboxId(selectedLayerId),
|
||||||
|
name: REGIONAL_PROMPT_LAYER_BBOX_NAME,
|
||||||
|
strokeWidth: 1,
|
||||||
|
});
|
||||||
|
konvaLayer.add(rect);
|
||||||
|
}
|
||||||
|
rect.setAttrs({
|
||||||
|
visible: true,
|
||||||
|
x: bbox.x,
|
||||||
|
y: bbox.y,
|
||||||
|
width: bbox.width,
|
||||||
|
height: bbox.height,
|
||||||
|
stroke: selectedLayerId === selectedLayerId ? 'rgba(153, 187, 189, 1)' : 'rgba(255, 255, 255, 0.149)',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
||||||
return regionalPrompts.layers.find((l) => l.id === regionalPrompts.selectedLayer)?.color;
|
return regionalPrompts.layers.find((l) => l.id === regionalPrompts.selectedLayer)?.color;
|
||||||
});
|
});
|
||||||
@ -253,7 +294,7 @@ export const LogicalStage = ({ container }: Props) => {
|
|||||||
}, [stage, state.tool, cursorPosition, state.brushSize, selectedLayerColor]);
|
}, [stage, state.tool, cursorPosition, state.brushSize, selectedLayerColor]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
console.log('init effect');
|
console.log('Initializing stage');
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -263,12 +304,13 @@ export const LogicalStage = ({ container }: Props) => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
return () => {
|
return () => {
|
||||||
|
console.log('Cleaning up stage');
|
||||||
$stage.get()?.destroy();
|
$stage.get()?.destroy();
|
||||||
};
|
};
|
||||||
}, [container]);
|
}, [container]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
console.log('event effect');
|
console.log('Adding stage listeners');
|
||||||
if (!stage) {
|
if (!stage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -279,6 +321,7 @@ export const LogicalStage = ({ container }: Props) => {
|
|||||||
stage.on('mouseleave', onMouseLeave);
|
stage.on('mouseleave', onMouseLeave);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
console.log('Cleaning up stage listeners');
|
||||||
stage.off('mousedown', onMouseDown);
|
stage.off('mousedown', onMouseDown);
|
||||||
stage.off('mouseup', onMouseUp);
|
stage.off('mouseup', onMouseUp);
|
||||||
stage.off('mousemove', onMouseMove);
|
stage.off('mousemove', onMouseMove);
|
||||||
@ -288,7 +331,7 @@ export const LogicalStage = ({ container }: Props) => {
|
|||||||
}, [stage, onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave]);
|
}, [stage, onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
console.log('stage dims effect');
|
console.log('Updating stage dimensions');
|
||||||
if (!stage) {
|
if (!stage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -303,59 +346,28 @@ export const LogicalStage = ({ container }: Props) => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onBboxChanged = useCallback(
|
||||||
|
(layerId: string, bbox: IRect) => {
|
||||||
|
dispatch(layerBboxChanged({ layerId, bbox }));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
console.log('obj effect');
|
console.log('Rendering layers');
|
||||||
if (!stage) {
|
if (!stage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLayers(stage, state.layers, state.selectedLayer, getOnDragMove);
|
renderLayers(stage, state.layers, state.selectedLayer, getOnDragMove);
|
||||||
}, [getOnDragMove, stage, state.layers, state.selectedLayer]);
|
}, [getOnDragMove, stage, state.layers, state.selectedLayer]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (!stage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stage.container().style.cursor = state.tool === 'move' ? 'default' : 'none';
|
|
||||||
}, [stage, state.tool]);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
console.log('bbox effect');
|
console.log('bbox effect');
|
||||||
if (!stage) {
|
if (!stage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
renderBbox(stage, state.tool, state.selectedLayer, onBboxChanged);
|
||||||
if (state.tool !== 'move') {
|
}, [dispatch, stage, state.tool, state.selectedLayer, onBboxChanged]);
|
||||||
// Tool was just changed to something other than move - hide all layer bounding boxes
|
|
||||||
for (const n of stage.find('.layer-bbox')) {
|
|
||||||
n.visible(false);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const konvaLayer of stage.find<Konva.Layer>(`.${REGIONAL_PROMPT_LAYER_NAME}`)) {
|
|
||||||
const bbox = getKonvaLayerBbox(konvaLayer);
|
|
||||||
dispatch(layerBboxChanged({ layerId: konvaLayer.id(), bbox }));
|
|
||||||
let rect = konvaLayer.findOne<Konva.Rect>('.layer-bbox');
|
|
||||||
if (!rect) {
|
|
||||||
rect = new Konva.Rect({
|
|
||||||
id: `${konvaLayer.id()}-bbox`,
|
|
||||||
name: 'layer-bbox',
|
|
||||||
strokeWidth: 1,
|
|
||||||
});
|
|
||||||
konvaLayer.add(rect);
|
|
||||||
konvaLayer.on('mousedown', () => {
|
|
||||||
dispatch(layerSelected(konvaLayer.id()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
rect.visible(true);
|
|
||||||
rect.x(bbox.x);
|
|
||||||
rect.y(bbox.y);
|
|
||||||
rect.width(bbox.width);
|
|
||||||
rect.height(bbox.height);
|
|
||||||
rect.stroke(state.selectedLayer === konvaLayer.id() ? 'rgba(153, 187, 189, 1)' : 'rgba(255, 255, 255, 0.149)');
|
|
||||||
}
|
|
||||||
}, [dispatch, stage, state.tool, state.selectedLayer]);
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@ -369,11 +381,7 @@ export const StageComponent = () => {
|
|||||||
const container = useStore($container);
|
const container = useStore($container);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<chakra.div
|
<chakra.div ref={containerRef} tabIndex={-1} borderWidth={1} borderRadius="base" h="min-content" />
|
||||||
ref={containerRef}
|
|
||||||
tabIndex={-1}
|
|
||||||
sx={{ borderWidth: 1, borderRadius: 'base', flexGrow: 0, h: 'min-content' }}
|
|
||||||
/>
|
|
||||||
<LogicalStage container={container} />
|
<LogicalStage container={container} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -294,7 +294,9 @@ export const BRUSH_PREVIEW_BORDER_INNER_ID = 'brushPreviewBorderInner';
|
|||||||
export const BRUSH_PREVIEW_BORDER_OUTER_ID = 'brushPreviewBorderOuter';
|
export const BRUSH_PREVIEW_BORDER_OUTER_ID = 'brushPreviewBorderOuter';
|
||||||
export const REGIONAL_PROMPT_LAYER_NAME = 'regionalPromptLayer';
|
export const REGIONAL_PROMPT_LAYER_NAME = 'regionalPromptLayer';
|
||||||
export const REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME = 'regionalPromptLayerObjectGroup';
|
export const REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME = 'regionalPromptLayerObjectGroup';
|
||||||
|
export const REGIONAL_PROMPT_LAYER_BBOX_NAME = 'regionalPromptLayerBbox';
|
||||||
export const getPromptRegionLayerId = (layerId: string) => `layer_${layerId}`;
|
export const getPromptRegionLayerId = (layerId: string) => `layer_${layerId}`;
|
||||||
export const getPromptRegionLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`;
|
export const getPromptRegionLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`;
|
||||||
export const getPromptRegionLayerObjectGroupId = (layerId: string, groupId: string) =>
|
export const getPromptRegionLayerObjectGroupId = (layerId: string, groupId: string) =>
|
||||||
`${layerId}.objectGroup_${groupId}`;
|
`${layerId}.objectGroup_${groupId}`;
|
||||||
|
export const getPromptRegionLayerBboxId = (layerId: string) => `${layerId}.bbox`;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user