diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 50678dbfec..a0025df8c7 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -1817,9 +1817,8 @@
"settings": {
"snapToGrid": {
"label": "Snap to Grid",
- "off": "Off",
- "8": "8px",
- "64": "64px"
+ "on": "On",
+ "off": "Off"
}
},
"HUD": {
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUD.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUD.tsx
index 869e58f9cc..5e7d45eb7a 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUD.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUD.tsx
@@ -1,8 +1,8 @@
import { Grid } from '@invoke-ai/ui-library';
import { CanvasHUDItemAutoSave } from 'features/controlLayers/components/HUD/CanvasHUDItemAutoSave';
import { CanvasHUDItemBbox } from 'features/controlLayers/components/HUD/CanvasHUDItemBbox';
-import { CanvasHUDItemGridSize } from 'features/controlLayers/components/HUD/CanvasHUDItemGridSize';
import { CanvasHUDItemScaledBbox } from 'features/controlLayers/components/HUD/CanvasHUDItemScaledBbox';
+import { CanvasHUDItemSnapToGrid } from 'features/controlLayers/components/HUD/CanvasHUDItemSnapToGrid';
import { memo } from 'react';
export const CanvasHUD = memo(() => {
@@ -19,7 +19,7 @@ export const CanvasHUD = memo(() => {
>
-
+
);
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUDItemGridSize.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUDItemGridSize.tsx
deleted file mode 100644
index bdaea4eb5e..0000000000
--- a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUDItemGridSize.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { useAppSelector } from 'app/store/storeHooks';
-import { CanvasHUDItem } from 'features/controlLayers/components/HUD/CanvasHUDItem';
-import { selectGridSize } from 'features/controlLayers/store/canvasSettingsSlice';
-import { memo, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-
-export const CanvasHUDItemGridSize = memo(() => {
- const { t } = useTranslation();
- const snap = useAppSelector(selectGridSize);
- const snapString = useMemo(() => {
- switch (snap) {
- case 1:
- return t('controlLayers.settings.snapToGrid.off');
- case 8:
- return t('controlLayers.settings.snapToGrid.8');
- case 64:
- return t('controlLayers.settings.snapToGrid.64');
- }
- }, [snap, t]);
-
- return ;
-});
-
-CanvasHUDItemGridSize.displayName = 'CanvasHUDItemGridSize';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUDItemSnapToGrid.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUDItemSnapToGrid.tsx
new file mode 100644
index 0000000000..190942e0c8
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasHUDItemSnapToGrid.tsx
@@ -0,0 +1,19 @@
+import { useAppSelector } from 'app/store/storeHooks';
+import { CanvasHUDItem } from 'features/controlLayers/components/HUD/CanvasHUDItem';
+import { selectSnapToGrid } from 'features/controlLayers/store/canvasSettingsSlice';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+export const CanvasHUDItemSnapToGrid = memo(() => {
+ const { t } = useTranslation();
+ const snapToGrid = useAppSelector(selectSnapToGrid);
+
+ return (
+
+ );
+});
+
+CanvasHUDItemSnapToGrid.displayName = 'CanvasHUDItemSnapToGrid';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsGridSize.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsGridSize.tsx
index acef052638..91cebd4bd6 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsGridSize.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsGridSize.tsx
@@ -1,59 +1,24 @@
-import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
-import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
+import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { selectGridSize, settingsGridSizeChanged } from 'features/controlLayers/store/canvasSettingsSlice';
-import { isGridSize } from 'features/controlLayers/store/types';
-import { memo, useCallback, useMemo } from 'react';
+import { selectSnapToGrid, settingsSnapToGridToggled } from 'features/controlLayers/store/canvasSettingsSlice';
+import type { ChangeEventHandler } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-const getValue = (valueString: string) => {
- switch (valueString) {
- case 'off':
- return 1;
- case '8':
- return 8;
- case '64':
- return 64;
- default:
- return null;
- }
-};
-
-export const CanvasSettingsGridSize = memo(() => {
+export const CanvasSettingsSnapToGridCheckbox = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const gridSize = useAppSelector(selectGridSize);
- const options = useMemo(
- () => [
- { label: t('controlLayers.settings.snapToGrid.off'), value: 'off' },
- { label: t('controlLayers.settings.snapToGrid.8'), value: '8' },
- { label: t('controlLayers.settings.snapToGrid.64'), value: '64' },
- ],
- [t]
- );
- const onChange = useCallback(
- (v) => {
- if (!v) {
- return;
- }
- const value = getValue(v.value);
- if (!isGridSize(value)) {
- return;
- }
- dispatch(settingsGridSizeChanged(value));
- },
- [dispatch]
- );
- const value = useMemo(() => options.find((o) => getValue(o.value) === gridSize), [options, gridSize]);
+ const snapToGrid = useAppSelector(selectSnapToGrid);
+ const onChange = useCallback>(() => {
+ dispatch(settingsSnapToGridToggled());
+ }, [dispatch]);
return (
-
-
- {t('controlLayers.settings.snapToGrid.label')}
-
-
+
+ {t('controlLayers.settings.snapToGrid.label')}
+
);
});
-CanvasSettingsGridSize.displayName = 'CanvasSettingsSnapToGrid';
+CanvasSettingsSnapToGridCheckbox.displayName = 'CanvasSettingsSnapToGrid';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx
index ea55d6501f..ff697ae059 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx
@@ -15,7 +15,7 @@ import { CanvasSettingsClearHistoryButton } from 'features/controlLayers/compone
import { CanvasSettingsClipToBboxCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsClipToBboxCheckbox';
import { CanvasSettingsCompositeMaskedRegionsCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsCompositeMaskedRegionsCheckbox';
import { CanvasSettingsDynamicGridSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsDynamicGridSwitch';
-import { CanvasSettingsGridSize } from 'features/controlLayers/components/Settings/CanvasSettingsGridSize';
+import { CanvasSettingsSnapToGridCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsGridSize';
import { CanvasSettingsInvertScrollCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsInvertScrollCheckbox';
import { CanvasSettingsLogDebugInfoButton } from 'features/controlLayers/components/Settings/CanvasSettingsLogDebugInfo';
import { CanvasSettingsRecalculateRectsButton } from 'features/controlLayers/components/Settings/CanvasSettingsRecalculateRectsButton';
@@ -40,8 +40,8 @@ export const CanvasSettingsPopover = memo(() => {
+
-
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityTransformer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityTransformer.ts
index 389e2bd8f4..65413cd780 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityTransformer.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityTransformer.ts
@@ -277,7 +277,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
// If the user is not holding shift, the transform is retaining aspect ratio. It's not possible to snap to the grid
// in this case, because that would change the aspect ratio. So, we only snap to the grid when shift is held.
- const gridSize = this.manager.stateApi.$shiftKey.get() ? this.manager.stateApi.getSettings().gridSize : 1;
+ const gridSize = this.manager.stateApi.$shiftKey.get() ? this.manager.stateApi.getGridSize() : 1;
// We need to snap the anchor to the selected grid size, but the positions provided to this callback are absolute,
// scaled coordinates. They need to be converted to stage coordinates, snapped, then converted back to absolute
@@ -380,7 +380,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
onDragMove = () => {
// Snap the interaction rect to the grid
- const { gridSize } = this.manager.stateApi.getSettings();
+ const gridSize = this.manager.stateApi.getGridSize();
this.konva.proxyRect.x(roundToMultiple(this.konva.proxyRect.x(), gridSize));
this.konva.proxyRect.y(roundToMultiple(this.konva.proxyRect.y(), gridSize));
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts
index ab8ebba5cf..382f6c8816 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts
@@ -212,6 +212,18 @@ export class CanvasStateApiModule extends CanvasModuleBase {
return this.runSelector(selectCanvasSettingsSlice);
};
+ getGridSize = (): number => {
+ const snapToGrid = this.getSettings().snapToGrid;
+ if (!snapToGrid) {
+ return 1;
+ }
+ const useFine = this.$ctrlKey.get() || this.$metaKey.get();
+ if (useFine) {
+ return 8;
+ }
+ return 64;
+ };
+
/**
* Gets the regions state from redux.
*/
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts
index df91957585..9edcc482bc 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts
@@ -1,7 +1,7 @@
import type { PayloadAction, Selector } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
-import type { GridSize, RgbaColor } from 'features/controlLayers/store/types';
+import type { RgbaColor } from 'features/controlLayers/store/types';
type CanvasSettingsState = {
/**
@@ -59,7 +59,7 @@ type CanvasSettingsState = {
/**
* The snap-to-grid setting for the canvas.
*/
- gridSize: GridSize;
+ snapToGrid: boolean;
// TODO(psyche): These are copied from old canvas state, need to be implemented
// imageSmoothing: boolean;
// preserveMaskedArea: boolean;
@@ -78,7 +78,7 @@ const initialState: CanvasSettingsState = {
sendToCanvas: false,
compositeMaskedRegions: false,
autoProcessFilter: true,
- gridSize: 64,
+ snapToGrid: true,
};
export const canvasSettingsSlice = createSlice({
@@ -118,8 +118,8 @@ export const canvasSettingsSlice = createSlice({
settingsAutoProcessFilterToggled: (state) => {
state.autoProcessFilter = !state.autoProcessFilter;
},
- settingsGridSizeChanged: (state, action: PayloadAction) => {
- state.gridSize = action.payload;
+ settingsSnapToGridToggled: (state) => {
+ state.snapToGrid = !state.snapToGrid;
},
},
});
@@ -136,7 +136,7 @@ export const {
settingsSendToCanvasChanged,
settingsCompositeMaskedRegionsChanged,
settingsAutoProcessFilterToggled,
- settingsGridSizeChanged,
+ settingsSnapToGridToggled,
} = canvasSettingsSlice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
@@ -159,4 +159,4 @@ export const selectAutoSave = createCanvasSettingsSelector((settings) => setting
export const selectDynamicGrid = createCanvasSettingsSelector((settings) => settings.dynamicGrid);
export const selectShowHUD = createCanvasSettingsSelector((settings) => settings.showHUD);
export const selectAutoProcessFilter = createCanvasSettingsSelector((settings) => settings.autoProcessFilter);
-export const selectGridSize = createCanvasSettingsSelector((settings) => settings.gridSize);
+export const selectSnapToGrid = createCanvasSettingsSelector((settings) => settings.snapToGrid);