From ed7cfa73e44681ec959b436dca93bc73015905d6 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Wed, 18 Sep 2024 11:41:18 +1000
Subject: [PATCH] feat(ui): show only raster layers while staging
This is expose as a setting int he settings popover. On by default for distraction-free staging.
---
invokeai/frontend/web/public/locales/en.json | 3 +-
.../Settings/CanvasSettingsPopover.tsx | 2 +
...ShowOnlyRasterLayersWhileStagingSwitch.tsx | 29 +++++++++++++
.../CanvasEntity/CanvasEntityAdapterBase.ts | 29 +++++++++----
.../CanvasEntityObjectRenderer.ts | 4 +-
.../store/canvasSettingsSlice.ts | 12 ++++++
.../features/controlLayers/store/selectors.ts | 41 ++++++++++++++++++-
7 files changed, 107 insertions(+), 13 deletions(-)
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch.tsx
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index bfcf0bf842..a3c3da7eb2 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -1871,7 +1871,8 @@
"preserveMask": {
"label": "Preserve Masked Region",
"alert": "Preserving Masked Region"
- }
+ },
+ "showOnlyRasterLayersWhileStaging": "Show Only Raster Layers While Staging"
},
"HUD": {
"bbox": "Bbox",
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 3a0cef390e..bd0b7deeb6 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx
@@ -22,6 +22,7 @@ import { CanvasSettingsOutputOnlyMaskedRegionsCheckbox } from 'features/controlL
import { CanvasSettingsPreserveMaskCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsPreserveMaskCheckbox';
import { CanvasSettingsRecalculateRectsButton } from 'features/controlLayers/components/Settings/CanvasSettingsRecalculateRectsButton';
import { CanvasSettingsShowHUDSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsShowHUDSwitch';
+import { CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch';
import { CanvasSettingsShowProgressOnCanvas } from 'features/controlLayers/components/Settings/CanvasSettingsShowProgressOnCanvasSwitch';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -45,6 +46,7 @@ export const CanvasSettingsPopover = memo(() => {
+
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch.tsx
new file mode 100644
index 0000000000..de6dfefaba
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch.tsx
@@ -0,0 +1,29 @@
+import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import {
+ selectShowOnlyRasterLayersWhileStaging,
+ settingsShowOnlyRasterLayersWhileStagingToggled,
+} from 'features/controlLayers/store/canvasSettingsSlice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+
+export const CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const showOnlyRasterLayersWhileStaging = useAppSelector(selectShowOnlyRasterLayersWhileStaging);
+ const onChange = useCallback(() => {
+ dispatch(settingsShowOnlyRasterLayersWhileStagingToggled());
+ }, [dispatch]);
+
+ return (
+
+
+ {t('controlLayers.settings.showOnlyRasterLayersWhileStaging')}
+
+
+
+ );
+});
+
+CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch.displayName =
+ 'CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch';
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterBase.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterBase.ts
index deda8b28b2..22cccec092 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterBase.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterBase.ts
@@ -1,4 +1,6 @@
+import type { Selector } from '@reduxjs/toolkit';
import { createSelector } from '@reduxjs/toolkit';
+import type { RootState } from 'app/store/store';
import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone';
import type { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityBufferObjectRenderer';
@@ -7,7 +9,7 @@ import type { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/Ca
import type { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityTransformer';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
-import { getIsHiddenSelector, selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
+import { buildEntityIsHiddenSelector, selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier, CanvasRenderableEntityState, Rect } from 'features/controlLayers/store/types';
import Konva from 'konva';
import { atom, computed } from 'nanostores';
@@ -73,6 +75,8 @@ export abstract class CanvasEntityAdapterBase<
*/
abstract getHashableState: () => SerializableObject;
+ private _selectIsHidden: Selector | null = null;
+
/**
* The Konva nodes that make up the entity adapter:
* - A Konva.Layer to hold the everything
@@ -149,13 +153,7 @@ export abstract class CanvasEntityAdapterBase<
* - The entity's opacity/visibility
* - The entity's transformer interaction state, which will show/hide the entity's selection outline
*/
- this.subscriptions.add(
- this.manager.stateApi.createStoreSubscription(getIsHiddenSelector(this.entityIdentifier.type), (isHidden) => {
- this.$isHidden.set(isHidden);
- this.syncOpacity();
- this.transformer.syncInteractionState();
- })
- );
+ this.subscriptions.add(this.manager.stateApi.createStoreSubscription(this.selectIsHidden, this.syncVisibility));
}
/**
@@ -166,12 +164,21 @@ export abstract class CanvasEntityAdapterBase<
(canvas) => selectEntity(canvas, this.entityIdentifier) as T | undefined
);
+ // This must be a getter because the selector depends on the entityIdentifier, which is set in the constructor.
+ get selectIsHidden() {
+ if (!this._selectIsHidden) {
+ this._selectIsHidden = buildEntityIsHiddenSelector(this.entityIdentifier);
+ }
+ return this._selectIsHidden;
+ }
+
initialize = async () => {
this.log.debug('Initializing module');
await this.sync(this.manager.stateApi.runSelector(this.selectState), undefined);
this.transformer.initialize();
await this.renderer.initialize();
this.syncZIndices();
+ this.syncVisibility();
};
syncZIndices = () => {
@@ -224,6 +231,12 @@ export abstract class CanvasEntityAdapterBase<
this.renderer.updateOpacity();
};
+ syncVisibility = () => {
+ const isHidden = this.manager.stateApi.runSelector(this.selectIsHidden);
+ this.$isHidden.set(isHidden);
+ this.konva.layer.visible(!isHidden);
+ };
+
/**
* Synchronizes the entity's locked state with the canvas.
*/
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts
index bbad07f5e8..836bf35d2e 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts
@@ -247,9 +247,7 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
updateOpacity = () => {
this.log.trace('Updating opacity');
- const opacity = this.manager.stateApi.getIsTypeHidden(this.parent.entityIdentifier.type)
- ? 0
- : this.parent.state.opacity;
+ const opacity = this.parent.state.opacity;
if (this.konva.compositing) {
this.konva.compositing.group.opacity(opacity);
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts
index ad80408b49..0acde3798e 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts
@@ -72,6 +72,10 @@ type CanvasSettingsState = {
* Whether to preserve the masked region instead of inpainting it.
*/
preserveMask: boolean;
+ /**
+ * Whether to show only raster layers while staging.
+ */
+ showOnlyRasterLayersWhileStaging: boolean;
};
const initialState: CanvasSettingsState = {
@@ -90,6 +94,7 @@ const initialState: CanvasSettingsState = {
showProgressOnCanvas: true,
bboxOverlay: false,
preserveMask: false,
+ showOnlyRasterLayersWhileStaging: true,
};
export const canvasSettingsSlice = createSlice({
@@ -141,6 +146,9 @@ export const canvasSettingsSlice = createSlice({
settingsPreserveMaskToggled: (state) => {
state.preserveMask = !state.preserveMask;
},
+ settingsShowOnlyRasterLayersWhileStagingToggled: (state) => {
+ state.showOnlyRasterLayersWhileStaging = !state.showOnlyRasterLayersWhileStaging;
+ },
},
});
@@ -160,6 +168,7 @@ export const {
settingsShowProgressOnCanvasToggled,
settingsBboxOverlayToggled,
settingsPreserveMaskToggled,
+ settingsShowOnlyRasterLayersWhileStagingToggled,
} = canvasSettingsSlice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
@@ -192,3 +201,6 @@ export const selectSendToCanvas = createCanvasSettingsSelector((canvasSettings)
export const selectShowProgressOnCanvas = createCanvasSettingsSelector(
(canvasSettings) => canvasSettings.showProgressOnCanvas
);
+export const selectShowOnlyRasterLayersWhileStaging = createCanvasSettingsSelector(
+ (canvasSettings) => canvasSettings.showOnlyRasterLayersWhileStaging
+);
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts b/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts
index 46a82bd801..da0c5f5848 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts
@@ -1,5 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
+import { selectShowOnlyRasterLayersWhileStaging } from 'features/controlLayers/store/canvasSettingsSlice';
+import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import type {
CanvasControlLayerState,
@@ -11,6 +13,7 @@ import type {
CanvasRegionalGuidanceState,
CanvasState,
} from 'features/controlLayers/store/types';
+import { isRasterLayerEntityIdentifier } from 'features/controlLayers/store/types';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { assert } from 'tsafe';
@@ -269,7 +272,7 @@ const selectRegionalGuidanceIsHidden = createSelector(selectCanvasSlice, (canvas
/**
* Returns the hidden selector for the given entity type.
*/
-export const getIsHiddenSelector = (type: CanvasEntityType) => {
+const getSelectIsTypeHidden = (type: CanvasEntityType) => {
switch (type) {
case 'raster_layer':
return selectRasterLayersIsHidden;
@@ -284,6 +287,42 @@ export const getIsHiddenSelector = (type: CanvasEntityType) => {
}
};
+/**
+ * Builds a selector taht selects if the entity is hidden.
+ */
+export const buildEntityIsHiddenSelector = (entityIdentifier: CanvasEntityIdentifier) => {
+ const selectIsTypeHidden = getSelectIsTypeHidden(entityIdentifier.type);
+ return createSelector(
+ [selectCanvasSlice, selectIsTypeHidden, selectIsStaging, selectShowOnlyRasterLayersWhileStaging],
+ (canvas, isTypeHidden, isStaging, showOnlyRasterLayersWhileStaging) => {
+ const entity = selectEntityOrThrow(canvas, entityIdentifier);
+
+ // An entity is hidden if:
+ // - The entity type is hidden
+ // - The entity is disabled
+ // - The entity is locked
+ // - The entity is not a raster layer and we are staging and the option to show only raster layers is enabled
+
+ if (isTypeHidden) {
+ return true;
+ }
+ if (!entity.isEnabled) {
+ return true;
+ }
+ if (entity.isLocked) {
+ return true;
+ }
+ if (isStaging && showOnlyRasterLayersWhileStaging) {
+ // When staging, we only show raster layers. This allows the user to easily see how the new generation fits in
+ // with the rest of the canvas without the masks and control layers getting in the way.
+ return !isRasterLayerEntityIdentifier(entityIdentifier);
+ }
+
+ return false;
+ }
+ );
+};
+
export const selectWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.width);
export const selectHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.height);
export const selectAspectRatioID = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.aspectRatio.id);