diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx index 20ee186a4c..834903c0f7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx @@ -1,18 +1,20 @@ import { Box, Flex, Text } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppSelector } from 'app/store/storeHooks'; -import { $stageScale } from 'features/controlLayers/store/controlLayersSlice'; +import { $stageAttrs } from 'features/controlLayers/store/controlLayersSlice'; import { round } from 'lodash-es'; import { memo } from 'react'; export const HeadsUpDisplay = memo(() => { - const stageScale = useStore($stageScale); + const stageAttrs = useStore($stageAttrs); const layerCount = useAppSelector((s) => s.controlLayers.present.layers.length); const bbox = useAppSelector((s) => s.controlLayers.present.bbox); return ( - + + + diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index 736f7b655d..6782ccb36f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -26,8 +26,7 @@ import { $lastMouseDownPos, $selectedLayer, $shouldInvertBrushSizeScrollDirection, - $stagePos, - $stageScale, + $stageAttrs, $tool, $toolBuffer, bboxChanged, @@ -89,7 +88,6 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, const lastCursorPos = useStore($lastCursorPos); const lastMouseDownPos = useStore($lastMouseDownPos); const isMouseDown = useStore($isMouseDown); - const stageScale = useStore($stageScale); const isDrawing = useStore($isDrawing); const brushColor = useAppSelector(selectBrushColor); const selectedLayer = useAppSelector(selectSelectedLayer); @@ -197,8 +195,7 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, $lastMouseDownPos, $lastCursorPos, $lastAddedPoint, - $stageScale, - $stagePos, + $stageAttrs, $brushSize, $brushColor, $brushSpacingPx, @@ -236,6 +233,13 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, const fitStageToContainer = () => { stage.width(container.offsetWidth); stage.height(container.offsetHeight); + $stageAttrs.set({ + x: stage.x(), + y: stage.y(), + width: stage.width(), + height: stage.height(), + scale: stage.scaleX(), + }); }; const resizeObserver = new ResizeObserver(fitStageToContainer); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts index cf95e8902c..d4818bfee3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts @@ -1,15 +1,16 @@ import { calculateNewBrushSize } from 'features/canvas/hooks/useCanvasZoom'; import { CANVAS_SCALE_BY, MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/canvas/util/constants'; import { getIsMouseDown, getScaledFlooredCursorPosition, snapPosToStage } from 'features/controlLayers/konva/util'; -import { - type AddBrushLineArg, - type AddEraserLineArg, - type AddPointToLineArg, - type AddRectShapeArg, - DEFAULT_RGBA_COLOR, - type Layer, - type Tool, +import type { + AddBrushLineArg, + AddEraserLineArg, + AddPointToLineArg, + AddRectShapeArg, + Layer, + StageAttrs, + Tool, } from 'features/controlLayers/store/types'; +import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types'; import type Konva from 'konva'; import type { Vector2d } from 'konva/lib/types'; import { clamp } from 'lodash-es'; @@ -27,8 +28,7 @@ type SetStageEventHandlersArg = { $lastMouseDownPos: WritableAtom; $lastCursorPos: WritableAtom; $lastAddedPoint: WritableAtom; - $stageScale: WritableAtom; - $stagePos: WritableAtom; + $stageAttrs: WritableAtom; $brushColor: WritableAtom; $brushSize: WritableAtom; $brushSpacingPx: WritableAtom; @@ -93,8 +93,7 @@ export const setStageEventHandlers = ({ $lastMouseDownPos, $lastCursorPos, $lastAddedPoint, - $stagePos, - $stageScale, + $stageAttrs, $brushColor, $brushSize, $brushSpacingPx, @@ -333,15 +332,31 @@ export const setStageEventHandlers = ({ stage.scaleX(newScale); stage.scaleY(newScale); stage.position(newPos); - $stageScale.set(newScale); - $stagePos.set(newPos); + $stageAttrs.set({ ...newPos, width: stage.width(), height: stage.height(), scale: newScale }); } }); + stage.on('dragmove', () => { + $stageAttrs.set({ + x: stage.x(), + y: stage.y(), + width: stage.width(), + height: stage.height(), + scale: stage.scaleX(), + }); + }); + stage.on('dragend', () => { // Stage position should always be an integer, else we get fractional pixels which are blurry stage.x(Math.floor(stage.x())); stage.y(Math.floor(stage.y())); + $stageAttrs.set({ + x: stage.x(), + y: stage.y(), + width: stage.width(), + height: stage.height(), + scale: stage.scaleX(), + }); }); const onKeyDown = (e: KeyboardEvent) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/naming.ts b/invokeai/frontend/web/src/features/controlLayers/konva/naming.ts index f348393767..7fd088dc73 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/naming.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/naming.ts @@ -13,6 +13,9 @@ export const PREVIEW_RECT_ID = 'preview_layer.rect'; export const PREVIEW_GENERATION_BBOX_GROUP = 'preview_layer.gen_bbox_group'; export const PREVIEW_GENERATION_BBOX_TRANSFORMER = 'preview_layer.gen_bbox_transformer'; export const PREVIEW_GENERATION_BBOX_DUMMY_RECT = 'preview_layer.gen_bbox_dummy_rect'; +export const PREVIEW_DOCUMENT_SIZE_GROUP = 'preview_layer.doc_size_group'; +export const PREVIEW_DOCUMENT_SIZE_STAGE_RECT = 'preview_layer.doc_size_stage_rect'; +export const PREVIEW_DOCUMENT_SIZE_DOCUMENT_RECT = 'preview_layer.doc_size_doc_rect'; // Names for Konva layers and objects (comparable to CSS classes) export const LAYER_BBOX_NAME = 'layer.bbox'; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts index 573b591295..80a2a4cc4c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts @@ -60,6 +60,7 @@ import type { RasterLayer, RegionalGuidanceLayer, RgbaColor, + StageAttrs, Tool, } from './types'; import { @@ -997,8 +998,13 @@ export const $lastCursorPos = atom(null); export const $isPreviewVisible = atom(true); export const $lastAddedPoint = atom(null); export const $isSpaceDown = atom(false); -export const $stageScale = atom(1); -export const $stagePos = atom({ x: 0, y: 0 }); +export const $stageAttrs = atom({ + x: 0, + y: 0, + width: 0, + height: 0, + scale: 0, +}); // Some nanostores that are manually synced to redux state to provide imperative access // TODO(psyche): This is a hack, figure out another way to handle this... diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 04a18b3839..860c8b5586 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -272,6 +272,7 @@ export type ControlLayersState = { bbox: IRect; }; +export type StageAttrs = { x: number; y: number; width: number; height: number; scale: number }; export type AddEraserLineArg = { layerId: string; points: [number, number, number, number] }; export type AddBrushLineArg = AddEraserLineArg & { color: RgbaColor }; export type AddPointToLineArg = { layerId: string; point: [number, number] };