mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): revised docstrings for renderers & simplified api
This commit is contained in:
parent
196779ff19
commit
a2ef8d9d47
@ -1,11 +1,4 @@
|
|||||||
import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
|
import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
|
||||||
import { renderBackgroundLayer } from 'features/controlLayers/konva/renderers/background';
|
|
||||||
import {
|
|
||||||
renderDocumentBoundsOverlay,
|
|
||||||
renderToolPreview,
|
|
||||||
scaleToolPreview,
|
|
||||||
} from 'features/controlLayers/konva/renderers/preview';
|
|
||||||
import { fitDocumentToStage } from 'features/controlLayers/konva/renderers/stage';
|
|
||||||
import { getScaledFlooredCursorPosition } from 'features/controlLayers/konva/util';
|
import { getScaledFlooredCursorPosition } from 'features/controlLayers/konva/util';
|
||||||
import type {
|
import type {
|
||||||
BrushLineAddedArg,
|
BrushLineAddedArg,
|
||||||
@ -52,7 +45,6 @@ type Arg = {
|
|||||||
getSelectedEntity: () => CanvasEntity | null;
|
getSelectedEntity: () => CanvasEntity | null;
|
||||||
getSpaceKey: () => boolean;
|
getSpaceKey: () => boolean;
|
||||||
setSpaceKey: (val: boolean) => void;
|
setSpaceKey: (val: boolean) => void;
|
||||||
getDocument: () => CanvasV2State['document'];
|
|
||||||
getBbox: () => CanvasV2State['bbox'];
|
getBbox: () => CanvasV2State['bbox'];
|
||||||
getSettings: () => CanvasV2State['settings'];
|
getSettings: () => CanvasV2State['settings'];
|
||||||
onBrushLineAdded: (arg: BrushLineAddedArg, entityType: CanvasEntity['type']) => void;
|
onBrushLineAdded: (arg: BrushLineAddedArg, entityType: CanvasEntity['type']) => void;
|
||||||
@ -160,7 +152,6 @@ export const setStageEventHandlers = ({
|
|||||||
getSelectedEntity,
|
getSelectedEntity,
|
||||||
getSpaceKey,
|
getSpaceKey,
|
||||||
setSpaceKey,
|
setSpaceKey,
|
||||||
getDocument,
|
|
||||||
getBbox,
|
getBbox,
|
||||||
getSettings,
|
getSettings,
|
||||||
onBrushLineAdded,
|
onBrushLineAdded,
|
||||||
@ -176,16 +167,7 @@ export const setStageEventHandlers = ({
|
|||||||
stage.on('mouseenter', () => {
|
stage.on('mouseenter', () => {
|
||||||
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(
|
manager.renderers.renderToolPreview();
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region mousedown
|
//#region mousedown
|
||||||
@ -306,16 +288,7 @@ export const setStageEventHandlers = ({
|
|||||||
setLastAddedPoint(pos);
|
setLastAddedPoint(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderToolPreview(
|
manager.renderers.renderToolPreview();
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region mouseup
|
//#region mouseup
|
||||||
@ -354,16 +327,7 @@ export const setStageEventHandlers = ({
|
|||||||
setLastMouseDownPos(null);
|
setLastMouseDownPos(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToolPreview(
|
manager.renderers.renderToolPreview();
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region mousemove
|
//#region mousemove
|
||||||
@ -469,17 +433,7 @@ export const setStageEventHandlers = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
manager.renderers.renderToolPreview();
|
||||||
renderToolPreview(
|
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region mouseleave
|
//#region mouseleave
|
||||||
@ -508,16 +462,7 @@ export const setStageEventHandlers = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToolPreview(
|
manager.renderers.renderToolPreview();
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region wheel
|
//#region wheel
|
||||||
@ -558,21 +503,11 @@ 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(manager);
|
manager.renderers.renderBackground();
|
||||||
scaleToolPreview(manager, getToolState());
|
manager.renderers.renderDocumentOverlay();
|
||||||
renderDocumentBoundsOverlay(manager, getDocument);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderToolPreview(
|
manager.renderers.renderToolPreview();
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region dragmove
|
//#region dragmove
|
||||||
@ -584,18 +519,9 @@ export const setStageEventHandlers = ({
|
|||||||
height: stage.height(),
|
height: stage.height(),
|
||||||
scale: stage.scaleX(),
|
scale: stage.scaleX(),
|
||||||
});
|
});
|
||||||
renderBackgroundLayer(manager);
|
manager.renderers.renderBackground();
|
||||||
renderDocumentBoundsOverlay(manager, getDocument);
|
manager.renderers.renderDocumentOverlay();
|
||||||
renderToolPreview(
|
manager.renderers.renderToolPreview();
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region dragend
|
//#region dragend
|
||||||
@ -608,16 +534,7 @@ export const setStageEventHandlers = ({
|
|||||||
height: stage.height(),
|
height: stage.height(),
|
||||||
scale: stage.scaleX(),
|
scale: stage.scaleX(),
|
||||||
});
|
});
|
||||||
renderToolPreview(
|
manager.renderers.renderToolPreview();
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region key
|
//#region key
|
||||||
@ -638,22 +555,12 @@ export const setStageEventHandlers = ({
|
|||||||
setTool('view');
|
setTool('view');
|
||||||
setSpaceKey(true);
|
setSpaceKey(true);
|
||||||
} else if (e.key === 'r') {
|
} else if (e.key === 'r') {
|
||||||
const stageAttrs = fitDocumentToStage(stage, getDocument());
|
manager.renderers.fitDocumentToStage();
|
||||||
setStageAttrs(stageAttrs);
|
manager.renderers.renderToolPreview();
|
||||||
scaleToolPreview(manager, getToolState());
|
manager.renderers.renderBackground();
|
||||||
renderBackgroundLayer(manager);
|
manager.renderers.renderDocumentOverlay();
|
||||||
renderDocumentBoundsOverlay(manager, getDocument);
|
|
||||||
}
|
}
|
||||||
renderToolPreview(
|
manager.renderers.renderToolPreview();
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
window.addEventListener('keydown', onKeyDown);
|
window.addEventListener('keydown', onKeyDown);
|
||||||
|
|
||||||
@ -671,16 +578,7 @@ export const setStageEventHandlers = ({
|
|||||||
setToolBuffer(null);
|
setToolBuffer(null);
|
||||||
setSpaceKey(false);
|
setSpaceKey(false);
|
||||||
}
|
}
|
||||||
renderToolPreview(
|
manager.renderers.renderToolPreview();
|
||||||
manager,
|
|
||||||
getToolState(),
|
|
||||||
getCurrentFill(),
|
|
||||||
getSelectedEntity(),
|
|
||||||
getLastCursorPos(),
|
|
||||||
getLastMouseDownPos(),
|
|
||||||
getIsDrawing(),
|
|
||||||
getIsMouseDown()
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
window.addEventListener('keyup', onKeyUp);
|
window.addEventListener('keyup', onKeyUp);
|
||||||
|
|
||||||
|
@ -1,20 +1,6 @@
|
|||||||
import { createBackgroundLayer } from 'features/controlLayers/konva/renderers/background';
|
import type { BrushLine, CanvasEntity, EraserLine, ImageObject, RectShape } from 'features/controlLayers/store/types';
|
||||||
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';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
export type BrushLineObjectRecord = {
|
export type BrushLineObjectRecord = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -50,61 +36,62 @@ export type ImageObjectRecord = {
|
|||||||
|
|
||||||
type ObjectRecord = BrushLineObjectRecord | EraserLineObjectRecord | RectShapeObjectRecord | ImageObjectRecord;
|
type ObjectRecord = BrushLineObjectRecord | EraserLineObjectRecord | RectShapeObjectRecord | ImageObjectRecord;
|
||||||
|
|
||||||
|
type KonvaRenderers = {
|
||||||
|
renderRegions: () => void;
|
||||||
|
renderLayers: () => void;
|
||||||
|
renderControlAdapters: () => void;
|
||||||
|
renderInpaintMask: () => void;
|
||||||
|
renderBbox: () => void;
|
||||||
|
renderDocumentOverlay: () => void;
|
||||||
|
renderBackground: () => void;
|
||||||
|
renderToolPreview: () => void;
|
||||||
|
fitDocumentToStage: () => void;
|
||||||
|
arrangeEntities: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BackgroundLayer = {
|
||||||
|
layer: Konva.Layer;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PreviewLayer = {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export class KonvaNodeManager {
|
export class KonvaNodeManager {
|
||||||
stage: Konva.Stage;
|
stage: Konva.Stage;
|
||||||
adapters: Map<string, KonvaEntityAdapter>;
|
adapters: Map<string, KonvaEntityAdapter>;
|
||||||
background: { layer: Konva.Layer };
|
_background: BackgroundLayer | null;
|
||||||
preview: {
|
_preview: PreviewLayer | null;
|
||||||
layer: Konva.Layer;
|
_renderers: KonvaRenderers | null;
|
||||||
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(
|
constructor(stage: Konva.Stage) {
|
||||||
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._renderers = null;
|
||||||
|
this._preview = null;
|
||||||
|
this._background = null;
|
||||||
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(entity: CanvasEntity, konvaLayer: Konva.Layer, konvaObjectGroup: Konva.Group): KonvaEntityAdapter {
|
add(entity: CanvasEntity, konvaLayer: Konva.Layer, konvaObjectGroup: Konva.Group): KonvaEntityAdapter {
|
||||||
@ -133,6 +120,33 @@ export class KonvaNodeManager {
|
|||||||
adapter.konvaLayer.destroy();
|
adapter.konvaLayer.destroy();
|
||||||
return this.adapters.delete(id);
|
return this.adapters.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set renderers(renderers: KonvaRenderers) {
|
||||||
|
this._renderers = renderers;
|
||||||
|
}
|
||||||
|
|
||||||
|
get renderers(): KonvaRenderers {
|
||||||
|
assert(this._renderers !== null, 'Konva renderers have not been set');
|
||||||
|
return this._renderers;
|
||||||
|
}
|
||||||
|
|
||||||
|
set preview(preview: PreviewLayer) {
|
||||||
|
this._preview = preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
get preview(): PreviewLayer {
|
||||||
|
assert(this._preview !== null, 'Konva preview layer has not been set');
|
||||||
|
return this._preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
set background(background: BackgroundLayer) {
|
||||||
|
this._background = background;
|
||||||
|
}
|
||||||
|
|
||||||
|
get background(): BackgroundLayer {
|
||||||
|
assert(this._background !== null, 'Konva background layer has not been set');
|
||||||
|
return this._background;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KonvaEntityAdapter {
|
export class KonvaEntityAdapter {
|
||||||
|
@ -1,23 +1,37 @@
|
|||||||
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 { CanvasV2State } from 'features/controlLayers/store/types';
|
||||||
|
|
||||||
export const arrangeEntities = (
|
/**
|
||||||
manager: KonvaNodeManager,
|
* Gets a function to arrange the entities in the konva stage.
|
||||||
layers: LayerEntity[],
|
* @param manager The konva node manager
|
||||||
controlAdapters: ControlAdapterEntity[],
|
* @param getLayerEntityStates A function to get all layer entity states
|
||||||
regions: RegionEntity[]
|
* @param getControlAdapterEntityStates A function to get all control adapter entity states
|
||||||
): void => {
|
* @param getRegionEntityStates A function to get all region entity states
|
||||||
let zIndex = 0;
|
* @returns An arrange entities function
|
||||||
manager.background.layer.zIndex(++zIndex);
|
*/
|
||||||
for (const layer of layers) {
|
export const getArrangeEntities =
|
||||||
manager.get(layer.id)?.konvaLayer.zIndex(++zIndex);
|
(arg: {
|
||||||
}
|
manager: KonvaNodeManager;
|
||||||
for (const ca of controlAdapters) {
|
getLayerEntityStates: () => CanvasV2State['layers']['entities'];
|
||||||
manager.get(ca.id)?.konvaLayer.zIndex(++zIndex);
|
getControlAdapterEntityStates: () => CanvasV2State['controlAdapters']['entities'];
|
||||||
}
|
getRegionEntityStates: () => CanvasV2State['regions']['entities'];
|
||||||
for (const rg of regions) {
|
}) =>
|
||||||
manager.get(rg.id)?.konvaLayer.zIndex(++zIndex);
|
(): void => {
|
||||||
}
|
const { manager, getLayerEntityStates, getControlAdapterEntityStates, getRegionEntityStates } = arg;
|
||||||
manager.get('inpaint_mask')?.konvaLayer.zIndex(++zIndex);
|
const layers = getLayerEntityStates();
|
||||||
manager.preview.layer.zIndex(++zIndex);
|
const controlAdapters = getControlAdapterEntityStates();
|
||||||
};
|
const regions = getRegionEntityStates();
|
||||||
|
let zIndex = 0;
|
||||||
|
manager.background.layer.zIndex(++zIndex);
|
||||||
|
for (const layer of layers) {
|
||||||
|
manager.get(layer.id)?.konvaLayer.zIndex(++zIndex);
|
||||||
|
}
|
||||||
|
for (const ca of controlAdapters) {
|
||||||
|
manager.get(ca.id)?.konvaLayer.zIndex(++zIndex);
|
||||||
|
}
|
||||||
|
for (const rg of regions) {
|
||||||
|
manager.get(rg.id)?.konvaLayer.zIndex(++zIndex);
|
||||||
|
}
|
||||||
|
manager.get('inpaint_mask')?.konvaLayer.zIndex(++zIndex);
|
||||||
|
manager.preview.layer.zIndex(++zIndex);
|
||||||
|
};
|
||||||
|
@ -6,6 +6,11 @@ import Konva from 'konva';
|
|||||||
const baseGridLineColor = getArbitraryBaseColor(27);
|
const baseGridLineColor = getArbitraryBaseColor(27);
|
||||||
const fineGridLineColor = getArbitraryBaseColor(18);
|
const fineGridLineColor = getArbitraryBaseColor(18);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the grid spacing. The value depends on the stage scale - at higher scales, the grid spacing is smaller.
|
||||||
|
* @param scale The stage scale
|
||||||
|
* @returns The grid spacing based on the stage scale
|
||||||
|
*/
|
||||||
const getGridSpacing = (scale: number): number => {
|
const getGridSpacing = (scale: number): number => {
|
||||||
if (scale >= 2) {
|
if (scale >= 2) {
|
||||||
return 8;
|
return 8;
|
||||||
@ -25,9 +30,19 @@ const getGridSpacing = (scale: number): number => {
|
|||||||
return 256;
|
return 256;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the background konva layer.
|
||||||
|
* @returns The background konva layer
|
||||||
|
*/
|
||||||
export const createBackgroundLayer = (): Konva.Layer => new Konva.Layer({ id: BACKGROUND_LAYER_ID, listening: false });
|
export const createBackgroundLayer = (): Konva.Layer => new Konva.Layer({ id: BACKGROUND_LAYER_ID, listening: false });
|
||||||
|
|
||||||
export const renderBackgroundLayer = (manager: KonvaNodeManager): void => {
|
/**
|
||||||
|
* Gets a render function for the background layer.
|
||||||
|
* @param arg.manager The konva node manager
|
||||||
|
* @returns A function to render the background grid
|
||||||
|
*/
|
||||||
|
export const getRenderBackground = (arg: { manager: KonvaNodeManager }) => (): void => {
|
||||||
|
const { manager } = arg;
|
||||||
const background = manager.background.layer;
|
const background = manager.background.layer;
|
||||||
background.zIndex(0);
|
background.zIndex(0);
|
||||||
const scale = manager.stage.scaleX();
|
const scale = manager.stage.scaleX();
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
createObjectGroup,
|
createObjectGroup,
|
||||||
updateImageSource,
|
updateImageSource,
|
||||||
} from 'features/controlLayers/konva/renderers/objects';
|
} from 'features/controlLayers/konva/renderers/objects';
|
||||||
import type { ControlAdapterEntity } from 'features/controlLayers/store/types';
|
import type { CanvasV2State, ControlAdapterEntity } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
@ -17,8 +17,8 @@ import { assert } from 'tsafe';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a control adapter layer.
|
* Gets a control adapter entity's konva nodes and entity adapter, creating them if they do not exist.
|
||||||
* @param stage The konva stage
|
* @param manager The konva node manager
|
||||||
* @param entity The control adapter layer state
|
* @param entity The control adapter layer state
|
||||||
*/
|
*/
|
||||||
const getControlAdapter = (manager: KonvaNodeManager, entity: ControlAdapterEntity): KonvaEntityAdapter => {
|
const getControlAdapter = (manager: KonvaNodeManager, entity: ControlAdapterEntity): KonvaEntityAdapter => {
|
||||||
@ -37,11 +37,9 @@ const getControlAdapter = (manager: KonvaNodeManager, entity: ControlAdapterEnti
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a control adapter layer. If the layer doesn't already exist, it is created. Otherwise, the layer is updated
|
* Renders a control adapter.
|
||||||
* with the current image source and attributes.
|
* @param manager The konva node manager
|
||||||
* @param stage The konva stage
|
* @param entity The control adapter entity state
|
||||||
* @param entity The control adapter layer state
|
|
||||||
* @param getImageDTO A function to retrieve an image DTO from the server, used to update the image source
|
|
||||||
*/
|
*/
|
||||||
export const renderControlAdapter = async (manager: KonvaNodeManager, entity: ControlAdapterEntity): Promise<void> => {
|
export const renderControlAdapter = async (manager: KonvaNodeManager, entity: ControlAdapterEntity): Promise<void> => {
|
||||||
const adapter = getControlAdapter(manager, entity);
|
const adapter = getControlAdapter(manager, entity);
|
||||||
@ -101,14 +99,27 @@ export const renderControlAdapter = async (manager: KonvaNodeManager, entity: Co
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderControlAdapters = (manager: KonvaNodeManager, entities: ControlAdapterEntity[]): void => {
|
/**
|
||||||
// Destroy nonexistent layers
|
* Gets a function to render all control adapters.
|
||||||
for (const adapters of manager.getAll('control_adapter')) {
|
* @param manager The konva node manager
|
||||||
if (!entities.find((ca) => ca.id === adapters.id)) {
|
* @param getControlAdapterEntityStates A function to get all control adapter entities
|
||||||
manager.destroy(adapters.id);
|
* @returns A function to render all control adapters
|
||||||
|
*/
|
||||||
|
export const getRenderControlAdapters =
|
||||||
|
(arg: {
|
||||||
|
manager: KonvaNodeManager;
|
||||||
|
getControlAdapterEntityStates: () => CanvasV2State['controlAdapters']['entities'];
|
||||||
|
}) =>
|
||||||
|
(): void => {
|
||||||
|
const { manager, getControlAdapterEntityStates } = arg;
|
||||||
|
const entities = getControlAdapterEntityStates();
|
||||||
|
// Destroy nonexistent layers
|
||||||
|
for (const adapters of manager.getAll('control_adapter')) {
|
||||||
|
if (!entities.find((ca) => ca.id === adapters.id)) {
|
||||||
|
manager.destroy(adapters.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
for (const entity of entities) {
|
||||||
for (const entity of entities) {
|
renderControlAdapter(manager, entity);
|
||||||
renderControlAdapter(manager, entity);
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
@ -16,24 +16,11 @@ import {
|
|||||||
getRectShape,
|
getRectShape,
|
||||||
} from 'features/controlLayers/konva/renderers/objects';
|
} from 'features/controlLayers/konva/renderers/objects';
|
||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import type {
|
import type { CanvasEntity, CanvasV2State, InpaintMaskEntity, PosChangedArg } from 'features/controlLayers/store/types';
|
||||||
CanvasEntity,
|
|
||||||
CanvasEntityIdentifier,
|
|
||||||
InpaintMaskEntity,
|
|
||||||
PosChangedArg,
|
|
||||||
Tool,
|
|
||||||
} from 'features/controlLayers/store/types';
|
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logic for creating and rendering regional guidance layers.
|
* Creates the "compositing rect" for the inpaint mask.
|
||||||
*
|
|
||||||
* Some special handling is needed to render layer opacity correctly using a "compositing rect". See the comments
|
|
||||||
* in `renderRGLayer`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the "compositing rect" for a regional guidance layer.
|
|
||||||
* @param konvaLayer The konva layer
|
* @param konvaLayer The konva layer
|
||||||
*/
|
*/
|
||||||
const createCompositingRect = (konvaLayer: Konva.Layer): Konva.Rect => {
|
const createCompositingRect = (konvaLayer: Konva.Layer): Konva.Rect => {
|
||||||
@ -43,23 +30,24 @@ const createCompositingRect = (konvaLayer: Konva.Layer): Konva.Rect => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a regional guidance layer.
|
* Gets the singleton inpaint mask entity's konva nodes and entity adapter, creating them if they do not exist.
|
||||||
* @param stage The konva stage
|
* @param manager The konva node manager
|
||||||
* @param entity The regional guidance layer state
|
* @param entityState The inpaint mask entity state
|
||||||
* @param onLayerPosChanged Callback for when the layer's position changes
|
* @param onPosChanged Callback for when the position changes (e.g. the entity is dragged)
|
||||||
|
* @returns The konva entity adapter for the inpaint mask
|
||||||
*/
|
*/
|
||||||
const getInpaintMask = (
|
const getInpaintMask = (
|
||||||
manager: KonvaNodeManager,
|
manager: KonvaNodeManager,
|
||||||
entity: InpaintMaskEntity,
|
entityState: InpaintMaskEntity,
|
||||||
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
||||||
): KonvaEntityAdapter => {
|
): KonvaEntityAdapter => {
|
||||||
const adapter = manager.get(entity.id);
|
const adapter = manager.get(entityState.id);
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
// This layer hasn't been added to the konva state yet
|
// This layer hasn't been added to the konva state yet
|
||||||
const konvaLayer = new Konva.Layer({
|
const konvaLayer = new Konva.Layer({
|
||||||
id: entity.id,
|
id: entityState.id,
|
||||||
name: INPAINT_MASK_LAYER_NAME,
|
name: INPAINT_MASK_LAYER_NAME,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
dragDistance: 0,
|
dragDistance: 0,
|
||||||
@ -69,166 +57,175 @@ const getInpaintMask = (
|
|||||||
// the position - we do not need to call this on the `dragmove` event.
|
// the position - we do not need to call this on the `dragmove` event.
|
||||||
if (onPosChanged) {
|
if (onPosChanged) {
|
||||||
konvaLayer.on('dragend', function (e) {
|
konvaLayer.on('dragend', function (e) {
|
||||||
onPosChanged({ id: entity.id, x: Math.floor(e.target.x()), y: Math.floor(e.target.y()) }, 'inpaint_mask');
|
onPosChanged({ id: entityState.id, x: Math.floor(e.target.x()), y: Math.floor(e.target.y()) }, 'inpaint_mask');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const konvaObjectGroup = createObjectGroup(konvaLayer, INPAINT_MASK_LAYER_OBJECT_GROUP_NAME);
|
const konvaObjectGroup = createObjectGroup(konvaLayer, INPAINT_MASK_LAYER_OBJECT_GROUP_NAME);
|
||||||
return manager.add(entity, konvaLayer, konvaObjectGroup);
|
return manager.add(entityState, konvaLayer, konvaObjectGroup);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a raster layer.
|
* Gets the inpaint mask render function.
|
||||||
* @param stage The konva stage
|
* @param manager The konva node manager
|
||||||
* @param entity The regional guidance layer state
|
* @param getEntityState A function to get the inpaint mask entity state
|
||||||
* @param globalMaskLayerOpacity The global mask layer opacity
|
* @param getMaskOpacity A function to get the mask opacity
|
||||||
* @param tool The current tool
|
* @param getToolState A function to get the tool state
|
||||||
* @param onPosChanged Callback for when the layer's position changes
|
* @param getSelectedEntity A function to get the selected entity
|
||||||
|
* @param onPosChanged Callback for when the position changes (e.g. the entity is dragged)
|
||||||
|
* @returns The inpaint mask render function
|
||||||
*/
|
*/
|
||||||
export const renderInpaintMask = (
|
export const getRenderInpaintMask =
|
||||||
manager: KonvaNodeManager,
|
(arg: {
|
||||||
entity: InpaintMaskEntity,
|
manager: KonvaNodeManager;
|
||||||
globalMaskLayerOpacity: number,
|
getInpaintMaskEntityState: () => CanvasV2State['inpaintMask'];
|
||||||
tool: Tool,
|
getMaskOpacity: () => number;
|
||||||
selectedEntityIdentifier: CanvasEntityIdentifier | null,
|
getToolState: () => CanvasV2State['tool'];
|
||||||
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
getSelectedEntity: () => CanvasEntity | null;
|
||||||
): void => {
|
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void;
|
||||||
const adapter = getInpaintMask(manager, entity, onPosChanged);
|
}) =>
|
||||||
|
(): void => {
|
||||||
|
const { manager, getInpaintMaskEntityState, getMaskOpacity, getToolState, getSelectedEntity, onPosChanged } = arg;
|
||||||
|
const entity = getInpaintMaskEntityState();
|
||||||
|
const globalMaskLayerOpacity = getMaskOpacity();
|
||||||
|
const toolState = getToolState();
|
||||||
|
const selectedEntity = getSelectedEntity();
|
||||||
|
const adapter = getInpaintMask(manager, entity, onPosChanged);
|
||||||
|
|
||||||
// Update the layer's position and listening state
|
// Update the layer's position and listening state
|
||||||
adapter.konvaLayer.setAttrs({
|
adapter.konvaLayer.setAttrs({
|
||||||
listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events
|
listening: toolState.selected === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events
|
||||||
x: Math.floor(entity.x),
|
x: Math.floor(entity.x),
|
||||||
y: Math.floor(entity.y),
|
y: Math.floor(entity.y),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
||||||
const rgbColor = rgbColorToString(entity.fill);
|
const rgbColor = rgbColorToString(entity.fill);
|
||||||
|
|
||||||
// We use caching to handle "global" layer opacity, but caching is expensive and we should only do it when required.
|
// We use caching to handle "global" layer opacity, but caching is expensive and we should only do it when required.
|
||||||
let groupNeedsCache = false;
|
let groupNeedsCache = false;
|
||||||
|
|
||||||
const objectIds = entity.objects.map(mapId);
|
const objectIds = entity.objects.map(mapId);
|
||||||
// Destroy any objects that are no longer in state
|
// Destroy any objects that are no longer in state
|
||||||
for (const objectRecord of adapter.getAll()) {
|
for (const objectRecord of adapter.getAll()) {
|
||||||
if (!objectIds.includes(objectRecord.id)) {
|
if (!objectIds.includes(objectRecord.id)) {
|
||||||
adapter.destroy(objectRecord.id);
|
adapter.destroy(objectRecord.id);
|
||||||
|
groupNeedsCache = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const obj of entity.objects) {
|
||||||
|
if (obj.type === 'brush_line') {
|
||||||
|
const objectRecord = getBrushLine(adapter, obj, INPAINT_MASK_LAYER_BRUSH_LINE_NAME);
|
||||||
|
|
||||||
|
// Only update the points if they have changed. The point values are never mutated, they are only added to the
|
||||||
|
// array, so checking the length is sufficient to determine if we need to re-cache.
|
||||||
|
if (objectRecord.konvaLine.points().length !== obj.points.length) {
|
||||||
|
objectRecord.konvaLine.points(obj.points);
|
||||||
|
groupNeedsCache = true;
|
||||||
|
}
|
||||||
|
// Only update the color if it has changed.
|
||||||
|
if (objectRecord.konvaLine.stroke() !== rgbColor) {
|
||||||
|
objectRecord.konvaLine.stroke(rgbColor);
|
||||||
|
groupNeedsCache = true;
|
||||||
|
}
|
||||||
|
} else if (obj.type === 'eraser_line') {
|
||||||
|
const objectRecord = getEraserLine(adapter, obj, INPAINT_MASK_LAYER_ERASER_LINE_NAME);
|
||||||
|
|
||||||
|
// Only update the points if they have changed. The point values are never mutated, they are only added to the
|
||||||
|
// array, so checking the length is sufficient to determine if we need to re-cache.
|
||||||
|
if (objectRecord.konvaLine.points().length !== obj.points.length) {
|
||||||
|
objectRecord.konvaLine.points(obj.points);
|
||||||
|
groupNeedsCache = true;
|
||||||
|
}
|
||||||
|
// Only update the color if it has changed.
|
||||||
|
if (objectRecord.konvaLine.stroke() !== rgbColor) {
|
||||||
|
objectRecord.konvaLine.stroke(rgbColor);
|
||||||
|
groupNeedsCache = true;
|
||||||
|
}
|
||||||
|
} else if (obj.type === 'rect_shape') {
|
||||||
|
const objectRecord = getRectShape(adapter, obj, INPAINT_MASK_LAYER_RECT_SHAPE_NAME);
|
||||||
|
|
||||||
|
// Only update the color if it has changed.
|
||||||
|
if (objectRecord.konvaRect.fill() !== rgbColor) {
|
||||||
|
objectRecord.konvaRect.fill(rgbColor);
|
||||||
|
groupNeedsCache = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update layer visibility if it has changed.
|
||||||
|
if (adapter.konvaLayer.visible() !== entity.isEnabled) {
|
||||||
|
adapter.konvaLayer.visible(entity.isEnabled);
|
||||||
groupNeedsCache = true;
|
groupNeedsCache = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const obj of entity.objects) {
|
if (adapter.konvaObjectGroup.getChildren().length === 0) {
|
||||||
if (obj.type === 'brush_line') {
|
// No objects - clear the cache to reset the previous pixel data
|
||||||
const objectRecord = getBrushLine(adapter, obj, INPAINT_MASK_LAYER_BRUSH_LINE_NAME);
|
|
||||||
|
|
||||||
// Only update the points if they have changed. The point values are never mutated, they are only added to the
|
|
||||||
// array, so checking the length is sufficient to determine if we need to re-cache.
|
|
||||||
if (objectRecord.konvaLine.points().length !== obj.points.length) {
|
|
||||||
objectRecord.konvaLine.points(obj.points);
|
|
||||||
groupNeedsCache = true;
|
|
||||||
}
|
|
||||||
// Only update the color if it has changed.
|
|
||||||
if (objectRecord.konvaLine.stroke() !== rgbColor) {
|
|
||||||
objectRecord.konvaLine.stroke(rgbColor);
|
|
||||||
groupNeedsCache = true;
|
|
||||||
}
|
|
||||||
} else if (obj.type === 'eraser_line') {
|
|
||||||
const objectRecord = getEraserLine(adapter, obj, INPAINT_MASK_LAYER_ERASER_LINE_NAME);
|
|
||||||
|
|
||||||
// Only update the points if they have changed. The point values are never mutated, they are only added to the
|
|
||||||
// array, so checking the length is sufficient to determine if we need to re-cache.
|
|
||||||
if (objectRecord.konvaLine.points().length !== obj.points.length) {
|
|
||||||
objectRecord.konvaLine.points(obj.points);
|
|
||||||
groupNeedsCache = true;
|
|
||||||
}
|
|
||||||
// Only update the color if it has changed.
|
|
||||||
if (objectRecord.konvaLine.stroke() !== rgbColor) {
|
|
||||||
objectRecord.konvaLine.stroke(rgbColor);
|
|
||||||
groupNeedsCache = true;
|
|
||||||
}
|
|
||||||
} else if (obj.type === 'rect_shape') {
|
|
||||||
const objectRecord = getRectShape(adapter, obj, INPAINT_MASK_LAYER_RECT_SHAPE_NAME);
|
|
||||||
|
|
||||||
// Only update the color if it has changed.
|
|
||||||
if (objectRecord.konvaRect.fill() !== rgbColor) {
|
|
||||||
objectRecord.konvaRect.fill(rgbColor);
|
|
||||||
groupNeedsCache = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only update layer visibility if it has changed.
|
|
||||||
if (adapter.konvaLayer.visible() !== entity.isEnabled) {
|
|
||||||
adapter.konvaLayer.visible(entity.isEnabled);
|
|
||||||
groupNeedsCache = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adapter.konvaObjectGroup.getChildren().length === 0) {
|
|
||||||
// No objects - clear the cache to reset the previous pixel data
|
|
||||||
adapter.konvaObjectGroup.clearCache();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const compositingRect =
|
|
||||||
adapter.konvaLayer.findOne<Konva.Rect>(`.${COMPOSITING_RECT_NAME}`) ?? createCompositingRect(adapter.konvaLayer);
|
|
||||||
const isSelected = selectedEntityIdentifier?.id === entity.id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the group is selected, we use a rect of the selected preview color, composited over the shapes. This allows
|
|
||||||
* shapes to render as a "raster" layer with all pixels drawn at the same color and opacity.
|
|
||||||
*
|
|
||||||
* Without this special handling, each shape is drawn individually with the given opacity, atop the other shapes. The
|
|
||||||
* effect is like if you have a Photoshop Group consisting of many shapes, each of which has the given opacity.
|
|
||||||
* Overlapping shapes will have their colors blended together, and the final color is the result of all the shapes.
|
|
||||||
*
|
|
||||||
* Instead, with the special handling, the effect is as if you drew all the shapes at 100% opacity, flattened them to
|
|
||||||
* a single raster image, and _then_ applied the 50% opacity.
|
|
||||||
*/
|
|
||||||
if (isSelected && tool !== 'move') {
|
|
||||||
// We must clear the cache first so Konva will re-draw the group with the new compositing rect
|
|
||||||
if (adapter.konvaObjectGroup.isCached()) {
|
|
||||||
adapter.konvaObjectGroup.clearCache();
|
adapter.konvaObjectGroup.clearCache();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
|
|
||||||
adapter.konvaObjectGroup.opacity(1);
|
|
||||||
|
|
||||||
compositingRect.setAttrs({
|
const compositingRect =
|
||||||
// The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already
|
adapter.konvaLayer.findOne<Konva.Rect>(`.${COMPOSITING_RECT_NAME}`) ?? createCompositingRect(adapter.konvaLayer);
|
||||||
...(!entity.bboxNeedsUpdate && entity.bbox ? entity.bbox : getLayerBboxFast(adapter.konvaLayer)),
|
const isSelected = selectedEntity?.id === entity.id;
|
||||||
fill: rgbColor,
|
|
||||||
opacity: globalMaskLayerOpacity,
|
/**
|
||||||
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
* When the group is selected, we use a rect of the selected preview color, composited over the shapes. This allows
|
||||||
globalCompositeOperation: 'source-in',
|
* shapes to render as a "raster" layer with all pixels drawn at the same color and opacity.
|
||||||
visible: true,
|
*
|
||||||
// This rect must always be on top of all other shapes
|
* Without this special handling, each shape is drawn individually with the given opacity, atop the other shapes. The
|
||||||
zIndex: adapter.konvaObjectGroup.getChildren().length,
|
* effect is like if you have a Photoshop Group consisting of many shapes, each of which has the given opacity.
|
||||||
});
|
* Overlapping shapes will have their colors blended together, and the final color is the result of all the shapes.
|
||||||
} else {
|
*
|
||||||
// The compositing rect should only be shown when the layer is selected.
|
* Instead, with the special handling, the effect is as if you drew all the shapes at 100% opacity, flattened them to
|
||||||
compositingRect.visible(false);
|
* a single raster image, and _then_ applied the 50% opacity.
|
||||||
// Cache only if needed - or if we are on this code path and _don't_ have a cache
|
*/
|
||||||
if (groupNeedsCache || !adapter.konvaObjectGroup.isCached()) {
|
if (isSelected && toolState.selected !== 'move') {
|
||||||
adapter.konvaObjectGroup.cache();
|
// We must clear the cache first so Konva will re-draw the group with the new compositing rect
|
||||||
|
if (adapter.konvaObjectGroup.isCached()) {
|
||||||
|
adapter.konvaObjectGroup.clearCache();
|
||||||
|
}
|
||||||
|
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
|
||||||
|
adapter.konvaObjectGroup.opacity(1);
|
||||||
|
|
||||||
|
compositingRect.setAttrs({
|
||||||
|
// The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already
|
||||||
|
...(!entity.bboxNeedsUpdate && entity.bbox ? entity.bbox : getLayerBboxFast(adapter.konvaLayer)),
|
||||||
|
fill: rgbColor,
|
||||||
|
opacity: globalMaskLayerOpacity,
|
||||||
|
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
||||||
|
globalCompositeOperation: 'source-in',
|
||||||
|
visible: true,
|
||||||
|
// This rect must always be on top of all other shapes
|
||||||
|
zIndex: adapter.konvaObjectGroup.getChildren().length,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// The compositing rect should only be shown when the layer is selected.
|
||||||
|
compositingRect.visible(false);
|
||||||
|
// Cache only if needed - or if we are on this code path and _don't_ have a cache
|
||||||
|
if (groupNeedsCache || !adapter.konvaObjectGroup.isCached()) {
|
||||||
|
adapter.konvaObjectGroup.cache();
|
||||||
|
}
|
||||||
|
// Updating group opacity does not require re-caching
|
||||||
|
adapter.konvaObjectGroup.opacity(globalMaskLayerOpacity);
|
||||||
}
|
}
|
||||||
// Updating group opacity does not require re-caching
|
|
||||||
adapter.konvaObjectGroup.opacity(globalMaskLayerOpacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// const bboxRect =
|
// const bboxRect =
|
||||||
// regionMap.konvaLayer.findOne<Konva.Rect>(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(rg, regionMap.konvaLayer);
|
// regionMap.konvaLayer.findOne<Konva.Rect>(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(rg, regionMap.konvaLayer);
|
||||||
|
|
||||||
// if (rg.bbox) {
|
// if (rg.bbox) {
|
||||||
// const active = !rg.bboxNeedsUpdate && isSelected && tool === 'move';
|
// const active = !rg.bboxNeedsUpdate && isSelected && tool === 'move';
|
||||||
// bboxRect.setAttrs({
|
// bboxRect.setAttrs({
|
||||||
// visible: active,
|
// visible: active,
|
||||||
// listening: active,
|
// listening: active,
|
||||||
// x: rg.bbox.x,
|
// x: rg.bbox.x,
|
||||||
// y: rg.bbox.y,
|
// y: rg.bbox.y,
|
||||||
// width: rg.bbox.width,
|
// width: rg.bbox.width,
|
||||||
// height: rg.bbox.height,
|
// height: rg.bbox.height,
|
||||||
// stroke: isSelected ? BBOX_SELECTED_STROKE : '',
|
// stroke: isSelected ? BBOX_SELECTED_STROKE : '',
|
||||||
// });
|
// });
|
||||||
// } else {
|
// } else {
|
||||||
// bboxRect.visible(false);
|
// bboxRect.visible(false);
|
||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
getRectShape,
|
getRectShape,
|
||||||
} from 'features/controlLayers/konva/renderers/objects';
|
} from 'features/controlLayers/konva/renderers/objects';
|
||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import type { CanvasEntity, LayerEntity, PosChangedArg, Tool } from 'features/controlLayers/store/types';
|
import type { CanvasEntity, CanvasV2State, LayerEntity, PosChangedArg, Tool } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,10 +23,11 @@ import Konva from 'konva';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a raster layer.
|
* Gets layer entity's konva nodes and entity adapter, creating them if they do not exist.
|
||||||
* @param stage The konva stage
|
* @param manager The konva node manager
|
||||||
* @param entity The raster layer state
|
* @param entity The layer entity state
|
||||||
* @param onPosChanged Callback for when the layer's position changes
|
* @param onPosChanged Callback for when the layer's position changes
|
||||||
|
* @returns The konva entity adapter for the layer
|
||||||
*/
|
*/
|
||||||
const getLayer = (
|
const getLayer = (
|
||||||
manager: KonvaNodeManager,
|
manager: KonvaNodeManager,
|
||||||
@ -58,9 +59,9 @@ const getLayer = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a regional guidance layer.
|
* Renders a layer.
|
||||||
* @param stage The konva stage
|
* @param manager The konva node manager
|
||||||
* @param entity The regional guidance layer state
|
* @param entity The layer entity state
|
||||||
* @param tool The current tool
|
* @param tool The current tool
|
||||||
* @param onPosChanged Callback for when the layer's position changes
|
* @param onPosChanged Callback for when the layer's position changes
|
||||||
*/
|
*/
|
||||||
@ -133,19 +134,32 @@ export const renderLayer = async (
|
|||||||
adapter.konvaObjectGroup.opacity(entity.opacity);
|
adapter.konvaObjectGroup.opacity(entity.opacity);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderLayers = (
|
/**
|
||||||
manager: KonvaNodeManager,
|
* Gets a function to render all layers.
|
||||||
entities: LayerEntity[],
|
* @param manager The konva node manager
|
||||||
tool: Tool,
|
* @param getLayerEntityStates A function to get all layer entities
|
||||||
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
* @param getToolState A function to get the current tool state
|
||||||
): void => {
|
* @param onPosChanged Callback for when the layer's position changes
|
||||||
// Destroy nonexistent layers
|
* @returns A function to render all layers
|
||||||
for (const adapter of manager.getAll('layer')) {
|
*/
|
||||||
if (!entities.find((l) => l.id === adapter.id)) {
|
export const getRenderLayers =
|
||||||
manager.destroy(adapter.id);
|
(arg: {
|
||||||
|
manager: KonvaNodeManager;
|
||||||
|
getLayerEntityStates: () => CanvasV2State['layers']['entities'];
|
||||||
|
getToolState: () => CanvasV2State['tool'];
|
||||||
|
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void;
|
||||||
|
}) =>
|
||||||
|
(): void => {
|
||||||
|
const { manager, getLayerEntityStates, getToolState, onPosChanged } = arg;
|
||||||
|
const entities = getLayerEntityStates();
|
||||||
|
const tool = getToolState();
|
||||||
|
// Destroy nonexistent layers
|
||||||
|
for (const adapter of manager.getAll('layer')) {
|
||||||
|
if (!entities.find((l) => l.id === adapter.id)) {
|
||||||
|
manager.destroy(adapter.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
for (const entity of entities) {
|
||||||
for (const entity of entities) {
|
renderLayer(manager, entity, tool.selected, onPosChanged);
|
||||||
renderLayer(manager, entity, tool, onPosChanged);
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
@ -19,14 +19,29 @@ import {
|
|||||||
PREVIEW_TOOL_GROUP_ID,
|
PREVIEW_TOOL_GROUP_ID,
|
||||||
} from 'features/controlLayers/konva/naming';
|
} from 'features/controlLayers/konva/naming';
|
||||||
import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
|
import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
|
||||||
import type { CanvasEntity, CanvasV2State, RgbaColor, Tool } from 'features/controlLayers/store/types';
|
import type { CanvasEntity, CanvasV2State, RgbaColor } 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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the konva preview layer.
|
||||||
|
* @returns The konva preview layer
|
||||||
|
*/
|
||||||
export const createPreviewLayer = (): Konva.Layer => new Konva.Layer({ id: PREVIEW_LAYER_ID, listening: true });
|
export const createPreviewLayer = (): Konva.Layer => new Konva.Layer({ id: PREVIEW_LAYER_ID, listening: true });
|
||||||
|
|
||||||
export const createBboxPreview = (
|
/**
|
||||||
|
* Creates the bbox konva nodes.
|
||||||
|
* @param stage The konva stage
|
||||||
|
* @param getBbox A function to get the bbox
|
||||||
|
* @param onBboxTransformed A callback for when the bbox is transformed
|
||||||
|
* @param getShiftKey A function to get the shift key state
|
||||||
|
* @param getCtrlKey A function to get the ctrl key state
|
||||||
|
* @param getMetaKey A function to get the meta key state
|
||||||
|
* @param getAltKey A function to get the alt key state
|
||||||
|
* @returns The bbox nodes
|
||||||
|
*/
|
||||||
|
export const createBboxNodes = (
|
||||||
stage: Konva.Stage,
|
stage: Konva.Stage,
|
||||||
getBbox: () => IRect,
|
getBbox: () => IRect,
|
||||||
onBboxTransformed: (bbox: IRect) => void,
|
onBboxTransformed: (bbox: IRect) => void,
|
||||||
@ -227,18 +242,40 @@ 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 = (manager: KonvaNodeManager, bbox: IRect, tool: Tool): void => {
|
/**
|
||||||
manager.preview.bbox.group.listening(tool === 'bbox');
|
* Gets the bbox render function.
|
||||||
// This updates the bbox during transformation
|
* @param manager The konva node manager
|
||||||
manager.preview.bbox.rect.setAttrs({ ...bbox, scaleX: 1, scaleY: 1, listening: tool === 'bbox' });
|
* @param getBbox A function to get the bbox
|
||||||
manager.preview.bbox.transformer.setAttrs({
|
* @param getToolState A function to get the tool state
|
||||||
listening: tool === 'bbox',
|
* @returns The bbox render function
|
||||||
enabledAnchors: tool === 'bbox' ? ALL_ANCHORS : NO_ANCHORS,
|
*/
|
||||||
});
|
export const getRenderBbox =
|
||||||
};
|
(manager: KonvaNodeManager, getBbox: () => CanvasV2State['bbox'], getToolState: () => CanvasV2State['tool']) =>
|
||||||
|
(): void => {
|
||||||
|
const bbox = getBbox();
|
||||||
|
const toolState = getToolState();
|
||||||
|
manager.preview.bbox.group.listening(toolState.selected === 'bbox');
|
||||||
|
// This updates the bbox during transformation
|
||||||
|
manager.preview.bbox.rect.setAttrs({
|
||||||
|
x: bbox.x,
|
||||||
|
y: bbox.y,
|
||||||
|
width: bbox.width,
|
||||||
|
height: bbox.height,
|
||||||
|
scaleX: 1,
|
||||||
|
scaleY: 1,
|
||||||
|
listening: toolState.selected === 'bbox',
|
||||||
|
});
|
||||||
|
manager.preview.bbox.transformer.setAttrs({
|
||||||
|
listening: toolState.selected === 'bbox',
|
||||||
|
enabledAnchors: toolState.selected === 'bbox' ? ALL_ANCHORS : NO_ANCHORS,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const createToolPreview = (stage: Konva.Stage): KonvaNodeManager['preview']['tool'] => {
|
/**
|
||||||
const scale = stage.scaleX();
|
* Gets the tool preview konva nodes.
|
||||||
|
* @returns The tool preview konva nodes
|
||||||
|
*/
|
||||||
|
export const createToolPreviewNodes = (): KonvaNodeManager['preview']['tool'] => {
|
||||||
const group = 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
|
||||||
@ -253,7 +290,7 @@ export const createToolPreview = (stage: Konva.Stage): KonvaNodeManager['preview
|
|||||||
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,
|
||||||
strokeEnabled: true,
|
strokeEnabled: true,
|
||||||
});
|
});
|
||||||
brushGroup.add(brushBorderInner);
|
brushGroup.add(brushBorderInner);
|
||||||
@ -261,7 +298,7 @@ export const createToolPreview = (stage: Konva.Stage): KonvaNodeManager['preview
|
|||||||
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,
|
||||||
strokeEnabled: true,
|
strokeEnabled: true,
|
||||||
});
|
});
|
||||||
brushGroup.add(brushBorderOuter);
|
brushGroup.add(brushBorderOuter);
|
||||||
@ -290,111 +327,138 @@ export const createToolPreview = (stage: Konva.Stage): KonvaNodeManager['preview
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the preview layer.
|
* Gets the tool preview (brush, eraser, rect) render function.
|
||||||
* @param stage The konva stage
|
* @param arg.manager The konva node manager
|
||||||
* @param tool The selected tool
|
* @param arg.getToolState The selected tool
|
||||||
* @param currentFill The selected layer's color
|
* @param arg.currentFill The selected layer's color
|
||||||
* @param selectedEntity The selected layer's type
|
* @param arg.selectedEntity The selected layer's type
|
||||||
* @param globalMaskLayerOpacity The global mask layer opacity
|
* @param arg.globalMaskLayerOpacity The global mask layer opacity
|
||||||
* @param cursorPos The cursor position
|
* @param arg.cursorPos The cursor position
|
||||||
* @param lastMouseDownPos The position of the last mouse down event - used for the rect tool
|
* @param arg.lastMouseDownPos The position of the last mouse down event - used for the rect tool
|
||||||
* @param brushSize The brush size
|
* @param arg.brushSize The brush size
|
||||||
|
* @returns The tool preview render function
|
||||||
*/
|
*/
|
||||||
export const renderToolPreview = (
|
export const getRenderToolPreview =
|
||||||
manager: KonvaNodeManager,
|
(arg: {
|
||||||
toolState: CanvasV2State['tool'],
|
manager: KonvaNodeManager;
|
||||||
currentFill: RgbaColor,
|
getToolState: () => CanvasV2State['tool'];
|
||||||
selectedEntity: CanvasEntity | null,
|
getCurrentFill: () => RgbaColor;
|
||||||
cursorPos: Vector2d | null,
|
getSelectedEntity: () => CanvasEntity | null;
|
||||||
lastMouseDownPos: Vector2d | null,
|
getLastCursorPos: () => Vector2d | null;
|
||||||
isDrawing: boolean,
|
getLastMouseDownPos: () => Vector2d | null;
|
||||||
isMouseDown: boolean
|
getIsDrawing: () => boolean;
|
||||||
): void => {
|
getIsMouseDown: () => boolean;
|
||||||
const stage = manager.stage;
|
}) =>
|
||||||
const layerCount = manager.adapters.size;
|
(): void => {
|
||||||
const tool = toolState.selected;
|
const {
|
||||||
const isDrawableEntity =
|
manager,
|
||||||
selectedEntity?.type === 'regional_guidance' ||
|
getToolState,
|
||||||
selectedEntity?.type === 'layer' ||
|
getCurrentFill,
|
||||||
selectedEntity?.type === 'inpaint_mask';
|
getSelectedEntity,
|
||||||
|
getLastCursorPos,
|
||||||
|
getLastMouseDownPos,
|
||||||
|
getIsDrawing,
|
||||||
|
getIsMouseDown,
|
||||||
|
} = arg;
|
||||||
|
|
||||||
// Update the stage's pointer style
|
const stage = manager.stage;
|
||||||
if (tool === 'view') {
|
const layerCount = manager.adapters.size;
|
||||||
// View gets a hand
|
const toolState = getToolState();
|
||||||
stage.container().style.cursor = isMouseDown ? 'grabbing' : 'grab';
|
const currentFill = getCurrentFill();
|
||||||
} else if (layerCount === 0) {
|
const selectedEntity = getSelectedEntity();
|
||||||
// We have no layers, so we should not render any tool
|
const cursorPos = getLastCursorPos();
|
||||||
stage.container().style.cursor = 'default';
|
const lastMouseDownPos = getLastMouseDownPos();
|
||||||
} else if (!isDrawableEntity) {
|
const isDrawing = getIsDrawing();
|
||||||
// Non-drawable layers don't have tools
|
const isMouseDown = getIsMouseDown();
|
||||||
stage.container().style.cursor = 'not-allowed';
|
const tool = toolState.selected;
|
||||||
} else if (tool === 'move') {
|
const isDrawableEntity =
|
||||||
// Move tool gets a pointer
|
selectedEntity?.type === 'regional_guidance' ||
|
||||||
stage.container().style.cursor = 'default';
|
selectedEntity?.type === 'layer' ||
|
||||||
} else if (tool === 'rect') {
|
selectedEntity?.type === 'inpaint_mask';
|
||||||
// Rect gets a crosshair
|
|
||||||
stage.container().style.cursor = 'crosshair';
|
|
||||||
} else if (tool === 'brush' || tool === 'eraser') {
|
|
||||||
// Hide the native cursor and use the konva-rendered brush preview
|
|
||||||
stage.container().style.cursor = 'none';
|
|
||||||
} else if (tool === 'bbox') {
|
|
||||||
stage.container().style.cursor = 'default';
|
|
||||||
}
|
|
||||||
|
|
||||||
stage.draggable(tool === 'view');
|
// Update the stage's pointer style
|
||||||
|
if (tool === 'view') {
|
||||||
if (!cursorPos || layerCount === 0 || !isDrawableEntity) {
|
// View gets a hand
|
||||||
// We can bail early if the mouse isn't over the stage or there are no layers
|
stage.container().style.cursor = isMouseDown ? 'grabbing' : 'grab';
|
||||||
manager.preview.tool.group.visible(false);
|
} else if (layerCount === 0) {
|
||||||
} else {
|
// We have no layers, so we should not render any tool
|
||||||
manager.preview.tool.group.visible(true);
|
stage.container().style.cursor = 'default';
|
||||||
|
} else if (!isDrawableEntity) {
|
||||||
// No need to render the brush preview if the cursor position or color is missing
|
// Non-drawable layers don't have tools
|
||||||
if (cursorPos && (tool === 'brush' || tool === 'eraser')) {
|
stage.container().style.cursor = 'not-allowed';
|
||||||
const scale = stage.scaleX();
|
} else if (tool === 'move') {
|
||||||
// Update the fill circle
|
// Move tool gets a pointer
|
||||||
const radius = (tool === 'brush' ? toolState.brush.width : toolState.eraser.width) / 2;
|
stage.container().style.cursor = 'default';
|
||||||
manager.preview.tool.brush.fill.setAttrs({
|
} else if (tool === 'rect') {
|
||||||
x: cursorPos.x,
|
// Rect gets a crosshair
|
||||||
y: cursorPos.y,
|
stage.container().style.cursor = 'crosshair';
|
||||||
radius,
|
} else if (tool === 'brush' || tool === 'eraser') {
|
||||||
fill: isDrawing ? '' : rgbaColorToString(currentFill),
|
// Hide the native cursor and use the konva-rendered brush preview
|
||||||
globalCompositeOperation: tool === 'brush' ? 'source-over' : 'destination-out',
|
stage.container().style.cursor = 'none';
|
||||||
});
|
} else if (tool === 'bbox') {
|
||||||
|
stage.container().style.cursor = 'default';
|
||||||
// Update the inner border of the brush preview
|
|
||||||
manager.preview.tool.brush.innerBorder.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
|
|
||||||
|
|
||||||
// Update the outer border of the brush preview
|
|
||||||
manager.preview.tool.brush.outerBorder.setAttrs({
|
|
||||||
x: cursorPos.x,
|
|
||||||
y: cursorPos.y,
|
|
||||||
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
|
||||||
});
|
|
||||||
|
|
||||||
scaleToolPreview(manager, toolState);
|
|
||||||
|
|
||||||
manager.preview.tool.brush.group.visible(true);
|
|
||||||
} else {
|
|
||||||
manager.preview.tool.brush.group.visible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursorPos && lastMouseDownPos && tool === 'rect') {
|
stage.draggable(tool === 'view');
|
||||||
manager.preview.tool.rect.rect.setAttrs({
|
|
||||||
x: Math.min(cursorPos.x, lastMouseDownPos.x),
|
|
||||||
y: Math.min(cursorPos.y, lastMouseDownPos.y),
|
|
||||||
width: Math.abs(cursorPos.x - lastMouseDownPos.x),
|
|
||||||
height: Math.abs(cursorPos.y - lastMouseDownPos.y),
|
|
||||||
fill: rgbaColorToString(currentFill),
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
manager.preview.tool.rect.rect.visible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const scaleToolPreview = (manager: KonvaNodeManager, toolState: CanvasV2State['tool']): void => {
|
if (!cursorPos || layerCount === 0 || !isDrawableEntity) {
|
||||||
|
// We can bail early if the mouse isn't over the stage or there are no layers
|
||||||
|
manager.preview.tool.group.visible(false);
|
||||||
|
} else {
|
||||||
|
manager.preview.tool.group.visible(true);
|
||||||
|
|
||||||
|
// No need to render the brush preview if the cursor position or color is missing
|
||||||
|
if (cursorPos && (tool === 'brush' || tool === 'eraser')) {
|
||||||
|
const scale = stage.scaleX();
|
||||||
|
// Update the fill circle
|
||||||
|
const radius = (tool === 'brush' ? toolState.brush.width : toolState.eraser.width) / 2;
|
||||||
|
manager.preview.tool.brush.fill.setAttrs({
|
||||||
|
x: cursorPos.x,
|
||||||
|
y: cursorPos.y,
|
||||||
|
radius,
|
||||||
|
fill: isDrawing ? '' : rgbaColorToString(currentFill),
|
||||||
|
globalCompositeOperation: tool === 'brush' ? 'source-over' : 'destination-out',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the inner border of the brush preview
|
||||||
|
manager.preview.tool.brush.innerBorder.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
|
||||||
|
|
||||||
|
// Update the outer border of the brush preview
|
||||||
|
manager.preview.tool.brush.outerBorder.setAttrs({
|
||||||
|
x: cursorPos.x,
|
||||||
|
y: cursorPos.y,
|
||||||
|
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||||
|
});
|
||||||
|
|
||||||
|
scaleToolPreview(manager, toolState);
|
||||||
|
|
||||||
|
manager.preview.tool.brush.group.visible(true);
|
||||||
|
} else {
|
||||||
|
manager.preview.tool.brush.group.visible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursorPos && lastMouseDownPos && tool === 'rect') {
|
||||||
|
manager.preview.tool.rect.rect.setAttrs({
|
||||||
|
x: Math.min(cursorPos.x, lastMouseDownPos.x),
|
||||||
|
y: Math.min(cursorPos.y, lastMouseDownPos.y),
|
||||||
|
width: Math.abs(cursorPos.x - lastMouseDownPos.x),
|
||||||
|
height: Math.abs(cursorPos.y - lastMouseDownPos.y),
|
||||||
|
fill: rgbaColorToString(currentFill),
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
manager.preview.tool.rect.rect.visible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scales the tool preview nodes. Depending on the scale of the stage, the border width and radius of the brush preview
|
||||||
|
* need to be adjusted.
|
||||||
|
* @param manager The konva node manager
|
||||||
|
* @param toolState The tool state
|
||||||
|
*/
|
||||||
|
const scaleToolPreview = (manager: KonvaNodeManager, toolState: CanvasV2State['tool']): void => {
|
||||||
const scale = manager.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;
|
||||||
manager.preview.tool.brush.innerBorder.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
manager.preview.tool.brush.innerBorder.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
||||||
@ -404,6 +468,10 @@ export const scaleToolPreview = (manager: KonvaNodeManager, toolState: CanvasV2S
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the document overlay konva nodes.
|
||||||
|
* @returns The document overlay konva nodes
|
||||||
|
*/
|
||||||
export const createDocumentOverlay = (): KonvaNodeManager['preview']['documentOverlay'] => {
|
export const createDocumentOverlay = (): KonvaNodeManager['preview']['documentOverlay'] => {
|
||||||
const group = new Konva.Group({ id: 'document_overlay_group', listening: false });
|
const group = new Konva.Group({ id: 'document_overlay_group', listening: false });
|
||||||
const outerRect = new Konva.Rect({
|
const outerRect = new Konva.Rect({
|
||||||
@ -423,32 +491,37 @@ export const createDocumentOverlay = (): KonvaNodeManager['preview']['documentOv
|
|||||||
return { group, innerRect, outerRect };
|
return { group, innerRect, outerRect };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderDocumentBoundsOverlay = (
|
/**
|
||||||
manager: KonvaNodeManager,
|
* Gets the document overlay render function.
|
||||||
getDocument: () => CanvasV2State['document']
|
* @param arg.manager The konva node manager
|
||||||
): void => {
|
* @param arg.getDocument A function to get the document state
|
||||||
const document = getDocument();
|
* @returns The document overlay render function
|
||||||
const stage = manager.stage;
|
*/
|
||||||
|
export const getRenderDocumentOverlay =
|
||||||
|
(arg: { manager: KonvaNodeManager; getDocument: () => CanvasV2State['document'] }) => (): void => {
|
||||||
|
const { manager, getDocument } = arg;
|
||||||
|
const document = getDocument();
|
||||||
|
const stage = manager.stage;
|
||||||
|
|
||||||
manager.preview.documentOverlay.group.zIndex(0);
|
manager.preview.documentOverlay.group.zIndex(0);
|
||||||
|
|
||||||
const x = stage.x();
|
const x = stage.x();
|
||||||
const y = stage.y();
|
const y = stage.y();
|
||||||
const width = stage.width();
|
const width = stage.width();
|
||||||
const height = stage.height();
|
const height = stage.height();
|
||||||
const scale = stage.scaleX();
|
const scale = stage.scaleX();
|
||||||
|
|
||||||
manager.preview.documentOverlay.outerRect.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,
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.preview.documentOverlay.innerRect.setAttrs({
|
manager.preview.documentOverlay.innerRect.setAttrs({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: document.width,
|
width: document.width,
|
||||||
height: document.height,
|
height: document.height,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -19,19 +19,13 @@ import { mapId } from 'features/controlLayers/konva/util';
|
|||||||
import type {
|
import type {
|
||||||
CanvasEntity,
|
CanvasEntity,
|
||||||
CanvasEntityIdentifier,
|
CanvasEntityIdentifier,
|
||||||
|
CanvasV2State,
|
||||||
PosChangedArg,
|
PosChangedArg,
|
||||||
RegionEntity,
|
RegionEntity,
|
||||||
Tool,
|
Tool,
|
||||||
} from 'features/controlLayers/store/types';
|
} from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
|
|
||||||
/**
|
|
||||||
* Logic for creating and rendering regional guidance layers.
|
|
||||||
*
|
|
||||||
* Some special handling is needed to render layer opacity correctly using a "compositing rect". See the comments
|
|
||||||
* in `renderRGLayer`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the "compositing rect" for a regional guidance layer.
|
* Creates the "compositing rect" for a regional guidance layer.
|
||||||
* @param konvaLayer The konva layer
|
* @param konvaLayer The konva layer
|
||||||
@ -43,10 +37,11 @@ const createCompositingRect = (konvaLayer: Konva.Layer): Konva.Rect => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a regional guidance layer.
|
* Gets a region's konva nodes and entity adapter, creating them if they do not exist.
|
||||||
* @param stage The konva stage
|
* @param stage The konva stage
|
||||||
* @param entity The regional guidance layer state
|
* @param entity The regional guidance layer state
|
||||||
* @param onLayerPosChanged Callback for when the layer's position changes
|
* @param onLayerPosChanged Callback for when the layer's position changes
|
||||||
|
* @returns The konva entity adapter for the region
|
||||||
*/
|
*/
|
||||||
const getRegion = (
|
const getRegion = (
|
||||||
manager: KonvaNodeManager,
|
manager: KonvaNodeManager,
|
||||||
@ -78,7 +73,7 @@ const getRegion = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a raster layer.
|
* Renders a region.
|
||||||
* @param stage The konva stage
|
* @param stage The konva stage
|
||||||
* @param entity The regional guidance layer state
|
* @param entity The regional guidance layer state
|
||||||
* @param globalMaskLayerOpacity The global mask layer opacity
|
* @param globalMaskLayerOpacity The global mask layer opacity
|
||||||
@ -233,21 +228,40 @@ export const renderRegion = (
|
|||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderRegions = (
|
/**
|
||||||
manager: KonvaNodeManager,
|
* Gets a function to render all regions.
|
||||||
entities: RegionEntity[],
|
* @param arg.manager The konva node manager
|
||||||
maskOpacity: number,
|
* @param arg.getRegionEntityStates A function to get all region entities
|
||||||
tool: Tool,
|
* @param arg.getMaskOpacity A function to get the mask opacity
|
||||||
selectedEntityIdentifier: CanvasEntityIdentifier | null,
|
* @param arg.getToolState A function to get the tool state
|
||||||
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
|
* @param arg.getSelectedEntity A function to get the selectedEntity
|
||||||
): void => {
|
* @param arg.onPosChanged A callback for when the position of an entity changes
|
||||||
// Destroy nonexistent layers
|
* @returns A function to render all regions
|
||||||
for (const adapter of manager.getAll('regional_guidance')) {
|
*/
|
||||||
if (!entities.find((rg) => rg.id === adapter.id)) {
|
export const getRenderRegions =
|
||||||
manager.destroy(adapter.id);
|
(arg: {
|
||||||
|
manager: KonvaNodeManager;
|
||||||
|
getRegionEntityStates: () => CanvasV2State['regions']['entities'];
|
||||||
|
getMaskOpacity: () => number;
|
||||||
|
getToolState: () => CanvasV2State['tool'];
|
||||||
|
getSelectedEntity: () => CanvasEntity | null;
|
||||||
|
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void;
|
||||||
|
}) =>
|
||||||
|
() => {
|
||||||
|
const { manager, getRegionEntityStates, getMaskOpacity, getToolState, getSelectedEntity, onPosChanged } = arg;
|
||||||
|
const entities = getRegionEntityStates();
|
||||||
|
const maskOpacity = getMaskOpacity();
|
||||||
|
const toolState = getToolState();
|
||||||
|
const selectedEntity = getSelectedEntity();
|
||||||
|
|
||||||
|
// Destroy the konva nodes for nonexistent entities
|
||||||
|
for (const adapter of manager.getAll('regional_guidance')) {
|
||||||
|
if (!entities.find((rg) => rg.id === adapter.id)) {
|
||||||
|
manager.destroy(adapter.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
renderRegion(manager, entity, maskOpacity, tool, selectedEntityIdentifier, onPosChanged);
|
renderRegion(manager, entity, maskOpacity, toolState.selected, selectedEntity, onPosChanged);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,19 +5,23 @@ import { $isDebugging } from 'app/store/nanostores/isDebugging';
|
|||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import { setStageEventHandlers } from 'features/controlLayers/konva/events';
|
import { setStageEventHandlers } from 'features/controlLayers/konva/events';
|
||||||
import { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
|
import { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
|
||||||
import { arrangeEntities } from 'features/controlLayers/konva/renderers/arrange';
|
import { getArrangeEntities } from 'features/controlLayers/konva/renderers/arrange';
|
||||||
import { renderBackgroundLayer } from 'features/controlLayers/konva/renderers/background';
|
import { createBackgroundLayer, getRenderBackground } from 'features/controlLayers/konva/renderers/background';
|
||||||
import { updateBboxes } from 'features/controlLayers/konva/renderers/bbox';
|
import { updateBboxes } from 'features/controlLayers/konva/renderers/bbox';
|
||||||
import { renderControlAdapters } from 'features/controlLayers/konva/renderers/controlAdapters';
|
import { getRenderControlAdapters } from 'features/controlLayers/konva/renderers/controlAdapters';
|
||||||
import { renderInpaintMask } from 'features/controlLayers/konva/renderers/inpaintMask';
|
import { getRenderInpaintMask } from 'features/controlLayers/konva/renderers/inpaintMask';
|
||||||
import { renderLayers } from 'features/controlLayers/konva/renderers/layers';
|
import { getRenderLayers } from 'features/controlLayers/konva/renderers/layers';
|
||||||
import {
|
import {
|
||||||
renderBboxPreview,
|
createBboxNodes,
|
||||||
renderDocumentBoundsOverlay,
|
createDocumentOverlay,
|
||||||
scaleToolPreview,
|
createPreviewLayer,
|
||||||
|
createToolPreviewNodes,
|
||||||
|
getRenderBbox,
|
||||||
|
getRenderDocumentOverlay,
|
||||||
|
getRenderToolPreview,
|
||||||
} from 'features/controlLayers/konva/renderers/preview';
|
} from 'features/controlLayers/konva/renderers/preview';
|
||||||
import { renderRegions } from 'features/controlLayers/konva/renderers/regions';
|
import { getRenderRegions } from 'features/controlLayers/konva/renderers/regions';
|
||||||
import { fitDocumentToStage } from 'features/controlLayers/konva/renderers/stage';
|
import { getFitDocumentToStage } from 'features/controlLayers/konva/renderers/stage';
|
||||||
import {
|
import {
|
||||||
$stageAttrs,
|
$stageAttrs,
|
||||||
bboxChanged,
|
bboxChanged,
|
||||||
@ -67,8 +71,8 @@ export const $nodeManager = atom<KonvaNodeManager | null>(null);
|
|||||||
/**
|
/**
|
||||||
* Initializes the canvas renderer. It subscribes to the redux store and listens for changes directly, bypassing the
|
* Initializes the canvas renderer. It subscribes to the redux store and listens for changes directly, bypassing the
|
||||||
* react rendering cycle entirely, improving canvas performance.
|
* react rendering cycle entirely, improving canvas performance.
|
||||||
* @param store The Redux store
|
* @param store The redux store
|
||||||
* @param stage The Konva stage
|
* @param stage The konva stage
|
||||||
* @param container The stage's target container element
|
* @param container The stage's target container element
|
||||||
* @returns A cleanup function
|
* @returns A cleanup function
|
||||||
*/
|
*/
|
||||||
@ -180,7 +184,7 @@ export const initializeRenderer = (
|
|||||||
dispatch(toolBufferChanged(toolBuffer));
|
dispatch(toolBufferChanged(toolBuffer));
|
||||||
};
|
};
|
||||||
|
|
||||||
const _getSelectedEntity = (canvasV2: CanvasV2State): CanvasEntity | null => {
|
const selectSelectedEntity = (canvasV2: CanvasV2State): CanvasEntity | null => {
|
||||||
const identifier = canvasV2.selectedEntityIdentifier;
|
const identifier = canvasV2.selectedEntityIdentifier;
|
||||||
let selectedEntity: CanvasEntity | null = null;
|
let selectedEntity: CanvasEntity | null = null;
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
@ -202,10 +206,14 @@ export const initializeRenderer = (
|
|||||||
return selectedEntity;
|
return selectedEntity;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _getCurrentFill = (canvasV2: CanvasV2State, selectedEntity: CanvasEntity | null) => {
|
const selectCurrentFill = (canvasV2: CanvasV2State, selectedEntity: CanvasEntity | null) => {
|
||||||
let currentFill: RgbaColor = canvasV2.tool.fill;
|
let currentFill: RgbaColor = canvasV2.tool.fill;
|
||||||
if (selectedEntity && selectedEntity.type === 'regional_guidance') {
|
if (selectedEntity) {
|
||||||
currentFill = { ...selectedEntity.fill, a: canvasV2.settings.maskOpacity };
|
if (selectedEntity.type === 'regional_guidance') {
|
||||||
|
currentFill = { ...selectedEntity.fill, a: canvasV2.settings.maskOpacity };
|
||||||
|
} else if (selectedEntity.type === 'inpaint_mask') {
|
||||||
|
currentFill = { ...canvasV2.inpaintMask.fill, a: canvasV2.settings.maskOpacity };
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
currentFill = canvasV2.tool.fill;
|
currentFill = canvasV2.tool.fill;
|
||||||
}
|
}
|
||||||
@ -223,14 +231,20 @@ export const initializeRenderer = (
|
|||||||
|
|
||||||
// Read-only state, derived from redux
|
// Read-only state, derived from redux
|
||||||
let prevCanvasV2 = getState().canvasV2;
|
let prevCanvasV2 = getState().canvasV2;
|
||||||
let prevSelectedEntity: CanvasEntity | null = _getSelectedEntity(prevCanvasV2);
|
let canvasV2 = getState().canvasV2;
|
||||||
let prevCurrentFill: RgbaColor = _getCurrentFill(prevCanvasV2, prevSelectedEntity);
|
let prevSelectedEntity: CanvasEntity | null = selectSelectedEntity(prevCanvasV2);
|
||||||
|
let prevCurrentFill: RgbaColor = selectCurrentFill(prevCanvasV2, prevSelectedEntity);
|
||||||
const getSelectedEntity = () => prevSelectedEntity;
|
const getSelectedEntity = () => prevSelectedEntity;
|
||||||
const getCurrentFill = () => prevCurrentFill;
|
const getCurrentFill = () => prevCurrentFill;
|
||||||
const getBbox = () => getState().canvasV2.bbox;
|
const getBbox = () => canvasV2.bbox;
|
||||||
const getDocument = () => getState().canvasV2.document;
|
const getDocument = () => canvasV2.document;
|
||||||
const getToolState = () => getState().canvasV2.tool;
|
const getToolState = () => canvasV2.tool;
|
||||||
const getSettings = () => getState().canvasV2.settings;
|
const getSettings = () => canvasV2.settings;
|
||||||
|
const getRegionEntityStates = () => canvasV2.regions.entities;
|
||||||
|
const getLayerEntityStates = () => canvasV2.layers.entities;
|
||||||
|
const getControlAdapterEntityStates = () => canvasV2.controlAdapters.entities;
|
||||||
|
const getMaskOpacity = () => canvasV2.settings.maskOpacity;
|
||||||
|
const getInpaintMaskEntityState = () => canvasV2.inpaintMask;
|
||||||
|
|
||||||
// Read-write state, ephemeral interaction state
|
// Read-write state, ephemeral interaction state
|
||||||
let isDrawing = false;
|
let isDrawing = false;
|
||||||
@ -269,10 +283,22 @@ export const initializeRenderer = (
|
|||||||
spaceKey = val;
|
spaceKey = val;
|
||||||
};
|
};
|
||||||
|
|
||||||
const manager = new KonvaNodeManager(stage, getBbox, onBboxTransformed, $shift.get, $ctrl.get, $meta.get, $alt.get);
|
const manager = new KonvaNodeManager(stage);
|
||||||
console.log(manager);
|
|
||||||
$nodeManager.set(manager);
|
$nodeManager.set(manager);
|
||||||
|
|
||||||
|
manager.background = { layer: createBackgroundLayer() };
|
||||||
|
manager.stage.add(manager.background.layer);
|
||||||
|
manager.preview = {
|
||||||
|
layer: createPreviewLayer(),
|
||||||
|
bbox: createBboxNodes(stage, getBbox, onBboxTransformed, $shift.get, $ctrl.get, $meta.get, $alt.get),
|
||||||
|
tool: createToolPreviewNodes(),
|
||||||
|
documentOverlay: createDocumentOverlay(),
|
||||||
|
};
|
||||||
|
manager.preview.layer.add(manager.preview.bbox.group);
|
||||||
|
manager.preview.layer.add(manager.preview.tool.group);
|
||||||
|
manager.preview.layer.add(manager.preview.documentOverlay.group);
|
||||||
|
manager.stage.add(manager.preview.layer);
|
||||||
|
|
||||||
const cleanupListeners = setStageEventHandlers({
|
const cleanupListeners = setStageEventHandlers({
|
||||||
manager,
|
manager,
|
||||||
getToolState,
|
getToolState,
|
||||||
@ -292,7 +318,6 @@ export const initializeRenderer = (
|
|||||||
getSpaceKey,
|
getSpaceKey,
|
||||||
setSpaceKey,
|
setSpaceKey,
|
||||||
setStageAttrs: $stageAttrs.set,
|
setStageAttrs: $stageAttrs.set,
|
||||||
getDocument,
|
|
||||||
getBbox,
|
getBbox,
|
||||||
getSettings,
|
getSettings,
|
||||||
onBrushLineAdded,
|
onBrushLineAdded,
|
||||||
@ -309,16 +334,57 @@ 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);
|
||||||
|
|
||||||
|
manager.renderers = {
|
||||||
|
renderRegions: getRenderRegions({
|
||||||
|
manager,
|
||||||
|
getRegionEntityStates,
|
||||||
|
getMaskOpacity,
|
||||||
|
getToolState,
|
||||||
|
getSelectedEntity,
|
||||||
|
onPosChanged,
|
||||||
|
}),
|
||||||
|
renderLayers: getRenderLayers({ manager, getLayerEntityStates, getToolState, onPosChanged }),
|
||||||
|
renderControlAdapters: getRenderControlAdapters({ manager, getControlAdapterEntityStates }),
|
||||||
|
renderInpaintMask: getRenderInpaintMask({
|
||||||
|
manager,
|
||||||
|
getInpaintMaskEntityState,
|
||||||
|
getMaskOpacity,
|
||||||
|
getToolState,
|
||||||
|
getSelectedEntity,
|
||||||
|
onPosChanged,
|
||||||
|
}),
|
||||||
|
renderBbox: getRenderBbox(manager, getBbox, getToolState),
|
||||||
|
renderToolPreview: getRenderToolPreview({
|
||||||
|
manager,
|
||||||
|
getToolState,
|
||||||
|
getCurrentFill,
|
||||||
|
getSelectedEntity,
|
||||||
|
getLastCursorPos,
|
||||||
|
getLastMouseDownPos,
|
||||||
|
getIsDrawing,
|
||||||
|
getIsMouseDown,
|
||||||
|
}),
|
||||||
|
renderDocumentOverlay: getRenderDocumentOverlay({ manager, getDocument }),
|
||||||
|
renderBackground: getRenderBackground({ manager }),
|
||||||
|
fitDocumentToStage: getFitDocumentToStage({ manager, getDocument, setStageAttrs: $stageAttrs.set }),
|
||||||
|
arrangeEntities: getArrangeEntities({
|
||||||
|
manager,
|
||||||
|
getLayerEntityStates,
|
||||||
|
getControlAdapterEntityStates,
|
||||||
|
getRegionEntityStates,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
const renderCanvas = () => {
|
const renderCanvas = () => {
|
||||||
const { canvasV2 } = store.getState();
|
canvasV2 = store.getState().canvasV2;
|
||||||
|
|
||||||
if (prevCanvasV2 === canvasV2 && !isFirstRender) {
|
if (prevCanvasV2 === canvasV2 && !isFirstRender) {
|
||||||
logIfDebugging('No changes detected, skipping render');
|
logIfDebugging('No changes detected, skipping render');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedEntity = _getSelectedEntity(canvasV2);
|
const selectedEntity = selectSelectedEntity(canvasV2);
|
||||||
const currentFill = _getCurrentFill(canvasV2, selectedEntity);
|
const currentFill = selectCurrentFill(canvasV2, selectedEntity);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isFirstRender ||
|
isFirstRender ||
|
||||||
@ -326,7 +392,7 @@ export const initializeRenderer = (
|
|||||||
canvasV2.tool.selected !== prevCanvasV2.tool.selected
|
canvasV2.tool.selected !== prevCanvasV2.tool.selected
|
||||||
) {
|
) {
|
||||||
logIfDebugging('Rendering layers');
|
logIfDebugging('Rendering layers');
|
||||||
renderLayers(manager, canvasV2.layers.entities, canvasV2.tool.selected, onPosChanged);
|
manager.renderers.renderLayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -336,14 +402,7 @@ export const initializeRenderer = (
|
|||||||
canvasV2.tool.selected !== prevCanvasV2.tool.selected
|
canvasV2.tool.selected !== prevCanvasV2.tool.selected
|
||||||
) {
|
) {
|
||||||
logIfDebugging('Rendering regions');
|
logIfDebugging('Rendering regions');
|
||||||
renderRegions(
|
manager.renderers.renderRegions();
|
||||||
manager,
|
|
||||||
canvasV2.regions.entities,
|
|
||||||
canvasV2.settings.maskOpacity,
|
|
||||||
canvasV2.tool.selected,
|
|
||||||
canvasV2.selectedEntityIdentifier,
|
|
||||||
onPosChanged
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -353,29 +412,22 @@ export const initializeRenderer = (
|
|||||||
canvasV2.tool.selected !== prevCanvasV2.tool.selected
|
canvasV2.tool.selected !== prevCanvasV2.tool.selected
|
||||||
) {
|
) {
|
||||||
logIfDebugging('Rendering inpaint mask');
|
logIfDebugging('Rendering inpaint mask');
|
||||||
renderInpaintMask(
|
manager.renderers.renderInpaintMask();
|
||||||
manager,
|
|
||||||
canvasV2.inpaintMask,
|
|
||||||
canvasV2.settings.maskOpacity,
|
|
||||||
canvasV2.tool.selected,
|
|
||||||
canvasV2.selectedEntityIdentifier,
|
|
||||||
onPosChanged
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFirstRender || canvasV2.controlAdapters.entities !== prevCanvasV2.controlAdapters.entities) {
|
if (isFirstRender || canvasV2.controlAdapters.entities !== prevCanvasV2.controlAdapters.entities) {
|
||||||
logIfDebugging('Rendering control adapters');
|
logIfDebugging('Rendering control adapters');
|
||||||
renderControlAdapters(manager, canvasV2.controlAdapters.entities);
|
manager.renderers.renderControlAdapters();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFirstRender || canvasV2.document !== prevCanvasV2.document) {
|
if (isFirstRender || canvasV2.document !== prevCanvasV2.document) {
|
||||||
logIfDebugging('Rendering document bounds overlay');
|
logIfDebugging('Rendering document bounds overlay');
|
||||||
renderDocumentBoundsOverlay(manager, getDocument);
|
manager.renderers.renderDocumentOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
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(manager, canvasV2.bbox, canvasV2.tool.selected);
|
manager.renderers.renderBbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -395,7 +447,7 @@ export const initializeRenderer = (
|
|||||||
canvasV2.regions.entities !== prevCanvasV2.regions.entities
|
canvasV2.regions.entities !== prevCanvasV2.regions.entities
|
||||||
) {
|
) {
|
||||||
logIfDebugging('Arranging entities');
|
logIfDebugging('Arranging entities');
|
||||||
arrangeEntities(manager, canvasV2.layers.entities, canvasV2.controlAdapters.entities, canvasV2.regions.entities);
|
manager.renderers.arrangeEntities();
|
||||||
}
|
}
|
||||||
|
|
||||||
prevCanvasV2 = canvasV2;
|
prevCanvasV2 = canvasV2;
|
||||||
@ -419,8 +471,8 @@ export const initializeRenderer = (
|
|||||||
height: stage.height(),
|
height: stage.height(),
|
||||||
scale: stage.scaleX(),
|
scale: stage.scaleX(),
|
||||||
});
|
});
|
||||||
renderBackgroundLayer(manager);
|
manager.renderers.renderBackground();
|
||||||
renderDocumentBoundsOverlay(manager, getDocument);
|
manager.renderers.renderDocumentOverlay();
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(fitStageToContainer);
|
const resizeObserver = new ResizeObserver(fitStageToContainer);
|
||||||
@ -431,10 +483,8 @@ export const initializeRenderer = (
|
|||||||
|
|
||||||
logIfDebugging('First render of konva stage');
|
logIfDebugging('First render of konva stage');
|
||||||
// On first render, the document should be fit to the stage.
|
// On first render, the document should be fit to the stage.
|
||||||
const stageAttrs = fitDocumentToStage(stage, prevCanvasV2.document);
|
manager.renderers.fitDocumentToStage();
|
||||||
// The HUD displays some of the stage attributes, so we need to update it here.
|
manager.renderers.renderToolPreview();
|
||||||
$stageAttrs.set(stageAttrs);
|
|
||||||
scaleToolPreview(manager, getToolState());
|
|
||||||
renderCanvas();
|
renderCanvas();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -1,16 +1,32 @@
|
|||||||
import { DOCUMENT_FIT_PADDING_PX } from 'features/controlLayers/konva/constants';
|
import { DOCUMENT_FIT_PADDING_PX } from 'features/controlLayers/konva/constants';
|
||||||
|
import type { KonvaNodeManager } from 'features/controlLayers/konva/nodeManager';
|
||||||
import type { CanvasV2State, StageAttrs } from 'features/controlLayers/store/types';
|
import type { CanvasV2State, StageAttrs } from 'features/controlLayers/store/types';
|
||||||
import type Konva from 'konva';
|
|
||||||
|
|
||||||
export const fitDocumentToStage = (stage: Konva.Stage, document: CanvasV2State['document']): StageAttrs => {
|
/**
|
||||||
// Fit & center the document on the stage
|
* Gets a function to fit the document to the stage, resetting the stage scale to 100%.
|
||||||
const width = stage.width();
|
* If the document is smaller than the stage, the stage scale is increased to fit the document.
|
||||||
const height = stage.height();
|
* @param arg.manager The konva node manager
|
||||||
const docWidthWithBuffer = document.width + DOCUMENT_FIT_PADDING_PX * 2;
|
* @param arg.getDocument A function to get the current document state
|
||||||
const docHeightWithBuffer = document.height + DOCUMENT_FIT_PADDING_PX * 2;
|
* @param arg.setStageAttrs A function to set the stage attributes
|
||||||
const scale = Math.min(Math.min(width / docWidthWithBuffer, height / docHeightWithBuffer), 1);
|
* @returns A function to fit the document to the stage
|
||||||
const x = (width - docWidthWithBuffer * scale) / 2 + DOCUMENT_FIT_PADDING_PX * scale;
|
*/
|
||||||
const y = (height - docHeightWithBuffer * scale) / 2 + DOCUMENT_FIT_PADDING_PX * scale;
|
export const getFitDocumentToStage =
|
||||||
stage.setAttrs({ x, y, width, height, scaleX: scale, scaleY: scale });
|
(arg: {
|
||||||
return { x, y, width, height, scale };
|
manager: KonvaNodeManager;
|
||||||
};
|
getDocument: () => CanvasV2State['document'];
|
||||||
|
setStageAttrs: (stageAttrs: StageAttrs) => void;
|
||||||
|
}) =>
|
||||||
|
(): void => {
|
||||||
|
const { manager, getDocument, setStageAttrs } = arg;
|
||||||
|
const document = getDocument();
|
||||||
|
// Fit & center the document on the stage
|
||||||
|
const width = manager.stage.width();
|
||||||
|
const height = manager.stage.height();
|
||||||
|
const docWidthWithBuffer = document.width + DOCUMENT_FIT_PADDING_PX * 2;
|
||||||
|
const docHeightWithBuffer = document.height + DOCUMENT_FIT_PADDING_PX * 2;
|
||||||
|
const scale = Math.min(Math.min(width / docWidthWithBuffer, height / docHeightWithBuffer), 1);
|
||||||
|
const x = (width - docWidthWithBuffer * scale) / 2 + DOCUMENT_FIT_PADDING_PX * scale;
|
||||||
|
const y = (height - docHeightWithBuffer * scale) / 2 + DOCUMENT_FIT_PADDING_PX * scale;
|
||||||
|
manager.stage.setAttrs({ x, y, width, height, scaleX: scale, scaleY: scale });
|
||||||
|
setStageAttrs({ x, y, width, height, scale });
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user