feat(ui): split out settings state from canvas rendering state

This commit is contained in:
psychedelicious 2024-08-26 22:02:56 +10:00
parent 3af6d79852
commit a58b91b221
13 changed files with 82 additions and 58 deletions

View File

@ -6,6 +6,7 @@ import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone';
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
import { canvasSettingsPersistConfig, canvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice';
import { toolPersistConfig, toolSlice } from 'features/controlLayers/store/toolSlice';
@ -61,6 +62,7 @@ const allReducers = {
[stylePresetSlice.name]: stylePresetSlice.reducer,
[paramsSlice.name]: paramsSlice.reducer,
[toolSlice.name]: toolSlice.reducer,
[canvasSettingsSlice.name]: canvasSettingsSlice.reducer,
};
const rootReducer = combineReducers(allReducers);
@ -104,6 +106,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
[stylePresetPersistConfig.name]: stylePresetPersistConfig,
[paramsPersistConfig.name]: paramsPersistConfig,
[toolPersistConfig.name]: toolPersistConfig,
[canvasSettingsPersistConfig.name]: canvasSettingsPersistConfig,
};
const unserialize: UnserializeFunction = (data, key) => {

View File

@ -1,13 +1,13 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { settingsAutoSaveToggled } from 'features/controlLayers/store/canvasV2Slice';
import { settingsAutoSaveToggled } from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export const CanvasSettingsAutoSaveCheckbox = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const autoSave = useAppSelector((s) => s.canvasV2.settings.autoSave);
const autoSave = useAppSelector((s) => s.canvasSettings.autoSave);
const onChange = useCallback(() => dispatch(settingsAutoSaveToggled()), [dispatch]);
return (
<FormControl w="full">

View File

@ -1,6 +1,6 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { clipToBboxChanged } from 'features/controlLayers/store/canvasV2Slice';
import { clipToBboxChanged } from 'features/controlLayers/store/canvasSettingsSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
export const CanvasSettingsClipToBboxCheckbox = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const clipToBbox = useAppSelector((s) => s.canvasV2.settings.clipToBbox);
const clipToBbox = useAppSelector((s) => s.canvasSettings.clipToBbox);
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)),
[dispatch]

View File

@ -1,13 +1,13 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { settingsDynamicGridToggled } from 'features/controlLayers/store/canvasV2Slice';
import { settingsDynamicGridToggled } from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export const CanvasSettingsDynamicGridSwitch = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const dynamicGrid = useAppSelector((s) => s.canvasV2.settings.dynamicGrid);
const dynamicGrid = useAppSelector((s) => s.canvasSettings.dynamicGrid);
const onChange = useCallback(() => {
dispatch(settingsDynamicGridToggled());
}, [dispatch]);

View File

