mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): split out settings state from canvas rendering state
This commit is contained in:
parent
3af6d79852
commit
a58b91b221
@ -6,6 +6,7 @@ import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
|||||||
import type { SerializableObject } from 'common/types';
|
import type { SerializableObject } from 'common/types';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
||||||
|
import { canvasSettingsPersistConfig, canvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice';
|
import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice';
|
||||||
import { toolPersistConfig, toolSlice } from 'features/controlLayers/store/toolSlice';
|
import { toolPersistConfig, toolSlice } from 'features/controlLayers/store/toolSlice';
|
||||||
@ -61,6 +62,7 @@ const allReducers = {
|
|||||||
[stylePresetSlice.name]: stylePresetSlice.reducer,
|
[stylePresetSlice.name]: stylePresetSlice.reducer,
|
||||||
[paramsSlice.name]: paramsSlice.reducer,
|
[paramsSlice.name]: paramsSlice.reducer,
|
||||||
[toolSlice.name]: toolSlice.reducer,
|
[toolSlice.name]: toolSlice.reducer,
|
||||||
|
[canvasSettingsSlice.name]: canvasSettingsSlice.reducer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const rootReducer = combineReducers(allReducers);
|
const rootReducer = combineReducers(allReducers);
|
||||||
@ -104,6 +106,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
|||||||
[stylePresetPersistConfig.name]: stylePresetPersistConfig,
|
[stylePresetPersistConfig.name]: stylePresetPersistConfig,
|
||||||
[paramsPersistConfig.name]: paramsPersistConfig,
|
[paramsPersistConfig.name]: paramsPersistConfig,
|
||||||
[toolPersistConfig.name]: toolPersistConfig,
|
[toolPersistConfig.name]: toolPersistConfig,
|
||||||
|
[canvasSettingsPersistConfig.name]: canvasSettingsPersistConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
const unserialize: UnserializeFunction = (data, key) => {
|
const unserialize: UnserializeFunction = (data, key) => {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
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 { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const CanvasSettingsAutoSaveCheckbox = memo(() => {
|
export const CanvasSettingsAutoSaveCheckbox = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const autoSave = useAppSelector((s) => s.canvasV2.settings.autoSave);
|
const autoSave = useAppSelector((s) => s.canvasSettings.autoSave);
|
||||||
const onChange = useCallback(() => dispatch(settingsAutoSaveToggled()), [dispatch]);
|
const onChange = useCallback(() => dispatch(settingsAutoSaveToggled()), [dispatch]);
|
||||||
return (
|
return (
|
||||||
<FormControl w="full">
|
<FormControl w="full">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
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 type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
export const CanvasSettingsClipToBboxCheckbox = memo(() => {
|
export const CanvasSettingsClipToBboxCheckbox = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const clipToBbox = useAppSelector((s) => s.canvasV2.settings.clipToBbox);
|
const clipToBbox = useAppSelector((s) => s.canvasSettings.clipToBbox);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)),
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)),
|
||||||
[dispatch]
|
[dispatch]
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
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 { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const CanvasSettingsDynamicGridSwitch = memo(() => {
|
export const CanvasSettingsDynamicGridSwitch = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const dynamicGrid = useAppSelector((s) => s.canvasV2.settings.dynamicGrid);
|
const dynamicGrid = useAppSelector((s) => s.canvasSettings.dynamicGrid);
|
||||||
const onChange = useCallback(() => {
|
const onChange = useCallback(() => {
|
||||||
dispatch(settingsDynamicGridToggled());
|
dispatch(settingsDynamicGridToggled());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
@ -52,7 +52,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const StageComponent = memo(({ asPreview = false }: 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(
|
const [stage] = useState(
|
||||||
() =>
|
() =>
|
||||||
|
@ -4,6 +4,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
|||||||
import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
||||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
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 { CanvasV2State } from 'features/controlLayers/store/types';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
|
|||||||
subscriptions = new Set<() => void>();
|
subscriptions = new Set<() => void>();
|
||||||
|
|
||||||
state: CanvasV2State | null = null;
|
state: CanvasV2State | null = null;
|
||||||
|
settings: CanvasSettingsState | null = null;
|
||||||
|
|
||||||
constructor(manager: CanvasManager) {
|
constructor(manager: CanvasManager) {
|
||||||
super();
|
super();
|
||||||
@ -29,20 +31,24 @@ export class CanvasRenderingModule extends CanvasModuleBase {
|
|||||||
|
|
||||||
render = async () => {
|
render = async () => {
|
||||||
const state = this.manager.stateApi.getCanvasState();
|
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');
|
this.log.trace('First render');
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevState = this.state;
|
const prevState = this.state;
|
||||||
this.state = 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
|
// No changes to state - no need to render
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderBackground(state, prevState);
|
this.renderBackground(settings, prevSettings);
|
||||||
await this.renderRasterLayers(state, prevState);
|
await this.renderRasterLayers(state, prevState);
|
||||||
await this.renderControlLayers(prevState, state);
|
await this.renderControlLayers(prevState, state);
|
||||||
await this.renderRegionalGuidance(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());
|
this.manager.stateApi.$currentFill.set(this.manager.stateApi.getCurrentFill());
|
||||||
|
|
||||||
// We have no prev state for the first render
|
// We have no prev state for the first render
|
||||||
if (!prevState) {
|
if (!prevState && !prevSettings) {
|
||||||
this.manager.setCanvasManager();
|
this.manager.setCanvasManager();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -66,8 +72,8 @@ export class CanvasRenderingModule extends CanvasModuleBase {
|
|||||||
return { ...this.manager.getLoggingContext(), path: this.manager.path.join('.') };
|
return { ...this.manager.getLoggingContext(), path: this.manager.path.join('.') };
|
||||||
};
|
};
|
||||||
|
|
||||||
renderBackground = (state: CanvasV2State, prevState: CanvasV2State | null) => {
|
renderBackground = (settings: CanvasSettingsState, prevSettings: CanvasSettingsState | null) => {
|
||||||
if (!prevState || state.settings.dynamicGrid !== prevState.settings.dynamicGrid) {
|
if (!prevSettings || settings.dynamicGrid !== prevSettings.dynamicGrid) {
|
||||||
this.manager.background.render();
|
this.manager.background.render();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -150,7 +150,7 @@ export class CanvasStateApiModule extends CanvasModuleBase {
|
|||||||
return this.store.getState().tool;
|
return this.store.getState().tool;
|
||||||
};
|
};
|
||||||
getSettings = () => {
|
getSettings = () => {
|
||||||
return this.getCanvasState().settings;
|
return this.store.getState().canvasSettings;
|
||||||
};
|
};
|
||||||
getRegionsState = () => {
|
getRegionsState = () => {
|
||||||
return this.getCanvasState().regions;
|
return this.getCanvasState().regions;
|
||||||
|
@ -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: [],
|
||||||
|
};
|
@ -14,7 +14,6 @@ import { rasterLayersReducers } from 'features/controlLayers/store/rasterLayersR
|
|||||||
import { regionsReducers } from 'features/controlLayers/store/regionsReducers';
|
import { regionsReducers } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { selectAllEntities, selectAllEntitiesOfType, selectEntity } from 'features/controlLayers/store/selectors';
|
import { selectAllEntities, selectAllEntitiesOfType, selectEntity } from 'features/controlLayers/store/selectors';
|
||||||
import { sessionReducers } from 'features/controlLayers/store/sessionReducers';
|
import { sessionReducers } from 'features/controlLayers/store/sessionReducers';
|
||||||
import { settingsReducers } from 'features/controlLayers/store/settingsReducers';
|
|
||||||
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
|
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
|
||||||
import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify';
|
import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify';
|
||||||
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
||||||
@ -66,16 +65,6 @@ const initialState: CanvasV2State = {
|
|||||||
height: 512,
|
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: {
|
session: {
|
||||||
mode: 'generate',
|
mode: 'generate',
|
||||||
isStaging: false,
|
isStaging: false,
|
||||||
@ -97,7 +86,6 @@ export const canvasV2Slice = createSlice({
|
|||||||
...bboxReducers,
|
...bboxReducers,
|
||||||
// move out
|
// move out
|
||||||
...lorasReducers,
|
...lorasReducers,
|
||||||
...settingsReducers,
|
|
||||||
...sessionReducers,
|
...sessionReducers,
|
||||||
entitySelected: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
entitySelected: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
const { entityIdentifier } = action.payload;
|
const { entityIdentifier } = action.payload;
|
||||||
@ -390,10 +378,7 @@ export const canvasV2Slice = createSlice({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
clipToBboxChanged,
|
|
||||||
canvasReset,
|
canvasReset,
|
||||||
settingsDynamicGridToggled,
|
|
||||||
settingsAutoSaveToggled,
|
|
||||||
// All entities
|
// All entities
|
||||||
entitySelected,
|
entitySelected,
|
||||||
entityNameChanged,
|
entityNameChanged,
|
||||||
|
@ -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>;
|
|
@ -715,15 +715,6 @@ export type CanvasV2State = {
|
|||||||
entities: CanvasIPAdapterState[];
|
entities: CanvasIPAdapterState[];
|
||||||
};
|
};
|
||||||
loras: LoRA[];
|
loras: LoRA[];
|
||||||
settings: {
|
|
||||||
imageSmoothing: boolean;
|
|
||||||
showHUD: boolean;
|
|
||||||
autoSave: boolean;
|
|
||||||
preserveMaskedArea: boolean;
|
|
||||||
cropToBboxOnSave: boolean;
|
|
||||||
clipToBbox: boolean;
|
|
||||||
dynamicGrid: boolean;
|
|
||||||
};
|
|
||||||
bbox: {
|
bbox: {
|
||||||
rect: {
|
rect: {
|
||||||
x: number;
|
x: number;
|
||||||
|
@ -31,8 +31,8 @@ export const buildSD1Graph = async (
|
|||||||
const generationMode = manager.compositor.getGenerationMode();
|
const generationMode = manager.compositor.getGenerationMode();
|
||||||
log.debug({ generationMode }, 'Building SD1/SD2 graph');
|
log.debug({ generationMode }, 'Building SD1/SD2 graph');
|
||||||
|
|
||||||
const { canvasV2, params } = state;
|
const { canvasV2, params, canvasSettings } = state;
|
||||||
const { bbox, session, settings } = canvasV2;
|
const { bbox, session } = canvasV2;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
model,
|
model,
|
||||||
@ -274,7 +274,7 @@ export const buildSD1Graph = async (
|
|||||||
canvasOutput = addWatermarker(g, canvasOutput);
|
canvasOutput = addWatermarker(g, canvasOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldSaveToGallery = session.mode === 'generate' || settings.autoSave;
|
const shouldSaveToGallery = session.mode === 'generate' || canvasSettings.autoSave;
|
||||||
|
|
||||||
g.updateNode(canvasOutput, {
|
g.updateNode(canvasOutput, {
|
||||||
id: getPrefixedId('canvas_output'),
|
id: getPrefixedId('canvas_output'),
|
||||||
|
@ -31,8 +31,8 @@ export const buildSDXLGraph = async (
|
|||||||
const generationMode = manager.compositor.getGenerationMode();
|
const generationMode = manager.compositor.getGenerationMode();
|
||||||
log.debug({ generationMode }, 'Building SDXL graph');
|
log.debug({ generationMode }, 'Building SDXL graph');
|
||||||
|
|
||||||
const { params, canvasV2 } = state;
|
const { params, canvasV2, canvasSettings } = state;
|
||||||
const { bbox, session, settings } = canvasV2;
|
const { bbox, session } = canvasV2;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
model,
|
model,
|
||||||
@ -277,7 +277,7 @@ export const buildSDXLGraph = async (
|
|||||||
canvasOutput = addWatermarker(g, canvasOutput);
|
canvasOutput = addWatermarker(g, canvasOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldSaveToGallery = session.mode === 'generate' || settings.autoSave;
|
const shouldSaveToGallery = session.mode === 'generate' || canvasSettings.autoSave;
|
||||||
|
|
||||||
g.updateNode(canvasOutput, {
|
g.updateNode(canvasOutput, {
|
||||||
id: getPrefixedId('canvas_output'),
|
id: getPrefixedId('canvas_output'),
|
||||||
|
Loading…
Reference in New Issue
Block a user