feat(ui): rework isolated previewing

Previously the setting was `showOnlyRasterLayersWhileStaging`. This has been renamed to `isolatedStagingPreview`. Works the same.

Also added `isolatedFilteringPreview` an `isolatedTransformingPreview`. These work the same way, but they isolate the current selected layer. There are toggles in the canvas settings popover _and_ the filter/transform popups (same setting).
This commit is contained in:
psychedelicious
2024-09-23 16:36:58 +10:00
committed by Kent Keirsey
parent 137624983c
commit fd7fa6f4ab
11 changed files with 195 additions and 54 deletions

View File

@@ -8,7 +8,9 @@ import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/kon
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer';
import {
selectAutoProcessFilter,
selectIsolatedFilteringPreview,
settingsAutoProcessFilterToggled,
settingsIsolatedFilteringPreviewToggled,
} from 'features/controlLayers/store/canvasSettingsSlice';
import type { FilterConfig } from 'features/controlLayers/store/filters';
import { IMAGE_FILTERS } from 'features/controlLayers/store/filters';
@@ -23,6 +25,10 @@ const FilterBox = memo(({ adapter }: { adapter: CanvasEntityAdapterRasterLayer |
const isProcessing = useStore(adapter.filterer.$isProcessing);
const hasProcessed = useStore(adapter.filterer.$hasProcessed);
const autoProcessFilter = useAppSelector(selectAutoProcessFilter);
const isolatedFilteringPreview = useAppSelector(selectIsolatedFilteringPreview);
const onChangeIsolatedPreview = useCallback(() => {
dispatch(settingsIsolatedFilteringPreviewToggled());
}, [dispatch]);
const onChangeFilterConfig = useCallback(
(filterConfig: FilterConfig) => {
@@ -59,7 +65,7 @@ const FilterBox = memo(({ adapter }: { adapter: CanvasEntityAdapterRasterLayer |
transitionProperty="height"
transitionDuration="normal"
>
<Flex w="full">
<Flex w="full" gap={4}>
<Heading size="md" color="base.300" userSelect="none">
{t('controlLayers.filter.filter')}
</Heading>
@@ -68,6 +74,10 @@ const FilterBox = memo(({ adapter }: { adapter: CanvasEntityAdapterRasterLayer |
<FormLabel m={0}>{t('controlLayers.filter.autoProcess')}</FormLabel>
<Switch size="sm" isChecked={autoProcessFilter} onChange={onChangeAutoProcessFilter} />
</FormControl>
<FormControl w="min-content">
<FormLabel m={0}>{t('controlLayers.settings.isolatedPreview')}</FormLabel>
<Switch size="sm" isChecked={isolatedFilteringPreview} onChange={onChangeIsolatedPreview} />
</FormControl>
</Flex>
<FilterTypeSelect filterType={config.type} onChange={onChangeFilterType} />
<FilterSettings filterConfig={config} onChange={onChangeFilterConfig} />

View File

@@ -0,0 +1,28 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
selectIsolatedFilteringPreview,
settingsIsolatedFilteringPreviewToggled,
} from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export const CanvasSettingsIsolatedFilteringPreviewSwitch = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isolatedFilteringPreview = useAppSelector(selectIsolatedFilteringPreview);
const onChange = useCallback(() => {
dispatch(settingsIsolatedFilteringPreviewToggled());
}, [dispatch]);
return (
<FormControl>
<FormLabel m={0} flexGrow={1}>
{t('controlLayers.settings.isolatedFilteringPreview')}
</FormLabel>
<Switch size="sm" isChecked={isolatedFilteringPreview} onChange={onChange} />
</FormControl>
);
});
CanvasSettingsIsolatedFilteringPreviewSwitch.displayName = 'CanvasSettingsIsolatedFilteringPreviewSwitch';

View File

@@ -0,0 +1,28 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
selectIsolatedStagingPreview,
settingsIsolatedStagingPreviewToggled,
} from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export const CanvasSettingsIsolatedStagingPreviewSwitch = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isolatedStagingPreview = useAppSelector(selectIsolatedStagingPreview);
const onChange = useCallback(() => {
dispatch(settingsIsolatedStagingPreviewToggled());
}, [dispatch]);
return (
<FormControl>
<FormLabel m={0} flexGrow={1}>
{t('controlLayers.settings.isolatedStagingPreview')}
</FormLabel>
<Switch size="sm" isChecked={isolatedStagingPreview} onChange={onChange} />
</FormControl>
);
});
CanvasSettingsIsolatedStagingPreviewSwitch.displayName = 'CanvasSettingsIsolatedStagingPreviewSwitch';

View File

@@ -0,0 +1,28 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
selectIsolatedTransformingPreview,
settingsIsolatedTransformingPreviewToggled,
} from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export const CanvasSettingsIsolatedTransformingPreviewSwitch = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isolatedTransformingPreview = useAppSelector(selectIsolatedTransformingPreview);
const onChange = useCallback(() => {
dispatch(settingsIsolatedTransformingPreviewToggled());
}, [dispatch]);
return (
<FormControl>
<FormLabel m={0} flexGrow={1}>
{t('controlLayers.settings.isolatedTransformingPreview')}
</FormLabel>
<Switch size="sm" isChecked={isolatedTransformingPreview} onChange={onChange} />
</FormControl>
);
});
CanvasSettingsIsolatedTransformingPreviewSwitch.displayName = 'CanvasSettingsIsolatedTransformingPreviewSwitch';

View File

@@ -16,12 +16,14 @@ import { CanvasSettingsClipToBboxCheckbox } from 'features/controlLayers/compone
import { CanvasSettingsDynamicGridSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsDynamicGridSwitch';
import { CanvasSettingsSnapToGridCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsGridSize';
import { CanvasSettingsInvertScrollCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsInvertScrollCheckbox';
import { CanvasSettingsIsolatedFilteringPreviewSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsIsolatedFilteringPreviewSwitch';
import { CanvasSettingsIsolatedStagingPreviewSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsIsolatedStagingPreviewSwitch';
import { CanvasSettingsIsolatedTransformingPreviewSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsIsolatedTransformingPreviewSwitch';
import { CanvasSettingsLogDebugInfoButton } from 'features/controlLayers/components/Settings/CanvasSettingsLogDebugInfo';
import { CanvasSettingsOutputOnlyMaskedRegionsCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsOutputOnlyMaskedRegionsCheckbox';
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';
@@ -49,7 +51,9 @@ export const CanvasSettingsPopover = memo(() => {
<CanvasSettingsOutputOnlyMaskedRegionsCheckbox />
<CanvasSettingsSnapToGridCheckbox />
<CanvasSettingsShowProgressOnCanvas />
<CanvasSettingsShowOnlyRasterLayersWhileStagingSwitch />
<CanvasSettingsIsolatedStagingPreviewSwitch />
<CanvasSettingsIsolatedFilteringPreviewSwitch />
<CanvasSettingsIsolatedTransformingPreviewSwitch />
<CanvasSettingsDynamicGridSwitch />
<CanvasSettingsBboxOverlaySwitch />
<CanvasSettingsShowHUDSwitch />

View File

@@ -1,29 +0,0 @@
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,14 +1,24 @@
import { Button, ButtonGroup, Flex, Heading, Spacer } from '@invoke-ai/ui-library';
import { Button, ButtonGroup, Flex, FormControl, FormLabel, Heading, Spacer, Switch } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEntity/types';
import { memo } from 'react';
import {
selectIsolatedTransformingPreview,
settingsIsolatedTransformingPreviewToggled,
} from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowsCounterClockwiseBold, PiArrowsOutBold, PiCheckBold, PiXBold } from 'react-icons/pi';
const TransformBox = memo(({ adapter }: { adapter: CanvasEntityAdapter }) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isProcessing = useStore(adapter.transformer.$isProcessing);
const isolatedTransformingPreview = useAppSelector(selectIsolatedTransformingPreview);
const onChangeIsolatedPreview = useCallback(() => {
dispatch(settingsIsolatedTransformingPreviewToggled());
}, [dispatch]);
return (
<Flex
@@ -23,9 +33,16 @@ const TransformBox = memo(({ adapter }: { adapter: CanvasEntityAdapter }) => {
transitionProperty="height"
transitionDuration="normal"
>
<Heading size="md" color="base.300" userSelect="none">
{t('controlLayers.transform.transform')}
</Heading>
<Flex w="full" gap={4}>
<Heading size="md" color="base.300" userSelect="none">
{t('controlLayers.transform.transform')}
</Heading>
<Spacer />
<FormControl w="min-content">
<FormLabel m={0}>{t('controlLayers.settings.isolatedPreview')}</FormLabel>
<Switch size="sm" isChecked={isolatedTransformingPreview} onChange={onChangeIsolatedPreview} />
</FormControl>
</Flex>
<ButtonGroup isAttached={false} size="sm" w="full">
<Button
leftIcon={<PiArrowsOutBold />}

View File

@@ -9,6 +9,10 @@ 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 {
selectIsolatedFilteringPreview,
selectIsolatedTransformingPreview,
} from 'features/controlLayers/store/canvasSettingsSlice';
import { buildEntityIsHiddenSelector, selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier, CanvasRenderableEntityState, Rect } from 'features/controlLayers/store/types';
import Konva from 'konva';
@@ -148,12 +152,22 @@ export abstract class CanvasEntityAdapterBase<
this.state = state;
/**
* When a group is hidden or shown, we need to update a few things:
* - The entity's individual isHidden flag
* - The entity's opacity/visibility
* - The entity's transformer interaction state, which will show/hide the entity's selection outline
* There are a number of reason we may need to show or hide a layer:
* - The entity is enabled/disabled
* - The entity type is hidden/shown
* - Staging status changes and `isolatedStagingPreview` is enabled
* - Global filtering status changes and `isolatedFilteringPreview` is enabled
* - Global transforming status changes and `isolatedTransformingPreview` is enabled
*/
this.subscriptions.add(this.manager.stateApi.createStoreSubscription(this.selectIsHidden, this.syncVisibility));
this.subscriptions.add(
this.manager.stateApi.createStoreSubscription(selectIsolatedFilteringPreview, this.syncVisibility)
);
this.subscriptions.add(this.manager.stateApi.$filteringAdapter.listen(this.syncVisibility));
this.subscriptions.add(
this.manager.stateApi.createStoreSubscription(selectIsolatedTransformingPreview, this.syncVisibility)
);
this.subscriptions.add(this.manager.stateApi.$transformingAdapter.listen(this.syncVisibility));
/**
* The tool preview may need to be updated when the entity is locked or disabled. For example, when we disable the
@@ -238,7 +252,23 @@ export abstract class CanvasEntityAdapterBase<
};
syncVisibility = () => {
const isHidden = this.manager.stateApi.runSelector(this.selectIsHidden);
let isHidden = this.manager.stateApi.runSelector(this.selectIsHidden);
// Handle isolated preview modes - if another entity is filtering or transforming, we may need to hide this entity.
if (this.manager.stateApi.runSelector(selectIsolatedFilteringPreview)) {
const filteringEntityIdentifier = this.manager.stateApi.$filteringAdapter.get()?.entityIdentifier;
if (filteringEntityIdentifier && filteringEntityIdentifier.id !== this.id) {
isHidden = true;
}
}
if (this.manager.stateApi.runSelector(selectIsolatedTransformingPreview)) {
const transformingEntityIdentifier = this.manager.stateApi.$transformingAdapter.get()?.entityIdentifier;
if (transformingEntityIdentifier && transformingEntityIdentifier.id !== this.id) {
isHidden = true;
}
}
this.$isHidden.set(isHidden);
this.konva.layer.visible(!isHidden);
};

View File

@@ -69,7 +69,15 @@ type CanvasSettingsState = {
/**
* Whether to show only raster layers while staging.
*/
showOnlyRasterLayersWhileStaging: boolean;
isolatedStagingPreview: boolean;
/**
* Whether to show only the selected layer while filtering.
*/
isolatedFilteringPreview: boolean;
/**
* Whether to show only the selected layer while transforming.
*/
isolatedTransformingPreview: boolean;
};
const initialState: CanvasSettingsState = {
@@ -87,7 +95,9 @@ const initialState: CanvasSettingsState = {
showProgressOnCanvas: true,
bboxOverlay: false,
preserveMask: false,
showOnlyRasterLayersWhileStaging: true,
isolatedStagingPreview: true,
isolatedFilteringPreview: true,
isolatedTransformingPreview: true,
};
export const canvasSettingsSlice = createSlice({
@@ -136,8 +146,14 @@ export const canvasSettingsSlice = createSlice({
settingsPreserveMaskToggled: (state) => {
state.preserveMask = !state.preserveMask;
},
settingsShowOnlyRasterLayersWhileStagingToggled: (state) => {
state.showOnlyRasterLayersWhileStaging = !state.showOnlyRasterLayersWhileStaging;
settingsIsolatedStagingPreviewToggled: (state) => {
state.isolatedStagingPreview = !state.isolatedStagingPreview;
},
settingsIsolatedFilteringPreviewToggled: (state) => {
state.isolatedFilteringPreview = !state.isolatedFilteringPreview;
},
settingsIsolatedTransformingPreviewToggled: (state) => {
state.isolatedTransformingPreview = !state.isolatedTransformingPreview;
},
},
});
@@ -157,7 +173,9 @@ export const {
settingsShowProgressOnCanvasToggled,
settingsBboxOverlayToggled,
settingsPreserveMaskToggled,
settingsShowOnlyRasterLayersWhileStagingToggled,
settingsIsolatedStagingPreviewToggled,
settingsIsolatedFilteringPreviewToggled,
settingsIsolatedTransformingPreviewToggled,
} = canvasSettingsSlice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
@@ -189,6 +207,10 @@ export const selectSendToCanvas = createCanvasSettingsSelector((canvasSettings)
export const selectShowProgressOnCanvas = createCanvasSettingsSelector(
(canvasSettings) => canvasSettings.showProgressOnCanvas
);
export const selectShowOnlyRasterLayersWhileStaging = createCanvasSettingsSelector(
(canvasSettings) => canvasSettings.showOnlyRasterLayersWhileStaging
export const selectIsolatedStagingPreview = createCanvasSettingsSelector((settings) => settings.isolatedStagingPreview);
export const selectIsolatedFilteringPreview = createCanvasSettingsSelector(
(settings) => settings.isolatedFilteringPreview
);
export const selectIsolatedTransformingPreview = createCanvasSettingsSelector(
(settings) => settings.isolatedTransformingPreview
);

View File

@@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import { selectShowOnlyRasterLayersWhileStaging } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectIsolatedStagingPreview } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import type {
@@ -294,8 +294,8 @@ const getSelectIsTypeHidden = (type: CanvasEntityType) => {
export const buildEntityIsHiddenSelector = (entityIdentifier: CanvasEntityIdentifier) => {
const selectIsTypeHidden = getSelectIsTypeHidden(entityIdentifier.type);
return createSelector(
[selectCanvasSlice, selectIsTypeHidden, selectIsStaging, selectShowOnlyRasterLayersWhileStaging],
(canvas, isTypeHidden, isStaging, showOnlyRasterLayersWhileStaging) => {
[selectCanvasSlice, selectIsTypeHidden, selectIsStaging, selectIsolatedStagingPreview],
(canvas, isTypeHidden, isStaging, isolatedStagingPreview) => {
const entity = selectEntity(canvas, entityIdentifier);
// An entity is hidden if:
@@ -311,7 +311,7 @@ export const buildEntityIsHiddenSelector = (entityIdentifier: CanvasEntityIdenti
if (!entity.isEnabled) {
return true;
}
if (isStaging && showOnlyRasterLayersWhileStaging) {
if (isStaging && isolatedStagingPreview) {
// 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);