mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): rough out canvas mode
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { Button, ButtonGroup } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { sessionModeChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const CanvasModeSwitcher = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const mode = useAppSelector((s) => s.canvasV2.session.mode);
|
||||
const onClickGenerate = useCallback(() => dispatch(sessionModeChanged({ mode: 'generate' })), [dispatch]);
|
||||
const onClickCompose = useCallback(() => dispatch(sessionModeChanged({ mode: 'compose' })), [dispatch]);
|
||||
|
||||
return (
|
||||
<ButtonGroup variant="outline">
|
||||
<Button onClick={onClickGenerate} colorScheme={mode === 'generate' ? 'invokeBlue' : 'base'}>
|
||||
{t('controlLayers.generateMode')}
|
||||
</Button>
|
||||
<Button onClick={onClickCompose} colorScheme={mode === 'compose' ? 'invokeBlue' : 'base'}>
|
||||
{t('controlLayers.composeMode')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasModeSwitcher.displayName = 'CanvasModeSwitcher';
|
||||
@@ -23,7 +23,7 @@ export const CanvasResetViewButton = memo(() => {
|
||||
if (!canvasManager) {
|
||||
return;
|
||||
}
|
||||
canvasManager.stage.resetView();
|
||||
canvasManager.stage.fitLayersToStage();
|
||||
}, [canvasManager]);
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
import { Flex, Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasModeSwitcher } from 'features/controlLayers/components/CanvasModeSwitcher';
|
||||
import { CanvasResetViewButton } from 'features/controlLayers/components/CanvasResetViewButton';
|
||||
import { CanvasScale } from 'features/controlLayers/components/CanvasScale';
|
||||
import { CanvasSettingsPopover } from 'features/controlLayers/components/Settings/CanvasSettingsPopover';
|
||||
@@ -23,11 +24,12 @@ export const ControlLayersToolbar = memo(() => {
|
||||
<ToolChooser />
|
||||
{tool === 'brush' && <ToolBrushWidth />}
|
||||
{tool === 'eraser' && <ToolEraserWidth />}
|
||||
<ToolFillColorPicker />
|
||||
<Spacer />
|
||||
<CanvasScale />
|
||||
<CanvasResetViewButton />
|
||||
<Spacer />
|
||||
<ToolFillColorPicker />
|
||||
<CanvasModeSwitcher />
|
||||
<UndoRedoButtonGroup />
|
||||
<CanvasSettingsPopover />
|
||||
<ViewerToggleMenu />
|
||||
|
||||
@@ -70,13 +70,6 @@ export class CanvasProgressImageModule {
|
||||
return;
|
||||
}
|
||||
|
||||
const { isStaging } = this.manager.stateApi.getSession();
|
||||
|
||||
if (!isStaging) {
|
||||
release();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
const { x, y, width, height } = this.manager.stateApi.getBbox().rect;
|
||||
|
||||
@@ -33,6 +33,7 @@ export class CanvasStageModule {
|
||||
const resizeObserver = new ResizeObserver(this.fitStageToContainer);
|
||||
resizeObserver.observe(this.container);
|
||||
this.fitStageToContainer();
|
||||
this.fitLayersToStage();
|
||||
|
||||
return () => {
|
||||
this.log.debug('Destroying stage');
|
||||
@@ -91,10 +92,20 @@ export class CanvasStageModule {
|
||||
}
|
||||
};
|
||||
|
||||
resetView() {
|
||||
this.log.trace('Resetting view');
|
||||
const { width, height } = this.getSize();
|
||||
fitBboxToStage = () => {
|
||||
this.log.trace('Fitting bbox to stage');
|
||||
const bbox = this.manager.stateApi.getBbox();
|
||||
this.fitRect(bbox.rect);
|
||||
};
|
||||
|
||||
fitLayersToStage() {
|
||||
this.log.trace('Fitting layers to stage');
|
||||
const rect = this.getVisibleRect();
|
||||
this.fitRect(rect);
|
||||
}
|
||||
|
||||
fitRect = (rect: Rect) => {
|
||||
const { width, height } = this.getSize();
|
||||
|
||||
const padding = 20; // Padding in absolute pixels
|
||||
|
||||
@@ -118,7 +129,7 @@ export class CanvasStageModule {
|
||||
y,
|
||||
scale,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the center of the stage in either absolute or relative coordinates
|
||||
|
||||
@@ -132,6 +132,7 @@ const initialState: CanvasV2State = {
|
||||
refinerStart: 0.8,
|
||||
},
|
||||
session: {
|
||||
mode: 'generate',
|
||||
isStaging: false,
|
||||
stagedImages: [],
|
||||
selectedStagedImageIndex: 0,
|
||||
@@ -474,6 +475,7 @@ export const {
|
||||
clipToBboxChanged,
|
||||
canvasReset,
|
||||
settingsDynamicGridToggled,
|
||||
settingsAutoSaveToggled,
|
||||
// All entities
|
||||
entitySelected,
|
||||
entityNameChanged,
|
||||
@@ -601,6 +603,7 @@ export const {
|
||||
sessionStagingAreaReset,
|
||||
sessionNextStagedImageSelected,
|
||||
sessionPrevStagedImageSelected,
|
||||
sessionModeChanged,
|
||||
} = canvasV2Slice.actions;
|
||||
|
||||
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import type { CanvasV2State, StagingAreaImage } from 'features/controlLayers/store/types';
|
||||
import type { CanvasV2State, SessionMode, StagingAreaImage } from 'features/controlLayers/store/types';
|
||||
|
||||
export const sessionReducers = {
|
||||
sessionStartedStaging: (state) => {
|
||||
@@ -45,4 +45,8 @@ export const sessionReducers = {
|
||||
state.tool.selectedBuffer = null;
|
||||
}
|
||||
},
|
||||
sessionModeChanged: (state, action: PayloadAction<{ mode: SessionMode }>) => {
|
||||
const { mode } = action.payload;
|
||||
state.session.mode = mode;
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||
|
||||
@@ -8,4 +8,7 @@ export const settingsReducers = {
|
||||
settingsDynamicGridToggled: (state) => {
|
||||
state.settings.dynamicGrid = !state.settings.dynamicGrid;
|
||||
},
|
||||
settingsAutoSaveToggled: (state) => {
|
||||
state.settings.autoSave = !state.settings.autoSave;
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||
|
||||
@@ -834,6 +834,8 @@ export type StagingAreaImage = {
|
||||
offsetY: number;
|
||||
};
|
||||
|
||||
export type SessionMode = 'generate' | 'compose';
|
||||
|
||||
export type CanvasV2State = {
|
||||
_version: 3;
|
||||
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
||||
@@ -929,6 +931,7 @@ export type CanvasV2State = {
|
||||
refinerStart: number;
|
||||
};
|
||||
session: {
|
||||
mode: SessionMode;
|
||||
isStaging: boolean;
|
||||
stagedImages: StagingAreaImage[];
|
||||
selectedStagedImageIndex: number;
|
||||
|
||||
@@ -41,7 +41,7 @@ export const buildSD1Graph = async (state: RootState, manager: CanvasManager): P
|
||||
const generationMode = manager.compositor.getGenerationMode();
|
||||
log.debug({ generationMode }, 'Building SD1/SD2 graph');
|
||||
|
||||
const { bbox, params } = state.canvasV2;
|
||||
const { bbox, params, session, settings } = state.canvasV2;
|
||||
|
||||
const {
|
||||
model,
|
||||
@@ -249,10 +249,11 @@ export const buildSD1Graph = async (state: RootState, manager: CanvasManager): P
|
||||
canvasOutput = addWatermarker(g, canvasOutput);
|
||||
}
|
||||
|
||||
// This is the terminal node and must always save to gallery.
|
||||
const shouldSaveToGallery = session.mode === 'generate' || settings.autoSave;
|
||||
|
||||
g.updateNode(canvasOutput, {
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: false,
|
||||
is_intermediate: !shouldSaveToGallery,
|
||||
use_cache: false,
|
||||
board: getBoardField(state),
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ export const buildSDXLGraph = async (state: RootState, manager: CanvasManager):
|
||||
const generationMode = manager.compositor.getGenerationMode();
|
||||
log.debug({ generationMode }, 'Building SDXL graph');
|
||||
|
||||
const { bbox, params } = state.canvasV2;
|
||||
const { bbox, params, session, settings } = state.canvasV2;
|
||||
|
||||
const {
|
||||
model,
|
||||
@@ -246,10 +246,11 @@ export const buildSDXLGraph = async (state: RootState, manager: CanvasManager):
|
||||
canvasOutput = addWatermarker(g, canvasOutput);
|
||||
}
|
||||
|
||||
// This is the terminal node and must always save to gallery.
|
||||
const shouldSaveToGallery = session.mode === 'generate' || settings.autoSave;
|
||||
|
||||
g.updateNode(canvasOutput, {
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: false,
|
||||
is_intermediate: !shouldSaveToGallery,
|
||||
use_cache: false,
|
||||
board: getBoardField(state),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user