feat(ui): move canvas tool to nanostores

I was troubleshooting a hotkeys issue on canvas and thought I had broken the tool logic in a past change so I redid it moving it to nanostores. In the end, the issue was an upstream but with the hotkeys library, but I like having tool in nanostores so I'm leaving it.

It's ephemeral interaction state anyways, doesn't need to be in redux.
This commit is contained in:
psychedelicious 2024-01-09 18:13:38 +11:00 committed by Kent Keirsey
parent b501bd709f
commit 21ab650ac0
13 changed files with 144 additions and 160 deletions

View File

@ -14,6 +14,7 @@ import {
$isMouseOverBoundingBox, $isMouseOverBoundingBox,
$isMovingStage, $isMovingStage,
$isTransformingBoundingBox, $isTransformingBoundingBox,
$tool,
} from 'features/canvas/store/canvasNanostore'; } from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
@ -61,7 +62,6 @@ const IAICanvas = () => {
); );
const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid); const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid);
const stageScale = useAppSelector((s) => s.canvas.stageScale); const stageScale = useAppSelector((s) => s.canvas.stageScale);
const tool = useAppSelector((s) => s.canvas.tool);
const shouldShowIntermediates = useAppSelector( const shouldShowIntermediates = useAppSelector(
(s) => s.canvas.shouldShowIntermediates (s) => s.canvas.shouldShowIntermediates
); );
@ -78,6 +78,7 @@ const IAICanvas = () => {
const isMovingStage = useStore($isMovingStage); const isMovingStage = useStore($isMovingStage);
const isTransformingBoundingBox = useStore($isTransformingBoundingBox); const isTransformingBoundingBox = useStore($isTransformingBoundingBox);
const isMouseOverBoundingBox = useStore($isMouseOverBoundingBox); const isMouseOverBoundingBox = useStore($isMouseOverBoundingBox);
const tool = useStore($tool);
useCanvasHotkeys(); useCanvasHotkeys();
const canvasStageRefCallback = useCallback((el: Konva.Stage) => { const canvasStageRefCallback = useCallback((el: Konva.Stage) => {
setCanvasStage(el as Konva.Stage); setCanvasStage(el as Konva.Stage);

View File

@ -8,11 +8,11 @@ import {
} from 'common/util/roundDownToMultiple'; } from 'common/util/roundDownToMultiple';
import { import {
$isDrawing, $isDrawing,
$isMouseOverBoundingBox,
$isMouseOverBoundingBoxOutline,
$isMovingBoundingBox, $isMovingBoundingBox,
$isTransformingBoundingBox, $isTransformingBoundingBox,
setIsMouseOverBoundingBox, $tool,
setIsMovingBoundingBox,
setIsTransformingBoundingBox,
} from 'features/canvas/store/canvasNanostore'; } from 'features/canvas/store/canvasNanostore';
import { import {
aspectRatioChanged, aspectRatioChanged,
@ -30,7 +30,7 @@ import type Konva from 'konva';
import type { GroupConfig } from 'konva/lib/Group'; import type { GroupConfig } from 'konva/lib/Group';
import type { KonvaEventObject } from 'konva/lib/Node'; import type { KonvaEventObject } from 'konva/lib/Node';
import type { Vector2d } from 'konva/lib/types'; import type { Vector2d } from 'konva/lib/types';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { Group, Rect, Transformer } from 'react-konva'; import { Group, Rect, Transformer } from 'react-konva';
@ -49,18 +49,19 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
); );
const stageScale = useAppSelector((s) => s.canvas.stageScale); const stageScale = useAppSelector((s) => s.canvas.stageScale);
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid); const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
const tool = useAppSelector((s) => s.canvas.tool);
const hitStrokeWidth = useAppSelector((s) => 20 / s.canvas.stageScale); const hitStrokeWidth = useAppSelector((s) => 20 / s.canvas.stageScale);
const aspectRatio = useAppSelector((s) => s.canvas.aspectRatio); const aspectRatio = useAppSelector((s) => s.canvas.aspectRatio);
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const transformerRef = useRef<Konva.Transformer>(null); const transformerRef = useRef<Konva.Transformer>(null);
const shapeRef = useRef<Konva.Rect>(null); const shapeRef = useRef<Konva.Rect>(null);
const shift = useStore($shift); const shift = useStore($shift);
const tool = useStore($tool);
const isDrawing = useStore($isDrawing); const isDrawing = useStore($isDrawing);
const isMovingBoundingBox = useStore($isMovingBoundingBox); const isMovingBoundingBox = useStore($isMovingBoundingBox);
const isTransformingBoundingBox = useStore($isTransformingBoundingBox); const isTransformingBoundingBox = useStore($isTransformingBoundingBox);
const [isMouseOverBoundingBoxOutline, setIsMouseOverBoundingBoxOutline] = const isMouseOverBoundingBoxOutline = useStore(
useState(false); $isMouseOverBoundingBoxOutline
);
useEffect(() => { useEffect(() => {
if (!transformerRef.current || !shapeRef.current) { if (!transformerRef.current || !shapeRef.current) {
@ -228,43 +229,43 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
); );
const handleStartedTransforming = useCallback(() => { const handleStartedTransforming = useCallback(() => {
setIsTransformingBoundingBox(true); $isTransformingBoundingBox.set(true);
}, []); }, []);
const handleEndedTransforming = useCallback(() => { const handleEndedTransforming = useCallback(() => {
setIsTransformingBoundingBox(false); $isTransformingBoundingBox.set(false);
setIsMovingBoundingBox(false); $isMovingBoundingBox.set(false);
setIsMouseOverBoundingBox(false); $isMouseOverBoundingBox.set(false);
setIsMouseOverBoundingBoxOutline(false); $isMouseOverBoundingBoxOutline.set(false);
}, []); }, []);
const handleStartedMoving = useCallback(() => { const handleStartedMoving = useCallback(() => {
setIsMovingBoundingBox(true); $isMovingBoundingBox.set(true);
}, []); }, []);
const handleEndedModifying = useCallback(() => { const handleEndedModifying = useCallback(() => {
setIsTransformingBoundingBox(false); $isTransformingBoundingBox.set(false);
setIsMovingBoundingBox(false); $isMovingBoundingBox.set(false);
setIsMouseOverBoundingBox(false); $isMouseOverBoundingBox.set(false);
setIsMouseOverBoundingBoxOutline(false); $isMouseOverBoundingBoxOutline.set(false);
}, []); }, []);
const handleMouseOver = useCallback(() => { const handleMouseOver = useCallback(() => {
setIsMouseOverBoundingBoxOutline(true); $isMouseOverBoundingBoxOutline.set(true);
}, []); }, []);
const handleMouseOut = useCallback(() => { const handleMouseOut = useCallback(() => {
!isTransformingBoundingBox && !isTransformingBoundingBox &&
!isMovingBoundingBox && !isMovingBoundingBox &&
setIsMouseOverBoundingBoxOutline(false); $isMouseOverBoundingBoxOutline.set(false);
}, [isMovingBoundingBox, isTransformingBoundingBox]); }, [isMovingBoundingBox, isTransformingBoundingBox]);
const handleMouseEnterBoundingBox = useCallback(() => { const handleMouseEnterBoundingBox = useCallback(() => {
setIsMouseOverBoundingBox(true); $isMouseOverBoundingBox.set(true);
}, []); }, []);
const handleMouseLeaveBoundingBox = useCallback(() => { const handleMouseLeaveBoundingBox = useCallback(() => {
setIsMouseOverBoundingBox(false); $isMouseOverBoundingBox.set(false);
}, []); }, []);
const stroke = useMemo(() => { const stroke = useMemo(() => {

View File

@ -1,4 +1,5 @@
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIColorPicker from 'common/components/IAIColorPicker'; import IAIColorPicker from 'common/components/IAIColorPicker';
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup'; import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
@ -10,14 +11,16 @@ import {
InvPopoverTrigger, InvPopoverTrigger,
} from 'common/components/InvPopover/wrapper'; } from 'common/components/InvPopover/wrapper';
import { InvSlider } from 'common/components/InvSlider/InvSlider'; import { InvSlider } from 'common/components/InvSlider/InvSlider';
import { resetToolInteractionState } from 'features/canvas/store/canvasNanostore'; import {
$tool,
resetToolInteractionState,
} from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
addEraseRect, addEraseRect,
addFillRect, addFillRect,
setBrushColor, setBrushColor,
setBrushSize, setBrushSize,
setTool,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { InvIconButton, InvPopover } from 'index'; import { InvIconButton, InvPopover } from 'index';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
@ -34,9 +37,11 @@ import {
PiXBold, PiXBold,
} from 'react-icons/pi'; } from 'react-icons/pi';
const marks = [1, 25, 50, 75, 100];
const IAICanvasToolChooserOptions = () => { const IAICanvasToolChooserOptions = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const tool = useAppSelector((s) => s.canvas.tool); const tool = useStore($tool);
const brushColor = useAppSelector((s) => s.canvas.brushColor); const brushColor = useAppSelector((s) => s.canvas.brushColor);
const brushSize = useAppSelector((s) => s.canvas.brushSize); const brushSize = useAppSelector((s) => s.canvas.brushSize);
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
@ -163,17 +168,17 @@ const IAICanvasToolChooserOptions = () => {
); );
const handleSelectBrushTool = useCallback(() => { const handleSelectBrushTool = useCallback(() => {
dispatch(setTool('brush')); $tool.set('brush');
resetToolInteractionState(); resetToolInteractionState();
}, [dispatch]); }, []);
const handleSelectEraserTool = useCallback(() => { const handleSelectEraserTool = useCallback(() => {
dispatch(setTool('eraser')); $tool.set('eraser');
resetToolInteractionState(); resetToolInteractionState();
}, [dispatch]); }, []);
const handleSelectColorPickerTool = useCallback(() => { const handleSelectColorPickerTool = useCallback(() => {
dispatch(setTool('colorPicker')); $tool.set('colorPicker');
resetToolInteractionState(); resetToolInteractionState();
}, [dispatch]); }, []);
const handleFillRect = useCallback(() => { const handleFillRect = useCallback(() => {
dispatch(addFillRect()); dispatch(addFillRect());
}, [dispatch]); }, [dispatch]);
@ -281,5 +286,3 @@ const IAICanvasToolChooserOptions = () => {
}; };
export default memo(IAICanvasToolChooserOptions); export default memo(IAICanvasToolChooserOptions);
const marks = [1, 25, 50, 75, 100];

View File

@ -1,4 +1,5 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup'; import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
import { InvControl } from 'common/components/InvControl/InvControl'; import { InvControl } from 'common/components/InvControl/InvControl';
@ -14,13 +15,13 @@ import {
canvasMerged, canvasMerged,
canvasSavedToGallery, canvasSavedToGallery,
} from 'features/canvas/store/actions'; } from 'features/canvas/store/actions';
import { $tool } from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
resetCanvas, resetCanvas,
resetCanvasView, resetCanvasView,
setIsMaskEnabled, setIsMaskEnabled,
setLayer, setLayer,
setTool,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import type { CanvasLayer } from 'features/canvas/store/canvasTypes'; import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes'; import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
@ -50,7 +51,7 @@ const IAICanvasToolbar = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled); const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
const layer = useAppSelector((s) => s.canvas.layer); const layer = useAppSelector((s) => s.canvas.layer);
const tool = useAppSelector((s) => s.canvas.tool); const tool = useStore($tool);
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = getCanvasBaseLayer();
const { t } = useTranslation(); const { t } = useTranslation();
@ -133,8 +134,8 @@ const IAICanvasToolbar = () => {
); );
const handleSelectMoveTool = useCallback(() => { const handleSelectMoveTool = useCallback(() => {
dispatch(setTool('move')); $tool.set('move');
}, [dispatch]); }, []);
const handleClickResetCanvasView = useSingleAndDoubleClick( const handleClickResetCanvasView = useSingleAndDoubleClick(
() => handleResetCanvasView(false), () => handleResetCanvasView(false),

View File

@ -1,8 +1,8 @@
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
$isMovingBoundingBox, $isMovingBoundingBox,
setIsMovingStage, $isMovingStage,
$tool,
} from 'features/canvas/store/canvasNanostore'; } from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { setStageCoordinates } from 'features/canvas/store/canvasSlice'; import { setStageCoordinates } from 'features/canvas/store/canvasSlice';
@ -12,18 +12,19 @@ import { useCallback } from 'react';
const useCanvasDrag = () => { const useCanvasDrag = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
const tool = useAppSelector((s) => s.canvas.tool);
const isMovingBoundingBox = useStore($isMovingBoundingBox);
const handleDragStart = useCallback(() => { const handleDragStart = useCallback(() => {
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) { if (
!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())
) {
return; return;
} }
setIsMovingStage(true); $isMovingStage.set(true);
}, [isMovingBoundingBox, isStaging, tool]); }, [isStaging]);
const handleDragMove = useCallback( const handleDragMove = useCallback(
(e: KonvaEventObject<MouseEvent>) => { (e: KonvaEventObject<MouseEvent>) => {
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) { const tool = $tool.get();
if (!((tool === 'move' || isStaging) && !$isMovingBoundingBox.get())) {
return; return;
} }
@ -31,15 +32,17 @@ const useCanvasDrag = () => {
dispatch(setStageCoordinates(newCoordinates)); dispatch(setStageCoordinates(newCoordinates));
}, },
[dispatch, isMovingBoundingBox, isStaging, tool] [dispatch, isStaging]
); );
const handleDragEnd = useCallback(() => { const handleDragEnd = useCallback(() => {
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) { if (
!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())
) {
return; return;
} }
setIsMovingStage(false); $isMovingStage.set(false);
}, [isMovingBoundingBox, isStaging, tool]); }, [isStaging]);
return { return {
handleDragStart, handleDragStart,

View File

@ -1,5 +1,7 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
$tool,
$toolStash,
resetCanvasInteractionState, resetCanvasInteractionState,
resetToolInteractionState, resetToolInteractionState,
} from 'features/canvas/store/canvasNanostore'; } from 'features/canvas/store/canvasNanostore';
@ -9,12 +11,10 @@ import {
setIsMaskEnabled, setIsMaskEnabled,
setShouldShowBoundingBox, setShouldShowBoundingBox,
setShouldSnapToGrid, setShouldSnapToGrid,
setTool,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import type { CanvasTool } from 'features/canvas/store/canvasTypes';
import { getCanvasStage } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasStage } from 'features/canvas/util/konvaInstanceProvider';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useCallback, useRef } from 'react'; import { useCallback, useEffect } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
const useInpaintingCanvasHotkeys = () => { const useInpaintingCanvasHotkeys = () => {
@ -23,11 +23,9 @@ const useInpaintingCanvasHotkeys = () => {
const shouldShowBoundingBox = useAppSelector( const shouldShowBoundingBox = useAppSelector(
(s) => s.canvas.shouldShowBoundingBox (s) => s.canvas.shouldShowBoundingBox
); );
const tool = useAppSelector((s) => s.canvas.tool);
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled); const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid); const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
const previousToolRef = useRef<CanvasTool | null>(null);
const canvasStage = getCanvasStage(); const canvasStage = getCanvasStage();
// Beta Keys // Beta Keys
@ -96,37 +94,53 @@ const useInpaintingCanvasHotkeys = () => {
[activeTabName, shouldShowBoundingBox] [activeTabName, shouldShowBoundingBox]
); );
useHotkeys( useEffect(() => {
['space'], window.addEventListener('keydown', (e) => {
if (e.key === ' ' && !e.repeat) {
console.log('spaceeee');
}
});
}, []);
const onKeyDown = useCallback(
(e: KeyboardEvent) => { (e: KeyboardEvent) => {
if (e.repeat) { if (e.repeat || e.key !== ' ') {
return;
}
if ($toolStash.get() || $tool.get() === 'move') {
return; return;
} }
canvasStage?.container().focus(); canvasStage?.container().focus();
$toolStash.set($tool.get());
if (tool !== 'move') { $tool.set('move');
previousToolRef.current = tool; resetToolInteractionState();
dispatch(setTool('move'));
resetToolInteractionState();
}
if (
tool === 'move' &&
previousToolRef.current &&
previousToolRef.current !== 'move'
) {
dispatch(setTool(previousToolRef.current));
previousToolRef.current = 'move';
}
}, },
{ [canvasStage]
keyup: true,
keydown: true,
preventDefault: true,
},
[tool, previousToolRef]
); );
const onKeyUp = useCallback(
(e: KeyboardEvent) => {
if (e.repeat || e.key !== ' ') {
return;
}
if (!$toolStash.get() || $tool.get() !== 'move') {
return;
}
canvasStage?.container().focus();
$tool.set($toolStash.get() ?? 'move');
$toolStash.set(null);
},
[canvasStage]
);
useEffect(() => {
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
return () => {
window.removeEventListener('keydown', onKeyDown);
window.removeEventListener('keyup', onKeyUp);
};
}, [onKeyDown, onKeyUp]);
}; };
export default useInpaintingCanvasHotkeys; export default useInpaintingCanvasHotkeys;

