From 6ae46d7c8b1f79764a33018173cd3efe55bf3e3a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 7 Aug 2024 22:50:35 +1000 Subject: [PATCH] feat(ui): add manual scale controls --- invokeai/frontend/web/public/locales/en.json | 2 + .../controlLayers/components/BrushWidth.tsx | 2 +- .../controlLayers/components/CanvasScale.tsx | 114 ++++++++++++++++++ .../components/ControlLayersToolbar.tsx | 2 + .../controlLayers/components/EraserWidth.tsx | 2 +- .../controlLayers/konva/CanvasManager.ts | 78 ++++++++++++ .../features/controlLayers/konva/events.ts | 27 +---- 7 files changed, 201 insertions(+), 26 deletions(-) create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/CanvasScale.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 33c42d1a40..34a5d6ba9d 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1653,6 +1653,8 @@ "moveForward": "Move Forward", "moveBackward": "Move Backward", "brushSize": "Brush Size", + "width": "Width", + "zoom": "Zoom", "controlLayers": "Control Layers", "globalMaskOpacity": "Global Mask Opacity", "autoNegative": "Auto Negative", diff --git a/invokeai/frontend/web/src/features/controlLayers/components/BrushWidth.tsx b/invokeai/frontend/web/src/features/controlLayers/components/BrushWidth.tsx index fdb7b33d84..fa72e54259 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/BrushWidth.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/BrushWidth.tsx @@ -29,7 +29,7 @@ export const BrushWidth = memo(() => { ); return ( - {t('controlLayers.brushWidth')} + {t('controlLayers.width')} (isNaN(Number(v)) ? '' : `${round(Number(v), 2).toLocaleString()}%`); + +export const CanvasScale = memo(() => { + const { t } = useTranslation(); + const canvasManager = useStore($canvasManager); + const stageAttrs = useStore($stageAttrs); + const [localScale, setLocalScale] = useState(stageAttrs.scale * 100); + + const onChange = useCallback( + (scale: number) => { + if (!canvasManager) { + return; + } + canvasManager.setStageScale(scale / 100); + }, + [canvasManager] + ); + + const onReset = useCallback(() => { + if (!canvasManager) { + return; + } + + canvasManager.setStageScale(1); + }, [canvasManager]); + + const onBlur = useCallback(() => { + if (!canvasManager) { + return; + } + if (isNaN(Number(localScale))) { + return; + } + canvasManager.setStageScale(clamp(localScale / 100, MIN_CANVAS_SCALE, MAX_CANVAS_SCALE)); + }, [canvasManager, localScale]); + + const onChangeNumberInput = useCallback((valueAsString: string, valueAsNumber: number) => { + setLocalScale(valueAsNumber); + }, []); + + const onKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Enter') { + onBlur(); + } + }, + [onBlur] + ); + + useEffect(() => { + setLocalScale(stageAttrs.scale * 100); + }, [stageAttrs.scale]); + + return ( + + {t('controlLayers.zoom')} + + + + + + + + + + + + + + } variant="link" /> + + ); +}); + +CanvasScale.displayName = 'CanvasScale'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx index 1b8d52585e..e8d31eecf9 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx @@ -4,6 +4,7 @@ import { Flex, Switch } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppSelector } from 'app/store/storeHooks'; import { BrushWidth } from 'features/controlLayers/components/BrushWidth'; +import { CanvasScale } from 'features/controlLayers/components/CanvasScale'; import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover'; import { EraserWidth } from 'features/controlLayers/components/EraserWidth'; import { FillColorPicker } from 'features/controlLayers/components/FillColorPicker'; @@ -52,6 +53,7 @@ export const ControlLayersToolbar = memo(() => { {tool === 'brush' && } {tool === 'eraser' && } + debug diff --git a/invokeai/frontend/web/src/features/controlLayers/components/EraserWidth.tsx b/invokeai/frontend/web/src/features/controlLayers/components/EraserWidth.tsx index 0903763f2d..deff803bb1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/EraserWidth.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/EraserWidth.tsx @@ -29,7 +29,7 @@ export const EraserWidth = memo(() => { ); return ( - {t('controlLayers.eraserWidth')} + {t('controlLayers.width')} void) => { // We need the absolute cursor position - not the scaled position const cursorPos = stage.getPointerPosition(); if (cursorPos) { - // Stage's x and y scale are always the same - const stageScale = stage.scaleX(); // When wheeling on trackpad, e.evt.ctrlKey is true - in that case, let's reverse the direction const delta = e.evt.ctrlKey ? -e.evt.deltaY : e.evt.deltaY; - const mousePointTo = { - x: (cursorPos.x - stage.x()) / stageScale, - y: (cursorPos.y - stage.y()) / stageScale, - }; - const newScale = clamp(stageScale * CANVAS_SCALE_BY ** delta, MIN_CANVAS_SCALE, MAX_CANVAS_SCALE); - const newPos = { - x: cursorPos.x - mousePointTo.x * newScale, - y: cursorPos.y - mousePointTo.y * newScale, - }; - - stage.scaleX(newScale); - stage.scaleY(newScale); - stage.position(newPos); - $stageAttrs.set({ - x: newPos.x, - y: newPos.y, - width: stage.width(), - height: stage.height(), - scale: newScale, - }); - manager.background.render(); + const scale = manager.getStageScale() * CANVAS_SCALE_BY ** delta; + manager.setStageScale(scale, cursorPos); } } manager.preview.tool.render();