mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): move ephemeral tool state out of redux
This commit is contained in:
parent
14c722c265
commit
9528287d56
@ -6,6 +6,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useMouseEvents } from 'features/regionalPrompts/hooks/mouseEventHooks';
|
||||
import {
|
||||
$cursorPosition,
|
||||
$tool,
|
||||
isRPLayer,
|
||||
rpLayerBboxChanged,
|
||||
rpLayerTranslated,
|
||||
@ -35,6 +36,7 @@ const useStageRenderer = (container: HTMLDivElement | null, wrapper: HTMLDivElem
|
||||
const height = useAppSelector((s) => s.generation.height);
|
||||
const state = useAppSelector((s) => s.regionalPrompts.present);
|
||||
const stage = useStore($stage);
|
||||
const tool = useStore($tool);
|
||||
const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave } = useMouseEvents();
|
||||
const cursorPosition = useStore($cursorPosition);
|
||||
const selectedLayerColor = useAppSelector(selectSelectedLayerColor);
|
||||
@ -116,27 +118,28 @@ const useStageRenderer = (container: HTMLDivElement | null, wrapper: HTMLDivElem
|
||||
}, [stage, width, height, wrapper]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
log.trace('Rendering brush preview');
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
renderBrushPreview(stage, state.tool, selectedLayerColor, cursorPosition, state.brushSize);
|
||||
}, [stage, state.tool, cursorPosition, state.brushSize, selectedLayerColor]);
|
||||
renderBrushPreview(stage, tool, selectedLayerColor, cursorPosition, state.brushSize);
|
||||
}, [stage, tool, cursorPosition, state.brushSize, selectedLayerColor]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
log.trace('Rendering layers');
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
renderLayers(stage, state.layers, state.selectedLayer, state.promptLayerOpacity, state.tool, onLayerPosChanged);
|
||||
}, [onLayerPosChanged, stage, state.layers, state.promptLayerOpacity, state.tool, state.selectedLayer]);
|
||||
renderLayers(stage, state.layers, state.selectedLayer, state.promptLayerOpacity, tool, onLayerPosChanged);
|
||||
}, [onLayerPosChanged, stage, state.layers, state.promptLayerOpacity, tool, state.selectedLayer]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
log.trace('Rendering bbox');
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
renderBbox(stage, state.tool, state.selectedLayer, onBboxChanged);
|
||||
}, [dispatch, stage, state.tool, state.selectedLayer, onBboxChanged]);
|
||||
renderBbox(stage, tool, state.selectedLayer, onBboxChanged);
|
||||
}, [dispatch, stage, tool, state.selectedLayer, onBboxChanged]);
|
||||
};
|
||||
|
||||
const $container = atom<HTMLDivElement | null>(null);
|
||||
|
@ -1,21 +1,20 @@
|
||||
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { toolChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $tool } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { PiArrowsOutCardinalBold, PiEraserBold, PiPaintBrushBold } from 'react-icons/pi';
|
||||
|
||||
export const ToolChooser: React.FC = () => {
|
||||
const tool = useAppSelector((s) => s.regionalPrompts.present.tool);
|
||||
const dispatch = useAppDispatch();
|
||||
const tool = useStore($tool);
|
||||
const setToolToBrush = useCallback(() => {
|
||||
dispatch(toolChanged('brush'));
|
||||
}, [dispatch]);
|
||||
$tool.set('brush');
|
||||
}, []);
|
||||
const setToolToEraser = useCallback(() => {
|
||||
dispatch(toolChanged('eraser'));
|
||||
}, [dispatch]);
|
||||
$tool.set('eraser');
|
||||
}, []);
|
||||
const setToolToMove = useCallback(() => {
|
||||
dispatch(toolChanged('move'));
|
||||
}, [dispatch]);
|
||||
$tool.set('move');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached>
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { getStore } from 'app/store/nanostores/store';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||
import {
|
||||
$cursorPosition,
|
||||
$isMouseDown,
|
||||
$isMouseOver,
|
||||
$tool,
|
||||
rpLayerLineAdded,
|
||||
rpLayerPointsAdded,
|
||||
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
@ -12,8 +13,6 @@ import type Konva from 'konva';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const getTool = () => getStore().getState().regionalPrompts.present.tool;
|
||||
|
||||
const getIsFocused = (stage: Konva.Stage) => {
|
||||
return stage.container().contains(document.activeElement);
|
||||
};
|
||||
@ -29,6 +28,8 @@ const syncCursorPos = (stage: Konva.Stage) => {
|
||||
|
||||
export const useMouseEvents = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const selectedLayer = useAppSelector((s) => s.regionalPrompts.present.selectedLayer);
|
||||
const tool = useStore($tool);
|
||||
|
||||
const onMouseDown = useCallback(
|
||||
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
||||
@ -41,12 +42,15 @@ export const useMouseEvents = () => {
|
||||
return;
|
||||
}
|
||||
$isMouseDown.set(true);
|
||||
const tool = getTool();
|
||||
if (!selectedLayer) {
|
||||
return;
|
||||
}
|
||||
// const tool = getTool();
|
||||
if (tool === 'brush' || tool === 'eraser') {
|
||||
dispatch(rpLayerLineAdded([pos.x, pos.y, pos.x, pos.y]));
|
||||
dispatch(rpLayerLineAdded({ layerId: selectedLayer, points: [pos.x, pos.y, pos.x, pos.y], tool }));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, selectedLayer, tool]
|
||||
);
|
||||
|
||||
const onMouseUp = useCallback(
|
||||
@ -55,12 +59,12 @@ export const useMouseEvents = () => {
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
const tool = getTool();
|
||||
// const tool = getTool();
|
||||
if ((tool === 'brush' || tool === 'eraser') && $isMouseDown.get()) {
|
||||
$isMouseDown.set(false);
|
||||
}
|
||||
},
|
||||
[]
|
||||
[tool]
|
||||
);
|
||||
|
||||
const onMouseMove = useCallback(
|
||||
@ -70,15 +74,15 @@ export const useMouseEvents = () => {
|
||||
return;
|
||||
}
|
||||
const pos = syncCursorPos(stage);
|
||||
if (!pos) {
|
||||
if (!pos || !selectedLayer) {
|
||||
return;
|
||||
}
|
||||
const tool = getTool();
|
||||
// const tool = getTool();
|
||||
if (getIsFocused(stage) && $isMouseOver.get() && $isMouseDown.get() && (tool === 'brush' || tool === 'eraser')) {
|
||||
dispatch(rpLayerPointsAdded([pos.x, pos.y]));
|
||||
dispatch(rpLayerPointsAdded({ layerId: selectedLayer, point: [pos.x, pos.y] }));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, selectedLayer, tool]
|
||||
);
|
||||
|
||||
const onMouseLeave = useCallback((e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
||||
@ -109,13 +113,15 @@ export const useMouseEvents = () => {
|
||||
$isMouseDown.set(false);
|
||||
} else {
|
||||
$isMouseDown.set(true);
|
||||
const tool = getTool();
|
||||
if (!selectedLayer) {
|
||||
return;
|
||||
}
|
||||
if (tool === 'brush' || tool === 'eraser') {
|
||||
dispatch(rpLayerLineAdded([pos.x, pos.y, pos.x, pos.y]));
|
||||
dispatch(rpLayerLineAdded({ layerId: selectedLayer, points: [pos.x, pos.y, pos.x, pos.y], tool }));
|
||||
}
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, selectedLayer, tool]
|
||||
);
|
||||
|
||||
return { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave };
|
||||
|
@ -10,7 +10,9 @@ import type { UndoableOptions } from 'redux-undo';
|
||||
import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export type Tool = 'brush' | 'eraser' | 'move';
|
||||
export type DrawingTool = 'brush' | 'eraser';
|
||||
|
||||
export type RPTool = DrawingTool | 'move';
|
||||
|
||||
type LayerObjectBase = {
|
||||
id: string;
|
||||
@ -27,7 +29,7 @@ type ImageObject = LayerObjectBase & {
|
||||
|
||||
type LineObject = LayerObjectBase & {
|
||||
kind: 'line';
|
||||
tool: Tool;
|
||||
tool: DrawingTool;
|
||||
strokeWidth: number;
|
||||
points: number[];
|
||||
};
|
||||
@ -63,7 +65,6 @@ export type Layer = RegionalPromptLayer;
|
||||
|
||||
type RegionalPromptsState = {
|
||||
_version: 1;
|
||||
tool: Tool;
|
||||
selectedLayer: string | null;
|
||||
layers: Layer[];
|
||||
brushSize: number;
|
||||
@ -73,7 +74,6 @@ type RegionalPromptsState = {
|
||||
|
||||
export const initialRegionalPromptsState: RegionalPromptsState = {
|
||||
_version: 1,
|
||||
tool: 'brush',
|
||||
selectedLayer: null,
|
||||
brushSize: 40,
|
||||
layers: [],
|
||||
@ -197,34 +197,45 @@ export const regionalPromptsSlice = createSlice({
|
||||
}
|
||||
},
|
||||
rpLayerLineAdded: {
|
||||
reducer: (state, action: PayloadAction<[number, number, number, number], string, { uuid: string }>) => {
|
||||
const layer = state.layers.find((l) => l.id === state.selectedLayer);
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
{ layerId: string; points: [number, number, number, number]; tool: DrawingTool },
|
||||
string,
|
||||
{ uuid: string }
|
||||
>
|
||||
) => {
|
||||
const { layerId, points, tool } = action.payload;
|
||||
const layer = state.layers.find((l) => l.id === layerId);
|
||||
if (isRPLayer(layer)) {
|
||||
const lineId = getRPLayerLineId(layer.id, action.meta.uuid);
|
||||
layer.objects.push({
|
||||
kind: 'line',
|
||||
tool: state.tool,
|
||||
tool: tool,
|
||||
id: lineId,
|
||||
points: [
|
||||
action.payload[0] - layer.x,
|
||||
action.payload[1] - layer.y,
|
||||
action.payload[2] - layer.x,
|
||||
action.payload[3] - layer.y,
|
||||
],
|
||||
// Points must be offset by the layer's x and y coordinates
|
||||
// TODO: Handle this in the event listener
|
||||
points: [points[0] - layer.x, points[1] - layer.y, points[2] - layer.x, points[3] - layer.y],
|
||||
strokeWidth: state.brushSize,
|
||||
});
|
||||
}
|
||||
},
|
||||
prepare: (payload: [number, number, number, number]) => ({ payload, meta: { uuid: uuidv4() } }),
|
||||
prepare: (payload: { layerId: string; points: [number, number, number, number]; tool: DrawingTool }) => ({
|
||||
payload,
|
||||
meta: { uuid: uuidv4() },
|
||||
}),
|
||||
},
|
||||
rpLayerPointsAdded: (state, action: PayloadAction<[number, number]>) => {
|
||||
const layer = state.layers.find((l) => l.id === state.selectedLayer);
|
||||
rpLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => {
|
||||
const { layerId, point } = action.payload;
|
||||
const layer = state.layers.find((l) => l.id === layerId);
|
||||
if (isRPLayer(layer)) {
|
||||
const lastLine = layer.objects.findLast(isLine);
|
||||
if (!lastLine) {
|
||||
return;
|
||||
}
|
||||
lastLine.points.push(action.payload[0] - layer.x, action.payload[1] - layer.y);
|
||||
// Points must be offset by the layer's x and y coordinates
|
||||
// TODO: Handle this in the event listener
|
||||
lastLine.points.push(point[0] - layer.x, point[1] - layer.y);
|
||||
}
|
||||
},
|
||||
rpLayerAutoNegativeChanged: (
|
||||
@ -242,9 +253,6 @@ export const regionalPromptsSlice = createSlice({
|
||||
brushSizeChanged: (state, action: PayloadAction<number>) => {
|
||||
state.brushSize = action.payload;
|
||||
},
|
||||
toolChanged: (state, action: PayloadAction<Tool>) => {
|
||||
state.tool = action.payload;
|
||||
},
|
||||
promptLayerOpacityChanged: (state, action: PayloadAction<number>) => {
|
||||
state.promptLayerOpacity = action.payload;
|
||||
},
|
||||
@ -304,7 +312,6 @@ export const {
|
||||
isEnabledChanged,
|
||||
brushSizeChanged,
|
||||
promptLayerOpacityChanged,
|
||||
toolChanged,
|
||||
} = regionalPromptsSlice.actions;
|
||||
|
||||
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;
|
||||
@ -316,6 +323,7 @@ const migrateRegionalPromptsState = (state: any): any => {
|
||||
|
||||
export const $isMouseDown = atom(false);
|
||||
export const $isMouseOver = atom(false);
|
||||
export const $tool = atom<RPTool>('brush');
|
||||
export const $cursorPosition = atom<Vector2d | null>(null);
|
||||
|
||||
// IDs for singleton layers and objects
|
||||
@ -394,10 +402,6 @@ export const regionalPromptsUndoableConfig: UndoableOptions<RegionalPromptsState
|
||||
if (rpLayerBboxChanged.match(action)) {
|
||||
return false;
|
||||
}
|
||||
// We don't want to record tool changes in the undo history
|
||||
if (toolChanged.match(action)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||
import type { Layer, RegionalPromptLayer, Tool } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import type { Layer, RegionalPromptLayer, RPTool } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import {
|
||||
BRUSH_PREVIEW_BORDER_INNER_ID,
|
||||
BRUSH_PREVIEW_BORDER_OUTER_ID,
|
||||
@ -36,7 +36,7 @@ const mapId = (object: { id: string }) => object.id;
|
||||
*/
|
||||
export const renderBrushPreview = (
|
||||
stage: Konva.Stage,
|
||||
tool: Tool,
|
||||
tool: RPTool,
|
||||
color: RgbColor | null,
|
||||
cursorPos: Vector2d | null,
|
||||
brushSize: number
|
||||
@ -130,7 +130,7 @@ const renderRPLayer = (
|
||||
rpLayer: RegionalPromptLayer,
|
||||
rpLayerIndex: number,
|
||||
selectedLayerId: string | null,
|
||||
tool: Tool,
|
||||
tool: RPTool,
|
||||
layerOpacity: number,
|
||||
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
|
||||
) => {
|
||||
@ -278,7 +278,7 @@ export const renderLayers = (
|
||||
reduxLayers: Layer[],
|
||||
selectedLayerId: string | null,
|
||||
layerOpacity: number,
|
||||
tool: Tool,
|
||||
tool: RPTool,
|
||||
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
|
||||
) => {
|
||||
const reduxLayerIds = reduxLayers.map(mapId);
|
||||
@ -312,7 +312,7 @@ const selectPromptLayerObjectGroup = (item: Node<NodeConfig>) =>
|
||||
*/
|
||||
export const renderBbox = (
|
||||
stage: Konva.Stage,
|
||||
tool: Tool,
|
||||
tool: RPTool,
|
||||
selectedLayerId: string | null,
|
||||
onBboxChanged: (layerId: string, bbox: IRect) => void
|
||||
) => {
|
||||
|
Loading…
Reference in New Issue
Block a user