View File

@ -1,7 +1,8 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
setIsDrawing, $isDrawing,
setIsMovingStage, $isMovingStage,
$tool,
} from 'features/canvas/store/canvasNanostore'; } from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { addLine } from 'features/canvas/store/canvasSlice'; import { addLine } from 'features/canvas/store/canvasSlice';
@ -15,7 +16,6 @@ import useColorPicker from './useColorUnderCursor';
const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => { const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const tool = useAppSelector((s) => s.canvas.tool);
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
const { commitColorUnderCursor } = useColorPicker(); const { commitColorUnderCursor } = useColorPicker();
@ -26,9 +26,10 @@ const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
} }
stageRef.current.container().focus(); stageRef.current.container().focus();
const tool = $tool.get();
if (tool === 'move' || isStaging) { if (tool === 'move' || isStaging) {
setIsMovingStage(true); $isMovingStage.set(true);
return; return;
} }
@ -45,12 +46,17 @@ const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
e.evt.preventDefault(); e.evt.preventDefault();
setIsDrawing(true); $isDrawing.set(true);
// Add a new line starting from the current cursor position. // Add a new line starting from the current cursor position.
dispatch(addLine([scaledCursorPosition.x, scaledCursorPosition.y])); dispatch(
addLine({
points: [scaledCursorPosition.x, scaledCursorPosition.y],
tool,
})
);
}, },
[stageRef, tool, isStaging, dispatch, commitColorUnderCursor] [stageRef, isStaging, dispatch, commitColorUnderCursor]
); );
}; };

