mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip regional prompting UI
- Add eraser tool, applies per layer
This commit is contained in:
parent
822dfa77fc
commit
52ba4966c9
@ -11,6 +11,7 @@ import { createStore } from '../src/app/store/store';
|
||||
// @ts-ignore
|
||||
import translationEN from '../public/locales/en.json';
|
||||
import { ReduxInit } from './ReduxInit';
|
||||
import { $store } from 'app/store/nanostores/store';
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
lng: 'en',
|
||||
@ -25,6 +26,7 @@ i18n.use(initReactI18next).init({
|
||||
});
|
||||
|
||||
const store = createStore(undefined, false);
|
||||
$store.set(store)
|
||||
$baseUrl.set('http://localhost:9090');
|
||||
|
||||
const preview: Preview = {
|
||||
|
@ -24,6 +24,7 @@ export const LineComponent = ({ line, color }: Props) => {
|
||||
lineJoin="round"
|
||||
shadowForStrokeEnabled={false}
|
||||
listening={false}
|
||||
globalCompositeOperation={line.tool === 'brush' ? 'source-over' : 'destination-out'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButt
|
||||
import { BrushSize } from 'features/regionalPrompts/components/BrushSize';
|
||||
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
|
||||
import { RegionalPromptsStage } from 'features/regionalPrompts/components/RegionalPromptsStage';
|
||||
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
|
||||
import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
|
||||
const selectLayerIdsReversed = createSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
|
||||
@ -18,6 +19,7 @@ export const RegionalPromptsEditor = () => {
|
||||
<Flex flexDir="column" w={200} gap={4}>
|
||||
<AddLayerButton />
|
||||
<BrushSize />
|
||||
<ToolChooser />
|
||||
{layerIdsReversed.map((id) => (
|
||||
<LayerListItem key={id} id={id} />
|
||||
))}
|
||||
|
@ -0,0 +1,33 @@
|
||||
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { toolChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { PiEraserBold, PiPaintBrushBold } from 'react-icons/pi';
|
||||
|
||||
export const ToolChooser: React.FC = () => {
|
||||
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
||||
const dispatch = useAppDispatch();
|
||||
const setToolToBrush = useCallback(() => {
|
||||
dispatch(toolChanged('brush'));
|
||||
}, [dispatch]);
|
||||
const setToolToEraser = useCallback(() => {
|
||||
dispatch(toolChanged('eraser'));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached>
|
||||
<IconButton
|
||||
aria-label="Brush tool"
|
||||
icon={<PiPaintBrushBold />}
|
||||
variant={tool === 'brush' ? 'solid' : 'outline'}
|
||||
onClick={setToolToBrush}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label="Eraser tool"
|
||||
icon={<PiEraserBold />}
|
||||
variant={tool === 'eraser' ? 'solid' : 'outline'}
|
||||
onClick={setToolToEraser}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
@ -1,10 +1,10 @@
|
||||
import { getStore } from 'app/store/nanostores/store';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||
import {
|
||||
$cursorPosition,
|
||||
$isMouseDown,
|
||||
$isMouseOver,
|
||||
$tool,
|
||||
lineAdded,
|
||||
pointsAdded,
|
||||
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
@ -13,6 +13,8 @@ import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import type { MutableRefObject } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const getTool = () => getStore().getState().regionalPrompts.tool;
|
||||
|
||||
const getIsFocused = (stage: Konva.Stage) => {
|
||||
return stage.container().contains(document.activeElement);
|
||||
};
|
||||
@ -38,7 +40,8 @@ export const useMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) =>
|
||||
return;
|
||||
}
|
||||
$isMouseDown.set(true);
|
||||
if ($tool.get() === 'brush') {
|
||||
const tool = getTool();
|
||||
if (tool === 'brush' || tool === 'eraser') {
|
||||
dispatch(lineAdded([pos.x, pos.y]));
|
||||
}
|
||||
},
|
||||
@ -54,7 +57,8 @@ export const useMouseUp = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
||||
if (!stageRef.current) {
|
||||
return;
|
||||
}
|
||||
if ($tool.get() === 'brush' && $isMouseDown.get()) {
|
||||
const tool = getTool();
|
||||
if ((tool === 'brush' || tool === 'eraser') && $isMouseDown.get()) {
|
||||
// Add another point to the last line.
|
||||
$isMouseDown.set(false);
|
||||
const pos = syncCursorPos(stageRef.current);
|
||||
@ -80,7 +84,13 @@ export const useMouseMove = (stageRef: MutableRefObject<Konva.Stage | null>) =>
|
||||
if (!pos) {
|
||||
return;
|
||||
}
|
||||
if (getIsFocused(stageRef.current) && $isMouseOver.get() && $isMouseDown.get() && $tool.get() === 'brush') {
|
||||
const tool = getTool();
|
||||
if (
|
||||
getIsFocused(stageRef.current) &&
|
||||
$isMouseOver.get() &&
|
||||
$isMouseDown.get() &&
|
||||
(tool === 'brush' || tool === 'eraser')
|
||||
) {
|
||||
dispatch(pointsAdded([pos.x, pos.y]));
|
||||
}
|
||||
},
|
||||
@ -123,7 +133,8 @@ export const useMouseEnter = (stageRef: MutableRefObject<Konva.Stage | null>) =>
|
||||
$isMouseDown.set(false);
|
||||
} else {
|
||||
$isMouseDown.set(true);
|
||||
if ($tool.get() === 'brush') {
|
||||
const tool = getTool();
|
||||
if (tool === 'brush' || tool === 'eraser') {
|
||||
dispatch(lineAdded([pos.x, pos.y]));
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import type { RgbColor } from 'react-colorful';
|
||||
import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export type Tool = 'brush' | 'eraser';
|
||||
|
||||
type LayerObjectBase = {
|
||||
id: string;
|
||||
isSelected: boolean;
|
||||
@ -25,6 +27,7 @@ type ImageObject = LayerObjectBase & {
|
||||
|
||||
export type LineObject = LayerObjectBase & {
|
||||
kind: 'line';
|
||||
tool: Tool;
|
||||
strokeWidth: number;
|
||||
points: number[];
|
||||
};
|
||||
@ -53,10 +56,9 @@ type PromptRegionLayer = LayerBase & {
|
||||
|
||||
type Layer = PromptRegionLayer;
|
||||
|
||||
type Tool = 'brush';
|
||||
|
||||
type RegionalPromptsState = {
|
||||
_version: 1;
|
||||
tool: Tool;
|
||||
selectedLayer: string | null;
|
||||
layers: PromptRegionLayer[];
|
||||
brushSize: number;
|
||||
@ -64,6 +66,7 @@ type RegionalPromptsState = {
|
||||
|
||||
const initialRegionalPromptsState: RegionalPromptsState = {
|
||||
_version: 1,
|
||||
tool: 'brush',
|
||||
selectedLayer: null,
|
||||
brushSize: 40,
|
||||
layers: [],
|
||||
@ -144,7 +147,7 @@ export const regionalPromptsSlice = createSlice({
|
||||
if (!selectedLayer || selectedLayer.kind !== 'promptRegionLayer') {
|
||||
return;
|
||||
}
|
||||
selectedLayer.objects.push(buildLine(action.meta.id, action.payload, state.brushSize));
|
||||
selectedLayer.objects.push(buildLine(action.meta.id, action.payload, state.brushSize, state.tool));
|
||||
},
|
||||
prepare: (payload: number[]) => ({ payload, meta: { id: uuidv4() } }),
|
||||
},
|
||||
@ -162,6 +165,9 @@ export const regionalPromptsSlice = createSlice({
|
||||
brushSizeChanged: (state, action: PayloadAction<number>) => {
|
||||
state.brushSize = action.payload;
|
||||
},
|
||||
toolChanged: (state, action: PayloadAction<Tool>) => {
|
||||
state.tool = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -190,9 +196,10 @@ const buildLayer = (id: string, kind: Layer['kind'], layerCount: number): Layer
|
||||
assert(false, `Unknown layer kind: ${kind}`);
|
||||
};
|
||||
|
||||
const buildLine = (id: string, points: number[], brushSize: number): LineObject => ({
|
||||
const buildLine = (id: string, points: number[], brushSize: number, tool: Tool): LineObject => ({
|
||||
isSelected: false,
|
||||
kind: 'line',
|
||||
tool,
|
||||
id,
|
||||
points,
|
||||
strokeWidth: brushSize,
|
||||
@ -213,6 +220,7 @@ export const {
|
||||
layerMovedToFront,
|
||||
layerMovedBackward,
|
||||
layerMovedToBack,
|
||||
toolChanged,
|
||||
} = regionalPromptsSlice.actions;
|
||||
|
||||
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;
|
||||
@ -232,5 +240,4 @@ export const regionalPromptsPersistConfig: PersistConfig<RegionalPromptsState> =
|
||||
export const $isMouseDown = atom(false);
|
||||
export const $isMouseOver = atom(false);
|
||||
export const $cursorPosition = atom<Vector2d | null>(null);
|
||||
export const $tool = atom<Tool>('brush');
|
||||
export const $stage = atom<Konva.Stage | null>(null);
|
||||
|
Loading…
Reference in New Issue
Block a user