From 2aad3f89c3579792406db47ceaf2c04fd22a07cc Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Fri, 21 Jun 2024 12:52:39 +1000
Subject: [PATCH] feat(ui): inpaint mask UI components
---
.../components/ControlLayersPanelContent.tsx | 2 +
.../components/InpaintMask/IM.tsx | 26 +++++++++++
.../components/InpaintMask/IMActionsMenu.tsx | 28 +++++++++++
.../components/InpaintMask/IMHeader.tsx | 36 +++++++++++++++
.../InpaintMask/IMMaskFillColorPicker.tsx | 46 +++++++++++++++++++
.../components/InpaintMask/IMSettings.tsx | 8 ++++
.../controlLayers/components/ToolChooser.tsx | 9 +++-
.../controlLayers/store/canvasV2Slice.ts | 1 +
.../store/inpaintMaskReducers.ts | 2 +-
9 files changed, 156 insertions(+), 2 deletions(-)
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IM.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMActionsMenu.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMHeader.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMMaskFillColorPicker.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMSettings.tsx
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx
index 405bf43c21..9e771eb48e 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx
@@ -7,6 +7,7 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
import { CA } from 'features/controlLayers/components/ControlAdapter/CA';
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
+import { IM } from 'features/controlLayers/components/InpaintMask/IM';
import { IPA } from 'features/controlLayers/components/IPAdapter/IPA';
import { Layer } from 'features/controlLayers/components/Layer/Layer';
import { RG } from 'features/controlLayers/components/RegionalGuidance/RG';
@@ -34,6 +35,7 @@ export const ControlLayersPanelContent = memo(() => {
+
{entityCount > 0 && (
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IM.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IM.tsx
new file mode 100644
index 0000000000..3c0fb3b75b
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IM.tsx
@@ -0,0 +1,26 @@
+import { useDisclosure } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { rgbColorToString } from 'common/util/colorCodeTransformers';
+import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
+import { IMHeader } from 'features/controlLayers/components/InpaintMask/IMHeader';
+import { IMSettings } from 'features/controlLayers/components/InpaintMask/IMSettings';
+import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+
+export const IM = memo(() => {
+ const dispatch = useAppDispatch();
+ const selectedBorderColor = useAppSelector((s) => rgbColorToString(s.canvasV2.inpaintMask.fill));
+ const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === 'inpaint_mask');
+ const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
+ const onSelect = useCallback(() => {
+ dispatch(entitySelected({ id: 'inpaint_mask', type: 'inpaint_mask' }));
+ }, [dispatch]);
+ return (
+
+
+ {isOpen && }
+
+ );
+});
+
+IM.displayName = 'IM';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMActionsMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMActionsMenu.tsx
new file mode 100644
index 0000000000..14462abc73
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMActionsMenu.tsx
@@ -0,0 +1,28 @@
+import { Menu, MenuItem, MenuList } from '@invoke-ai/ui-library';
+import { useAppDispatch } from 'app/store/storeHooks';
+import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
+import { imReset } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
+
+export const IMActionsMenu = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const onReset = useCallback(() => {
+ dispatch(imReset());
+ }, [dispatch]);
+
+ return (
+
+ );
+});
+
+IMActionsMenu.displayName = 'IMActionsMenu';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMHeader.tsx
new file mode 100644
index 0000000000..1102e056f3
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMHeader.tsx
@@ -0,0 +1,36 @@
+import { Spacer } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
+import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
+import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
+import { IMActionsMenu } from 'features/controlLayers/components/InpaintMask/IMActionsMenu';
+import { imIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { IMMaskFillColorPicker } from './IMMaskFillColorPicker';
+
+type Props = {
+ onToggleVisibility: () => void;
+};
+
+export const IMHeader = memo(({ onToggleVisibility }: Props) => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const isEnabled = useAppSelector((s) => s.canvasV2.inpaintMask.isEnabled);
+ const onToggleIsEnabled = useCallback(() => {
+ dispatch(imIsEnabledToggled());
+ }, [dispatch]);
+
+ return (
+
+
+
+
+
+
+
+ );
+});
+
+IMHeader.displayName = 'IMHeader';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMMaskFillColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMMaskFillColorPicker.tsx
new file mode 100644
index 0000000000..144012143d
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMMaskFillColorPicker.tsx
@@ -0,0 +1,46 @@
+import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import RgbColorPicker from 'common/components/RgbColorPicker';
+import { rgbColorToString } from 'common/util/colorCodeTransformers';
+import { stopPropagation } from 'common/util/stopPropagation';
+import { imFillChanged } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import type { RgbColor } from 'react-colorful';
+import { useTranslation } from 'react-i18next';
+
+export const IMMaskFillColorPicker = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const fill = useAppSelector((s) => s.canvasV2.inpaintMask.fill);
+ const onChange = useCallback(
+ (fill: RgbColor) => {
+ dispatch(imFillChanged({ fill }));
+ },
+ [dispatch]
+ );
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+});
+
+IMMaskFillColorPicker.displayName = 'IMMaskFillColorPicker';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMSettings.tsx
new file mode 100644
index 0000000000..3c1b7296d9
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/IMSettings.tsx
@@ -0,0 +1,8 @@
+import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings';
+import { memo } from 'react';
+
+export const IMSettings = memo(() => {
+ return PLACEHOLDER;
+});
+
+IMSettings.displayName = 'IMSettings';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx
index 863a56c1bd..688be5f043 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx
@@ -3,6 +3,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
caDeleted,
+ imReset,
ipaDeleted,
layerDeleted,
layerReset,
@@ -85,9 +86,15 @@ export const ToolChooser: React.FC = () => {
if (type === 'regional_guidance') {
dispatch(rgReset({ id }));
}
+ if (type === 'inpaint_mask') {
+ dispatch(imReset());
+ }
}, [dispatch, selectedEntityIdentifier]);
const isResetEnabled = useMemo(
- () => selectedEntityIdentifier?.type === 'layer' || selectedEntityIdentifier?.type === 'regional_guidance',
+ () =>
+ selectedEntityIdentifier?.type === 'layer' ||
+ selectedEntityIdentifier?.type === 'regional_guidance' ||
+ selectedEntityIdentifier?.type === 'inpaint_mask',
[selectedEntityIdentifier]
);
useHotkeys('shift+c', resetSelectedLayer, { enabled: isResetEnabled }, [isResetEnabled, resetSelectedLayer]);
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts
index 9f282f9734..20bbeade0a 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts
@@ -319,6 +319,7 @@ export const {
imIsEnabledToggled,
imTranslated,
imBboxChanged,
+ imFillChanged,
imImageCacheChanged,
imBrushLineAdded,
imEraserLineAdded,
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts
index bff4a8a9bb..881028b0d6 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts
@@ -34,7 +34,7 @@ export const inpaintMaskReducers = {
state.inpaintMask.bbox = bbox;
state.inpaintMask.bboxNeedsUpdate = false;
},
- inpaintMaskFillChanged: (state, action: PayloadAction<{ fill: RgbColor }>) => {
+ imFillChanged: (state, action: PayloadAction<{ fill: RgbColor }>) => {
const { fill } = action.payload;
state.inpaintMask.fill = fill;
},