View File

@ -1,8 +1,8 @@
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
$cursorPosition,
$isDrawing, $isDrawing,
setCursorPosition, $tool,
} from 'features/canvas/store/canvasNanostore'; } from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice'; import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
@ -20,8 +20,6 @@ const useCanvasMouseMove = (
lastCursorPositionRef: MutableRefObject<Vector2d> lastCursorPositionRef: MutableRefObject<Vector2d>
) => { ) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isDrawing = useStore($isDrawing);
const tool = useAppSelector((s) => s.canvas.tool);
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
const { updateColorUnderCursor } = useColorPicker(); const { updateColorUnderCursor } = useColorPicker();
@ -36,16 +34,17 @@ const useCanvasMouseMove = (
return; return;
} }
setCursorPosition(scaledCursorPosition); $cursorPosition.set(scaledCursorPosition);
lastCursorPositionRef.current = scaledCursorPosition; lastCursorPositionRef.current = scaledCursorPosition;
const tool = $tool.get();
if (tool === 'colorPicker') { if (tool === 'colorPicker') {
updateColorUnderCursor(); updateColorUnderCursor();
return; return;
} }
if (!isDrawing || tool === 'move' || isStaging) { if (!$isDrawing.get() || tool === 'move' || isStaging) {
return; return;
} }
@ -56,11 +55,9 @@ const useCanvasMouseMove = (
}, [ }, [
didMouseMoveRef, didMouseMoveRef,
dispatch, dispatch,
isDrawing,
isStaging, isStaging,
lastCursorPositionRef, lastCursorPositionRef,
stageRef, stageRef,
tool,
updateColorUnderCursor, updateColorUnderCursor,
]); ]);
}; };

