diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageHeader.tsx index e366d0cad9..392580e3a1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageHeader.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageHeader.tsx @@ -10,7 +10,7 @@ import { selectRefImageEntityIds, } from 'features/controlLayers/store/refImagesSlice'; import { memo, useCallback, useMemo } from 'react'; -import { PiEyeBold, PiEyeSlashBold, PiTrashBold } from 'react-icons/pi'; +import { PiCircleBold, PiCircleFill, PiTrashBold } from 'react-icons/pi'; const textSx: SystemStyleObject = { color: 'base.300', @@ -41,7 +41,12 @@ export const RefImageHeader = memo(() => { Reference Image #{refImageNumber} - + + {!entity.isEnabled && ( + + Disabled + + )} { alignSelf="stretch" aria-label={entity.isEnabled ? 'Disable ref image' : 'Enable ref image'} onClick={toggleIsEnabled} - icon={entity.isEnabled ? : } - colorScheme={entity.isEnabled ? 'base' : 'warning'} + icon={entity.isEnabled ? : } /> { transform="translateX(-50%) translateY(-50%)" filter="drop-shadow(0px 0px 4px rgb(0, 0, 0)) drop-shadow(0px 0px 2px rgba(0, 0, 0, 1))" color="error.500" - boxSize={16} + boxSize={6} as={PiExclamationMarkBold} /> )} + {!entity.isEnabled && ( + + )} ); }); diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasInvertMaskHotkey.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasInvertMaskHotkey.ts new file mode 100644 index 0000000000..01698062e2 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasInvertMaskHotkey.ts @@ -0,0 +1,55 @@ +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; +import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy'; +import { inpaintMaskInverted } from 'features/controlLayers/store/canvasSlice'; +import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors'; +import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; +import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData'; +import { useCallback, useMemo } from 'react'; + +export const useCanvasInvertMaskHotkey = () => { + useAssertSingleton('useCanvasInvertMaskHotkey'); + const dispatch = useAppDispatch(); + const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier); + const canvasSlice = useAppSelector(selectCanvasSlice); + const isBusy = useCanvasIsBusy(); + + const handleInvertMask = useCallback(() => { + if (!selectedEntityIdentifier || selectedEntityIdentifier.type !== 'inpaint_mask') { + return; + } + + // Check if the selected entity has objects and there's a valid bounding box + const entity = canvasSlice.inpaintMasks.entities.find((entity) => entity.id === selectedEntityIdentifier.id); + const hasObjects = entity?.objects && entity.objects.length > 0; + const hasBbox = canvasSlice.bbox.rect.width > 0 && canvasSlice.bbox.rect.height > 0; + + if (!hasObjects || !hasBbox) { + return; + } + + dispatch( + inpaintMaskInverted({ entityIdentifier: selectedEntityIdentifier as CanvasEntityIdentifier<'inpaint_mask'> }) + ); + }, [dispatch, selectedEntityIdentifier, canvasSlice]); + + const isInvertMaskAllowed = useMemo(() => { + if (!selectedEntityIdentifier || selectedEntityIdentifier.type !== 'inpaint_mask') { + return false; + } + + const entity = canvasSlice.inpaintMasks.entities.find((entity) => entity.id === selectedEntityIdentifier.id); + const hasObjects = entity?.objects && entity.objects.length > 0; + const hasBbox = canvasSlice.bbox.rect.width > 0 && canvasSlice.bbox.rect.height > 0; + + return hasObjects && hasBbox; + }, [selectedEntityIdentifier, canvasSlice]); + + useRegisteredHotkeys({ + id: 'invertMask', + category: 'canvas', + callback: handleInvertMask, + options: { enabled: isInvertMaskAllowed && !isBusy, preventDefault: true }, + dependencies: [isInvertMaskAllowed, isBusy, handleInvertMask], + }); +};