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);