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": {