feat(ui): move ephemeral state into canvas classes

Things like `$lastCursorPos` are now created within the canvas drawing classes. Consumers in react access them via `useCanvasManager`.

For example:
```tsx
const canvasManager = useCanvasManager();
const lastCursorPos = useStore(canvasManager.stateApi.$lastCursorPos);
```
This commit is contained in:
psychedelicious 2024-08-26 19:14:56 +10:00
parent a3179e7a3f
commit 9c1732e2bb
6 changed files with 34 additions and 68 deletions

View File

@ -14,10 +14,9 @@ import {
PopoverTrigger,
} from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/controlLayers/konva/constants';
import { snapToNearest } from 'features/controlLayers/konva/util';
import { $stageAttrs } from 'features/controlLayers/store/canvasV2Slice';
import { clamp, round } from 'lodash-es';
import { computed } from 'nanostores';
import type { KeyboardEvent } from 'react';
@ -72,12 +71,10 @@ const sliderDefaultValue = mapScaleToSliderValue(100);
const snapCandidates = marks.slice(1, marks.length - 1);
const $scale = computed($stageAttrs, (attrs) => attrs.scale);
export const CanvasScale = memo(() => {
const { t } = useTranslation();
const canvasManager = useStore($canvasManager);
const scale = useStore($scale);
const canvasManager = useCanvasManager();
const scale = useStore(computed(canvasManager.stateApi.$stageAttrs, (attrs) => attrs.scale));
const [localScale, setLocalScale] = useState(scale * 100);
const onChangeSlider = useCallback(

View File

@ -1,24 +1,18 @@
import { Box, Flex, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import {
$isDrawing,
$isMouseDown,
$lastAddedPoint,
$lastCursorPos,
$lastMouseDownPos,
$stageAttrs,
} from 'features/controlLayers/store/canvasV2Slice';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { round } from 'lodash-es';
import { memo } from 'react';
export const HeadsUpDisplay = memo(() => {
const stageAttrs = useStore($stageAttrs);
const cursorPos = useStore($lastCursorPos);
const isDrawing = useStore($isDrawing);
const isMouseDown = useStore($isMouseDown);
const lastMouseDownPos = useStore($lastMouseDownPos);
const lastAddedPoint = useStore($lastAddedPoint);
const canvasManager = useCanvasManager();
const stageAttrs = useStore(canvasManager.stateApi.$stageAttrs);
const cursorPos = useStore(canvasManager.stateApi.$lastCursorPos);
const isDrawing = useStore(canvasManager.stateApi.$isDrawing);
const isMouseDown = useStore(canvasManager.stateApi.$isMouseDown);
const lastMouseDownPos = useStore(canvasManager.stateApi.$lastMouseDownPos);
const lastAddedPoint = useStore(canvasManager.stateApi.$lastAddedPoint);
const bbox = useAppSelector((s) => s.canvasV2.bbox);
return (

View File

@ -2,8 +2,8 @@ import { Button, ButtonGroup, IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { INTERACTION_SCOPES, useScopeOnMount } from 'common/hooks/interactionScopes';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import {
$shouldShowStagedImage,
sessionNextStagedImageSelected,
sessionPrevStagedImageSelected,
sessionStagedImageDiscarded,
@ -28,7 +28,8 @@ import { useChangeImageIsIntermediateMutation } from 'services/api/endpoints/ima
export const StagingAreaToolbar = memo(() => {
const dispatch = useAppDispatch();
const session = useAppSelector((s) => s.canvasV2.session);
const shouldShowStagedImage = useStore($shouldShowStagedImage);
const canvasManager = useCanvasManager();
const shouldShowStagedImage = useStore(canvasManager.stateApi.$shouldShowStagedImage);
const images = useMemo(() => session.stagedImages, [session]);
const selectedImage = useMemo(() => {
return images[session.selectedStagedImageIndex] ?? null;
@ -70,8 +71,8 @@ export const StagingAreaToolbar = memo(() => {
}, [dispatch]);
const onToggleShouldShowStagedImage = useCallback(() => {
$shouldShowStagedImage.set(!shouldShowStagedImage);
}, [shouldShowStagedImage]);
canvasManager.stateApi.$shouldShowStagedImage.set(!shouldShowStagedImage);
}, [canvasManager.stateApi.$shouldShowStagedImage, shouldShowStagedImage]);
const onSaveStagingImage = useCallback(() => {
if (!selectedImage) {

View File

@ -4,16 +4,6 @@ import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLaye
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import {
$isDrawing,
$isMouseDown,
$isProcessingTransform,
$lastAddedPoint,
$lastCursorPos,
$lastMouseDownPos,
$shouldShowStagedImage,
$spaceKey,
$stageAttrs,
$transformingEntity,
bboxChanged,
brushWidthChanged,
entityBrushLineAdded,
@ -36,6 +26,7 @@ import type {
CanvasRasterLayerState,
CanvasRegionalGuidanceState,
CanvasV2State,
Coordinate,
EntityBrushLineAddedPayload,
EntityEraserLineAddedPayload,
EntityIdentifierPayload,
@ -45,6 +36,7 @@ import type {
Rect,
RgbaColor,
RgbColor,
StageAttrs,
Tool,
} from 'features/controlLayers/store/types';
import { RGBA_BLACK } from 'features/controlLayers/store/types';
@ -243,8 +235,8 @@ export class CanvasStateApiModule {
}
};
$transformingEntity = $transformingEntity;
$isProcessingTransform = $isProcessingTransform;
$transformingEntity = atom<CanvasEntityIdentifier | null>(null);
$isProcessingTransform = atom<boolean>(false);
$toolState: WritableAtom<CanvasV2State['tool']> = atom();
$currentFill: WritableAtom<RgbaColor> = atom();
@ -253,17 +245,23 @@ export class CanvasStateApiModule {
$colorUnderCursor: WritableAtom<RgbColor> = atom(RGBA_BLACK);
// Read-write state, ephemeral interaction state
$isDrawing = $isDrawing;
$isMouseDown = $isMouseDown;
$lastAddedPoint = $lastAddedPoint;
$lastMouseDownPos = $lastMouseDownPos;
$lastCursorPos = $lastCursorPos;
$isDrawing = atom<boolean>(false);
$isMouseDown = atom<boolean>(false);
$lastAddedPoint = atom<Coordinate | null>(null);
$lastMouseDownPos = atom<Coordinate | null>(null);
$lastCursorPos = atom<Coordinate | null>(null);
$lastCanvasProgressEvent = $lastCanvasProgressEvent;
$spaceKey = $spaceKey;
$spaceKey = atom<boolean>(false);
$altKey = $alt;
$ctrlKey = $ctrl;
$metaKey = $meta;
$shiftKey = $shift;
$shouldShowStagedImage = $shouldShowStagedImage;
$stageAttrs = $stageAttrs;
$shouldShowStagedImage = atom(true);
$stageAttrs = atom<StageAttrs>({
x: 0,
y: 0,
width: 0,
height: 0,
scale: 0,
});
}

View File

@ -22,20 +22,17 @@ import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify';
import { initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { pick } from 'lodash-es';
import { atom } from 'nanostores';
import { assert } from 'tsafe';
import type {
CanvasEntityIdentifier,
CanvasV2State,
Coordinate,
EntityBrushLineAddedPayload,
EntityEraserLineAddedPayload,
EntityIdentifierPayload,
EntityMovedPayload,
EntityRasterizedPayload,
EntityRectAddedPayload,
StageAttrs,
} from './types';
import { getEntityIdentifier, isDrawableEntity } from './types';
@ -564,25 +561,6 @@ const migrate = (state: any): any => {
return state;
};
// Ephemeral state that does not need to be in redux
export const $isPreviewVisible = atom(true);
export const $stageAttrs = atom<StageAttrs>({
x: 0,
y: 0,
width: 0,
height: 0,
scale: 0,
});
export const $shouldShowStagedImage = atom(true);
export const $isDrawing = atom<boolean>(false);
export const $isMouseDown = atom<boolean>(false);
export const $lastAddedPoint = atom<Coordinate | null>(null);
export const $lastMouseDownPos = atom<Coordinate | null>(null);
export const $lastCursorPos = atom<Coordinate | null>(null);
export const $spaceKey = atom<boolean>(false);
export const $transformingEntity = atom<CanvasEntityIdentifier | null>(null);
export const $isProcessingTransform = atom<boolean>(false);
export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
name: canvasV2Slice.name,
initialState,

View File

@ -5,7 +5,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { CanvasEntityListMenuButton } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuButton';
import { CanvasPanelContent } from 'features/controlLayers/components/CanvasPanelContent';
import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityCount } from 'features/controlLayers/store/selectors';
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
@ -56,7 +55,6 @@ const ParametersPanelTextToImage = () => {
if (i === 1) {
dispatch(isImageViewerOpenChanged(false));
}
$isPreviewVisible.set(i === 0);
},
[dispatch]
);