fix(ui): buffered drawing edge cases

This commit is contained in:
psychedelicious 2024-07-05 10:00:47 +10:00
parent 41c195d936
commit d029680ac1
6 changed files with 211 additions and 250 deletions

View File

@ -1,12 +1,24 @@
import { Box, Flex, Text } from '@invoke-ai/ui-library'; import { Box, Flex, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { $stageAttrs } from 'features/controlLayers/store/canvasV2Slice'; import {
$isDrawing,
$isMouseDown,
$lastAddedPoint,
$lastCursorPos,
$lastMouseDownPos,
$stageAttrs,
} from 'features/controlLayers/store/canvasV2Slice';
import { round } from 'lodash-es'; import { round } from 'lodash-es';
import { memo } from 'react'; import { memo } from 'react';
export const HeadsUpDisplay = memo(() => { export const HeadsUpDisplay = memo(() => {
const stageAttrs = useStore($stageAttrs); const stageAttrs = useStore($stageAttrs);
const cursorPos = useStore($lastCursorPos);
const isDrawing = useStore($isDrawing);
const isMouseDown = useStore($isMouseDown);
const lastMouseDownPos = useStore($lastMouseDownPos);
const lastAddedPoint = useStore($lastAddedPoint);
const bbox = useAppSelector((s) => s.canvasV2.bbox); const bbox = useAppSelector((s) => s.canvasV2.bbox);
const document = useAppSelector((s) => s.canvasV2.document); const document = useAppSelector((s) => s.canvasV2.document);
@ -25,6 +37,20 @@ export const HeadsUpDisplay = memo(() => {
<HUDItem label="BBox Height % 8" value={round(bbox.height % 8, 2)} /> <HUDItem label="BBox Height % 8" value={round(bbox.height % 8, 2)} />
<HUDItem label="BBox X % 8" value={round(bbox.x % 8, 2)} /> <HUDItem label="BBox X % 8" value={round(bbox.x % 8, 2)} />
<HUDItem label="BBox Y % 8" value={round(bbox.y % 8, 2)} /> <HUDItem label="BBox Y % 8" value={round(bbox.y % 8, 2)} />
<HUDItem
label="Cursor Position"
value={cursorPos ? `${round(cursorPos.x, 2)}, ${round(cursorPos.y, 2)}` : '?, ?'}
/>
<HUDItem label="Is Drawing" value={isDrawing ? 'True' : 'False'} />
<HUDItem label="Is Mouse Down" value={isMouseDown ? 'True' : 'False'} />
<HUDItem
label="Last Mouse Down Pos"
value={lastMouseDownPos ? `${round(lastMouseDownPos.x, 2)}, ${round(lastMouseDownPos.y, 2)}` : '?, ?'}
/>
<HUDItem
label="Last Added Point"
value={lastAddedPoint ? `${round(lastAddedPoint.x, 2)}, ${round(lastAddedPoint.y, 2)}` : '?, ?'}
/>
</Flex> </Flex>
); );
}); });

View File

@ -1,25 +1,8 @@
import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import { getLayerBboxId, LAYER_BBOX_NAME } from 'features/controlLayers/konva/naming'; import type { EraserLine } from 'features/controlLayers/store/types';
import type { CanvasEntity, EraserLine } from 'features/controlLayers/store/types';
import { RGBA_RED } from 'features/controlLayers/store/types'; import { RGBA_RED } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
/**
* Creates a bounding box rect for a layer.
* @param entity The layer state for the layer to create the bounding box for
* @param konvaLayer The konva layer to attach the bounding box to
*/
export const createBboxRect = (entity: CanvasEntity, konvaLayer: Konva.Layer): Konva.Rect => {
const rect = new Konva.Rect({
id: getLayerBboxId(entity.id),
name: LAYER_BBOX_NAME,
strokeWidth: 1,
visible: false,
});
konvaLayer.add(rect);
return rect;
};
export class CanvasEraserLine { export class CanvasEraserLine {
id: string; id: string;
konvaLineGroup: Konva.Group; konvaLineGroup: Konva.Group;

View File

@ -22,7 +22,7 @@ export class CanvasInpaintMask {
transformer: Konva.Transformer; transformer: Konva.Transformer;
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>; objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
private drawingBuffer: BrushLine | EraserLine | null; private drawingBuffer: BrushLine | EraserLine | null;
private prevInpaintMaskState: InpaintMaskEntity; private inpaintMaskState: InpaintMaskEntity;
constructor(entity: InpaintMaskEntity, manager: CanvasManager) { constructor(entity: InpaintMaskEntity, manager: CanvasManager) {
this.id = INPAINT_MASK_LAYER_ID; this.id = INPAINT_MASK_LAYER_ID;
@ -60,7 +60,7 @@ export class CanvasInpaintMask {
this.group.add(this.compositingRect); this.group.add(this.compositingRect);
this.objects = new Map(); this.objects = new Map();
this.drawingBuffer = null; this.drawingBuffer = null;
this.prevInpaintMaskState = entity; this.inpaintMaskState = entity;
} }
destroy(): void { destroy(): void {
@ -79,7 +79,7 @@ export class CanvasInpaintMask {
} }
await this.renderObject(this.drawingBuffer, true); await this.renderObject(this.drawingBuffer, true);
this.updateGroup(true, this.prevInpaintMaskState); this.updateGroup(true);
} }
} }
@ -96,6 +96,8 @@ export class CanvasInpaintMask {
} }
async render(inpaintMaskState: InpaintMaskEntity) { async render(inpaintMaskState: InpaintMaskEntity) {
this.inpaintMaskState = inpaintMaskState;
// Update the layer's position and listening state // Update the layer's position and listening state
this.group.setAttrs({ this.group.setAttrs({
x: inpaintMaskState.x, x: inpaintMaskState.x,
@ -117,11 +119,18 @@ export class CanvasInpaintMask {
} }
for (const obj of inpaintMaskState.objects) { for (const obj of inpaintMaskState.objects) {
didDraw = await this.renderObject(obj); if (await this.renderObject(obj)) {
didDraw = true;
}
} }
this.updateGroup(didDraw, inpaintMaskState); if (this.drawingBuffer) {
this.prevInpaintMaskState = inpaintMaskState; if (await this.renderObject(this.drawingBuffer)) {
didDraw = true;
}
}
this.updateGroup(didDraw);
} }
private async renderObject(obj: InpaintMaskEntity['objects'][number], force = false): Promise<boolean> { private async renderObject(obj: InpaintMaskEntity['objects'][number], force = false): Promise<boolean> {
@ -172,18 +181,15 @@ export class CanvasInpaintMask {
return false; return false;
} }
updateGroup(didDraw: boolean, inpaintMaskState: InpaintMaskEntity) { updateGroup(didDraw: boolean) {
// Only update layer visibility if it has changed. this.layer.visible(this.inpaintMaskState.isEnabled);
if (this.layer.visible() !== inpaintMaskState.isEnabled) {
this.layer.visible(inpaintMaskState.isEnabled);
}
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work // The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
this.group.opacity(1); this.group.opacity(1);
if (didDraw) { if (didDraw) {
// 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(inpaintMaskState.fill); const rgbColor = rgbColorToString(this.inpaintMaskState.fill);
const maskOpacity = this.manager.stateApi.getMaskOpacity(); const maskOpacity = this.manager.stateApi.getMaskOpacity();
this.compositingRect.setAttrs({ this.compositingRect.setAttrs({

View File

@ -20,7 +20,7 @@ export class CanvasLayer {
transformer: Konva.Transformer; transformer: Konva.Transformer;
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>; objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
private drawingBuffer: BrushLine | EraserLine | null; private drawingBuffer: BrushLine | EraserLine | null;
private prevLayerState: LayerEntity; private layerState: LayerEntity;
constructor(entity: LayerEntity, manager: CanvasManager) { constructor(entity: LayerEntity, manager: CanvasManager) {
this.id = entity.id; this.id = entity.id;
@ -59,7 +59,7 @@ export class CanvasLayer {
this.objects = new Map(); this.objects = new Map();
this.drawingBuffer = null; this.drawingBuffer = null;
this.prevLayerState = entity; this.layerState = entity;
} }
destroy(): void { destroy(): void {
@ -74,7 +74,7 @@ export class CanvasLayer {
if (obj) { if (obj) {
this.drawingBuffer = obj; this.drawingBuffer = obj;
await this.renderObject(this.drawingBuffer, true); await this.renderObject(this.drawingBuffer, true);
this.updateGroup(true, this.prevLayerState); this.updateGroup(true);
} else { } else {
this.drawingBuffer = null; this.drawingBuffer = null;
} }
@ -93,6 +93,8 @@ export class CanvasLayer {
} }
async render(layerState: LayerEntity) { async render(layerState: LayerEntity) {
this.layerState = layerState;
// Update the layer's position and listening state // Update the layer's position and listening state
this.group.setAttrs({ this.group.setAttrs({
x: layerState.x, x: layerState.x,
@ -114,24 +116,18 @@ export class CanvasLayer {
} }
for (const obj of layerState.objects) { for (const obj of layerState.objects) {
didDraw = await this.renderObject(obj); if (await this.renderObject(obj)) {
didDraw = true;
}
} }
if (this.drawingBuffer) { if (this.drawingBuffer) {
didDraw = await this.renderObject(this.drawingBuffer); if (await this.renderObject(this.drawingBuffer)) {
didDraw = true;
}
} }
// Only update layer visibility if it has changed. this.updateGroup(didDraw);
if (this.layer.visible() !== layerState.isEnabled) {
this.layer.visible(layerState.isEnabled);
}
this.group.opacity(layerState.opacity);
// The layer only listens when using the move tool - otherwise the stage is handling mouse events
this.updateGroup(didDraw, this.prevLayerState);
this.prevLayerState = layerState;
} }
private async renderObject(obj: LayerEntity['objects'][number], force = false): Promise<boolean> { private async renderObject(obj: LayerEntity['objects'][number], force = false): Promise<boolean> {
@ -184,7 +180,7 @@ export class CanvasLayer {
if (!image) { if (!image) {
image = await new CanvasImage(obj, { image = await new CanvasImage(obj, {
onLoad: () => { onLoad: () => {
this.updateGroup(true, this.prevLayerState); this.updateGroup(true);
}, },
}); });
this.objects.set(image.id, image); this.objects.set(image.id, image);
@ -200,7 +196,10 @@ export class CanvasLayer {
return false; return false;
} }
updateGroup(didDraw: boolean, _: LayerEntity) { updateGroup(didDraw: boolean) {
this.layer.visible(this.layerState.isEnabled);
this.group.opacity(this.layerState.opacity);
const isSelected = this.manager.stateApi.getIsSelected(this.id); const isSelected = this.manager.stateApi.getIsSelected(this.id);
const selectedTool = this.manager.stateApi.getToolState().selected; const selectedTool = this.manager.stateApi.getToolState().selected;

View File

@ -22,7 +22,7 @@ export class CanvasRegion {
transformer: Konva.Transformer; transformer: Konva.Transformer;
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>; objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
private drawingBuffer: BrushLine | EraserLine | null; private drawingBuffer: BrushLine | EraserLine | null;
private prevRegionState: RegionEntity; private regionState: RegionEntity;
constructor(entity: RegionEntity, manager: CanvasManager) { constructor(entity: RegionEntity, manager: CanvasManager) {
this.id = entity.id; this.id = entity.id;
@ -60,7 +60,7 @@ export class CanvasRegion {
this.group.add(this.compositingRect); this.group.add(this.compositingRect);
this.objects = new Map(); this.objects = new Map();
this.drawingBuffer = null; this.drawingBuffer = null;
this.prevRegionState = entity; this.regionState = entity;
} }
destroy(): void { destroy(): void {
@ -78,7 +78,7 @@ export class CanvasRegion {
this.drawingBuffer.color = RGBA_RED; this.drawingBuffer.color = RGBA_RED;
} }
await this.renderObject(this.drawingBuffer, true); await this.renderObject(this.drawingBuffer, true);
this.updateGroup(true, this.prevRegionState); this.updateGroup(true);
} }
} }
@ -95,6 +95,8 @@ export class CanvasRegion {
} }
async render(regionState: RegionEntity) { async render(regionState: RegionEntity) {
this.regionState = regionState;
// Update the layer's position and listening state // Update the layer's position and listening state
this.group.setAttrs({ this.group.setAttrs({
x: regionState.x, x: regionState.x,
@ -116,11 +118,18 @@ export class CanvasRegion {
} }
for (const obj of regionState.objects) { for (const obj of regionState.objects) {
didDraw = await this.renderObject(obj); if (await this.renderObject(obj)) {
didDraw = true;
}
} }
this.updateGroup(didDraw, regionState); if (this.drawingBuffer) {
this.prevRegionState = regionState; if (await this.renderObject(this.drawingBuffer)) {
didDraw = true;
}
}
this.updateGroup(didDraw);
} }
private async renderObject(obj: RegionEntity['objects'][number], force = false): Promise<boolean> { private async renderObject(obj: RegionEntity['objects'][number], force = false): Promise<boolean> {
@ -171,18 +180,15 @@ export class CanvasRegion {
return false; return false;
} }
updateGroup(didDraw: boolean, regionState: RegionEntity) { updateGroup(didDraw: boolean) {
// Only update layer visibility if it has changed. this.layer.visible(this.regionState.isEnabled);
if (this.layer.visible() !== regionState.isEnabled) {
this.layer.visible(regionState.isEnabled);
}
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work // The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
this.group.opacity(1); this.group.opacity(1);
if (didDraw) { if (didDraw) {
// 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(regionState.fill); const rgbColor = rgbColorToString(this.regionState.fill);
const maskOpacity = this.manager.stateApi.getMaskOpacity(); const maskOpacity = this.manager.stateApi.getMaskOpacity();
this.compositingRect.setAttrs({ this.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 // The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already

View File

@ -1,14 +1,22 @@
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getScaledCursorPosition } from 'features/controlLayers/konva/util'; import { getScaledCursorPosition } from 'features/controlLayers/konva/util';
import type { CanvasEntity, CanvasV2State, Position } from 'features/controlLayers/store/types'; import type {
CanvasEntity,
CanvasV2State,
InpaintMaskEntity,
LayerEntity,
Position,
RegionEntity,
} from 'features/controlLayers/store/types';
import { isDrawableEntity, isDrawableEntityAdapter } from 'features/controlLayers/store/types'; import { isDrawableEntity, isDrawableEntityAdapter } from 'features/controlLayers/store/types';
import type Konva from 'konva'; import type Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node';
import type { Vector2d } from 'konva/lib/types'; import type { Vector2d } from 'konva/lib/types';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { BRUSH_SPACING_TARGET_SCALE, CANVAS_SCALE_BY, MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from './constants'; import { BRUSH_SPACING_TARGET_SCALE, CANVAS_SCALE_BY, MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from './constants';
import { getBrushLineId } from './naming'; import { getBrushLineId, getEraserLineId } from './naming';
/** /**
* Updates the last cursor position atom with the current cursor position, returning the new position or `null` if the * Updates the last cursor position atom with the current cursor position, returning the new position or `null` if the
@ -109,9 +117,6 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
getCurrentFill, getCurrentFill,
setTool, setTool,
setToolBuffer, setToolBuffer,
getIsDrawing,
setIsDrawing,
getIsMouseDown,
setIsMouseDown, setIsMouseDown,
getLastMouseDownPos, getLastMouseDownPos,
setLastMouseDownPos, setLastMouseDownPos,
@ -125,14 +130,36 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
setSpaceKey, setSpaceKey,
getBbox, getBbox,
getSettings, getSettings,
onBrushLineAdded,
onEraserLineAdded,
onPointAddedToLine,
onRectShapeAdded, onRectShapeAdded,
onBrushWidthChanged, onBrushWidthChanged,
onEraserWidthChanged, onEraserWidthChanged,
} = stateApi; } = stateApi;
function getIsPrimaryMouseDown(e: KonvaEventObject<MouseEvent>) {
return e.evt.buttons === 1;
}
function getClip(entity: RegionEntity | LayerEntity | InpaintMaskEntity) {
const settings = getSettings();
const bbox = getBbox();
if (settings.clipToBbox) {
return {
x: bbox.x - entity.x,
y: bbox.y - entity.y,
width: bbox.width,
height: bbox.height,
};
} else {
return {
x: -stage.x() / stage.scaleX() - entity.x,
y: -stage.y() / stage.scaleY() - entity.y,
width: stage.width() / stage.scaleX(),
height: stage.height() / stage.scaleY(),
};
}
}
//#region mouseenter //#region mouseenter
stage.on('mouseenter', () => { stage.on('mouseenter', () => {
manager.preview.tool.render(); manager.preview.tool.render();
@ -152,43 +179,32 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
isDrawableEntity(selectedEntity) && isDrawableEntity(selectedEntity) &&
selectedEntityAdapter && selectedEntityAdapter &&
isDrawableEntityAdapter(selectedEntityAdapter) && isDrawableEntityAdapter(selectedEntityAdapter) &&
!getSpaceKey() !getSpaceKey() &&
getIsPrimaryMouseDown(e)
) { ) {
setIsDrawing(true);
setLastMouseDownPos(pos); setLastMouseDownPos(pos);
if (toolState.selected === 'brush') { if (toolState.selected === 'brush') {
const bbox = getBbox();
const settings = getSettings();
const clip = settings.clipToBbox
? {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
}
: null;
if (e.evt.shiftKey) { if (e.evt.shiftKey) {
const lastAddedPoint = getLastAddedPoint(); const lastAddedPoint = getLastAddedPoint();
// Create a straight line if holding shift // Create a straight line if holding shift
if (lastAddedPoint) { if (lastAddedPoint) {
onBrushLineAdded( if (selectedEntityAdapter.getDrawingBuffer()) {
{ selectedEntityAdapter.finalizeDrawingBuffer();
id: selectedEntity.id, }
points: [ await selectedEntityAdapter.setDrawingBuffer({
lastAddedPoint.x - selectedEntity.x, id: getBrushLineId(selectedEntityAdapter.id, uuidv4()),
lastAddedPoint.y - selectedEntity.y, type: 'brush_line',
pos.x - selectedEntity.x, points: [
pos.y - selectedEntity.y, lastAddedPoint.x - selectedEntity.x,
], lastAddedPoint.y - selectedEntity.y,
color: getCurrentFill(), pos.x - selectedEntity.x,
width: toolState.brush.width, pos.y - selectedEntity.y,
clip, ],
}, strokeWidth: toolState.brush.width,
selectedEntity.type color: getCurrentFill(),
); clip: getClip(selectedEntity),
});
} }
} else { } else {
if (selectedEntityAdapter.getDrawingBuffer()) { if (selectedEntityAdapter.getDrawingBuffer()) {
@ -205,64 +221,39 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
], ],
strokeWidth: toolState.brush.width, strokeWidth: toolState.brush.width,
color: getCurrentFill(), color: getCurrentFill(),
clip, clip: getClip(selectedEntity),
}); });
// onBrushLineAdded(
// {
// id: selectedEntity.id,
// points: [
// pos.x - selectedEntity.x,
// pos.y - selectedEntity.y,
// pos.x - selectedEntity.x,
// pos.y - selectedEntity.y,
// ],
// color: getCurrentFill(),
// width: toolState.brush.width,
// clip,
// },
// selectedEntity.type
// );
} }
setLastAddedPoint(pos); setLastAddedPoint(pos);
} }
if (toolState.selected === 'eraser') { if (toolState.selected === 'eraser') {
const bbox = getBbox();
const settings = getSettings();
const clip = settings.clipToBbox
? {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
}
: null;
if (e.evt.shiftKey) { if (e.evt.shiftKey) {
// Create a straight line if holding shift // Create a straight line if holding shift
const lastAddedPoint = getLastAddedPoint(); const lastAddedPoint = getLastAddedPoint();
if (lastAddedPoint) { if (lastAddedPoint) {
onEraserLineAdded( if (selectedEntityAdapter.getDrawingBuffer()) {
{ selectedEntityAdapter.finalizeDrawingBuffer();
id: selectedEntity.id, }
points: [ await selectedEntityAdapter.setDrawingBuffer({
lastAddedPoint.x - selectedEntity.x, id: getBrushLineId(selectedEntityAdapter.id, uuidv4()),
lastAddedPoint.y - selectedEntity.y, type: 'eraser_line',
pos.x - selectedEntity.x, points: [
pos.y - selectedEntity.y, lastAddedPoint.x - selectedEntity.x,
], lastAddedPoint.y - selectedEntity.y,
width: toolState.eraser.width, pos.x - selectedEntity.x,
clip, pos.y - selectedEntity.y,
}, ],
selectedEntity.type strokeWidth: toolState.eraser.width,
); clip: getClip(selectedEntity),
});
} }
} else { } else {
if (selectedEntityAdapter.getDrawingBuffer()) { if (selectedEntityAdapter.getDrawingBuffer()) {
selectedEntityAdapter.finalizeDrawingBuffer(); selectedEntityAdapter.finalizeDrawingBuffer();
} }
await selectedEntityAdapter.setDrawingBuffer({ await selectedEntityAdapter.setDrawingBuffer({
id: getBrushLineId(selectedEntityAdapter.id, uuidv4()), id: getEraserLineId(selectedEntityAdapter.id, uuidv4()),
type: 'eraser_line', type: 'eraser_line',
points: [ points: [
pos.x - selectedEntity.x, pos.x - selectedEntity.x,
@ -271,23 +262,8 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
pos.y - selectedEntity.y, pos.y - selectedEntity.y,
], ],
strokeWidth: toolState.eraser.width, strokeWidth: toolState.eraser.width,
clip, clip: getClip(selectedEntity),
}); });
// onEraserLineAdded(
// {
// id: selectedEntity.id,
// points: [
// pos.x - selectedEntity.x,
// pos.y - selectedEntity.y,
// pos.x - selectedEntity.x,
// pos.y - selectedEntity.y,
// ],
// width: toolState.eraser.width,
// clip,
// },
// selectedEntity.type
// );
} }
setLastAddedPoint(pos); setLastAddedPoint(pos);
} }
@ -296,7 +272,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
}); });
//#region mouseup //#region mouseup
stage.on('mouseup', async () => { stage.on('mouseup', async (e) => {
setIsMouseDown(false); setIsMouseDown(false);
const pos = getLastCursorPos(); const pos = getLastCursorPos();
const selectedEntity = getSelectedEntity(); const selectedEntity = getSelectedEntity();
@ -349,7 +325,6 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
} }
} }
setIsDrawing(false);
setLastMouseDownPos(null); setLastMouseDownPos(null);
} }
@ -357,7 +332,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
}); });
//#region mousemove //#region mousemove
stage.on('mousemove', async () => { stage.on('mousemove', async (e) => {
const toolState = getToolState(); const toolState = getToolState();
const pos = updateLastCursorPos(stage, setLastCursorPos); const pos = updateLastCursorPos(stage, setLastCursorPos);
const selectedEntity = getSelectedEntity(); const selectedEntity = getSelectedEntity();
@ -370,11 +345,11 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
selectedEntityAdapter && selectedEntityAdapter &&
isDrawableEntityAdapter(selectedEntityAdapter) && isDrawableEntityAdapter(selectedEntityAdapter) &&
!getSpaceKey() && !getSpaceKey() &&
getIsMouseDown() getIsPrimaryMouseDown(e)
) { ) {
if (toolState.selected === 'brush') { if (toolState.selected === 'brush') {
if (getIsDrawing()) { const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer(); if (drawingBuffer) {
if (drawingBuffer?.type === 'brush_line') { if (drawingBuffer?.type === 'brush_line') {
const lastAddedPoint = getLastAddedPoint(); const lastAddedPoint = getLastAddedPoint();
const nextPoint = getNextPoint(pos, toolState, lastAddedPoint); const nextPoint = getNextPoint(pos, toolState, lastAddedPoint);
@ -386,52 +361,31 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
} else { } else {
await selectedEntityAdapter.setDrawingBuffer(null); await selectedEntityAdapter.setDrawingBuffer(null);
} }
// Continue the last line
// maybeAddNextPoint(
// selectedEntity,
// pos,
// getToolState,
// getLastAddedPoint,
// setLastAddedPoint,
// onPointAddedToLine
// );
} else { } else {
const bbox = getBbox(); if (selectedEntityAdapter.getDrawingBuffer()) {
const settings = getSettings(); selectedEntityAdapter.finalizeDrawingBuffer();
}
const clip = settings.clipToBbox await selectedEntityAdapter.setDrawingBuffer({
? { id: getBrushLineId(selectedEntityAdapter.id, uuidv4()),
x: bbox.x, type: 'brush_line',
y: bbox.y, points: [
width: bbox.width, pos.x - selectedEntity.x,
height: bbox.height, pos.y - selectedEntity.y,
} pos.x - selectedEntity.x,
: null; pos.y - selectedEntity.y,
// Start a new line ],
onBrushLineAdded( strokeWidth: toolState.brush.width,
{ color: getCurrentFill(),
id: selectedEntity.id, clip: getClip(selectedEntity),
points: [ });
pos.x - selectedEntity.x,
pos.y - selectedEntity.y,
pos.x - selectedEntity.x,
pos.y - selectedEntity.y,
],
width: toolState.brush.width,
color: getCurrentFill(),
clip,
},
selectedEntity.type
);
setLastAddedPoint(pos); setLastAddedPoint(pos);
setIsDrawing(true);
} }
} }
if (toolState.selected === 'eraser') { if (toolState.selected === 'eraser') {
if (getIsDrawing()) { const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer(); if (drawingBuffer) {
if (drawingBuffer?.type === 'eraser_line') { if (drawingBuffer.type === 'eraser_line') {
const lastAddedPoint = getLastAddedPoint(); const lastAddedPoint = getLastAddedPoint();
const nextPoint = getNextPoint(pos, toolState, lastAddedPoint); const nextPoint = getNextPoint(pos, toolState, lastAddedPoint);
if (nextPoint) { if (nextPoint) {
@ -442,45 +396,23 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
} else { } else {
await selectedEntityAdapter.setDrawingBuffer(null); await selectedEntityAdapter.setDrawingBuffer(null);
} }
// Continue the last line
// maybeAddNextPoint(
// selectedEntity,
// pos,
// getToolState,
// getLastAddedPoint,
// setLastAddedPoint,
// onPointAddedToLine
// );
} else { } else {
const bbox = getBbox(); if (selectedEntityAdapter.getDrawingBuffer()) {
const settings = getSettings(); selectedEntityAdapter.finalizeDrawingBuffer();
}
const clip = settings.clipToBbox await selectedEntityAdapter.setDrawingBuffer({
? { id: getEraserLineId(selectedEntityAdapter.id, uuidv4()),
x: bbox.x, type: 'eraser_line',
y: bbox.y, points: [
width: bbox.width, pos.x - selectedEntity.x,
height: bbox.height, pos.y - selectedEntity.y,
} pos.x - selectedEntity.x,
: null; pos.y - selectedEntity.y,
// Start a new line ],
onEraserLineAdded( strokeWidth: toolState.eraser.width,
{ clip: getClip(selectedEntity),
id: selectedEntity.id, });
points: [
pos.x - selectedEntity.x,
pos.y - selectedEntity.y,
pos.x - selectedEntity.x,
pos.y - selectedEntity.y,
],
width: toolState.eraser.width,
clip,
},
selectedEntity.type
);
setLastAddedPoint(pos); setLastAddedPoint(pos);
setIsDrawing(true);
} }
} }
} }
@ -488,22 +420,32 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
}); });
//#region mouseleave //#region mouseleave
stage.on('mouseleave', () => { stage.on('mouseleave', async (e) => {
const pos = updateLastCursorPos(stage, setLastCursorPos); const pos = updateLastCursorPos(stage, setLastCursorPos);
setIsDrawing(false);
setLastCursorPos(null); setLastCursorPos(null);
setLastMouseDownPos(null); setLastMouseDownPos(null);
const selectedEntity = getSelectedEntity(); const selectedEntity = getSelectedEntity();
const selectedEntityAdapter = getSelectedEntityAdapter();
const toolState = getToolState(); const toolState = getToolState();
if (pos && selectedEntity && isDrawableEntity(selectedEntity) && !getSpaceKey() && getIsMouseDown()) { if (
if (getIsMouseDown()) { pos &&
if (toolState.selected === 'brush') { selectedEntity &&
onPointAddedToLine({ id: selectedEntity.id, point: [pos.x, pos.y] }, selectedEntity.type); isDrawableEntity(selectedEntity) &&
} selectedEntityAdapter &&
if (toolState.selected === 'eraser') { isDrawableEntityAdapter(selectedEntityAdapter) &&
onPointAddedToLine({ id: selectedEntity.id, point: [pos.x, pos.y] }, selectedEntity.type); !getSpaceKey() &&
} getIsPrimaryMouseDown(e)
) {
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
if (toolState.selected === 'brush' && drawingBuffer?.type === 'brush_line') {
drawingBuffer.points.push(pos.x - selectedEntity.x, pos.y - selectedEntity.y);
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
selectedEntityAdapter.finalizeDrawingBuffer();
} else if (toolState.selected === 'eraser' && drawingBuffer?.type === 'eraser_line') {
drawingBuffer.points.push(pos.x - selectedEntity.x, pos.y - selectedEntity.y);
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
selectedEntityAdapter.finalizeDrawingBuffer();
} }
} }
@ -592,7 +534,6 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
} }
if (e.key === 'Escape') { if (e.key === 'Escape') {
// Cancel shape drawing on escape // Cancel shape drawing on escape
setIsDrawing(false);
setLastMouseDownPos(null); setLastMouseDownPos(null);
} else if (e.key === ' ') { } else if (e.key === ' ') {
// Select the view tool on space key down // Select the view tool on space key down