refactor(ui): node manager handles more tedious annoying stuff

This commit is contained in:
psychedelicious 2024-06-20 16:09:13 +10:00
parent f41539532f
commit 3b864921ac
10 changed files with 246 additions and 279 deletions

View File

@ -1,3 +1,4 @@
import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
import { renderBackgroundLayer } from 'features/controlLayers/konva/renderers/background'; import { renderBackgroundLayer } from 'features/controlLayers/konva/renderers/background';
import { import {
renderDocumentBoundsOverlay, renderDocumentBoundsOverlay,
@ -32,7 +33,7 @@ import {
import { PREVIEW_TOOL_GROUP_ID } from './naming'; import { PREVIEW_TOOL_GROUP_ID } from './naming';
type Arg = { type Arg = {
stage: Konva.Stage; manager: KonvaNodeManager;
getToolState: () => CanvasV2State['tool']; getToolState: () => CanvasV2State['tool'];
getCurrentFill: () => RgbaColor; getCurrentFill: () => RgbaColor;
setTool: (tool: Tool) => void; setTool: (tool: Tool) => void;
@ -135,7 +136,7 @@ const maybeAddNextPoint = (
}; };
export const setStageEventHandlers = ({ export const setStageEventHandlers = ({
stage, manager,
getToolState, getToolState,
getCurrentFill, getCurrentFill,
setTool, setTool,
@ -164,16 +165,14 @@ export const setStageEventHandlers = ({
onBrushWidthChanged: onBrushSizeChanged, onBrushWidthChanged: onBrushSizeChanged,
onEraserWidthChanged: onEraserSizeChanged, onEraserWidthChanged: onEraserSizeChanged,
}: Arg): (() => void) => { }: Arg): (() => void) => {
const stage = manager.stage;
//#region mouseenter //#region mouseenter
stage.on('mouseenter', (e) => { stage.on('mouseenter', () => {
const stage = e.target.getStage();
if (!stage) {
return;
}
const tool = getToolState().selected; const tool = getToolState().selected;
stage.findOne<Konva.Layer>(`#${PREVIEW_TOOL_GROUP_ID}`)?.visible(tool === 'brush' || tool === 'eraser'); stage.findOne<Konva.Layer>(`#${PREVIEW_TOOL_GROUP_ID}`)?.visible(tool === 'brush' || tool === 'eraser');
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),
@ -186,10 +185,6 @@ export const setStageEventHandlers = ({
//#region mousedown //#region mousedown
stage.on('mousedown', (e) => { stage.on('mousedown', (e) => {
const stage = e.target.getStage();
if (!stage) {
return;
}
setIsMouseDown(true); setIsMouseDown(true);
const toolState = getToolState(); const toolState = getToolState();
const pos = updateLastCursorPos(stage, setLastCursorPos); const pos = updateLastCursorPos(stage, setLastCursorPos);
@ -307,7 +302,7 @@ export const setStageEventHandlers = ({
} }
} }
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),
@ -319,11 +314,7 @@ export const setStageEventHandlers = ({
}); });
//#region mouseup //#region mouseup
stage.on('mouseup', (e) => { stage.on('mouseup', () => {
const stage = e.target.getStage();
if (!stage) {
return;
}
setIsMouseDown(false); setIsMouseDown(false);
const pos = getLastCursorPos(); const pos = getLastCursorPos();
const selectedEntity = getSelectedEntity(); const selectedEntity = getSelectedEntity();
@ -360,7 +351,7 @@ export const setStageEventHandlers = ({
} }
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),
@ -372,11 +363,7 @@ export const setStageEventHandlers = ({
}); });
//#region mousemove //#region mousemove
stage.on('mousemove', (e) => { stage.on('mousemove', () => {
const stage = e.target.getStage();
if (!stage) {
return;
}
const toolState = getToolState(); const toolState = getToolState();
const pos = updateLastCursorPos(stage, setLastCursorPos); const pos = updateLastCursorPos(stage, setLastCursorPos);
const selectedEntity = getSelectedEntity(); const selectedEntity = getSelectedEntity();
@ -481,7 +468,7 @@ export const setStageEventHandlers = ({
} }
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),
@ -493,11 +480,7 @@ export const setStageEventHandlers = ({
}); });
//#region mouseleave //#region mouseleave
stage.on('mouseleave', (e) => { stage.on('mouseleave', () => {
const stage = e.target.getStage();
if (!stage) {
return;
}
const pos = updateLastCursorPos(stage, setLastCursorPos); const pos = updateLastCursorPos(stage, setLastCursorPos);
setIsDrawing(false); setIsDrawing(false);
setLastCursorPos(null); setLastCursorPos(null);
@ -525,7 +508,7 @@ export const setStageEventHandlers = ({
} }
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),
@ -574,13 +557,13 @@ export const setStageEventHandlers = ({
stage.scaleY(newScale); stage.scaleY(newScale);
stage.position(newPos); stage.position(newPos);
setStageAttrs({ ...newPos, width: stage.width(), height: stage.height(), scale: newScale }); setStageAttrs({ ...newPos, width: stage.width(), height: stage.height(), scale: newScale });
renderBackgroundLayer(stage); renderBackgroundLayer(manager);
scaleToolPreview(stage, getToolState()); scaleToolPreview(manager, getToolState());
renderDocumentBoundsOverlay(stage, getDocument); renderDocumentBoundsOverlay(manager, getDocument);
} }
} }
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),
@ -600,10 +583,10 @@ export const setStageEventHandlers = ({
height: stage.height(), height: stage.height(),
scale: stage.scaleX(), scale: stage.scaleX(),
}); });
renderBackgroundLayer(stage); renderBackgroundLayer(manager);
renderDocumentBoundsOverlay(stage, getDocument); renderDocumentBoundsOverlay(manager, getDocument);
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),
@ -625,7 +608,7 @@ export const setStageEventHandlers = ({
scale: stage.scaleX(), scale: stage.scaleX(),
}); });
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),
@ -656,12 +639,12 @@ export const setStageEventHandlers = ({
} else if (e.key === 'r') { } else if (e.key === 'r') {
const stageAttrs = fitDocumentToStage(stage, getDocument()); const stageAttrs = fitDocumentToStage(stage, getDocument());
setStageAttrs(stageAttrs); setStageAttrs(stageAttrs);
scaleToolPreview(stage, getToolState()); scaleToolPreview(manager, getToolState());
renderBackgroundLayer(stage); renderBackgroundLayer(manager);
renderDocumentBoundsOverlay(stage, getDocument); renderDocumentBoundsOverlay(manager, getDocument);
} }
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),
@ -688,7 +671,7 @@ export const setStageEventHandlers = ({
setSpaceKey(false); setSpaceKey(false);
} }
renderToolPreview( renderToolPreview(
stage, manager,
getToolState(), getToolState(),
getCurrentFill(), getCurrentFill(),
getSelectedEntity(), getSelectedEntity(),

View File

@ -1,4 +1,19 @@
import type { BrushLine, EraserLine, ImageObject, RectShape } from 'features/controlLayers/store/types'; import { createBackgroundLayer } from 'features/controlLayers/konva/renderers/background';
import {
createBboxPreview,
createDocumentOverlay,
createPreviewLayer,
createToolPreview,
} from 'features/controlLayers/konva/renderers/preview';
import type {
BrushLine,
CanvasEntity,
CanvasV2State,
EraserLine,
ImageObject,
Rect,
RectShape,
} from 'features/controlLayers/store/types';
import type Konva from 'konva'; import type Konva from 'konva';
export type BrushLineObjectRecord = { export type BrushLineObjectRecord = {
@ -37,25 +52,77 @@ type ObjectRecord = BrushLineObjectRecord | EraserLineObjectRecord | RectShapeOb
export class KonvaNodeManager { export class KonvaNodeManager {
stage: Konva.Stage; stage: Konva.Stage;
adapters: Map<string, EntityKonvaAdapter>; adapters: Map<string, KonvaEntityAdapter>;
background: { layer: Konva.Layer };
preview: {
layer: Konva.Layer;
bbox: {
group: Konva.Group;
rect: Konva.Rect;
transformer: Konva.Transformer;
};
tool: {
group: Konva.Group;
brush: {
group: Konva.Group;
fill: Konva.Circle;
innerBorder: Konva.Circle;
outerBorder: Konva.Circle;
};
rect: {
rect: Konva.Rect;
};
};
documentOverlay: {
group: Konva.Group;
innerRect: Konva.Rect;
outerRect: Konva.Rect;
};
};
constructor(stage: Konva.Stage) { constructor(
stage: Konva.Stage,
getBbox: () => CanvasV2State['bbox'],
onBboxTransformed: (bbox: Rect) => void,
getShiftKey: () => boolean,
getCtrlKey: () => boolean,
getMetaKey: () => boolean,
getAltKey: () => boolean
) {
this.stage = stage; this.stage = stage;
this.adapters = new Map(); this.adapters = new Map();
this.background = { layer: createBackgroundLayer() };
this.stage.add(this.background.layer);
this.preview = {
layer: createPreviewLayer(),
bbox: createBboxPreview(stage, getBbox, onBboxTransformed, getShiftKey, getCtrlKey, getMetaKey, getAltKey),
tool: createToolPreview(stage),
documentOverlay: createDocumentOverlay(),
};
this.preview.layer.add(this.preview.bbox.group);
this.preview.layer.add(this.preview.tool.group);
this.preview.layer.add(this.preview.documentOverlay.group);
this.stage.add(this.preview.layer);
} }
add(id: string, konvaLayer: Konva.Layer, konvaObjectGroup: Konva.Group): EntityKonvaAdapter { add(entity: CanvasEntity, konvaLayer: Konva.Layer, konvaObjectGroup: Konva.Group): KonvaEntityAdapter {
const adapter = new EntityKonvaAdapter(id, konvaLayer, konvaObjectGroup, this); const adapter = new KonvaEntityAdapter(entity, konvaLayer, konvaObjectGroup, this);
this.adapters.set(id, adapter); this.adapters.set(adapter.id, adapter);
return adapter; return adapter;
} }
get(id: string): EntityKonvaAdapter | undefined { get(id: string): KonvaEntityAdapter | undefined {
return this.adapters.get(id); return this.adapters.get(id);
} }
getAll(): EntityKonvaAdapter[] { getAll(type?: CanvasEntity['type']): KonvaEntityAdapter[] {
return Array.from(this.adapters.values()); if (type) {
return Array.from(this.adapters.values()).filter((adapter) => adapter.entityType === type);
} else {
return Array.from(this.adapters.values());
}
} }
destroy(id: string): boolean { destroy(id: string): boolean {
@ -68,15 +135,17 @@ export class KonvaNodeManager {
} }
} }
export class EntityKonvaAdapter { export class KonvaEntityAdapter {
id: string; id: string;
entityType: CanvasEntity['type'];
konvaLayer: Konva.Layer; // Every entity is associated with a konva layer konvaLayer: Konva.Layer; // Every entity is associated with a konva layer
konvaObjectGroup: Konva.Group; // Every entity's nodes are part of an object group konvaObjectGroup: Konva.Group; // Every entity's nodes are part of an object group
objectRecords: Map<string, ObjectRecord>; objectRecords: Map<string, ObjectRecord>;
manager: KonvaNodeManager; manager: KonvaNodeManager;
constructor(id: string, konvaLayer: Konva.Layer, konvaObjectGroup: Konva.Group, manager: KonvaNodeManager) { constructor(entity: CanvasEntity, konvaLayer: Konva.Layer, konvaObjectGroup: Konva.Group, manager: KonvaNodeManager) {
this.id = id; this.id = entity.id;
this.entityType = entity.type;
this.konvaLayer = konvaLayer; this.konvaLayer = konvaLayer;
this.konvaObjectGroup = konvaObjectGroup; this.konvaObjectGroup = konvaObjectGroup;
this.objectRecords = new Map(); this.objectRecords = new Map();

View File

@ -1,27 +1,22 @@
import { BACKGROUND_LAYER_ID, PREVIEW_LAYER_ID } from 'features/controlLayers/konva/naming';
import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager'; import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
import type { ControlAdapterEntity, LayerEntity, RegionEntity } from 'features/controlLayers/store/types'; import type { ControlAdapterEntity, LayerEntity, RegionEntity } from 'features/controlLayers/store/types';
import type Konva from 'konva';
export const arrangeEntities = ( export const arrangeEntities = (
stage: Konva.Stage, manager: KonvaNodeManager,
layerManager: KonvaNodeManager,
layers: LayerEntity[], layers: LayerEntity[],
controlAdapterManager: KonvaNodeManager,
controlAdapters: ControlAdapterEntity[], controlAdapters: ControlAdapterEntity[],
regionManager: KonvaNodeManager,
regions: RegionEntity[] regions: RegionEntity[]
): void => { ): void => {
let zIndex = 0; let zIndex = 0;
stage.findOne<Konva.Layer>(`#${BACKGROUND_LAYER_ID}`)?.zIndex(++zIndex); manager.background.layer.zIndex(++zIndex);
for (const layer of layers) { for (const layer of layers) {
layerManager.get(layer.id)?.konvaLayer.zIndex(++zIndex); manager.get(layer.id)?.konvaLayer.zIndex(++zIndex);
} }
for (const ca of controlAdapters) { for (const ca of controlAdapters) {
controlAdapterManager.get(ca.id)?.konvaLayer.zIndex(++zIndex); manager.get(ca.id)?.konvaLayer.zIndex(++zIndex);
} }
for (const rg of regions) { for (const rg of regions) {
regionManager.get(rg.id)?.konvaLayer.zIndex(++zIndex); manager.get(rg.id)?.konvaLayer.zIndex(++zIndex);
} }
stage.findOne<Konva.Layer>(`#${PREVIEW_LAYER_ID}`)?.zIndex(++zIndex); manager.preview.layer.zIndex(++zIndex);
}; };

View File

@ -1,5 +1,6 @@
import { getArbitraryBaseColor } from '@invoke-ai/ui-library'; import { getArbitraryBaseColor } from '@invoke-ai/ui-library';
import { BACKGROUND_LAYER_ID } from 'features/controlLayers/konva/naming'; import { BACKGROUND_LAYER_ID } from 'features/controlLayers/konva/naming';
import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
import Konva from 'konva'; import Konva from 'konva';
const baseGridLineColor = getArbitraryBaseColor(27); const baseGridLineColor = getArbitraryBaseColor(27);
@ -24,26 +25,17 @@ const getGridSpacing = (scale: number): number => {
return 256; return 256;
}; };
export const getBackgroundLayer = (stage: Konva.Stage): Konva.Layer => { export const createBackgroundLayer = (): Konva.Layer => new Konva.Layer({ id: BACKGROUND_LAYER_ID, listening: false });
let background = stage.findOne<Konva.Layer>(`#${BACKGROUND_LAYER_ID}`);
if (background) {
return background;
}
background = new Konva.Layer({ id: BACKGROUND_LAYER_ID }); export const renderBackgroundLayer = (manager: KonvaNodeManager): void => {
stage.add(background); const background = manager.background.layer;
return background;
};
export const renderBackgroundLayer = (stage: Konva.Stage): void => {
const background = getBackgroundLayer(stage);
background.zIndex(0); background.zIndex(0);
const scale = stage.scaleX(); const scale = manager.stage.scaleX();
const gridSpacing = getGridSpacing(scale); const gridSpacing = getGridSpacing(scale);
const x = stage.x(); const x = manager.stage.x();
const y = stage.y(); const y = manager.stage.y();
const width = stage.width(); const width = manager.stage.width();
const height = stage.height(); const height = manager.stage.height();
const stageRect = { const stageRect = {
x1: 0, x1: 0,
y1: 0, y1: 0,

View File

@ -1,6 +1,6 @@
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters'; import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
import { CA_LAYER_IMAGE_NAME, CA_LAYER_NAME, CA_LAYER_OBJECT_GROUP_NAME } from 'features/controlLayers/konva/naming'; import { CA_LAYER_IMAGE_NAME, CA_LAYER_NAME, CA_LAYER_OBJECT_GROUP_NAME } from 'features/controlLayers/konva/naming';
import type { EntityKonvaAdapter, ImageObjectRecord, KonvaNodeManager } from 'features/controlLayers/konva/nodeManager'; import type { ImageObjectRecord, KonvaEntityAdapter, KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
import { import {
createImageObjectGroup, createImageObjectGroup,
createObjectGroup, createObjectGroup,
@ -21,7 +21,7 @@ import { assert } from 'tsafe';
* @param stage The konva stage * @param stage The konva stage
* @param entity The control adapter layer state * @param entity The control adapter layer state
*/ */
const getControlAdapter = (manager: KonvaNodeManager, entity: ControlAdapterEntity): EntityKonvaAdapter => { const getControlAdapter = (manager: KonvaNodeManager, entity: ControlAdapterEntity): KonvaEntityAdapter => {
const adapter = manager.get(entity.id); const adapter = manager.get(entity.id);
if (adapter) { if (adapter) {
return adapter; return adapter;
@ -33,7 +33,7 @@ const getControlAdapter = (manager: KonvaNodeManager, entity: ControlAdapterEnti
listening: false, listening: false,
}); });
const konvaObjectGroup = createObjectGroup(konvaLayer, CA_LAYER_OBJECT_GROUP_NAME); const konvaObjectGroup = createObjectGroup(konvaLayer, CA_LAYER_OBJECT_GROUP_NAME);
return manager.add(entity.id, konvaLayer, konvaObjectGroup); return manager.add(entity, konvaLayer, konvaObjectGroup);
}; };
/** /**
@ -103,7 +103,7 @@ export const renderControlAdapter = async (manager: KonvaNodeManager, entity: Co
export const renderControlAdapters = (manager: KonvaNodeManager, entities: ControlAdapterEntity[]): void => { export const renderControlAdapters = (manager: KonvaNodeManager, entities: ControlAdapterEntity[]): void => {
// Destroy nonexistent layers // Destroy nonexistent layers
for (const adapters of manager.getAll()) { for (const adapters of manager.getAll('control_adapter')) {
if (!entities.find((ca) => ca.id === adapters.id)) { if (!entities.find((ca) => ca.id === adapters.id)) {
manager.destroy(adapters.id); manager.destroy(adapters.id);
} }

View File

@ -6,7 +6,7 @@ import {
RASTER_LAYER_OBJECT_GROUP_NAME, RASTER_LAYER_OBJECT_GROUP_NAME,
RASTER_LAYER_RECT_SHAPE_NAME, RASTER_LAYER_RECT_SHAPE_NAME,
} from 'features/controlLayers/konva/naming'; } from 'features/controlLayers/konva/naming';
import type { EntityKonvaAdapter, KonvaNodeManager } from 'features/controlLayers/konva/nodeManager'; import type { KonvaEntityAdapter, KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
import { import {
createImageObjectGroup, createImageObjectGroup,
createObjectGroup, createObjectGroup,
@ -29,11 +29,11 @@ import Konva from 'konva';
* @param onPosChanged Callback for when the layer's position changes * @param onPosChanged Callback for when the layer's position changes
*/ */
const getLayer = ( const getLayer = (
map: KonvaNodeManager, manager: KonvaNodeManager,
entity: LayerEntity, entity: LayerEntity,
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
): EntityKonvaAdapter => { ): KonvaEntityAdapter => {
const adapter = map.get(entity.id); const adapter = manager.get(entity.id);
if (adapter) { if (adapter) {
return adapter; return adapter;
} }
@ -54,7 +54,7 @@ const getLayer = (
} }
const konvaObjectGroup = createObjectGroup(konvaLayer, RASTER_LAYER_OBJECT_GROUP_NAME); const konvaObjectGroup = createObjectGroup(konvaLayer, RASTER_LAYER_OBJECT_GROUP_NAME);
return map.add(entity.id, konvaLayer, konvaObjectGroup); return manager.add(entity, konvaLayer, konvaObjectGroup);
}; };
/** /**
@ -140,7 +140,7 @@ export const renderLayers = (
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
): void => { ): void => {
// Destroy nonexistent layers // Destroy nonexistent layers
for (const adapter of manager.getAll()) { for (const adapter of manager.getAll('layer')) {
if (!entities.find((l) => l.id === adapter.id)) { if (!entities.find((l) => l.id === adapter.id)) {
manager.destroy(adapter.id); manager.destroy(adapter.id);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'features/controlLayers/konva/naming'; } from 'features/controlLayers/konva/naming';
import type { import type {
BrushLineObjectRecord, BrushLineObjectRecord,
EntityKonvaAdapter, KonvaEntityAdapter,
EraserLineObjectRecord, EraserLineObjectRecord,
ImageObjectRecord, ImageObjectRecord,
RectShapeObjectRecord, RectShapeObjectRecord,
@ -40,7 +40,7 @@ import { v4 as uuidv4 } from 'uuid';
* @param name The konva name for the line * @param name The konva name for the line
*/ */
export const getBrushLine = ( export const getBrushLine = (
adapter: EntityKonvaAdapter, adapter: KonvaEntityAdapter,
brushLine: BrushLine, brushLine: BrushLine,
name: string name: string
): BrushLineObjectRecord => { ): BrushLineObjectRecord => {
@ -75,7 +75,7 @@ export const getBrushLine = (
* @param name The konva name for the line * @param name The konva name for the line
*/ */
export const getEraserLine = ( export const getEraserLine = (
adapter: EntityKonvaAdapter, adapter: KonvaEntityAdapter,
eraserLine: EraserLine, eraserLine: EraserLine,
name: string name: string
): EraserLineObjectRecord => { ): EraserLineObjectRecord => {
@ -111,7 +111,7 @@ export const getEraserLine = (
* @param name The konva name for the rect * @param name The konva name for the rect
*/ */
export const getRectShape = ( export const getRectShape = (
adapter: EntityKonvaAdapter, adapter: KonvaEntityAdapter,
rectShape: RectShape, rectShape: RectShape,
name: string name: string
): RectShapeObjectRecord => { ): RectShapeObjectRecord => {
@ -203,7 +203,7 @@ export const updateImageSource = async (arg: {
* @returns The konva group for the image placeholder, and callbacks to handle loading and error states * @returns The konva group for the image placeholder, and callbacks to handle loading and error states
*/ */
export const createImageObjectGroup = (arg: { export const createImageObjectGroup = (arg: {
adapter: EntityKonvaAdapter; adapter: KonvaEntityAdapter;
obj: ImageObject; obj: ImageObject;
name: string; name: string;
getImageDTO?: (imageName: string) => Promise<ImageDTO | null>; getImageDTO?: (imageName: string) => Promise<ImageDTO | null>;

View File

@ -18,29 +18,15 @@ import {
PREVIEW_RECT_ID, PREVIEW_RECT_ID,
PREVIEW_TOOL_GROUP_ID, PREVIEW_TOOL_GROUP_ID,
} from 'features/controlLayers/konva/naming'; } from 'features/controlLayers/konva/naming';
import { selectRenderableLayers } from 'features/controlLayers/konva/util'; import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
import type { CanvasEntity, CanvasV2State, RgbaColor, Tool } from 'features/controlLayers/store/types'; import type { CanvasEntity, CanvasV2State, RgbaColor, Tool } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { IRect, Vector2d } from 'konva/lib/types'; import type { IRect, Vector2d } from 'konva/lib/types';
import { atom } from 'nanostores'; import { atom } from 'nanostores';
import { assert } from 'tsafe';
/** export const createPreviewLayer = (): Konva.Layer => new Konva.Layer({ id: PREVIEW_LAYER_ID, listening: true });
* Creates the singleton preview layer and all its objects.
* @param stage The konva stage
*/
const getPreviewLayer = (stage: Konva.Stage): Konva.Layer => {
let previewLayer = stage.findOne<Konva.Layer>(`#${PREVIEW_LAYER_ID}`);
if (previewLayer) {
return previewLayer;
}
// Initialize the preview layer & add to the stage
previewLayer = new Konva.Layer({ id: PREVIEW_LAYER_ID, listening: true });
stage.add(previewLayer);
return previewLayer;
};
export const getBboxPreviewGroup = ( export const createBboxPreview = (
stage: Konva.Stage, stage: Konva.Stage,
getBbox: () => IRect, getBbox: () => IRect,
onBboxTransformed: (bbox: IRect) => void, onBboxTransformed: (bbox: IRect) => void,
@ -48,14 +34,7 @@ export const getBboxPreviewGroup = (
getCtrlKey: () => boolean, getCtrlKey: () => boolean,
getMetaKey: () => boolean, getMetaKey: () => boolean,
getAltKey: () => boolean getAltKey: () => boolean
): Konva.Group => { ): { group: Konva.Group; rect: Konva.Rect; transformer: Konva.Transformer } => {
const previewLayer = getPreviewLayer(stage);
let bboxPreviewGroup = previewLayer.findOne<Konva.Group>(`#${PREVIEW_GENERATION_BBOX_GROUP}`);
if (bboxPreviewGroup) {
return bboxPreviewGroup;
}
// Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when // Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when
// transforming the bbox. // transforming the bbox.
const bbox = getBbox(); const bbox = getBbox();
@ -63,28 +42,28 @@ export const getBboxPreviewGroup = (
// Use a transformer for the generation bbox. Transformers need some shape to transform, we will use a fully // Use a transformer for the generation bbox. Transformers need some shape to transform, we will use a fully
// transparent rect for this purpose. // transparent rect for this purpose.
bboxPreviewGroup = new Konva.Group({ id: PREVIEW_GENERATION_BBOX_GROUP, listening: false }); const group = new Konva.Group({ id: PREVIEW_GENERATION_BBOX_GROUP, listening: false });
const bboxRect = new Konva.Rect({ const rect = new Konva.Rect({
id: PREVIEW_GENERATION_BBOX_DUMMY_RECT, id: PREVIEW_GENERATION_BBOX_DUMMY_RECT,
listening: false, listening: false,
strokeEnabled: false, strokeEnabled: false,
draggable: true, draggable: true,
...getBbox(), ...getBbox(),
}); });
bboxRect.on('dragmove', () => { rect.on('dragmove', () => {
const gridSize = getCtrlKey() || getMetaKey() ? 8 : 64; const gridSize = getCtrlKey() || getMetaKey() ? 8 : 64;
const oldBbox = getBbox(); const oldBbox = getBbox();
const newBbox: IRect = { const newBbox: IRect = {
...oldBbox, ...oldBbox,
x: roundToMultiple(bboxRect.x(), gridSize), x: roundToMultiple(rect.x(), gridSize),
y: roundToMultiple(bboxRect.y(), gridSize), y: roundToMultiple(rect.y(), gridSize),
}; };
bboxRect.setAttrs(newBbox); rect.setAttrs(newBbox);
if (oldBbox.x !== newBbox.x || oldBbox.y !== newBbox.y) { if (oldBbox.x !== newBbox.x || oldBbox.y !== newBbox.y) {
onBboxTransformed(newBbox); onBboxTransformed(newBbox);
} }
}); });
const bboxTransformer = new Konva.Transformer({ const transformer = new Konva.Transformer({
id: PREVIEW_GENERATION_BBOX_TRANSFORMER, id: PREVIEW_GENERATION_BBOX_TRANSFORMER,
borderDash: [5, 5], borderDash: [5, 5],
borderStroke: 'rgba(212,216,234,1)', borderStroke: 'rgba(212,216,234,1)',
@ -136,11 +115,11 @@ export const getBboxPreviewGroup = (
}, },
}); });
bboxTransformer.on('transform', () => { transformer.on('transform', () => {
// In the transform callback, we calculate the bbox's new dims and pos and update the konva object. // In the transform callback, we calculate the bbox's new dims and pos and update the konva object.
// Some special handling is needed depending on the anchor being dragged. // Some special handling is needed depending on the anchor being dragged.
const anchor = bboxTransformer.getActiveAnchor(); const anchor = transformer.getActiveAnchor();
if (!anchor) { if (!anchor) {
// Pretty sure we should always have an anchor here? // Pretty sure we should always have an anchor here?
return; return;
@ -163,14 +142,14 @@ export const getBboxPreviewGroup = (
} }
// The coords should be correct per the anchorDragBoundFunc. // The coords should be correct per the anchorDragBoundFunc.
let x = bboxRect.x(); let x = rect.x();
let y = bboxRect.y(); let y = rect.y();
// Konva transforms by scaling the dims, not directly changing width and height. At this point, the width and height // Konva transforms by scaling the dims, not directly changing width and height. At this point, the width and height
// *have not changed*, only the scale has changed. To get the final height, we need to scale the dims and then snap // *have not changed*, only the scale has changed. To get the final height, we need to scale the dims and then snap
// them to the grid. // them to the grid.
let width = roundToMultipleMin(bboxRect.width() * bboxRect.scaleX(), gridSize); let width = roundToMultipleMin(rect.width() * rect.scaleX(), gridSize);
let height = roundToMultipleMin(bboxRect.height() * bboxRect.scaleY(), gridSize); let height = roundToMultipleMin(rect.height() * rect.scaleY(), gridSize);
// If shift is held and we are resizing from a corner, retain aspect ratio - needs special handling. We skip this // If shift is held and we are resizing from a corner, retain aspect ratio - needs special handling. We skip this
// if alt/opt is held - this requires math too big for my brain. // if alt/opt is held - this requires math too big for my brain.
@ -210,7 +189,7 @@ export const getBboxPreviewGroup = (
// Update the bboxRect's attrs directly with the new transform, and reset its scale to 1. // Update the bboxRect's attrs directly with the new transform, and reset its scale to 1.
// TODO(psyche): In `renderBboxPreview()` we also call setAttrs, need to do it twice to ensure it renders correctly. // TODO(psyche): In `renderBboxPreview()` we also call setAttrs, need to do it twice to ensure it renders correctly.
// Gotta be a way to avoid setting it twice... // Gotta be a way to avoid setting it twice...
bboxRect.setAttrs({ ...bbox, scaleX: 1, scaleY: 1 }); rect.setAttrs({ ...bbox, scaleX: 1, scaleY: 1 });
// Update the bbox in internal state. // Update the bbox in internal state.
onBboxTransformed(bbox); onBboxTransformed(bbox);
@ -222,18 +201,17 @@ export const getBboxPreviewGroup = (
} }
}); });
bboxTransformer.on('transformend', () => { transformer.on('transformend', () => {
// Always update the aspect ratio buffer when the transform ends, so if the next transform starts with shift held, // Always update the aspect ratio buffer when the transform ends, so if the next transform starts with shift held,
// we have the correct aspect ratio to start from. // we have the correct aspect ratio to start from.
$aspectRatioBuffer.set(bboxRect.width() / bboxRect.height()); $aspectRatioBuffer.set(rect.width() / rect.height());
}); });
// The transformer will always be transforming the dummy rect // The transformer will always be transforming the dummy rect
bboxTransformer.nodes([bboxRect]); transformer.nodes([rect]);
bboxPreviewGroup.add(bboxRect); group.add(rect);
bboxPreviewGroup.add(bboxTransformer); group.add(transformer);
previewLayer.add(bboxPreviewGroup); return { group, rect, transformer };
return bboxPreviewGroup;
}; };
const ALL_ANCHORS: string[] = [ const ALL_ANCHORS: string[] = [
@ -249,79 +227,66 @@ const ALL_ANCHORS: string[] = [
const CORNER_ANCHORS: string[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']; const CORNER_ANCHORS: string[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
const NO_ANCHORS: string[] = []; const NO_ANCHORS: string[] = [];
export const renderBboxPreview = ( export const renderBboxPreview = (manager: KonvaNodeManager, bbox: IRect, tool: Tool): void => {
stage: Konva.Stage, manager.preview.bbox.group.listening(tool === 'bbox');
bbox: IRect,
tool: Tool,
getBbox: () => CanvasV2State['bbox'],
onBboxTransformed: (bbox: IRect) => void,
getShiftKey: () => boolean,
getCtrlKey: () => boolean,
getMetaKey: () => boolean,
getAltKey: () => boolean
): void => {
const bboxGroup = getBboxPreviewGroup(
stage,
getBbox,
onBboxTransformed,
getShiftKey,
getCtrlKey,
getMetaKey,
getAltKey
);
const bboxRect = bboxGroup.findOne<Konva.Rect>(`#${PREVIEW_GENERATION_BBOX_DUMMY_RECT}`);
const bboxTransformer = bboxGroup.findOne<Konva.Transformer>(`#${PREVIEW_GENERATION_BBOX_TRANSFORMER}`);
bboxGroup.listening(tool === 'bbox');
// This updates the bbox during transformation // This updates the bbox during transformation
bboxRect?.setAttrs({ ...bbox, scaleX: 1, scaleY: 1, listening: tool === 'bbox' }); manager.preview.bbox.rect.setAttrs({ ...bbox, scaleX: 1, scaleY: 1, listening: tool === 'bbox' });
bboxTransformer?.setAttrs({ listening: tool === 'bbox', enabledAnchors: tool === 'bbox' ? ALL_ANCHORS : NO_ANCHORS }); manager.preview.bbox.transformer.setAttrs({
listening: tool === 'bbox',
enabledAnchors: tool === 'bbox' ? ALL_ANCHORS : NO_ANCHORS,
});
}; };
export const getToolPreviewGroup = (stage: Konva.Stage): Konva.Group => { export const createToolPreview = (stage: Konva.Stage): KonvaNodeManager['preview']['tool'] => {
const previewLayer = getPreviewLayer(stage);
let toolPreviewGroup = previewLayer.findOne<Konva.Group>(`#${PREVIEW_TOOL_GROUP_ID}`);
if (toolPreviewGroup) {
return toolPreviewGroup;
}
const scale = stage.scaleX(); const scale = stage.scaleX();
toolPreviewGroup = new Konva.Group({ id: PREVIEW_TOOL_GROUP_ID }); const group = new Konva.Group({ id: PREVIEW_TOOL_GROUP_ID });
// Create the brush preview group & circles // Create the brush preview group & circles
const brushPreviewGroup = new Konva.Group({ id: PREVIEW_BRUSH_GROUP_ID }); const brushGroup = new Konva.Group({ id: PREVIEW_BRUSH_GROUP_ID });
const brushPreviewFill = new Konva.Circle({ const brushFill = new Konva.Circle({
id: PREVIEW_BRUSH_FILL_ID, id: PREVIEW_BRUSH_FILL_ID,
listening: false, listening: false,
strokeEnabled: false, strokeEnabled: false,
}); });
brushPreviewGroup.add(brushPreviewFill); brushGroup.add(brushFill);
const brushPreviewBorderInner = new Konva.Circle({ const brushBorderInner = new Konva.Circle({
id: PREVIEW_BRUSH_BORDER_INNER_ID, id: PREVIEW_BRUSH_BORDER_INNER_ID,
listening: false, listening: false,
stroke: BRUSH_BORDER_INNER_COLOR, stroke: BRUSH_BORDER_INNER_COLOR,
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale, strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
strokeEnabled: true, strokeEnabled: true,
}); });
brushPreviewGroup.add(brushPreviewBorderInner); brushGroup.add(brushBorderInner);
const brushPreviewBorderOuter = new Konva.Circle({ const brushBorderOuter = new Konva.Circle({
id: PREVIEW_BRUSH_BORDER_OUTER_ID, id: PREVIEW_BRUSH_BORDER_OUTER_ID,
listening: false, listening: false,
stroke: BRUSH_BORDER_OUTER_COLOR, stroke: BRUSH_BORDER_OUTER_COLOR,
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale, strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
strokeEnabled: true, strokeEnabled: true,
}); });
brushPreviewGroup.add(brushPreviewBorderOuter); brushGroup.add(brushBorderOuter);
// Create the rect preview - this is a rectangle drawn from the last mouse down position to the current cursor position // Create the rect preview - this is a rectangle drawn from the last mouse down position to the current cursor position
const rectPreview = new Konva.Rect({ const rect = new Konva.Rect({
id: PREVIEW_RECT_ID, id: PREVIEW_RECT_ID,
listening: false, listening: false,
strokeEnabled: false, strokeEnabled: false,
}); });
toolPreviewGroup.add(rectPreview); group.add(rect);
toolPreviewGroup.add(brushPreviewGroup); group.add(brushGroup);
previewLayer.add(toolPreviewGroup); return {
return toolPreviewGroup; group,
brush: {
group: brushGroup,
fill: brushFill,
innerBorder: brushBorderInner,
outerBorder: brushBorderOuter,
},
rect: {
rect,
},
};
}; };
/** /**
@ -336,7 +301,7 @@ export const getToolPreviewGroup = (stage: Konva.Stage): Konva.Group => {
* @param brushSize The brush size * @param brushSize The brush size
*/ */
export const renderToolPreview = ( export const renderToolPreview = (
stage: Konva.Stage, manager: KonvaNodeManager,
toolState: CanvasV2State['tool'], toolState: CanvasV2State['tool'],
currentFill: RgbaColor, currentFill: RgbaColor,
selectedEntity: CanvasEntity | null, selectedEntity: CanvasEntity | null,
@ -345,7 +310,8 @@ export const renderToolPreview = (
isDrawing: boolean, isDrawing: boolean,
isMouseDown: boolean isMouseDown: boolean
): void => { ): void => {
const layerCount = stage.find(selectRenderableLayers).length; const stage = manager.stage;
const layerCount = manager.adapters.size;
const tool = toolState.selected; const tool = toolState.selected;
// Update the stage's pointer style // Update the stage's pointer style
if (tool === 'view') { if (tool === 'view') {
@ -372,31 +338,22 @@ export const renderToolPreview = (
stage.draggable(tool === 'view'); stage.draggable(tool === 'view');
const toolPreviewGroup = getToolPreviewGroup(stage);
if ( if (
!cursorPos || !cursorPos ||
layerCount === 0 || layerCount === 0 ||
(selectedEntity?.type !== 'regional_guidance' && selectedEntity?.type !== 'layer') (selectedEntity?.type !== 'regional_guidance' && selectedEntity?.type !== 'layer')
) { ) {
// We can bail early if the mouse isn't over the stage or there are no layers // We can bail early if the mouse isn't over the stage or there are no layers
toolPreviewGroup.visible(false); manager.preview.tool.group.visible(false);
} else { } else {
toolPreviewGroup.visible(true); manager.preview.tool.group.visible(true);
const brushPreviewGroup = stage.findOne<Konva.Group>(`#${PREVIEW_BRUSH_GROUP_ID}`);
assert(brushPreviewGroup, 'Brush preview group not found');
const rectPreview = stage.findOne<Konva.Rect>(`#${PREVIEW_RECT_ID}`);
assert(rectPreview, 'Rect preview not found');
// No need to render the brush preview if the cursor position or color is missing // No need to render the brush preview if the cursor position or color is missing
if (cursorPos && (tool === 'brush' || tool === 'eraser')) { if (cursorPos && (tool === 'brush' || tool === 'eraser')) {
const scale = stage.scaleX(); const scale = stage.scaleX();
// Update the fill circle // Update the fill circle
const brushPreviewFill = brushPreviewGroup.findOne<Konva.Circle>(`#${PREVIEW_BRUSH_FILL_ID}`);
const radius = (tool === 'brush' ? toolState.brush.width : toolState.eraser.width) / 2; const radius = (tool === 'brush' ? toolState.brush.width : toolState.eraser.width) / 2;
brushPreviewFill?.setAttrs({ manager.preview.tool.brush.fill.setAttrs({
x: cursorPos.x, x: cursorPos.x,
y: cursorPos.y, y: cursorPos.y,
radius, radius,
@ -405,83 +362,74 @@ export const renderToolPreview = (
}); });
// Update the inner border of the brush preview // Update the inner border of the brush preview
const brushPreviewInner = brushPreviewGroup.findOne<Konva.Circle>(`#${PREVIEW_BRUSH_BORDER_INNER_ID}`); manager.preview.tool.brush.innerBorder.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
brushPreviewInner?.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
// Update the outer border of the brush preview // Update the outer border of the brush preview
const brushPreviewOuter = brushPreviewGroup.findOne<Konva.Circle>(`#${PREVIEW_BRUSH_BORDER_OUTER_ID}`); manager.preview.tool.brush.outerBorder.setAttrs({
brushPreviewOuter?.setAttrs({
x: cursorPos.x, x: cursorPos.x,
y: cursorPos.y, y: cursorPos.y,
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale, radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
}); });
scaleToolPreview(stage, toolState); scaleToolPreview(manager, toolState);
brushPreviewGroup.visible(true); manager.preview.tool.brush.group.visible(true);
} else { } else {
brushPreviewGroup.visible(false); manager.preview.tool.brush.group.visible(false);
} }
if (cursorPos && lastMouseDownPos && tool === 'rect') { if (cursorPos && lastMouseDownPos && tool === 'rect') {
const rectPreview = toolPreviewGroup.findOne<Konva.Rect>(`#${PREVIEW_RECT_ID}`); manager.preview.tool.rect.rect.setAttrs({
rectPreview?.setAttrs({
x: Math.min(cursorPos.x, lastMouseDownPos.x), x: Math.min(cursorPos.x, lastMouseDownPos.x),
y: Math.min(cursorPos.y, lastMouseDownPos.y), y: Math.min(cursorPos.y, lastMouseDownPos.y),
width: Math.abs(cursorPos.x - lastMouseDownPos.x), width: Math.abs(cursorPos.x - lastMouseDownPos.x),
height: Math.abs(cursorPos.y - lastMouseDownPos.y), height: Math.abs(cursorPos.y - lastMouseDownPos.y),
fill: rgbaColorToString(currentFill), fill: rgbaColorToString(currentFill),
visible: true,
}); });
rectPreview?.visible(true);
} else { } else {
rectPreview?.visible(false); manager.preview.tool.rect.rect.visible(false);
} }
} }
}; };
export const scaleToolPreview = (stage: Konva.Stage, toolState: CanvasV2State['tool']): void => { export const scaleToolPreview = (manager: KonvaNodeManager, toolState: CanvasV2State['tool']): void => {
const scale = stage.scaleX(); const scale = manager.stage.scaleX();
const radius = (toolState.selected === 'brush' ? toolState.brush.width : toolState.eraser.width) / 2; const radius = (toolState.selected === 'brush' ? toolState.brush.width : toolState.eraser.width) / 2;
const brushPreviewGroup = stage.findOne<Konva.Group>(`#${PREVIEW_BRUSH_GROUP_ID}`); manager.preview.tool.brush.innerBorder.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
brushPreviewGroup manager.preview.tool.brush.outerBorder.setAttrs({
?.findOne<Konva.Circle>(`#${PREVIEW_BRUSH_BORDER_INNER_ID}`) strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
?.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale); radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
brushPreviewGroup });
?.findOne<Konva.Circle>(`#${PREVIEW_BRUSH_BORDER_OUTER_ID}`)
?.setAttrs({ strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale, radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale });
}; };
const getDocumentOverlayGroup = (stage: Konva.Stage): Konva.Group => { export const createDocumentOverlay = (): KonvaNodeManager['preview']['documentOverlay'] => {
const previewLayer = getPreviewLayer(stage); const group = new Konva.Group({ id: 'document_overlay_group', listening: false });
let documentOverlayGroup = previewLayer.findOne<Konva.Group>('#document_overlay_group'); const outerRect = new Konva.Rect({
if (documentOverlayGroup) {
return documentOverlayGroup;
}
documentOverlayGroup = new Konva.Group({ id: 'document_overlay_group', listening: false });
const documentOverlayOuterRect = new Konva.Rect({
id: 'document_overlay_outer_rect', id: 'document_overlay_outer_rect',
listening: false, listening: false,
fill: getArbitraryBaseColor(10), fill: getArbitraryBaseColor(10),
opacity: 0.7, opacity: 0.7,
}); });
const documentOverlayInnerRect = new Konva.Rect({ const innerRect = new Konva.Rect({
id: 'document_overlay_inner_rect', id: 'document_overlay_inner_rect',
listening: false, listening: false,
fill: 'white', fill: 'white',
globalCompositeOperation: 'destination-out', globalCompositeOperation: 'destination-out',
}); });
documentOverlayGroup.add(documentOverlayOuterRect); group.add(outerRect);
documentOverlayGroup.add(documentOverlayInnerRect); group.add(innerRect);
previewLayer.add(documentOverlayGroup); return { group, innerRect, outerRect };
return documentOverlayGroup;
}; };
export const renderDocumentBoundsOverlay = (stage: Konva.Stage, getDocument: () => CanvasV2State['document']): void => { export const renderDocumentBoundsOverlay = (
manager: KonvaNodeManager,
getDocument: () => CanvasV2State['document']
): void => {
const document = getDocument(); const document = getDocument();
const documentOverlayGroup = getDocumentOverlayGroup(stage); const stage = manager.stage;
documentOverlayGroup.zIndex(0); manager.preview.documentOverlay.group.zIndex(0);
const x = stage.x(); const x = stage.x();
const y = stage.y(); const y = stage.y();
@ -489,14 +437,14 @@ export const renderDocumentBoundsOverlay = (stage: Konva.Stage, getDocument: ()
const height = stage.height(); const height = stage.height();
const scale = stage.scaleX(); const scale = stage.scaleX();
documentOverlayGroup.findOne<Konva.Rect>('#document_overlay_outer_rect')?.setAttrs({ manager.preview.documentOverlay.outerRect.setAttrs({
offsetX: x / scale, offsetX: x / scale,
offsetY: y / scale, offsetY: y / scale,
width: width / scale, width: width / scale,
height: height / scale, height: height / scale,
}); });
documentOverlayGroup.findOne<Konva.Rect>('#document_overlay_inner_rect')?.setAttrs({ manager.preview.documentOverlay.innerRect.setAttrs({
x: 0, x: 0,
y: 0, y: 0,
width: document.width, width: document.width,

View File

@ -7,7 +7,7 @@ import {
RG_LAYER_OBJECT_GROUP_NAME, RG_LAYER_OBJECT_GROUP_NAME,
RG_LAYER_RECT_SHAPE_NAME, RG_LAYER_RECT_SHAPE_NAME,
} from 'features/controlLayers/konva/naming'; } from 'features/controlLayers/konva/naming';
import type { EntityKonvaAdapter, KonvaNodeManager } from 'features/controlLayers/konva/nodeManager'; import type { KonvaEntityAdapter, KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
import { getLayerBboxFast } from 'features/controlLayers/konva/renderers/bbox'; import { getLayerBboxFast } from 'features/controlLayers/konva/renderers/bbox';
import { import {
createObjectGroup, createObjectGroup,
@ -52,7 +52,7 @@ const getRegion = (
manager: KonvaNodeManager, manager: KonvaNodeManager,
entity: RegionEntity, entity: RegionEntity,
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
): EntityKonvaAdapter => { ): KonvaEntityAdapter => {
const adapter = manager.get(entity.id); const adapter = manager.get(entity.id);
if (adapter) { if (adapter) {
return adapter; return adapter;
@ -74,7 +74,7 @@ const getRegion = (
} }
const konvaObjectGroup = createObjectGroup(konvaLayer, RG_LAYER_OBJECT_GROUP_NAME); const konvaObjectGroup = createObjectGroup(konvaLayer, RG_LAYER_OBJECT_GROUP_NAME);
return manager.add(entity.id, konvaLayer, konvaObjectGroup); return manager.add(entity, konvaLayer, konvaObjectGroup);
}; };
/** /**
@ -242,7 +242,7 @@ export const renderRegions = (
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
): void => { ): void => {
// Destroy nonexistent layers // Destroy nonexistent layers
for (const adapter of manager.getAll()) { for (const adapter of manager.getAll('regional_guidance')) {
if (!entities.find((rg) => rg.id === adapter.id)) { if (!entities.find((rg) => rg.id === adapter.id)) {
manager.destroy(adapter.id); manager.destroy(adapter.id);
} }

View File

@ -248,8 +248,10 @@ export const initializeRenderer = (
spaceKey = val; spaceKey = val;
}; };
const manager = new KonvaNodeManager(stage, getBbox, onBboxTransformed, $shift.get, $ctrl.get, $meta.get, $alt.get);
const cleanupListeners = setStageEventHandlers({ const cleanupListeners = setStageEventHandlers({
stage, manager,
getToolState, getToolState,
setTool, setTool,
setToolBuffer, setToolBuffer,
@ -284,10 +286,6 @@ export const initializeRenderer = (
// the entire state over when needed. // the entire state over when needed.
const debouncedUpdateBboxes = debounce(updateBboxes, 300); const debouncedUpdateBboxes = debounce(updateBboxes, 300);
const regionMap = new KonvaNodeManager(stage);
const layerMap = new KonvaNodeManager(stage);
const controlAdapterMap = new KonvaNodeManager(stage);
const renderCanvas = () => { const renderCanvas = () => {
const { canvasV2 } = store.getState(); const { canvasV2 } = store.getState();
@ -305,7 +303,7 @@ export const initializeRenderer = (
canvasV2.tool.selected !== prevCanvasV2.tool.selected canvasV2.tool.selected !== prevCanvasV2.tool.selected
) { ) {
logIfDebugging('Rendering layers'); logIfDebugging('Rendering layers');
renderLayers(layerMap, canvasV2.layers, canvasV2.tool.selected, onPosChanged); renderLayers(manager, canvasV2.layers, canvasV2.tool.selected, onPosChanged);
} }
if ( if (
@ -316,7 +314,7 @@ export const initializeRenderer = (
) { ) {
logIfDebugging('Rendering regions'); logIfDebugging('Rendering regions');
renderRegions( renderRegions(
regionMap, manager,
canvasV2.regions, canvasV2.regions,
canvasV2.settings.maskOpacity, canvasV2.settings.maskOpacity,
canvasV2.tool.selected, canvasV2.tool.selected,
@ -327,27 +325,17 @@ export const initializeRenderer = (
if (isFirstRender || canvasV2.controlAdapters !== prevCanvasV2.controlAdapters) { if (isFirstRender || canvasV2.controlAdapters !== prevCanvasV2.controlAdapters) {
logIfDebugging('Rendering control adapters'); logIfDebugging('Rendering control adapters');
renderControlAdapters(controlAdapterMap, canvasV2.controlAdapters); renderControlAdapters(manager, canvasV2.controlAdapters);
} }
if (isFirstRender || canvasV2.document !== prevCanvasV2.document) { if (isFirstRender || canvasV2.document !== prevCanvasV2.document) {
logIfDebugging('Rendering document bounds overlay'); logIfDebugging('Rendering document bounds overlay');
renderDocumentBoundsOverlay(stage, getDocument); renderDocumentBoundsOverlay(manager, getDocument);
} }
if (isFirstRender || canvasV2.bbox !== prevCanvasV2.bbox || canvasV2.tool.selected !== prevCanvasV2.tool.selected) { if (isFirstRender || canvasV2.bbox !== prevCanvasV2.bbox || canvasV2.tool.selected !== prevCanvasV2.tool.selected) {
logIfDebugging('Rendering generation bbox'); logIfDebugging('Rendering generation bbox');
renderBboxPreview( renderBboxPreview(manager, canvasV2.bbox, canvasV2.tool.selected);
stage,
canvasV2.bbox,
canvasV2.tool.selected,
getBbox,
onBboxTransformed,
$shift.get,
$ctrl.get,
$meta.get,
$alt.get
);
} }
if ( if (
@ -357,7 +345,7 @@ export const initializeRenderer = (
canvasV2.regions !== prevCanvasV2.regions canvasV2.regions !== prevCanvasV2.regions
) { ) {
logIfDebugging('Updating entity bboxes'); logIfDebugging('Updating entity bboxes');
debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged); // debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged);
} }
if ( if (
@ -367,15 +355,7 @@ export const initializeRenderer = (
canvasV2.regions !== prevCanvasV2.regions canvasV2.regions !== prevCanvasV2.regions
) { ) {
logIfDebugging('Arranging entities'); logIfDebugging('Arranging entities');
arrangeEntities( arrangeEntities(manager, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions);
stage,
layerMap,
canvasV2.layers,
controlAdapterMap,
canvasV2.controlAdapters,
regionMap,
canvasV2.regions
);
} }
prevCanvasV2 = canvasV2; prevCanvasV2 = canvasV2;
@ -399,8 +379,8 @@ export const initializeRenderer = (
height: stage.height(), height: stage.height(),
scale: stage.scaleX(), scale: stage.scaleX(),
}); });
renderBackgroundLayer(stage); renderBackgroundLayer(manager);
renderDocumentBoundsOverlay(stage, getDocument); renderDocumentBoundsOverlay(manager, getDocument);
}; };
const resizeObserver = new ResizeObserver(fitStageToContainer); const resizeObserver = new ResizeObserver(fitStageToContainer);
@ -414,7 +394,7 @@ export const initializeRenderer = (
const stageAttrs = fitDocumentToStage(stage, prevCanvasV2.document); const stageAttrs = fitDocumentToStage(stage, prevCanvasV2.document);
// The HUD displays some of the stage attributes, so we need to update it here. // The HUD displays some of the stage attributes, so we need to update it here.
$stageAttrs.set(stageAttrs); $stageAttrs.set(stageAttrs);
scaleToolPreview(stage, getToolState()); scaleToolPreview(manager, getToolState());
renderCanvas(); renderCanvas();
return () => { return () => {