diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index ad245191b44..74b36c913d7 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -2180,7 +2180,8 @@ "rgReferenceImagesNotSupported": "regional Reference Images not supported for selected base model", "rgAutoNegativeNotSupported": "Auto-Negative not supported for selected base model", "rgNoRegion": "no region drawn", - "fluxFillIncompatibleWithControlLoRA": "Control LoRA is not compatible with FLUX Fill" + "fluxFillIncompatibleWithControlLoRA": "Control LoRA is not compatible with FLUX Fill", + "bboxHidden": "Bounding box is hidden (shift+o to toggle)" }, "errors": { "unableToFindImage": "Unable to find image", diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsBboxVisibility.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsBboxVisibility.tsx new file mode 100644 index 00000000000..32b51e888e2 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsBboxVisibility.tsx @@ -0,0 +1,24 @@ +import { Alert, AlertIcon, AlertTitle } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; +import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +export const CanvasAlertsBboxVisibility = memo(() => { + const { t } = useTranslation(); + const canvasManager = useCanvasManager(); + const isBboxHidden = useStore(canvasManager.tool.tools.bbox.$isBboxHidden); + + if (!isBboxHidden) { + return null; + } + + return ( + + + {t('controlLayers.warnings.bboxHidden')} + + ); +}); + +CanvasAlertsBboxVisibility.displayName = 'CanvasAlertsBboxVisibility'; 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 1e7affd2bd1..2781f6e58f3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx @@ -15,6 +15,7 @@ import { useCanvasEntityQuickSwitchHotkey } from 'features/controlLayers/hooks/u import { useCanvasFilterHotkey } from 'features/controlLayers/hooks/useCanvasFilterHotkey'; import { useCanvasInvertMaskHotkey } from 'features/controlLayers/hooks/useCanvasInvertMaskHotkey'; import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey'; +import { useCanvasToggleBboxHotkey } from 'features/controlLayers/hooks/useCanvasToggleBboxHotkey'; import { useCanvasToggleNonRasterLayersHotkey } from 'features/controlLayers/hooks/useCanvasToggleNonRasterLayersHotkey'; import { useCanvasTransformHotkey } from 'features/controlLayers/hooks/useCanvasTransformHotkey'; import { useCanvasUndoRedoHotkeys } from 'features/controlLayers/hooks/useCanvasUndoRedoHotkeys'; @@ -31,6 +32,7 @@ export const CanvasToolbar = memo(() => { useCanvasFilterHotkey(); useCanvasInvertMaskHotkey(); useCanvasToggleNonRasterLayersHotkey(); + useCanvasToggleBboxHotkey(); return ( diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasToggleBboxHotkey.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasToggleBboxHotkey.ts new file mode 100644 index 00000000000..6dda338af52 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasToggleBboxHotkey.ts @@ -0,0 +1,18 @@ +import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; +import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData'; +import { useCallback } from 'react'; + +export const useCanvasToggleBboxHotkey = () => { + const canvasManager = useCanvasManager(); + + const handleToggleBboxVisibility = useCallback(() => { + canvasManager.tool.tools.bbox.toggleBboxVisibility(); + }, [canvasManager]); + + useRegisteredHotkeys({ + id: 'toggleBbox', + category: 'canvas', + callback: handleToggleBboxVisibility, + dependencies: [handleToggleBboxVisibility], + }); +}; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts index 0ed1c20eb4b..10f12f0c183 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts @@ -66,6 +66,11 @@ export class CanvasBboxToolModule extends CanvasModuleBase { */ $aspectRatioBuffer = atom(1); + /** + * Buffer to store the visibility of the bbox. + */ + $isBboxHidden = atom(false); + constructor(parent: CanvasToolModule) { super(); this.id = getPrefixedId(this.type); @@ -191,6 +196,9 @@ export class CanvasBboxToolModule extends CanvasModuleBase { // Update on busy state changes this.subscriptions.add(this.manager.$isBusy.listen(this.render)); + + // Listen for stage changes to update the bbox's visibility + this.subscriptions.add(this.$isBboxHidden.listen(this.render)); } // This is a noop. The cursor is changed when the cursor enters or leaves the bbox. @@ -206,13 +214,15 @@ export class CanvasBboxToolModule extends CanvasModuleBase { }; /** - * Renders the bbox. The bbox is only visible when the tool is set to 'bbox'. + * Renders the bbox. */ render = () => { const tool = this.manager.tool.$tool.get(); const { x, y, width, height } = this.manager.stateApi.runSelector(selectBbox).rect; + this.konva.group.visible(!this.$isBboxHidden.get()); + // We need to reach up to the preview layer to enable/disable listening so that the bbox can be interacted with. // If the mangaer is busy, we disable listening so the bbox cannot be interacted with. this.konva.group.listening(tool === 'bbox' && !this.manager.$isBusy.get()); @@ -478,4 +488,8 @@ export class CanvasBboxToolModule extends CanvasModuleBase { this.subscriptions.clear(); this.konva.group.destroy(); }; + + toggleBboxVisibility = () => { + this.$isBboxHidden.set(!this.$isBboxHidden.get()); + }; } 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 954c95447dd..99ee36fde39 100644 --- a/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts +++ b/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts @@ -126,6 +126,7 @@ export const useHotkeyData = (): HotkeysData => { addHotkey('canvas', 'cancelSegmentAnything', ['esc']); addHotkey('canvas', 'toggleNonRasterLayers', ['shift+h']); addHotkey('canvas', 'fitBboxToMasks', ['shift+b']); + addHotkey('canvas', 'toggleBbox', ['shift+o']); // Workflows addHotkey('workflows', 'addNode', ['shift+a', 'space']); diff --git a/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx b/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx index dc6672437f4..2c8a516f982 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx @@ -1,5 +1,6 @@ import { ContextMenu, Divider, Flex, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; +import { CanvasAlertsBboxVisibility } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsBboxVisibility'; import { CanvasAlertsInvocationProgress } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress'; import { CanvasAlertsPreserveMask } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsPreserveMask'; import { CanvasAlertsSaveAllImagesToGallery } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSaveAllImagesToGallery'; @@ -92,6 +93,7 @@ export const CanvasWorkspacePanel = memo(() => { +