From 1e906e68622bfc315767d41515223ef2dc62e0a7 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 7 Jul 2025 02:02:34 +0530 Subject: [PATCH 1/2] (ui) feat: add Snap to Grid to tool bar and give it a hotkey Shift+S will be the new hotkey for snap to grid toggle. --- invokeai/frontend/web/public/locales/en.json | 4 +++ .../components/Toolbar/CanvasToolbar.tsx | 5 +++ .../CanvasToolbarSnappingToolButton.tsx | 33 +++++++++++++++++++ .../hooks/useCanvasSnapToGridHotkey.ts | 19 +++++++++++ .../components/HotkeysModal/useHotkeyData.ts | 3 +- 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbarSnappingToolButton.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasSnapToGridHotkey.ts diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 23ac5fa733a..01bd91ea276 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -599,6 +599,10 @@ "toggleNonRasterLayers": { "title": "Toggle Non-Raster Layers", "desc": "Show or hide all non-raster layer categories (Control Layers, Inpaint Masks, Regional Guidance)." + }, + "snapToGrid": { + "title": "Snap to Grid", + "desc": "Snap the bounding box to canvas grid" } }, "workflows": { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx index 453d13b3c50..6387f8021ac 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx @@ -13,12 +13,15 @@ import { useCanvasDeleteLayerHotkey } from 'features/controlLayers/hooks/useCanv import { useCanvasEntityQuickSwitchHotkey } from 'features/controlLayers/hooks/useCanvasEntityQuickSwitchHotkey'; import { useCanvasFilterHotkey } from 'features/controlLayers/hooks/useCanvasFilterHotkey'; import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey'; +import { useCanvasSnapToGridHotkey } from 'features/controlLayers/hooks/useCanvasSnapToGridHotkey'; import { useCanvasToggleNonRasterLayersHotkey } from 'features/controlLayers/hooks/useCanvasToggleNonRasterLayersHotkey'; import { useCanvasTransformHotkey } from 'features/controlLayers/hooks/useCanvasTransformHotkey'; import { useCanvasUndoRedoHotkeys } from 'features/controlLayers/hooks/useCanvasUndoRedoHotkeys'; import { useNextPrevEntityHotkeys } from 'features/controlLayers/hooks/useNextPrevEntity'; import { memo } from 'react'; +import { CanvasToolbarSnappingToolButton } from './CanvasToolbarSnappingToolButton'; + export const CanvasToolbar = memo(() => { useCanvasResetLayerHotkey(); useCanvasDeleteLayerHotkey(); @@ -28,6 +31,7 @@ export const CanvasToolbar = memo(() => { useCanvasTransformHotkey(); useCanvasFilterHotkey(); useCanvasToggleNonRasterLayersHotkey(); + useCanvasSnapToGridHotkey(); return ( @@ -37,6 +41,7 @@ export const CanvasToolbar = memo(() => { + diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbarSnappingToolButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbarSnappingToolButton.tsx new file mode 100644 index 00000000000..478403a3844 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbarSnappingToolButton.tsx @@ -0,0 +1,33 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy'; +import { selectSnapToGrid, settingsSnapToGridToggled } from 'features/controlLayers/store/canvasSettingsSlice'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiGridFourBold } from 'react-icons/pi'; + +export const CanvasToolbarSnappingToolButton = memo(() => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const isBusy = useCanvasIsBusy(); + const snapToGrid = useAppSelector(selectSnapToGrid); + + const onClick = useCallback(() => { + dispatch(settingsSnapToGridToggled()); + }, [dispatch]); + + return ( + } + isDisabled={isBusy} + /> + ); +}); + +CanvasToolbarSnappingToolButton.displayName = 'CanvasToolbarSnappingToolButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasSnapToGridHotkey.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasSnapToGridHotkey.ts new file mode 100644 index 00000000000..767be324d85 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasSnapToGridHotkey.ts @@ -0,0 +1,19 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import { settingsSnapToGridToggled } from 'features/controlLayers/store/canvasSettingsSlice'; +import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData'; +import { useCallback } from 'react'; + +export const useCanvasSnapToGridHotkey = () => { + const dispatch = useAppDispatch(); + + const handleToggleSnapToGrid = useCallback(() => { + dispatch(settingsSnapToGridToggled()); + }, [dispatch]); + + useRegisteredHotkeys({ + id: 'snapToGrid', + category: 'canvas', + callback: handleToggleSnapToGrid, + dependencies: [handleToggleSnapToGrid], + }); +}; diff --git a/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts b/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts index 0241f45cecd..94259d317e1 100644 --- a/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts +++ b/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts @@ -123,6 +123,7 @@ export const useHotkeyData = (): HotkeysData => { addHotkey('canvas', 'applySegmentAnything', ['enter']); addHotkey('canvas', 'cancelSegmentAnything', ['esc']); addHotkey('canvas', 'toggleNonRasterLayers', ['shift+h']); + addHotkey('canvas', 'snapToGrid', ['shift+s']); // Workflows addHotkey('workflows', 'addNode', ['shift+a', 'space']); @@ -209,7 +210,7 @@ export const useRegisteredHotkeys = ({ id, category, callback, options, dependen enabled: data.isEnabled, } satisfies Options; } - // Otherwise, return the provided optiosn, but override the enabled state. + // Otherwise, return the provided options, but override the enabled state. return { ...options, enabled: data.isEnabled ? options.enabled : false, From 8350acbdb50969822ebe4d25460793558b6ea934 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 7 Jul 2025 02:07:43 +0530 Subject: [PATCH 2/2] chore: add some missing localization hotkey strings --- invokeai/frontend/web/public/locales/en.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 01bd91ea276..d91faf56831 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -431,6 +431,10 @@ "title": "Clear Queue", "desc": "Cancel and clear all queue items." }, + "selectGenerateTab": { + "title": "Select the Generate Tab", + "desc": "Selects the Generate tab." + }, "selectCanvasTab": { "title": "Select the Canvas Tab", "desc": "Selects the Canvas tab." @@ -602,7 +606,15 @@ }, "snapToGrid": { "title": "Snap to Grid", - "desc": "Snap the bounding box to canvas grid" + "desc": "Snap the bounding box to canvas grid." + }, + "applySegmentAnything": { + "title": "Apply Segment Anything", + "desc": "Apply the results of segmentation to the canvas." + }, + "cancelSegmentAnything": { + "title": "Cancel Segment Anything", + "desc": "Cancel the results of the segmentation." } }, "workflows": {