feat(ui): update move tool to show all bboxes, mouseover bbox strokes

This commit is contained in:
psychedelicious 2024-04-20 12:47:07 +10:00
parent 8a69fbd336
commit 39d036bb37
2 changed files with 87 additions and 51 deletions

View File

@ -9,6 +9,7 @@ import {
$tool, $tool,
isRPLayer, isRPLayer,
rpLayerBboxChanged, rpLayerBboxChanged,
rpLayerSelected,
rpLayerTranslated, rpLayerTranslated,
selectRegionalPromptsSlice, selectRegionalPromptsSlice,
} from 'features/regionalPrompts/store/regionalPromptsSlice'; } from 'features/regionalPrompts/store/regionalPromptsSlice';
@ -55,6 +56,13 @@ const useStageRenderer = (container: HTMLDivElement | null, wrapper: HTMLDivElem
[dispatch] [dispatch]
); );
const onBboxMouseDown = useCallback(
(layerId: string) => {
dispatch(rpLayerSelected(layerId));
},
[dispatch]
);
useLayoutEffect(() => { useLayoutEffect(() => {
log.trace('Initializing stage'); log.trace('Initializing stage');
if (!container) { if (!container) {
@ -138,8 +146,8 @@ const useStageRenderer = (container: HTMLDivElement | null, wrapper: HTMLDivElem
if (!stage) { if (!stage) {
return; return;
} }
renderBbox(stage, state.layers, state.selectedLayerId, tool, onBboxChanged); renderBbox(stage, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown);
}, [dispatch, stage, state.layers, state.selectedLayerId, tool, onBboxChanged]); }, [dispatch, stage, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown]);
}; };
const $container = atom<HTMLDivElement | null>(null); const $container = atom<HTMLDivElement | null>(null);

View File

@ -1,3 +1,4 @@
import { getStore } from 'app/store/nanostores/store';
import { rgbColorToString } from 'features/canvas/util/colorToString'; import { rgbColorToString } from 'features/canvas/util/colorToString';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition'; import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import type { Layer, RegionalPromptLayer, RPTool } from 'features/regionalPrompts/store/regionalPromptsSlice'; import type { Layer, RegionalPromptLayer, RPTool } from 'features/regionalPrompts/store/regionalPromptsSlice';
@ -22,9 +23,23 @@ import type { RgbColor } from 'react-colorful';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
const BBOX_SELECTED_STROKE = 'rgba(78, 190, 255, 1)';
const BBOX_NOT_SELECTED_STROKE = 'rgba(255, 255, 255, 0.353)';
const BBOX_NOT_SELECTED_MOUSEOVER_STROKE = 'rgba(255, 255, 255, 0.661)';
const BRUSH_PREVIEW_BORDER_INNER_COLOR = 'rgba(0,0,0,1)'; const BRUSH_PREVIEW_BORDER_INNER_COLOR = 'rgba(0,0,0,1)';
const BRUSH_PREVIEW_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)'; const BRUSH_PREVIEW_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)';
const GET_CLIENT_RECT_CONFIG = { skipTransform: true };
const mapId = (object: { id: string }) => object.id; const mapId = (object: { id: string }) => object.id;
const selectPromptLayerObjectGroup = (item: Node<NodeConfig>) => {
return item.name() !== REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME;
};
const getIsSelected = (layerId?: string | null) => {
if (!layerId) {
return false;
}
return layerId === getStore().getState().regionalPrompts.present.selectedLayerId;
};
/** /**
* Renders the brush preview for the selected tool. * Renders the brush preview for the selected tool.
@ -193,9 +208,9 @@ const renderRPLayer = (
stage.findOne<Konva.Layer>(`#${BRUSH_PREVIEW_LAYER_ID}`)?.moveToTop(); stage.findOne<Konva.Layer>(`#${BRUSH_PREVIEW_LAYER_ID}`)?.moveToTop();
} }
// Update the layer's position and listening state (only the selected layer is listening) // Update the layer's position and listening state
konvaLayer.setAttrs({ konvaLayer.setAttrs({
listening: rpLayer.id === selectedLayerIdId && tool === 'move', listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events
x: rpLayer.x, x: rpLayer.x,
y: rpLayer.y, y: rpLayer.y,
// There are rpLayers.length layers, plus a brush preview layer rendered on top of them, so the zIndex works // There are rpLayers.length layers, plus a brush preview layer rendered on top of them, so the zIndex works
@ -204,8 +219,10 @@ const renderRPLayer = (
}); });
const color = rgbColorToString(rpLayer.color); const color = rgbColorToString(rpLayer.color);
const konvaObjectGroup = konvaLayer.findOne<Konva.Group>(`.${REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME}`); const konvaObjectGroup = konvaLayer.findOne<Konva.Group>(`.${REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME}`);
assert(konvaObjectGroup, `Object group not found for layer ${rpLayer.id}`); assert(konvaObjectGroup, `Object group not found for layer ${rpLayer.id}`);
const transparencyRect = konvaLayer.findOne<Konva.Rect>(`#${getRPLayerTransparencyRectId(rpLayer.id)}`); const transparencyRect = konvaLayer.findOne<Konva.Rect>(`#${getRPLayerTransparencyRectId(rpLayer.id)}`);
assert(transparencyRect, `Transparency rect not found for layer ${rpLayer.id}`); assert(transparencyRect, `Transparency rect not found for layer ${rpLayer.id}`);
@ -299,11 +316,6 @@ export const renderLayers = (
} }
}; };
const selectPromptLayerObjectGroup = (item: Node<NodeConfig>) =>
item.name() !== REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME;
const GET_CLIENT_RECT_CONFIG = { skipTransform: true };
/** /**
* *
* @param stage The konva stage to render on. * @param stage The konva stage to render on.
@ -315,26 +327,23 @@ const GET_CLIENT_RECT_CONFIG = { skipTransform: true };
export const renderBbox = ( export const renderBbox = (
stage: Konva.Stage, stage: Konva.Stage,
reduxLayers: Layer[], reduxLayers: Layer[],
selectedLayerIdId: string | null, selectedLayerId: string | null,
tool: RPTool, tool: RPTool,
onBboxChanged: (layerId: string, bbox: IRect | null) => void onBboxChanged: (layerId: string, bbox: IRect | null) => void,
onBboxMouseDown: (layerId: string) => void
) => { ) => {
// Hide all bounding boxes // No selected layer or not using the move tool - nothing more to do here
if (tool !== 'move') {
for (const bboxRect of stage.find<Konva.Rect>(`.${REGIONAL_PROMPT_LAYER_BBOX_NAME}`)) { for (const bboxRect of stage.find<Konva.Rect>(`.${REGIONAL_PROMPT_LAYER_BBOX_NAME}`)) {
bboxRect.visible(false); bboxRect.visible(false);
bboxRect.listening(false); bboxRect.listening(false);
} }
// No selected layer or not using the move tool - nothing more to do here
if (!selectedLayerIdId || tool !== 'move') {
return; return;
} }
const reduxLayer = reduxLayers.find((layer) => layer.id === selectedLayerIdId); for (const reduxLayer of reduxLayers) {
assert(reduxLayer, `Selected layer ${selectedLayerIdId} not found in redux layers`); const konvaLayer = stage.findOne<Konva.Layer>(`#${reduxLayer.id}`);
assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`);
const konvaLayer = stage.findOne<Konva.Layer>(`#${selectedLayerIdId}`);
assert(konvaLayer, `Selected layer ${selectedLayerIdId} not found in stage`);
let bbox = reduxLayer.bbox; let bbox = reduxLayer.bbox;
@ -345,7 +354,8 @@ export const renderBbox = (
? getKonvaLayerBbox(konvaLayer, selectPromptLayerObjectGroup) ? getKonvaLayerBbox(konvaLayer, selectPromptLayerObjectGroup)
: konvaLayer.getClientRect(GET_CLIENT_RECT_CONFIG); : konvaLayer.getClientRect(GET_CLIENT_RECT_CONFIG);
onBboxChanged(selectedLayerIdId, bbox); // Update the layer's bbox in the redux store
onBboxChanged(reduxLayer.id, bbox);
} }
if (!bbox) { if (!bbox) {
@ -355,19 +365,37 @@ export const renderBbox = (
let rect = konvaLayer.findOne<Konva.Rect>(`.${REGIONAL_PROMPT_LAYER_BBOX_NAME}`); let rect = konvaLayer.findOne<Konva.Rect>(`.${REGIONAL_PROMPT_LAYER_BBOX_NAME}`);
if (!rect) { if (!rect) {
rect = new Konva.Rect({ rect = new Konva.Rect({
id: getPRLayerBboxId(selectedLayerIdId), id: getPRLayerBboxId(reduxLayer.id),
name: REGIONAL_PROMPT_LAYER_BBOX_NAME, name: REGIONAL_PROMPT_LAYER_BBOX_NAME,
strokeWidth: 1, strokeWidth: 1,
}); });
rect.on('mousedown', function () {
onBboxMouseDown(reduxLayer.id);
});
rect.on('mouseover', function (e) {
if (getIsSelected(e.target.getLayer()?.id())) {
this.stroke(BBOX_SELECTED_STROKE);
} else {
this.stroke(BBOX_NOT_SELECTED_MOUSEOVER_STROKE);
}
});
rect.on('mouseout', function (e) {
if (getIsSelected(e.target.getLayer()?.id())) {
this.stroke(BBOX_SELECTED_STROKE);
} else {
this.stroke(BBOX_NOT_SELECTED_STROKE);
}
});
konvaLayer.add(rect); konvaLayer.add(rect);
} }
rect.setAttrs({ rect.setAttrs({
visible: true, visible: true,
listening: true,
x: bbox.x, x: bbox.x,
y: bbox.y, y: bbox.y,
width: bbox.width, width: bbox.width,
height: bbox.height, height: bbox.height,
listening: true, stroke: reduxLayer.id === selectedLayerId ? BBOX_SELECTED_STROKE : BBOX_NOT_SELECTED_STROKE,
stroke: selectedLayerIdId === selectedLayerIdId ? 'rgba(153, 187, 189, 1)' : 'rgba(255, 255, 255, 0.149)',
}); });
}
}; };