@ -52,7 +52,7 @@ type Props = {
};
export const StageComponent = memo(({ asPreview = false }: Props) => {
const dynamicGrid = useAppSelector((s) => s.canvasV2.settings.dynamicGrid);
const dynamicGrid = useAppSelector((s) => s.canvasSettings.dynamicGrid);
const [stage] = useState(
() =>

View File

@ -4,6 +4,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { CanvasSettingsState } from 'features/controlLayers/store/canvasSettingsSlice';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import type { Logger } from 'roarr';
@ -17,6 +18,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
subscriptions = new Set<() => void>();
state: CanvasV2State | null = null;
settings: CanvasSettingsState | null = null;
constructor(manager: CanvasManager) {
super();
@ -29,20 +31,24 @@ export class CanvasRenderingModule extends CanvasModuleBase {
render = async () => {
const state = this.manager.stateApi.getCanvasState();
const settings = this.manager.stateApi.getSettings();
if (!this.state) {
if (!this.state || !this.settings) {
this.log.trace('First render');
}
const prevState = this.state;
this.state = state;
if (prevState === state) {
const prevSettings = this.settings;
this.settings = settings;
if (prevState === state && prevSettings === settings) {
// No changes to state - no need to render
return;
}
this.renderBackground(state, prevState);
this.renderBackground(settings, prevSettings);
await this.renderRasterLayers(state, prevState);
await this.renderControlLayers(prevState, state);
await this.renderRegionalGuidance(prevState, state);
@ -57,7 +63,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
this.manager.stateApi.$currentFill.set(this.manager.stateApi.getCurrentFill());
// We have no prev state for the first render
if (!prevState) {
if (!prevState && !prevSettings) {
this.manager.setCanvasManager();
}
};
@ -66,8 +72,8 @@ export class CanvasRenderingModule extends CanvasModuleBase {
return { ...this.manager.getLoggingContext(), path: this.manager.path.join('.') };
};
renderBackground = (state: CanvasV2State, prevState: CanvasV2State | null) => {
if (!prevState || state.settings.dynamicGrid !== prevState.settings.dynamicGrid) {
renderBackground = (settings: CanvasSettingsState, prevSettings: CanvasSettingsState | null) => {
if (!prevSettings || settings.dynamicGrid !== prevSettings.dynamicGrid) {
this.manager.background.render();
}
};

View File

@ -150,7 +150,7 @@ export class CanvasStateApiModule extends CanvasModuleBase {
return this.store.getState().tool;
};
getSettings = () => {
return this.getCanvasState().settings;
return this.store.getState().canvasSettings;
};
getRegionsState = () => {
return this.getCanvasState().regions;

View File

@ -0,0 +1,53 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig } from 'app/store/store';
export type CanvasSettingsState = {
imageSmoothing: boolean;
showHUD: boolean;
autoSave: boolean;
preserveMaskedArea: boolean;
cropToBboxOnSave: boolean;
clipToBbox: boolean;
dynamicGrid: boolean;
};
const initialState: CanvasSettingsState = {
// TODO(psyche): These are copied from old canvas state, need to be implemented
autoSave: false,
imageSmoothing: true,
preserveMaskedArea: false,
showHUD: true,
clipToBbox: false,
cropToBboxOnSave: false,
dynamicGrid: false,
};
export const canvasSettingsSlice = createSlice({
name: 'canvasSettings',
initialState,
reducers: {
clipToBboxChanged: (state, action: PayloadAction<boolean>) => {
state.clipToBbox = action.payload;
},
settingsDynamicGridToggled: (state) => {
state.dynamicGrid = !state.dynamicGrid;
},
settingsAutoSaveToggled: (state) => {
state.autoSave = !state.autoSave;
},
},
});
export const { clipToBboxChanged, settingsAutoSaveToggled, settingsDynamicGridToggled } = canvasSettingsSlice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrate = (state: any): any => {
return state;
};
export const canvasSettingsPersistConfig: PersistConfig<CanvasSettingsState> = {
name: canvasSettingsSlice.name,
initialState,
migrate,
persistDenylist: [],
};

View File

@ -14,7 +14,6 @@ import { rasterLayersReducers } from 'features/controlLayers/store/rasterLayersR
import { regionsReducers } from 'features/controlLayers/store/regionsReducers';
import { selectAllEntities, selectAllEntitiesOfType, selectEntity } from 'features/controlLayers/store/selectors';
import { sessionReducers } from 'features/controlLayers/store/sessionReducers';
import { settingsReducers } from 'features/controlLayers/store/settingsReducers';
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify';
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
@ -66,16 +65,6 @@ const initialState: CanvasV2State = {
height: 512,
},
},
settings: {
// TODO(psyche): These are copied from old canvas state, need to be implemented
autoSave: false,
imageSmoothing: true,
preserveMaskedArea: false,
showHUD: true,
clipToBbox: false,
cropToBboxOnSave: false,
dynamicGrid: false,
},
session: {
mode: 'generate',
isStaging: false,
@ -97,7 +86,6 @@ export const canvasV2Slice = createSlice({
...bboxReducers,
// move out
...lorasReducers,
...settingsReducers,
...sessionReducers,
entitySelected: (state, action: PayloadAction<EntityIdentifierPayload>) => {
const { entityIdentifier } = action.payload;
@ -390,10 +378,7 @@ export const canvasV2Slice = createSlice({
});
export const {
clipToBboxChanged,
canvasReset,
settingsDynamicGridToggled,
settingsAutoSaveToggled,
// All entities
entitySelected,
entityNameChanged,

View File

@ -1,14 +0,0 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import type { CanvasV2State } from 'features/controlLayers/store/types';
export const settingsReducers = {
clipToBboxChanged: (state, action: PayloadAction<boolean>) => {
state.settings.clipToBbox = action.payload;
},
settingsDynamicGridToggled: (state) => {
state.settings.dynamicGrid = !state.settings.dynamicGrid;
},
settingsAutoSaveToggled: (state) => {
state.settings.autoSave = !state.settings.autoSave;
},
} satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -715,15 +715,6 @@ export type CanvasV2State = {
entities: CanvasIPAdapterState[];
};
loras: LoRA[];
settings: {
imageSmoothing: boolean;
showHUD: boolean;
autoSave: boolean;
preserveMaskedArea: boolean;
cropToBboxOnSave: boolean;
clipToBbox: boolean;
dynamicGrid: boolean;
};
bbox: {
rect: {
x: number;

View File

@ -31,8 +31,8 @@ export const buildSD1Graph = async (
const generationMode = manager.compositor.getGenerationMode();
log.debug({ generationMode }, 'Building SD1/SD2 graph');
const { canvasV2, params } = state;
const { bbox, session, settings } = canvasV2;
const { canvasV2, params, canvasSettings } = state;
const { bbox, session } = canvasV2;
const {
model,
@ -274,7 +274,7 @@ export const buildSD1Graph = async (
canvasOutput = addWatermarker(g, canvasOutput);
}
const shouldSaveToGallery = session.mode === 'generate' || settings.autoSave;
const shouldSaveToGallery = session.mode === 'generate' || canvasSettings.autoSave;
g.updateNode(canvasOutput, {
id: getPrefixedId('canvas_output'),

View File

@ -31,8 +31,8 @@ export const buildSDXLGraph = async (
const generationMode = manager.compositor.getGenerationMode();
log.debug({ generationMode }, 'Building SDXL graph');
const { params, canvasV2 } = state;
const { bbox, session, settings } = canvasV2;
const { params, canvasV2, canvasSettings } = state;
const { bbox, session } = canvasV2;
const {
model,
@ -277,7 +277,7 @@ export const buildSDXLGraph = async (
canvasOutput = addWatermarker(g, canvasOutput);
}
const shouldSaveToGallery = session.mode === 'generate' || settings.autoSave;
const shouldSaveToGallery = session.mode === 'generate' || canvasSettings.autoSave;
g.updateNode(canvasOutput, {
id: getPrefixedId('canvas_output'),