feat(ui): move canvas interaction state to nanostores

This drastically reduces the computation needed when moving the cursor. It also correctly separates ephemeral interaction state from redux, where it is not needed.

Also removed some unused canvas state.
This commit is contained in:
psychedelicious 2024-01-01 12:23:25 +11:00 committed by Kent Keirsey
parent 2a38606342
commit 7c548c5bf3
16 changed files with 258 additions and 251 deletions

View File

@ -1,4 +1,5 @@
import { Box, chakra, Flex } from '@chakra-ui/react'; import { Box, chakra, Flex } from '@chakra-ui/react';
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
@ -9,6 +10,12 @@ import useCanvasMouseMove from 'features/canvas/hooks/useCanvasMouseMove';
import useCanvasMouseOut from 'features/canvas/hooks/useCanvasMouseOut'; import useCanvasMouseOut from 'features/canvas/hooks/useCanvasMouseOut';
import useCanvasMouseUp from 'features/canvas/hooks/useCanvasMouseUp'; import useCanvasMouseUp from 'features/canvas/hooks/useCanvasMouseUp';
import useCanvasWheel from 'features/canvas/hooks/useCanvasZoom'; import useCanvasWheel from 'features/canvas/hooks/useCanvasZoom';
import {
$isModifyingBoundingBox,
$isMouseOverBoundingBox,
$isMovingStage,
$isTransformingBoundingBox,
} from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { canvasResized } from 'features/canvas/store/canvasSlice'; import { canvasResized } from 'features/canvas/store/canvasSlice';
import { import {
@ -40,46 +47,27 @@ const selector = createMemoizedSelector(
isMaskEnabled, isMaskEnabled,
stageScale, stageScale,
shouldShowBoundingBox, shouldShowBoundingBox,
isTransformingBoundingBox,
isMouseOverBoundingBox,
isMovingBoundingBox,
stageDimensions, stageDimensions,
stageCoordinates, stageCoordinates,
tool, tool,
isMovingStage,
shouldShowIntermediates, shouldShowIntermediates,
shouldShowGrid,
shouldRestrictStrokesToBox, shouldRestrictStrokesToBox,
shouldShowGrid,
shouldAntialias, shouldAntialias,
} = canvas; } = canvas;
let stageCursor: string | undefined = 'none';
if (tool === 'move' || isStaging) {
if (isMovingStage) {
stageCursor = 'grabbing';
} else {
stageCursor = 'grab';
}
} else if (isTransformingBoundingBox) {
stageCursor = undefined;
} else if (shouldRestrictStrokesToBox && !isMouseOverBoundingBox) {
stageCursor = 'default';
}
return { return {
isMaskEnabled, isMaskEnabled,
isModifyingBoundingBox: isTransformingBoundingBox || isMovingBoundingBox,
shouldShowBoundingBox, shouldShowBoundingBox,
shouldShowGrid, shouldShowGrid,
stageCoordinates, stageCoordinates,
stageCursor,
stageDimensions, stageDimensions,
stageScale, stageScale,
tool, tool,
isStaging, isStaging,
shouldShowIntermediates, shouldShowIntermediates,
shouldAntialias, shouldAntialias,
shouldRestrictStrokesToBox,
}; };
} }
); );
@ -91,29 +79,51 @@ const ChakraStage = chakra(Stage, {
const IAICanvas = () => { const IAICanvas = () => {
const { const {
isMaskEnabled, isMaskEnabled,
isModifyingBoundingBox,
shouldShowBoundingBox, shouldShowBoundingBox,
shouldShowGrid, shouldShowGrid,
stageCoordinates, stageCoordinates,
stageCursor,
stageDimensions, stageDimensions,
stageScale, stageScale,
tool, tool,
isStaging, isStaging,
shouldShowIntermediates, shouldShowIntermediates,
shouldAntialias, shouldAntialias,
shouldRestrictStrokesToBox,
} = useAppSelector(selector); } = useAppSelector(selector);
useCanvasHotkeys(); useCanvasHotkeys();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const stageRef = useRef<Konva.Stage | null>(null); const stageRef = useRef<Konva.Stage | null>(null);
const canvasBaseLayerRef = useRef<Konva.Layer | null>(null); const canvasBaseLayerRef = useRef<Konva.Layer | null>(null);
const isModifyingBoundingBox = useStore($isModifyingBoundingBox);
const isMovingStage = useStore($isMovingStage);
const isTransformingBoundingBox = useStore($isTransformingBoundingBox);
const isMouseOverBoundingBox = useStore($isMouseOverBoundingBox);
const canvasStageRefCallback = useCallback((el: Konva.Stage) => { const canvasStageRefCallback = useCallback((el: Konva.Stage) => {
setCanvasStage(el as Konva.Stage); setCanvasStage(el as Konva.Stage);
stageRef.current = el; stageRef.current = el;
}, []); }, []);
const stageCursor = useMemo(() => {
if (tool === 'move' || isStaging) {
if (isMovingStage) {
return 'grabbing';
} else {
return 'grab';
}
} else if (isTransformingBoundingBox) {
return undefined;
} else if (shouldRestrictStrokesToBox && !isMouseOverBoundingBox) {
return 'default';
}
return 'none';
}, [
isMouseOverBoundingBox,
isMovingStage,
isStaging,
isTransformingBoundingBox,
shouldRestrictStrokesToBox,
tool,
]);
const canvasBaseLayerRefCallback = useCallback((el: Konva.Layer) => { const canvasBaseLayerRefCallback = useCallback((el: Konva.Layer) => {
setCanvasBaseLayer(el as Konva.Layer); setCanvasBaseLayer(el as Konva.Layer);
canvasBaseLayerRef.current = el; canvasBaseLayerRef.current = el;
@ -132,10 +142,9 @@ const IAICanvas = () => {
didMouseMoveRef, didMouseMoveRef,
lastCursorPositionRef lastCursorPositionRef
); );
const handleMouseOut = useCanvasMouseOut();
const { handleDragStart, handleDragMove, handleDragEnd } = const { handleDragStart, handleDragMove, handleDragEnd } =
useCanvasDragMove(); useCanvasDragMove();
const handleMouseOut = useCanvasMouseOut();
const handleContextMenu = useCallback( const handleContextMenu = useCallback(
(e: KonvaEventObject<MouseEvent>) => e.evt.preventDefault(), (e: KonvaEventObject<MouseEvent>) => e.evt.preventDefault(),
[] []

View File

@ -1,24 +1,18 @@
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { useStore } from '@nanostores/react';
import { stateSelector } from 'app/store/store'; import { $cursorPosition } from 'features/canvas/store/canvasNanostore';
import { useAppSelector } from 'app/store/storeHooks';
import roundToHundreth from 'features/canvas/util/roundToHundreth'; import roundToHundreth from 'features/canvas/util/roundToHundreth';
import { memo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const cursorPositionSelector = createSelector([stateSelector], ({ canvas }) => {
const { cursorPosition } = canvas;
const { cursorX, cursorY } = cursorPosition
? { cursorX: cursorPosition.x, cursorY: cursorPosition.y }
: { cursorX: -1, cursorY: -1 };
return `(${roundToHundreth(cursorX)}, ${roundToHundreth(cursorY)})`;
});
const IAICanvasStatusTextCursorPos = () => { const IAICanvasStatusTextCursorPos = () => {
const cursorCoordinatesString = useAppSelector(cursorPositionSelector);
const { t } = useTranslation(); const { t } = useTranslation();
const cursorPosition = useStore($cursorPosition);
const cursorCoordinatesString = useMemo(() => {
const x = cursorPosition?.x ?? -1;
const y = cursorPosition?.y ?? -1;
return `(${roundToHundreth(x)}, ${roundToHundreth(y)})`;
}, [cursorPosition?.x, cursorPosition?.y]);
return ( return (
<Box>{`${t( <Box>{`${t(

View File

@ -1,29 +1,31 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import {
$cursorPosition,
$isMovingBoundingBox,
$isTransformingBoundingBox,
} from 'features/canvas/store/canvasNanostore';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { import {
COLOR_PICKER_SIZE, COLOR_PICKER_SIZE,
COLOR_PICKER_STROKE_RADIUS, COLOR_PICKER_STROKE_RADIUS,
} from 'features/canvas/util/constants'; } from 'features/canvas/util/constants';
import type { GroupConfig } from 'konva/lib/Group'; import type { GroupConfig } from 'konva/lib/Group';
import { memo } from 'react'; import { memo, useMemo } from 'react';
import { Circle, Group } from 'react-konva'; import { Circle, Group } from 'react-konva';
const canvasBrushPreviewSelector = createMemoizedSelector( const canvasBrushPreviewSelector = createMemoizedSelector(
stateSelector, stateSelector,
({ canvas }) => { ({ canvas }) => {
const { const {
cursorPosition,
brushSize, brushSize,
colorPickerColor, colorPickerColor,
maskColor, maskColor,
brushColor, brushColor,
tool, tool,
layer, layer,
shouldShowBrush,
isMovingBoundingBox,
isTransformingBoundingBox,
stageScale, stageScale,
stageDimensions, stageDimensions,
boundingBoxCoordinates, boundingBoxCoordinates,
@ -80,9 +82,6 @@ const canvasBrushPreviewSelector = createMemoizedSelector(
// : undefined; // : undefined;
return { return {
cursorPosition,
brushX: cursorPosition ? cursorPosition.x : stageDimensions.width / 2,
brushY: cursorPosition ? cursorPosition.y : stageDimensions.height / 2,
radius: brushSize / 2, radius: brushSize / 2,
colorPickerOuterRadius: COLOR_PICKER_SIZE / stageScale, colorPickerOuterRadius: COLOR_PICKER_SIZE / stageScale,
colorPickerInnerRadius: colorPickerInnerRadius:
@ -92,16 +91,10 @@ const canvasBrushPreviewSelector = createMemoizedSelector(
colorPickerColorString: rgbaColorToString(colorPickerColor), colorPickerColorString: rgbaColorToString(colorPickerColor),
tool, tool,
layer, layer,
shouldShowBrush,
shouldDrawBrushPreview:
!(
isMovingBoundingBox ||
isTransformingBoundingBox ||
!cursorPosition
) && shouldShowBrush,
strokeWidth: 1.5 / stageScale, strokeWidth: 1.5 / stageScale,
dotRadius: 1.5 / stageScale, dotRadius: 1.5 / stageScale,
clip, clip,
stageDimensions,
}; };
} }
); );
@ -111,13 +104,10 @@ const canvasBrushPreviewSelector = createMemoizedSelector(
*/ */
const IAICanvasToolPreview = (props: GroupConfig) => { const IAICanvasToolPreview = (props: GroupConfig) => {
const { const {
brushX,
brushY,
radius, radius,
maskColorString, maskColorString,
tool, tool,
layer, layer,
shouldDrawBrushPreview,
dotRadius, dotRadius,
strokeWidth, strokeWidth,
brushColorString, brushColorString,
@ -125,8 +115,28 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
colorPickerInnerRadius, colorPickerInnerRadius,
colorPickerOuterRadius, colorPickerOuterRadius,
clip, clip,
stageDimensions,
} = useAppSelector(canvasBrushPreviewSelector); } = useAppSelector(canvasBrushPreviewSelector);
const cursorPosition = useStore($cursorPosition);
const isMovingBoundingBox = useStore($isMovingBoundingBox);
const isTransformingBoundingBox = useStore($isTransformingBoundingBox);
const brushX = useMemo<number>(
() => (cursorPosition ? cursorPosition.x : stageDimensions.width / 2),
[cursorPosition, stageDimensions]
);
const brushY = useMemo<number>(
() => (cursorPosition ? cursorPosition.y : stageDimensions.height / 2),
[cursorPosition, stageDimensions]
);
const shouldDrawBrushPreview = useMemo(
() =>
!(isMovingBoundingBox || isTransformingBoundingBox || !cursorPosition),
[cursorPosition, isMovingBoundingBox, isTransformingBoundingBox]
);
if (!shouldDrawBrushPreview) { if (!shouldDrawBrushPreview) {
return null; return null;
} }

View File

@ -1,3 +1,4 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
@ -6,11 +7,16 @@ import {
roundToMultiple, roundToMultiple,
} from 'common/util/roundDownToMultiple'; } from 'common/util/roundDownToMultiple';
import { import {
setBoundingBoxCoordinates, $isDrawing,
setBoundingBoxDimensions, $isMovingBoundingBox,
$isTransformingBoundingBox,
setIsMouseOverBoundingBox, setIsMouseOverBoundingBox,
setIsMovingBoundingBox, setIsMovingBoundingBox,
setIsTransformingBoundingBox, setIsTransformingBoundingBox,
} from 'features/canvas/store/canvasNanostore';
import {
setBoundingBoxCoordinates,
setBoundingBoxDimensions,
setShouldSnapToGrid, setShouldSnapToGrid,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import type Konva from 'konva'; import type Konva from 'konva';
@ -30,9 +36,6 @@ const boundingBoxPreviewSelector = createMemoizedSelector(
boundingBoxCoordinates, boundingBoxCoordinates,
boundingBoxDimensions, boundingBoxDimensions,
stageScale, stageScale,
isDrawing,
isTransformingBoundingBox,
isMovingBoundingBox,
tool, tool,
shouldSnapToGrid, shouldSnapToGrid,
} = canvas; } = canvas;
@ -42,9 +45,6 @@ const boundingBoxPreviewSelector = createMemoizedSelector(
return { return {
boundingBoxCoordinates, boundingBoxCoordinates,
boundingBoxDimensions, boundingBoxDimensions,
isDrawing,
isMovingBoundingBox,
isTransformingBoundingBox,
stageScale, stageScale,
shouldSnapToGrid, shouldSnapToGrid,
tool, tool,
@ -63,9 +63,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
const { const {
boundingBoxCoordinates, boundingBoxCoordinates,
boundingBoxDimensions, boundingBoxDimensions,
isDrawing,
isMovingBoundingBox,
isTransformingBoundingBox,
stageScale, stageScale,
shouldSnapToGrid, shouldSnapToGrid,
tool, tool,
@ -75,7 +73,9 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
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 isDrawing = useStore($isDrawing);
const isMovingBoundingBox = useStore($isMovingBoundingBox);
const isTransformingBoundingBox = useStore($isTransformingBoundingBox);
const [isMouseOverBoundingBoxOutline, setIsMouseOverBoundingBoxOutline] = const [isMouseOverBoundingBoxOutline, setIsMouseOverBoundingBoxOutline] =
useState(false); useState(false);
@ -209,26 +209,26 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
); );
const handleStartedTransforming = useCallback(() => { const handleStartedTransforming = useCallback(() => {
dispatch(setIsTransformingBoundingBox(true)); setIsTransformingBoundingBox(true);
}, [dispatch]); }, []);
const handleEndedTransforming = useCallback(() => { const handleEndedTransforming = useCallback(() => {
dispatch(setIsTransformingBoundingBox(false)); setIsTransformingBoundingBox(false);
dispatch(setIsMovingBoundingBox(false)); setIsMovingBoundingBox(false);
dispatch(setIsMouseOverBoundingBox(false)); setIsMouseOverBoundingBox(false);
setIsMouseOverBoundingBoxOutline(false); setIsMouseOverBoundingBoxOutline(false);
}, [dispatch]); }, []);
const handleStartedMoving = useCallback(() => { const handleStartedMoving = useCallback(() => {
dispatch(setIsMovingBoundingBox(true)); setIsMovingBoundingBox(true);
}, [dispatch]); }, []);
const handleEndedModifying = useCallback(() => { const handleEndedModifying = useCallback(() => {
dispatch(setIsTransformingBoundingBox(false)); setIsTransformingBoundingBox(false);
dispatch(setIsMovingBoundingBox(false)); setIsMovingBoundingBox(false);
dispatch(setIsMouseOverBoundingBox(false)); setIsMouseOverBoundingBox(false);
setIsMouseOverBoundingBoxOutline(false); setIsMouseOverBoundingBoxOutline(false);
}, [dispatch]); }, []);
const handleMouseOver = useCallback(() => { const handleMouseOver = useCallback(() => {
setIsMouseOverBoundingBoxOutline(true); setIsMouseOverBoundingBoxOutline(true);
@ -241,12 +241,12 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
}, [isMovingBoundingBox, isTransformingBoundingBox]); }, [isMovingBoundingBox, isTransformingBoundingBox]);
const handleMouseEnterBoundingBox = useCallback(() => { const handleMouseEnterBoundingBox = useCallback(() => {
dispatch(setIsMouseOverBoundingBox(true)); setIsMouseOverBoundingBox(true);
}, [dispatch]); }, []);
const handleMouseLeaveBoundingBox = useCallback(() => { const handleMouseLeaveBoundingBox = useCallback(() => {
dispatch(setIsMouseOverBoundingBox(false)); setIsMouseOverBoundingBox(false);
}, [dispatch]); }, []);
return ( return (
<Group {...rest}> <Group {...rest}>

View File

@ -12,6 +12,7 @@ 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 { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
addEraseRect, addEraseRect,
@ -176,12 +177,15 @@ const IAICanvasToolChooserOptions = () => {
const handleSelectBrushTool = useCallback(() => { const handleSelectBrushTool = useCallback(() => {
dispatch(setTool('brush')); dispatch(setTool('brush'));
resetToolInteractionState();
}, [dispatch]); }, [dispatch]);
const handleSelectEraserTool = useCallback(() => { const handleSelectEraserTool = useCallback(() => {
dispatch(setTool('eraser')); dispatch(setTool('eraser'));
resetToolInteractionState();
}, [dispatch]); }, [dispatch]);
const handleSelectColorPickerTool = useCallback(() => { const handleSelectColorPickerTool = useCallback(() => {
dispatch(setTool('colorPicker')); dispatch(setTool('colorPicker'));
resetToolInteractionState();
}, [dispatch]); }, [dispatch]);
const handleFillRect = useCallback(() => { const handleFillRect = useCallback(() => {
dispatch(addFillRect()); dispatch(addFillRect());

View File

@ -1,26 +1,25 @@
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
$isMovingBoundingBox,
setIsMovingStage, setIsMovingStage,
setStageCoordinates, } from 'features/canvas/store/canvasNanostore';
} from 'features/canvas/store/canvasSlice'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { setStageCoordinates } from 'features/canvas/store/canvasSlice';
import type { KonvaEventObject } from 'konva/lib/Node'; import type { KonvaEventObject } from 'konva/lib/Node';
import { useCallback } from 'react'; import { useCallback } from 'react';
const useCanvasDrag = () => { const useCanvasDrag = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const tool = useAppSelector((state) => state.canvas.tool);
const isMovingBoundingBox = useAppSelector(
(state) => state.canvas.isMovingBoundingBox
);
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
const tool = useAppSelector((state) => state.canvas.tool);
const isMovingBoundingBox = useStore($isMovingBoundingBox);
const handleDragStart = useCallback(() => { const handleDragStart = useCallback(() => {
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) { if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) {
return; return;
} }
dispatch(setIsMovingStage(true)); setIsMovingStage(true);
}, [dispatch, isMovingBoundingBox, isStaging, tool]); }, [isMovingBoundingBox, isStaging, tool]);
const handleDragMove = useCallback( const handleDragMove = useCallback(
(e: KonvaEventObject<MouseEvent>) => { (e: KonvaEventObject<MouseEvent>) => {
@ -39,10 +38,14 @@ const useCanvasDrag = () => {
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) { if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) {
return; return;
} }
dispatch(setIsMovingStage(false)); setIsMovingStage(false);
}, [dispatch, isMovingBoundingBox, isStaging, tool]); }, [isMovingBoundingBox, isStaging, tool]);
return { handleDragStart, handleDragMove, handleDragEnd }; return {
handleDragStart,
handleDragMove,
handleDragEnd,
};
}; };
export default useCanvasDrag; export default useCanvasDrag;

View File

@ -1,10 +1,13 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
resetCanvasInteractionState,
resetToolInteractionState,
} from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
clearMask, clearMask,
resetCanvasInteractionState,
setIsMaskEnabled, setIsMaskEnabled,
setShouldShowBoundingBox, setShouldShowBoundingBox,
setShouldSnapToGrid, setShouldSnapToGrid,
@ -20,7 +23,6 @@ const selector = createMemoizedSelector(
[stateSelector, activeTabNameSelector, isStagingSelector], [stateSelector, activeTabNameSelector, isStagingSelector],
({ canvas }, activeTabName, isStaging) => { ({ canvas }, activeTabName, isStaging) => {
const { const {
cursorPosition,
shouldLockBoundingBox, shouldLockBoundingBox,
shouldShowBoundingBox, shouldShowBoundingBox,
tool, tool,
@ -30,7 +32,6 @@ const selector = createMemoizedSelector(
return { return {
activeTabName, activeTabName,
isCursorOnCanvas: Boolean(cursorPosition),
shouldLockBoundingBox, shouldLockBoundingBox,
shouldShowBoundingBox, shouldShowBoundingBox,
tool, tool,
@ -102,7 +103,7 @@ const useInpaintingCanvasHotkeys = () => {
useHotkeys( useHotkeys(
'esc', 'esc',
() => { () => {
dispatch(resetCanvasInteractionState()); resetCanvasInteractionState();
}, },
{ {
enabled: () => true, enabled: () => true,
@ -134,6 +135,7 @@ const useInpaintingCanvasHotkeys = () => {
if (tool !== 'move') { if (tool !== 'move') {
previousToolRef.current = tool; previousToolRef.current = tool;
dispatch(setTool('move')); dispatch(setTool('move'));
resetToolInteractionState();
} }
if ( if (

View File

@ -1,12 +1,12 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
addLine,
setIsDrawing, setIsDrawing,
setIsMovingStage, setIsMovingStage,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { addLine } from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition'; import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import type Konva from 'konva'; import type Konva from 'konva';
@ -42,7 +42,7 @@ const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
stageRef.current.container().focus(); stageRef.current.container().focus();
if (tool === 'move' || isStaging) { if (tool === 'move' || isStaging) {
dispatch(setIsMovingStage(true)); setIsMovingStage(true);
return; return;
} }
@ -59,7 +59,7 @@ const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
e.evt.preventDefault(); e.evt.preventDefault();
dispatch(setIsDrawing(true)); setIsDrawing(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([scaledCursorPosition.x, scaledCursorPosition.y]));

View File

@ -1,11 +1,13 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
addPointToCurrentLine, $isDrawing,
setCursorPosition, setCursorPosition,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition'; import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import type Konva from 'konva'; import type Konva from 'konva';
@ -18,10 +20,9 @@ import useColorPicker from './useColorUnderCursor';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[activeTabNameSelector, stateSelector, isStagingSelector], [activeTabNameSelector, stateSelector, isStagingSelector],
(activeTabName, { canvas }, isStaging) => { (activeTabName, { canvas }, isStaging) => {
const { tool, isDrawing } = canvas; const { tool } = canvas;
return { return {
tool, tool,
isDrawing,
activeTabName, activeTabName,
isStaging, isStaging,
}; };
@ -34,7 +35,8 @@ const useCanvasMouseMove = (
lastCursorPositionRef: MutableRefObject<Vector2d> lastCursorPositionRef: MutableRefObject<Vector2d>
) => { ) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { isDrawing, tool, isStaging } = useAppSelector(selector); const isDrawing = useStore($isDrawing);
const { tool, isStaging } = useAppSelector(selector);
const { updateColorUnderCursor } = useColorPicker(); const { updateColorUnderCursor } = useColorPicker();
return useCallback(() => { return useCallback(() => {
@ -48,7 +50,7 @@ const useCanvasMouseMove = (
return; return;
} }
dispatch(setCursorPosition(scaledCursorPosition)); setCursorPosition(scaledCursorPosition);
lastCursorPositionRef.current = scaledCursorPosition; lastCursorPositionRef.current = scaledCursorPosition;

View File

@ -1,13 +1,12 @@
import { useAppDispatch } from 'app/store/storeHooks'; import { setCanvasInteractionStateMouseOut } from 'features/canvas/store/canvasNanostore';
import { mouseLeftCanvas } from 'features/canvas/store/canvasSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';
const useCanvasMouseOut = () => { const useCanvasMouseOut = () => {
const dispatch = useAppDispatch(); const onMouseOut = useCallback(() => {
setCanvasInteractionStateMouseOut();
}, []);
return useCallback(() => { return onMouseOut;
dispatch(mouseLeftCanvas());
}, [dispatch]);
}; };
export default useCanvasMouseOut; export default useCanvasMouseOut;

View File

@ -1,27 +1,27 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
$isDrawing,
setIsDrawing,
setIsMovingStage,
} from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
// addPointToCurrentEraserLine, // addPointToCurrentEraserLine,
addPointToCurrentLine, addPointToCurrentLine,
setIsDrawing,
setIsMovingStage,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition'; import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import type Konva from 'konva'; import type Konva from 'konva';
import type { MutableRefObject } from 'react'; import type { MutableRefObject } from 'react';
import { useCallback } from 'react'; import { useCallback } from 'react';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[activeTabNameSelector, stateSelector, isStagingSelector], [stateSelector, isStagingSelector],
(activeTabName, { canvas }, isStaging) => { ({ canvas }, isStaging) => {
const { tool, isDrawing } = canvas;
return { return {
tool, tool: canvas.tool,
isDrawing,
activeTabName,
isStaging, isStaging,
}; };
} }
@ -32,11 +32,12 @@ const useCanvasMouseUp = (
didMouseMoveRef: MutableRefObject<boolean> didMouseMoveRef: MutableRefObject<boolean>
) => { ) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { tool, isDrawing, isStaging } = useAppSelector(selector); const isDrawing = useStore($isDrawing);
const { tool, isStaging } = useAppSelector(selector);
return useCallback(() => { return useCallback(() => {
if (tool === 'move' || isStaging) { if (tool === 'move' || isStaging) {
dispatch(setIsMovingStage(false)); setIsMovingStage(false);
return; return;
} }
@ -59,7 +60,7 @@ const useCanvasMouseUp = (
} else { } else {
didMouseMoveRef.current = false; didMouseMoveRef.current = false;
} }
dispatch(setIsDrawing(false)); setIsDrawing(false);
}, [didMouseMoveRef, dispatch, isDrawing, isStaging, stageRef, tool]); }, [didMouseMoveRef, dispatch, isDrawing, isStaging, stageRef, tool]);
}; };

View File

@ -1,6 +1,8 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { $isMoveStageKeyHeld } from 'features/canvas/store/canvasNanostore';
import { import {
setStageCoordinates, setStageCoordinates,
setStageScale, setStageScale,
@ -16,17 +18,15 @@ import { clamp } from 'lodash-es';
import type { MutableRefObject } from 'react'; import type { MutableRefObject } from 'react';
import { useCallback } from 'react'; import { useCallback } from 'react';
const selector = createMemoizedSelector([stateSelector], ({ canvas }) => { const selector = createMemoizedSelector(
const { isMoveStageKeyHeld, stageScale } = canvas; [stateSelector],
return { (state) => state.canvas.stageScale
isMoveStageKeyHeld, );
stageScale,
};
});
const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => { const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { isMoveStageKeyHeld, stageScale } = useAppSelector(selector); const stageScale = useAppSelector(selector);
const isMoveStageKeyHeld = useStore($isMoveStageKeyHeld);
return useCallback( return useCallback(
(e: KonvaEventObject<WheelEvent>) => { (e: KonvaEventObject<WheelEvent>) => {

View File

@ -0,0 +1,77 @@
import type { Vector2d } from 'konva/lib/types';
import { atom, computed } from 'nanostores';
export const $cursorPosition = atom<Vector2d | null>(null);
export const $isDrawing = atom<boolean>(false);
export const $isMouseOverBoundingBox = atom<boolean>(false);
export const $isMoveBoundingBoxKeyHeld = atom<boolean>(false);
export const $isMoveStageKeyHeld = atom<boolean>(false);
export const $isMovingBoundingBox = atom<boolean>(false);
export const $isMovingStage = atom<boolean>(false);
export const $isTransformingBoundingBox = atom<boolean>(false);
export const $isModifyingBoundingBox = computed(
[$isTransformingBoundingBox, $isMovingBoundingBox],
(isTransformingBoundingBox, isMovingBoundingBox) =>
isTransformingBoundingBox || isMovingBoundingBox
);
export const resetCanvasInteractionState = () => {
$cursorPosition.set(null);
$isDrawing.set(false);
$isMouseOverBoundingBox.set(false);
$isMoveBoundingBoxKeyHeld.set(false);
$isMoveStageKeyHeld.set(false);
$isMovingBoundingBox.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 = () => {
setIsTransformingBoundingBox(false);
setIsMouseOverBoundingBox(false);
setIsMovingBoundingBox(false);
setIsMovingStage(false);
};
export const setCanvasInteractionStateMouseOut = () => {
setCursorPosition(null);
setIsDrawing(false);
setIsMouseOverBoundingBox(false);
setIsMovingBoundingBox(false);
setIsTransformingBoundingBox(false);
};

View File

@ -3,4 +3,4 @@ import type { CanvasState } from './canvasTypes';
/** /**
* Canvas slice persist denylist * Canvas slice persist denylist
*/ */
export const canvasPersistDenylist: (keyof CanvasState)[] = ['cursorPosition']; export const canvasPersistDenylist: (keyof CanvasState)[] = [];

View File

@ -36,6 +36,11 @@ import {
isCanvasMaskLine, isCanvasMaskLine,
} from './canvasTypes'; } from './canvasTypes';
/**
* The maximum history length to keep in the past/future layer states.
*/
const MAX_HISTORY = 128;
export const initialLayerState: CanvasLayerState = { export const initialLayerState: CanvasLayerState = {
objects: [], objects: [],
stagingArea: { stagingArea: {
@ -52,21 +57,11 @@ export const initialCanvasState: CanvasState = {
brushColor: { r: 90, g: 90, b: 255, a: 1 }, brushColor: { r: 90, g: 90, b: 255, a: 1 },
brushSize: 50, brushSize: 50,
colorPickerColor: { r: 90, g: 90, b: 255, a: 1 }, colorPickerColor: { r: 90, g: 90, b: 255, a: 1 },
cursorPosition: null,
futureLayerStates: [], futureLayerStates: [],
isDrawing: false,
isMaskEnabled: true, isMaskEnabled: true,
isMouseOverBoundingBox: false,
isMoveBoundingBoxKeyHeld: false,
isMoveStageKeyHeld: false,
isMovingBoundingBox: false,
isMovingStage: false,
isTransformingBoundingBox: false,
layer: 'base', layer: 'base',
layerState: initialLayerState, layerState: initialLayerState,
maskColor: { r: 255, g: 90, b: 90, a: 1 }, maskColor: { r: 255, g: 90, b: 90, a: 1 },
maxHistory: 128,
minimumStageScale: 1,
pastLayerStates: [], pastLayerStates: [],
scaledBoundingBoxDimensions: { width: 512, height: 512 }, scaledBoundingBoxDimensions: { width: 512, height: 512 },
shouldAntialias: true, shouldAntialias: true,
@ -77,10 +72,7 @@ export const initialCanvasState: CanvasState = {
shouldPreserveMaskedArea: false, shouldPreserveMaskedArea: false,
shouldRestrictStrokesToBox: true, shouldRestrictStrokesToBox: true,
shouldShowBoundingBox: true, shouldShowBoundingBox: true,
shouldShowBrush: true,
shouldShowBrushPreview: false,
shouldShowCanvasDebugInfo: false, shouldShowCanvasDebugInfo: false,
shouldShowCheckboardTransparency: false,
shouldShowGrid: true, shouldShowGrid: true,
shouldShowIntermediates: true, shouldShowIntermediates: true,
shouldShowStagingImage: true, shouldShowStagingImage: true,
@ -98,14 +90,7 @@ export const canvasSlice = createSlice({
initialState: initialCanvasState, initialState: initialCanvasState,
reducers: { reducers: {
setTool: (state, action: PayloadAction<CanvasTool>) => { setTool: (state, action: PayloadAction<CanvasTool>) => {
const tool = action.payload;
state.tool = action.payload; state.tool = action.payload;
if (tool !== 'move') {
state.isTransformingBoundingBox = false;
state.isMouseOverBoundingBox = false;
state.isMovingBoundingBox = false;
state.isMovingStage = false;
}
}, },
setLayer: (state, action: PayloadAction<CanvasLayer>) => { setLayer: (state, action: PayloadAction<CanvasLayer>) => {
state.layer = action.payload; state.layer = action.payload;
@ -146,21 +131,6 @@ export const canvasSlice = createSlice({
state.isMaskEnabled = action.payload; state.isMaskEnabled = action.payload;
state.layer = action.payload ? 'mask' : 'base'; state.layer = action.payload ? 'mask' : 'base';
}, },
setShouldShowCheckboardTransparency: (
state,
action: PayloadAction<boolean>
) => {
state.shouldShowCheckboardTransparency = action.payload;
},
setShouldShowBrushPreview: (state, action: PayloadAction<boolean>) => {
state.shouldShowBrushPreview = action.payload;
},
setShouldShowBrush: (state, action: PayloadAction<boolean>) => {
state.shouldShowBrush = action.payload;
},
setCursorPosition: (state, action: PayloadAction<Vector2d | null>) => {
state.cursorPosition = action.payload;
},
setInitialCanvasImage: (state, action: PayloadAction<ImageDTO>) => { setInitialCanvasImage: (state, action: PayloadAction<ImageDTO>) => {
const image = action.payload; const image = action.payload;
const { width, height } = image; const { width, height } = image;
@ -273,9 +243,6 @@ export const canvasSlice = createSlice({
) => { ) => {
state.shouldDarkenOutsideBoundingBox = action.payload; state.shouldDarkenOutsideBoundingBox = action.payload;
}, },
setIsDrawing: (state, action: PayloadAction<boolean>) => {
state.isDrawing = action.payload;
},
clearCanvasHistory: (state) => { clearCanvasHistory: (state) => {
state.pastLayerStates = []; state.pastLayerStates = [];
state.futureLayerStates = []; state.futureLayerStates = [];
@ -289,21 +256,6 @@ export const canvasSlice = createSlice({
setShouldShowBoundingBox: (state, action: PayloadAction<boolean>) => { setShouldShowBoundingBox: (state, action: PayloadAction<boolean>) => {
state.shouldShowBoundingBox = action.payload; state.shouldShowBoundingBox = action.payload;
}, },
setIsTransformingBoundingBox: (state, action: PayloadAction<boolean>) => {
state.isTransformingBoundingBox = action.payload;
},
setIsMovingBoundingBox: (state, action: PayloadAction<boolean>) => {
state.isMovingBoundingBox = action.payload;
},
setIsMouseOverBoundingBox: (state, action: PayloadAction<boolean>) => {
state.isMouseOverBoundingBox = action.payload;
},
setIsMoveBoundingBoxKeyHeld: (state, action: PayloadAction<boolean>) => {
state.isMoveBoundingBoxKeyHeld = action.payload;
},
setIsMoveStageKeyHeld: (state, action: PayloadAction<boolean>) => {
state.isMoveStageKeyHeld = action.payload;
},
canvasBatchIdAdded: (state, action: PayloadAction<string>) => { canvasBatchIdAdded: (state, action: PayloadAction<string>) => {
state.batchIds.push(action.payload); state.batchIds.push(action.payload);
}, },
@ -333,7 +285,7 @@ export const canvasSlice = createSlice({
state.pastLayerStates.push(cloneDeep(state.layerState)); state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > state.maxHistory) { if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift(); state.pastLayerStates.shift();
} }
@ -352,7 +304,7 @@ export const canvasSlice = createSlice({
discardStagedImages: (state) => { discardStagedImages: (state) => {
state.pastLayerStates.push(cloneDeep(state.layerState)); state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > state.maxHistory) { if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift(); state.pastLayerStates.shift();
} }
@ -371,7 +323,7 @@ export const canvasSlice = createSlice({
state.pastLayerStates.push(cloneDeep(state.layerState)); state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > state.maxHistory) { if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift(); state.pastLayerStates.shift();
} }
@ -390,7 +342,7 @@ export const canvasSlice = createSlice({
state.pastLayerStates.push(cloneDeep(state.layerState)); state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > state.maxHistory) { if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift(); state.pastLayerStates.shift();
} }
@ -419,7 +371,7 @@ export const canvasSlice = createSlice({
state.pastLayerStates.push(cloneDeep(state.layerState)); state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > state.maxHistory) { if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift(); state.pastLayerStates.shift();
} }
@ -461,7 +413,7 @@ export const canvasSlice = createSlice({
state.futureLayerStates.unshift(cloneDeep(state.layerState)); state.futureLayerStates.unshift(cloneDeep(state.layerState));
if (state.futureLayerStates.length > state.maxHistory) { if (state.futureLayerStates.length > MAX_HISTORY) {
state.futureLayerStates.pop(); state.futureLayerStates.pop();
} }
@ -476,7 +428,7 @@ export const canvasSlice = createSlice({
state.pastLayerStates.push(cloneDeep(state.layerState)); state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > state.maxHistory) { if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift(); state.pastLayerStates.shift();
} }
@ -485,9 +437,6 @@ export const canvasSlice = createSlice({
setShouldShowGrid: (state, action: PayloadAction<boolean>) => { setShouldShowGrid: (state, action: PayloadAction<boolean>) => {
state.shouldShowGrid = action.payload; state.shouldShowGrid = action.payload;
}, },
setIsMovingStage: (state, action: PayloadAction<boolean>) => {
state.isMovingStage = action.payload;
},
setShouldSnapToGrid: (state, action: PayloadAction<boolean>) => { setShouldSnapToGrid: (state, action: PayloadAction<boolean>) => {
state.shouldSnapToGrid = action.payload; state.shouldSnapToGrid = action.payload;
}, },
@ -653,7 +602,7 @@ export const canvasSlice = createSlice({
state.pastLayerStates.push(cloneDeep(state.layerState)); state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > state.maxHistory) { if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift(); state.pastLayerStates.shift();
} }
@ -773,23 +722,6 @@ export const canvasSlice = createSlice({
state.layerState.objects = [action.payload]; state.layerState.objects = [action.payload];
}, },
resetCanvasInteractionState: (state) => {
state.cursorPosition = null;
state.isDrawing = false;
state.isMouseOverBoundingBox = false;
state.isMoveBoundingBoxKeyHeld = false;
state.isMoveStageKeyHeld = false;
state.isMovingBoundingBox = false;
state.isMovingStage = false;
state.isTransformingBoundingBox = false;
},
mouseLeftCanvas: (state) => {
state.cursorPosition = null;
state.isDrawing = false;
state.isMouseOverBoundingBox = false;
state.isMovingBoundingBox = false;
state.isTransformingBoundingBox = false;
},
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => { builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
@ -848,12 +780,10 @@ export const {
commitStagingAreaImage, commitStagingAreaImage,
discardStagedImages, discardStagedImages,
fitBoundingBoxToStage, fitBoundingBoxToStage,
mouseLeftCanvas,
nextStagingAreaImage, nextStagingAreaImage,
prevStagingAreaImage, prevStagingAreaImage,
redo, redo,
resetCanvas, resetCanvas,
resetCanvasInteractionState,
resetCanvasView, resetCanvasView,
setBoundingBoxCoordinates, setBoundingBoxCoordinates,
setBoundingBoxDimensions, setBoundingBoxDimensions,
@ -863,16 +793,8 @@ export const {
setBrushColor, setBrushColor,
setBrushSize, setBrushSize,
setColorPickerColor, setColorPickerColor,
setCursorPosition,
setInitialCanvasImage, setInitialCanvasImage,
setIsDrawing,
setIsMaskEnabled, setIsMaskEnabled,
setIsMouseOverBoundingBox,
setIsMoveBoundingBoxKeyHeld,
setIsMoveStageKeyHeld,
setIsMovingBoundingBox,
setIsMovingStage,
setIsTransformingBoundingBox,
setLayer, setLayer,
setMaskColor, setMaskColor,
setMergedCanvas, setMergedCanvas,
@ -882,10 +804,7 @@ export const {
setShouldLockBoundingBox, setShouldLockBoundingBox,
setShouldPreserveMaskedArea, setShouldPreserveMaskedArea,
setShouldShowBoundingBox, setShouldShowBoundingBox,
setShouldShowBrush,
setShouldShowBrushPreview,
setShouldShowCanvasDebugInfo, setShouldShowCanvasDebugInfo,
setShouldShowCheckboardTransparency,
setShouldShowGrid, setShouldShowGrid,
setShouldShowIntermediates, setShouldShowIntermediates,
setShouldShowStagingImage, setShouldShowStagingImage,

View File

@ -123,21 +123,11 @@ export interface CanvasState {
brushColor: RgbaColor; brushColor: RgbaColor;
brushSize: number; brushSize: number;
colorPickerColor: RgbaColor; colorPickerColor: RgbaColor;
cursorPosition: Vector2d | null;
futureLayerStates: CanvasLayerState[]; futureLayerStates: CanvasLayerState[];
isDrawing: boolean;
isMaskEnabled: boolean; isMaskEnabled: boolean;
isMouseOverBoundingBox: boolean;
isMoveBoundingBoxKeyHeld: boolean;
isMoveStageKeyHeld: boolean;
isMovingBoundingBox: boolean;
isMovingStage: boolean;
isTransformingBoundingBox: boolean;
layer: CanvasLayer; layer: CanvasLayer;
layerState: CanvasLayerState; layerState: CanvasLayerState;
maskColor: RgbaColor; maskColor: RgbaColor;
maxHistory: number;
minimumStageScale: number;
pastLayerStates: CanvasLayerState[]; pastLayerStates: CanvasLayerState[];
scaledBoundingBoxDimensions: Dimensions; scaledBoundingBoxDimensions: Dimensions;
shouldAntialias: boolean; shouldAntialias: boolean;
@ -148,10 +138,7 @@ export interface CanvasState {
shouldPreserveMaskedArea: boolean; shouldPreserveMaskedArea: boolean;
shouldRestrictStrokesToBox: boolean; shouldRestrictStrokesToBox: boolean;
shouldShowBoundingBox: boolean; shouldShowBoundingBox: boolean;
shouldShowBrush: boolean;
shouldShowBrushPreview: boolean;
shouldShowCanvasDebugInfo: boolean; shouldShowCanvasDebugInfo: boolean;
shouldShowCheckboardTransparency: boolean;
shouldShowGrid: boolean; shouldShowGrid: boolean;
shouldShowIntermediates: boolean; shouldShowIntermediates: boolean;
shouldShowStagingImage: boolean; shouldShowStagingImage: boolean;