From a274e6f16507dfed1505a8feeb456d64a8704462 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:08:03 +1000 Subject: [PATCH] feat(ui): decouple konva logic from nanostores --- .../components/StageComponent.tsx | 37 +-- .../features/controlLayers/konva/events.ts | 218 ++++++++++-------- .../controlLayers/store/controlLayersSlice.ts | 2 +- 3 files changed, 146 insertions(+), 111 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index 6782ccb36f..23e21392d9 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -20,12 +20,12 @@ import { $brushSpacingPx, $isDrawing, $isMouseDown, - $isSpaceDown, $lastAddedPoint, $lastCursorPos, $lastMouseDownPos, $selectedLayer, $shouldInvertBrushSizeScrollDirection, + $spaceKey, $stageAttrs, $tool, $toolBuffer, @@ -188,20 +188,27 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, const cleanup = setStageEventHandlers({ stage, - $tool, - $toolBuffer, - $isDrawing, - $isMouseDown, - $lastMouseDownPos, - $lastCursorPos, - $lastAddedPoint, - $stageAttrs, - $brushSize, - $brushColor, - $brushSpacingPx, - $selectedLayer, - $shouldInvertBrushSizeScrollDirection, - $isSpaceDown, + getTool: $tool.get, + setTool: $tool.set, + getToolBuffer: $toolBuffer.get, + setToolBuffer: $toolBuffer.set, + getIsDrawing: $isDrawing.get, + setIsDrawing: $isDrawing.set, + getIsMouseDown: $isMouseDown.get, + setIsMouseDown: $isMouseDown.set, + getBrushColor: $brushColor.get, + getBrushSize: $brushSize.get, + getBrushSpacingPx: $brushSpacingPx.get, + getSelectedLayer: $selectedLayer.get, + getLastAddedPoint: $lastAddedPoint.get, + setLastAddedPoint: $lastAddedPoint.set, + getLastCursorPos: $lastCursorPos.get, + setLastCursorPos: $lastCursorPos.set, + getLastMouseDownPos: $lastMouseDownPos.get, + setLastMouseDownPos: $lastMouseDownPos.set, + getShouldInvert: $shouldInvertBrushSizeScrollDirection.get, + getSpaceKey: $spaceKey.get, + setStageAttrs: $stageAttrs.set, onBrushSizeChanged, onBrushLineAdded, onEraserLineAdded, diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts index d4818bfee3..fcc4c5b14b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts @@ -1,6 +1,6 @@ import { calculateNewBrushSize } from 'features/canvas/hooks/useCanvasZoom'; import { CANVAS_SCALE_BY, MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/canvas/util/constants'; -import { getIsMouseDown, getScaledFlooredCursorPosition, snapPosToStage } from 'features/controlLayers/konva/util'; +import { getScaledFlooredCursorPosition, snapPosToStage } from 'features/controlLayers/konva/util'; import type { AddBrushLineArg, AddEraserLineArg, @@ -14,27 +14,33 @@ import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types'; import type Konva from 'konva'; import type { Vector2d } from 'konva/lib/types'; import { clamp } from 'lodash-es'; -import type { WritableAtom } from 'nanostores'; import type { RgbaColor } from 'react-colorful'; import { PREVIEW_TOOL_GROUP_ID } from './naming'; -type SetStageEventHandlersArg = { +type Arg = { stage: Konva.Stage; - $tool: WritableAtom; - $toolBuffer: WritableAtom; - $isDrawing: WritableAtom; - $isMouseDown: WritableAtom; - $lastMouseDownPos: WritableAtom; - $lastCursorPos: WritableAtom; - $lastAddedPoint: WritableAtom; - $stageAttrs: WritableAtom; - $brushColor: WritableAtom; - $brushSize: WritableAtom; - $brushSpacingPx: WritableAtom; - $selectedLayer: WritableAtom; - $shouldInvertBrushSizeScrollDirection: WritableAtom; - $isSpaceDown: WritableAtom; + getTool: () => Tool; + setTool: (tool: Tool) => void; + getToolBuffer: () => Tool | null; + setToolBuffer: (tool: Tool | null) => void; + getIsDrawing: () => boolean; + setIsDrawing: (isDrawing: boolean) => void; + getIsMouseDown: () => boolean; + setIsMouseDown: (isMouseDown: boolean) => void; + getLastMouseDownPos: () => Vector2d | null; + setLastMouseDownPos: (pos: Vector2d | null) => void; + getLastCursorPos: () => Vector2d | null; + setLastCursorPos: (pos: Vector2d | null) => void; + getLastAddedPoint: () => Vector2d | null; + setLastAddedPoint: (pos: Vector2d | null) => void; + setStageAttrs: (attrs: StageAttrs) => void; + getBrushColor: () => RgbaColor; + getBrushSize: () => number; + getBrushSpacingPx: () => number; + getSelectedLayer: () => Layer | null; + getShouldInvert: () => boolean; + getSpaceKey: () => boolean; onBrushLineAdded: (arg: AddBrushLineArg) => void; onEraserLineAdded: (arg: AddEraserLineArg) => void; onPointAddedToLine: (arg: AddPointToLineArg) => void; @@ -46,14 +52,14 @@ type SetStageEventHandlersArg = { * Updates the last cursor position atom with the current cursor position, returning the new position or `null` if the * cursor is not over the stage. * @param stage The konva stage - * @param $lastCursorPos The last cursor pos as a nanostores atom + * @param setLastCursorPos The callback to store the cursor pos */ -const updateLastCursorPos = (stage: Konva.Stage, $lastCursorPos: WritableAtom) => { +const updateLastCursorPos = (stage: Konva.Stage, setLastCursorPos: Arg['setLastCursorPos']) => { const pos = getScaledFlooredCursorPosition(stage); if (!pos) { return null; } - $lastCursorPos.set(pos); + setLastCursorPos(pos); return pos; }; @@ -68,51 +74,59 @@ const updateLastCursorPos = (stage: Konva.Stage, $lastCursorPos: WritableAtom, - $brushSpacingPx: WritableAtom, - onPointAddedToLine: (arg: AddPointToLineArg) => void + getLastAddedPoint: Arg['getLastAddedPoint'], + setLastAddedPoint: Arg['setLastAddedPoint'], + getBrushSpacingPx: Arg['getBrushSpacingPx'], + onPointAddedToLine: Arg['onPointAddedToLine'] ) => { // Continue the last line - const lastAddedPoint = $lastAddedPoint.get(); + const lastAddedPoint = getLastAddedPoint(); if (lastAddedPoint) { // Dispatching redux events impacts perf substantially - using brush spacing keeps dispatches to a reasonable number - if (Math.hypot(lastAddedPoint.x - currentPos.x, lastAddedPoint.y - currentPos.y) < $brushSpacingPx.get()) { + if (Math.hypot(lastAddedPoint.x - currentPos.x, lastAddedPoint.y - currentPos.y) < getBrushSpacingPx()) { return; } } - $lastAddedPoint.set(currentPos); + setLastAddedPoint(currentPos); onPointAddedToLine({ layerId, point: [currentPos.x, currentPos.y] }); }; export const setStageEventHandlers = ({ stage, - $tool, - $toolBuffer, - $isDrawing, - $isMouseDown, - $lastMouseDownPos, - $lastCursorPos, - $lastAddedPoint, - $stageAttrs, - $brushColor, - $brushSize, - $brushSpacingPx, - $selectedLayer, - $shouldInvertBrushSizeScrollDirection, - $isSpaceDown, + getTool, + setTool, + getToolBuffer, + setToolBuffer, + getIsDrawing, + setIsDrawing, + getIsMouseDown, + setIsMouseDown, + getLastMouseDownPos, + setLastMouseDownPos, + getLastCursorPos, + setLastCursorPos, + getLastAddedPoint, + setLastAddedPoint, + setStageAttrs, + getBrushColor, + getBrushSize, + getBrushSpacingPx, + getSelectedLayer, + getShouldInvert, + getSpaceKey, onBrushLineAdded, onEraserLineAdded, onPointAddedToLine, onRectShapeAdded, onBrushSizeChanged, -}: SetStageEventHandlersArg): (() => void) => { +}: Arg): (() => void) => { //#region mouseenter stage.on('mouseenter', (e) => { const stage = e.target.getStage(); if (!stage) { return; } - const tool = $tool.get(); + const tool = getTool(); stage.findOne(`#${PREVIEW_TOOL_GROUP_ID}`)?.visible(tool === 'brush' || tool === 'eraser'); }); @@ -122,10 +136,10 @@ export const setStageEventHandlers = ({ if (!stage) { return; } - $isMouseDown.set(true); - const tool = $tool.get(); - const pos = updateLastCursorPos(stage, $lastCursorPos); - const selectedLayer = $selectedLayer.get(); + setIsMouseDown(true); + const tool = getTool(); + const pos = updateLastCursorPos(stage, setLastCursorPos); + const selectedLayer = getSelectedLayer(); if (!pos || !selectedLayer) { return; } @@ -133,7 +147,7 @@ export const setStageEventHandlers = ({ return; } - if ($isSpaceDown.get()) { + if (getSpaceKey()) { // No drawing when space is down - we are panning the stage return; } @@ -142,10 +156,10 @@ export const setStageEventHandlers = ({ onBrushLineAdded({ layerId: selectedLayer.id, points: [pos.x, pos.y, pos.x, pos.y], - color: selectedLayer.type === 'raster_layer' ? $brushColor.get() : DEFAULT_RGBA_COLOR, + color: selectedLayer.type === 'raster_layer' ? getBrushColor() : DEFAULT_RGBA_COLOR, }); - $isDrawing.set(true); - $lastMouseDownPos.set(pos); + setIsDrawing(true); + setLastMouseDownPos(pos); } if (tool === 'eraser') { @@ -153,13 +167,13 @@ export const setStageEventHandlers = ({ layerId: selectedLayer.id, points: [pos.x, pos.y, pos.x, pos.y], }); - $isDrawing.set(true); - $lastMouseDownPos.set(pos); + setIsDrawing(true); + setLastMouseDownPos(pos); } if (tool === 'rect') { - $isDrawing.set(true); - $lastMouseDownPos.set(snapPosToStage(pos, stage)); + setIsDrawing(true); + setLastMouseDownPos(snapPosToStage(pos, stage)); } }); @@ -169,9 +183,9 @@ export const setStageEventHandlers = ({ if (!stage) { return; } - $isMouseDown.set(false); - const pos = $lastCursorPos.get(); - const selectedLayer = $selectedLayer.get(); + setIsMouseDown(false); + const pos = getLastCursorPos(); + const selectedLayer = getSelectedLayer(); if (!pos || !selectedLayer) { return; @@ -180,15 +194,15 @@ export const setStageEventHandlers = ({ return; } - if ($isSpaceDown.get()) { + if (getSpaceKey()) { // No drawing when space is down - we are panning the stage return; } - const tool = $tool.get(); + const tool = getTool(); if (tool === 'rect') { - const lastMouseDownPos = $lastMouseDownPos.get(); + const lastMouseDownPos = getLastMouseDownPos(); if (lastMouseDownPos) { const snappedPos = snapPosToStage(pos, stage); onRectShapeAdded({ @@ -199,13 +213,13 @@ export const setStageEventHandlers = ({ width: Math.abs(snappedPos.x - lastMouseDownPos.x), height: Math.abs(snappedPos.y - lastMouseDownPos.y), }, - color: selectedLayer.type === 'raster_layer' ? $brushColor.get() : DEFAULT_RGBA_COLOR, + color: selectedLayer.type === 'raster_layer' ? getBrushColor() : DEFAULT_RGBA_COLOR, }); } } - $isDrawing.set(false); - $lastMouseDownPos.set(null); + setIsDrawing(false); + setLastMouseDownPos(null); }); //#region mousemove @@ -214,9 +228,9 @@ export const setStageEventHandlers = ({ if (!stage) { return; } - const tool = $tool.get(); - const pos = updateLastCursorPos(stage, $lastCursorPos); - const selectedLayer = $selectedLayer.get(); + const tool = getTool(); + const pos = updateLastCursorPos(stage, setLastCursorPos); + const selectedLayer = getSelectedLayer(); stage.findOne(`#${PREVIEW_TOOL_GROUP_ID}`)?.visible(tool === 'brush' || tool === 'eraser'); @@ -227,38 +241,52 @@ export const setStageEventHandlers = ({ return; } - if ($isSpaceDown.get()) { + if (getSpaceKey()) { // No drawing when space is down - we are panning the stage return; } - if (!getIsMouseDown(e)) { + if (!getIsMouseDown()) { return; } if (tool === 'brush') { - if ($isDrawing.get()) { + if (getIsDrawing()) { // Continue the last line - maybeAddNextPoint(selectedLayer.id, pos, $lastAddedPoint, $brushSpacingPx, onPointAddedToLine); + maybeAddNextPoint( + selectedLayer.id, + pos, + getLastAddedPoint, + setLastAddedPoint, + getBrushSpacingPx, + onPointAddedToLine + ); } else { // Start a new line onBrushLineAdded({ layerId: selectedLayer.id, points: [pos.x, pos.y, pos.x, pos.y], - color: selectedLayer.type === 'raster_layer' ? $brushColor.get() : DEFAULT_RGBA_COLOR, + color: selectedLayer.type === 'raster_layer' ? getBrushColor() : DEFAULT_RGBA_COLOR, }); - $isDrawing.set(true); + setIsDrawing(true); } } if (tool === 'eraser') { - if ($isDrawing.get()) { + if (getIsDrawing()) { // Continue the last line - maybeAddNextPoint(selectedLayer.id, pos, $lastAddedPoint, $brushSpacingPx, onPointAddedToLine); + maybeAddNextPoint( + selectedLayer.id, + pos, + getLastAddedPoint, + setLastAddedPoint, + getBrushSpacingPx, + onPointAddedToLine + ); } else { // Start a new line onEraserLineAdded({ layerId: selectedLayer.id, points: [pos.x, pos.y, pos.x, pos.y] }); - $isDrawing.set(true); + setIsDrawing(true); } } }); @@ -269,12 +297,12 @@ export const setStageEventHandlers = ({ if (!stage) { return; } - const pos = updateLastCursorPos(stage, $lastCursorPos); - $isDrawing.set(false); - $lastCursorPos.set(null); - $lastMouseDownPos.set(null); - const selectedLayer = $selectedLayer.get(); - const tool = $tool.get(); + const pos = updateLastCursorPos(stage, setLastCursorPos); + setIsDrawing(false); + setLastCursorPos(null); + setLastMouseDownPos(null); + const selectedLayer = getSelectedLayer(); + const tool = getTool(); stage.findOne(`#${PREVIEW_TOOL_GROUP_ID}`)?.visible(false); @@ -284,11 +312,11 @@ export const setStageEventHandlers = ({ if (selectedLayer.type !== 'regional_guidance_layer' && selectedLayer.type !== 'raster_layer') { return; } - if ($isSpaceDown.get()) { + if (getSpaceKey()) { // No drawing when space is down - we are panning the stage return; } - if (getIsMouseDown(e)) { + if (getIsMouseDown()) { if (tool === 'brush') { onPointAddedToLine({ layerId: selectedLayer.id, point: [pos.x, pos.y] }); } @@ -304,11 +332,11 @@ export const setStageEventHandlers = ({ if (e.evt.ctrlKey || e.evt.metaKey) { let delta = e.evt.deltaY; - if ($shouldInvertBrushSizeScrollDirection.get()) { + if (getShouldInvert()) { delta = -delta; } // Holding ctrl or meta while scrolling changes the brush size - onBrushSizeChanged(calculateNewBrushSize($brushSize.get(), delta)); + onBrushSizeChanged(calculateNewBrushSize(getBrushSize(), delta)); } else { // We need the absolute cursor position - not the scaled position const cursorPos = stage.getPointerPosition(); @@ -332,12 +360,12 @@ export const setStageEventHandlers = ({ stage.scaleX(newScale); stage.scaleY(newScale); stage.position(newPos); - $stageAttrs.set({ ...newPos, width: stage.width(), height: stage.height(), scale: newScale }); + setStageAttrs({ ...newPos, width: stage.width(), height: stage.height(), scale: newScale }); } }); stage.on('dragmove', () => { - $stageAttrs.set({ + setStageAttrs({ x: stage.x(), y: stage.y(), width: stage.width(), @@ -350,7 +378,7 @@ export const setStageEventHandlers = ({ // Stage position should always be an integer, else we get fractional pixels which are blurry stage.x(Math.floor(stage.x())); stage.y(Math.floor(stage.y())); - $stageAttrs.set({ + setStageAttrs({ x: stage.x(), y: stage.y(), width: stage.width(), @@ -365,11 +393,11 @@ export const setStageEventHandlers = ({ } // Cancel shape drawing on escape if (e.key === 'Escape') { - $isDrawing.set(false); - $lastMouseDownPos.set(null); + setIsDrawing(false); + setLastMouseDownPos(null); } else if (e.key === ' ') { - $toolBuffer.set($tool.get()); - $tool.set('view'); + setToolBuffer(getTool()); + setTool('view'); } }; window.addEventListener('keydown', onKeyDown); @@ -380,9 +408,9 @@ export const setStageEventHandlers = ({ return; } if (e.key === ' ') { - const toolBuffer = $toolBuffer.get(); - $tool.set(toolBuffer ?? 'move'); - $toolBuffer.set(null); + const toolBuffer = getToolBuffer(); + setTool(toolBuffer ?? 'move'); + setToolBuffer(null); } }; window.addEventListener('keyup', onKeyUp); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts index 80a2a4cc4c..444eca8946 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts @@ -997,7 +997,7 @@ export const $toolBuffer = atom(null); export const $lastCursorPos = atom(null); export const $isPreviewVisible = atom(true); export const $lastAddedPoint = atom(null); -export const $isSpaceDown = atom(false); +export const $spaceKey = atom(false); export const $stageAttrs = atom({ x: 0, y: 0,