diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 23ac5fa733a..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." @@ -599,6 +603,18 @@ "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." + }, + "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": { 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,