View File

@ -2,8 +2,8 @@ import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
$isDrawing, $isDrawing,
setIsDrawing, $isMovingStage,
setIsMovingStage, $tool,
} from 'features/canvas/store/canvasNanostore'; } from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice'; import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
@ -18,12 +18,11 @@ const useCanvasMouseUp = (
) => { ) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isDrawing = useStore($isDrawing); const isDrawing = useStore($isDrawing);
const tool = useAppSelector((s) => s.canvas.tool);
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
return useCallback(() => { return useCallback(() => {
if (tool === 'move' || isStaging) { if ($tool.get() === 'move' || isStaging) {
setIsMovingStage(false); $isMovingStage.set(false);
return; return;
} }
@ -46,8 +45,8 @@ const useCanvasMouseUp = (
} else { } else {
didMouseMoveRef.current = false; didMouseMoveRef.current = false;
} }
setIsDrawing(false); $isDrawing.set(false);
}, [didMouseMoveRef, dispatch, isDrawing, isStaging, stageRef, tool]); }, [didMouseMoveRef, dispatch, isDrawing, isStaging, stageRef]);
}; };
export default useCanvasMouseUp; export default useCanvasMouseUp;

View File

@ -1,4 +1,5 @@
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { $tool } from 'features/canvas/store/canvasNanostore';
import { import {
commitColorPickerColor, commitColorPickerColor,
setColorPickerColor, setColorPickerColor,
@ -51,6 +52,7 @@ const useColorPicker = () => {
const commitColorUnderCursor = useCallback(() => { const commitColorUnderCursor = useCallback(() => {
dispatch(commitColorPickerColor()); dispatch(commitColorPickerColor());
$tool.set('brush')
}, [dispatch]); }, [dispatch]);
return { updateColorUnderCursor, commitColorUnderCursor }; return { updateColorUnderCursor, commitColorUnderCursor };

View File

@ -1,7 +1,10 @@
import type { CanvasTool } from 'features/canvas/store/canvasTypes';
import type { Vector2d } from 'konva/lib/types'; import type { Vector2d } from 'konva/lib/types';
import { atom, computed } from 'nanostores'; import { atom, computed } from 'nanostores';
export const $cursorPosition = atom<Vector2d | null>(null); export const $cursorPosition = atom<Vector2d | null>(null);
export const $tool = atom<CanvasTool>('move');
export const $toolStash = atom<CanvasTool | null>(null);
export const $isDrawing = atom<boolean>(false); export const $isDrawing = atom<boolean>(false);
export const $isMouseOverBoundingBox = atom<boolean>(false); export const $isMouseOverBoundingBox = atom<boolean>(false);
export const $isMoveBoundingBoxKeyHeld = atom<boolean>(false); export const $isMoveBoundingBoxKeyHeld = atom<boolean>(false);
@ -9,6 +12,7 @@ export const $isMoveStageKeyHeld = atom<boolean>(false);
export const $isMovingBoundingBox = atom<boolean>(false); export const $isMovingBoundingBox = atom<boolean>(false);
export const $isMovingStage = atom<boolean>(false); export const $isMovingStage = atom<boolean>(false);
export const $isTransformingBoundingBox = atom<boolean>(false); export const $isTransformingBoundingBox = atom<boolean>(false);
export const $isMouseOverBoundingBoxOutline = atom<boolean>(false);
export const $isModifyingBoundingBox = computed( export const $isModifyingBoundingBox = computed(
[$isTransformingBoundingBox, $isMovingBoundingBox], [$isTransformingBoundingBox, $isMovingBoundingBox],
(isTransformingBoundingBox, isMovingBoundingBox) => (isTransformingBoundingBox, isMovingBoundingBox) =>
@ -25,49 +29,13 @@ export const resetCanvasInteractionState = () => {
$isMovingStage.set(false); $isMovingStage.set(false);
}; };
export const setCursorPosition = (cursorPosition: Vector2d | null) => {
$cursorPosition.set(cursorPosition);
};
export const setIsDrawing = (isDrawing: boolean) => {
$isDrawing.set(isDrawing);
};
export const setIsMouseOverBoundingBox = (isMouseOverBoundingBox: boolean) => {
$isMouseOverBoundingBox.set(isMouseOverBoundingBox);
};
export const setIsMoveBoundingBoxKeyHeld = (
isMoveBoundingBoxKeyHeld: boolean
) => {
$isMoveBoundingBoxKeyHeld.set(isMoveBoundingBoxKeyHeld);
};
export const setIsMoveStageKeyHeld = (isMoveStageKeyHeld: boolean) => {
$isMoveStageKeyHeld.set(isMoveStageKeyHeld);
};
export const setIsMovingBoundingBox = (isMovingBoundingBox: boolean) => {
$isMovingBoundingBox.set(isMovingBoundingBox);
};
export const setIsMovingStage = (isMovingStage: boolean) => {
$isMovingStage.set(isMovingStage);
};
export const setIsTransformingBoundingBox = (
isTransformingBoundingBox: boolean
) => {
$isTransformingBoundingBox.set(isTransformingBoundingBox);
};
export const resetToolInteractionState = () => { export const resetToolInteractionState = () => {
setIsTransformingBoundingBox(false); $isTransformingBoundingBox.set(false);
setIsMouseOverBoundingBox(false); $isMouseOverBoundingBox.set(false);
setIsMovingBoundingBox(false); $isMovingBoundingBox.set(false);
setIsMovingStage(false); $isMovingStage.set(false);
}; };
export const setCanvasInteractionStateMouseOut = () => { export const setCanvasInteractionStateMouseOut = () => {
setCursorPosition(null); $cursorPosition.set(null);
}; };

View File

@ -10,7 +10,6 @@ import calculateScale from 'features/canvas/util/calculateScale';
import { STAGE_PADDING_PERCENTAGE } from 'features/canvas/util/constants'; import { STAGE_PADDING_PERCENTAGE } from 'features/canvas/util/constants';
import floorCoordinates from 'features/canvas/util/floorCoordinates'; import floorCoordinates from 'features/canvas/util/floorCoordinates';
import getScaledBoundingBoxDimensions from 'features/canvas/util/getScaledBoundingBoxDimensions'; import getScaledBoundingBoxDimensions from 'features/canvas/util/getScaledBoundingBoxDimensions';
import roundDimensionsToMultiple from 'features/canvas/util/roundDimensionsToMultiple';
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
import { modelChanged } from 'features/parameters/store/generationSlice'; import { modelChanged } from 'features/parameters/store/generationSlice';
@ -86,7 +85,6 @@ export const initialCanvasState: CanvasState = {
stageCoordinates: { x: 0, y: 0 }, stageCoordinates: { x: 0, y: 0 },
stageDimensions: { width: 0, height: 0 }, stageDimensions: { width: 0, height: 0 },
stageScale: 1, stageScale: 1,
tool: 'brush',
batchIds: [], batchIds: [],
aspectRatio: { aspectRatio: {
id: '1:1', id: '1:1',
@ -119,18 +117,9 @@ export const canvasSlice = createSlice({
name: 'canvas', name: 'canvas',
initialState: initialCanvasState, initialState: initialCanvasState,
reducers: { reducers: {
setTool: (state, action: PayloadAction<CanvasTool>) => {
state.tool = action.payload;
},
setLayer: (state, action: PayloadAction<CanvasLayer>) => { setLayer: (state, action: PayloadAction<CanvasLayer>) => {
state.layer = action.payload; state.layer = action.payload;
}, },
toggleTool: (state) => {
const currentTool = state.tool;
if (currentTool !== 'move') {
state.tool = currentTool === 'brush' ? 'eraser' : 'brush';
}
},
setMaskColor: (state, action: PayloadAction<RgbaColor>) => { setMaskColor: (state, action: PayloadAction<RgbaColor>) => {
state.maskColor = action.payload; state.maskColor = action.payload;
}, },
@ -376,9 +365,13 @@ export const canvasSlice = createSlice({
state.futureLayerStates = []; state.futureLayerStates = [];
}, },
addLine: (state, action: PayloadAction<number[]>) => { addLine: (
const { tool, layer, brushColor, brushSize, shouldRestrictStrokesToBox } = state,
action: PayloadAction<{ points: number[]; tool: CanvasTool }>
) => {
const { layer, brushColor, brushSize, shouldRestrictStrokesToBox } =
state; state;
const { points, tool } = action.payload;
if (tool === 'move' || tool === 'colorPicker') { if (tool === 'move' || tool === 'colorPicker') {
return; return;
@ -401,7 +394,7 @@ export const canvasSlice = createSlice({
layer, layer,
tool, tool,
strokeWidth: newStrokeWidth, strokeWidth: newStrokeWidth,
points: action.payload, points,
...newColor, ...newColor,
}; };
@ -664,7 +657,6 @@ export const canvasSlice = createSlice({
...state.colorPickerColor, ...state.colorPickerColor,
a: state.brushColor.a, a: state.brushColor.a,
}; };
state.tool = 'brush';
}, },
setMergedCanvas: (state, action: PayloadAction<CanvasImage>) => { setMergedCanvas: (state, action: PayloadAction<CanvasImage>) => {
state.pastLayerStates.push(cloneDeep(state.layerState)); state.pastLayerStates.push(cloneDeep(state.layerState));
@ -771,9 +763,7 @@ export const {
setShouldSnapToGrid, setShouldSnapToGrid,
setStageCoordinates, setStageCoordinates,
setStageScale, setStageScale,
setTool,
toggleShouldLockBoundingBox, toggleShouldLockBoundingBox,
toggleTool,
undo, undo,
setScaledBoundingBoxDimensions, setScaledBoundingBoxDimensions,
setShouldRestrictStrokesToBox, setShouldRestrictStrokesToBox,

View File

@ -149,7 +149,6 @@ export interface CanvasState {
stageCoordinates: Vector2d; stageCoordinates: Vector2d;
stageDimensions: Dimensions; stageDimensions: Dimensions;
stageScale: number; stageScale: number;
tool: CanvasTool;
generationMode?: GenerationMode; generationMode?: GenerationMode;
batchIds: string[]; batchIds: string[];
aspectRatio: AspectRatioState; aspectRatio: AspectRatioState;