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);