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.
This commit is contained in:
psychedelicious
2024-09-18 11:41:18 +10:00
committed by Kent Keirsey
parent 4f8782f616
commit ed7cfa73e4
7 changed files with 107 additions and 13 deletions

View File

@@ -1871,7 +1871,8 @@
"preserveMask": {
"label": "Preserve Masked Region",
"alert": "Preserving Masked Region"
}
},
"showOnlyRasterLayersWhileStaging": "Show Only Raster Layers While Staging"
},
"HUD": {
"bbox": "Bbox",

View File

@@ -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(() => {
<CanvasSettingsOutputOnlyMaskedRegionsCheckbox />
<CanvasSettingsSnapToGridCheckbox />
<CanvasSettingsShowProgressOnCanvas />
<CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch />
<CanvasSettingsDynamicGridSwitch />
<CanvasSettingsBboxOverlaySwitch />
<CanvasSettingsShowHUDSwitch />

View File

@@ -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 (
<FormControl>
<FormLabel m={0} flexGrow={1}>
{t('controlLayers.settings.showOnlyRasterLayersWhileStaging')}
</FormLabel>
<Switch size="sm" isChecked={showOnlyRasterLayersWhileStaging} onChange={onChange} />
</FormControl>
);
});
CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch.displayName =
'CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch';

View File

@@ -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<RootState, boolean> | 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.
*/

View File

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

View File

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

View File

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