740 lines
20 KiB
TypeScript
740 lines
20 KiB
TypeScript
import type { HousePlannerCanvas } from './canvas';
|
|
import { HousePlannerCanvasHistory } from './history';
|
|
import {
|
|
BezierSegment,
|
|
History,
|
|
Layer,
|
|
LayerObject,
|
|
Line,
|
|
LineSegment,
|
|
ToolEvent,
|
|
Vec2,
|
|
} from './interfaces';
|
|
import { ToolType, SubToolType, BezierControl, LineControl } from './types';
|
|
import {
|
|
vec2Add,
|
|
vec2Equals,
|
|
vec2InCircle,
|
|
vec2Inverse,
|
|
vec2Snap,
|
|
vec2Sub,
|
|
} from './utils';
|
|
|
|
export class HousePlannerCanvasTools {
|
|
public selectedLayer?: Layer;
|
|
public selectedObjects: LayerObject[] = [];
|
|
public mousePosition: Vec2 = [0, 0];
|
|
public mousePositionSnapped: Vec2 = [0, 0];
|
|
public gridSnap = true;
|
|
public gridSnapScale = 8;
|
|
public tool: ToolType = 'line';
|
|
public subTool: SubToolType = 'line';
|
|
public history = new HousePlannerCanvasHistory();
|
|
public lastStrokeWidth = 16;
|
|
public lastColor = '#000';
|
|
private dragging = false;
|
|
private pinching = false;
|
|
private lastPinchLength = 0;
|
|
private moved = false;
|
|
private holdShift = false;
|
|
private selectError = 16;
|
|
private bezierControls: BezierSegment[] = [];
|
|
private lineControls: LineSegment[] = [];
|
|
private handlingBezier: BezierControl | null = null;
|
|
private handlingLine: LineControl | null = null;
|
|
private drawingLine: Line | null = null;
|
|
private clickedOn: LayerObject | null = null;
|
|
private movingObject = false;
|
|
|
|
constructor(public manager: HousePlannerCanvas) {}
|
|
|
|
get canvas() {
|
|
return this.manager.canvas;
|
|
}
|
|
|
|
get ctx() {
|
|
return this.manager.ctx;
|
|
}
|
|
|
|
isSelected(object: LayerObject) {
|
|
return this.selectedObjects.indexOf(object) > -1;
|
|
}
|
|
|
|
selectLayer(layer: Layer) {
|
|
if (this.selectedLayer) {
|
|
this.selectedLayer.active = false;
|
|
for (const item of this.selectedLayer.contents) {
|
|
item.selected = false;
|
|
}
|
|
this.selectedObjects.length = 0;
|
|
}
|
|
this.selectedLayer = layer;
|
|
this.selectedLayer.active = true;
|
|
}
|
|
|
|
selectObject(object?: LayerObject | null, add = false) {
|
|
if (!object) {
|
|
if (!add) {
|
|
this.selectedObjects.forEach((object) => {
|
|
object.selected = false;
|
|
});
|
|
this.selectedObjects.length = 0;
|
|
}
|
|
this.manager.draw();
|
|
return;
|
|
}
|
|
|
|
if (this.selectedObjects.includes(object)) {
|
|
// Unselect in multi-select
|
|
if (add) {
|
|
const foundAt = this.selectedObjects.indexOf(object);
|
|
object.selected = false;
|
|
this.selectedObjects.splice(foundAt, 1);
|
|
this.manager.draw();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Select in multi-select
|
|
if (add) {
|
|
object.selected = true;
|
|
this.selectedObjects.push(object);
|
|
this.manager.draw();
|
|
return;
|
|
}
|
|
|
|
this.selectedObjects.forEach((obj) => {
|
|
obj.selected = false;
|
|
});
|
|
|
|
object.selected = true;
|
|
|
|
this.selectedObjects = [object];
|
|
this.manager.draw();
|
|
}
|
|
|
|
getMousedObject() {
|
|
if (!this.selectedLayer?.visible) return null;
|
|
const [x, y] = this.mousePosition;
|
|
for (const object of this.selectedLayer.contents) {
|
|
if ((object as Line).segments) {
|
|
const moused = this.manager.isOnLine(
|
|
object as Line,
|
|
x,
|
|
y,
|
|
this.selectError
|
|
);
|
|
if (moused) return object;
|
|
}
|
|
// TODO: other kinds of objects
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getMousedLineSegment(line: Line) {
|
|
if (!this.selectedLayer?.visible) return null;
|
|
const [x, y] = this.mousePosition;
|
|
let lastSegment = null;
|
|
for (const segment of line.segments) {
|
|
const lastPoint = lastSegment ? lastSegment.end : (segment.start as Vec2);
|
|
if (
|
|
this.manager.isOnSegment(
|
|
line,
|
|
segment,
|
|
lastPoint,
|
|
x,
|
|
y,
|
|
this.selectError
|
|
)
|
|
) {
|
|
return segment;
|
|
}
|
|
lastSegment = segment;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
drawHighlights() {
|
|
for (const object of this.selectedObjects) {
|
|
const line = object as Line;
|
|
if (line.segments) {
|
|
const path = this.manager.makeLinePath(line);
|
|
this.manager.setupLine(line, this.selectError, '#00ddff55');
|
|
this.ctx.stroke(path);
|
|
}
|
|
// TODO: other kinds of objects
|
|
}
|
|
}
|
|
|
|
drawBezierControls(bezier: BezierSegment, previousEnd: Vec2) {
|
|
this.bezierControls.push(bezier);
|
|
const [cp1x, cp1y] = bezier.startControl;
|
|
const [cp2x, cp2y] = bezier.endControl;
|
|
const [endx, endy] = bezier.end;
|
|
const [prevx, prevy] = previousEnd;
|
|
this.ctx.fillStyle = '#00ddffaa';
|
|
this.ctx.strokeStyle = '#00ddffaa';
|
|
this.ctx.lineWidth = 2;
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(cp1x, cp1y, this.selectError / 2, 0, 2 * Math.PI);
|
|
this.ctx.fill();
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(cp2x, cp2y, this.selectError / 2, 0, 2 * Math.PI);
|
|
this.ctx.fill();
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(cp1x, cp1y);
|
|
this.ctx.lineTo(prevx, prevy);
|
|
this.ctx.stroke();
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(cp2x, cp2y);
|
|
this.ctx.lineTo(endx, endy);
|
|
this.ctx.stroke();
|
|
}
|
|
|
|
drawLineControls(line: LineSegment, previousEnd: Vec2) {
|
|
this.lineControls.push(line);
|
|
const [endx, endy] = line.end;
|
|
this.ctx.fillStyle = '#00ddffaa';
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(endx, endy, this.selectError / 2, 0, 2 * Math.PI);
|
|
this.ctx.fill();
|
|
|
|
if (line.start) {
|
|
const [startx, starty] = line.start;
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(startx, starty, this.selectError / 2, 0, 2 * Math.PI);
|
|
this.ctx.fill();
|
|
}
|
|
}
|
|
|
|
drawControls() {
|
|
this.bezierControls.length = 0;
|
|
this.lineControls.length = 0;
|
|
for (const object of this.selectedObjects) {
|
|
const line = object as Line;
|
|
if (line.segments && line.render) {
|
|
let lastSegment = null;
|
|
for (const segment of line.segments) {
|
|
const bezier = segment as BezierSegment;
|
|
const previousPoint = lastSegment ? lastSegment.end : segment.start!;
|
|
if (bezier.startControl && bezier.endControl) {
|
|
this.drawBezierControls(bezier, previousPoint);
|
|
}
|
|
this.drawLineControls(segment, previousPoint);
|
|
lastSegment = segment;
|
|
}
|
|
}
|
|
// TODO: other kinds of objects
|
|
}
|
|
|
|
if (this.tool === 'line') {
|
|
if (this.selectedObjects.length) return;
|
|
const [mx, my] = this.mousePositionSnapped;
|
|
this.ctx.fillStyle = '#00ddffaa';
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(mx, my, this.selectError / 2, 0, 2 * Math.PI);
|
|
this.ctx.fill();
|
|
}
|
|
}
|
|
|
|
cleanUp() {}
|
|
|
|
setTool(tool: typeof this.tool, subTool?: typeof this.subTool) {
|
|
this.tool = tool;
|
|
if (subTool !== undefined) this.subTool = subTool;
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent<ToolEvent>('hpc:tool', {
|
|
detail: { primary: this.tool, secondary: this.subTool },
|
|
})
|
|
);
|
|
}
|
|
|
|
onMouseMove(e: MouseEvent) {
|
|
this.dragEvent(e.clientX, e.clientY);
|
|
}
|
|
onMouseDown(e: MouseEvent) {
|
|
this.mousePosition = [e.clientX, e.clientY];
|
|
this.mousePositionSnapped = this.gridSnap
|
|
? vec2Snap(this.mousePosition, this.gridSnapScale)
|
|
: this.mousePosition;
|
|
this.dragging = true;
|
|
this.moved = false;
|
|
|
|
this.pointerDown();
|
|
}
|
|
onMouseUp(e: MouseEvent) {
|
|
this.dragging = false;
|
|
this.pointerUp();
|
|
}
|
|
|
|
onTouchMove(ev: TouchEvent) {
|
|
ev.preventDefault();
|
|
|
|
if (ev.touches.length === 2 && this.pinching) {
|
|
const pinchLength = Math.hypot(
|
|
ev.touches[0].pageX - ev.touches[1].pageX,
|
|
ev.touches[0].pageY - ev.touches[1].pageY
|
|
);
|
|
|
|
// TODO: zoom
|
|
// if (this.lastPinchLength) {
|
|
// const delta = pinchLength / this.lastPinchLength;
|
|
// const scaleX = (ev.touches[0].clientX - this._posx) / this._zoom;
|
|
// const scaleY = (ev.touches[0].clientY - this._posy) / this._zoom;
|
|
|
|
// delta > 0 ? (this._zoom *= delta) : (this._zoom /= delta);
|
|
// this._zoom = clamp(this._zoom, 1, 100);
|
|
|
|
// this._posx = ev.touches[0].clientX - scaleX * this._zoom;
|
|
// this._posy = ev.touches[0].clientY - scaleY * this._zoom;
|
|
// }
|
|
// this.lastPinchLength = pinchLength;
|
|
}
|
|
|
|
this.dragEvent(ev.touches[0].clientX, ev.touches[0].clientY);
|
|
}
|
|
|
|
onTouchStart(e: TouchEvent) {
|
|
e.preventDefault();
|
|
const touch = e.touches[0] || e.changedTouches[0];
|
|
this.mousePosition = [touch.pageX, touch.pageY];
|
|
this.mousePositionSnapped = this.gridSnap
|
|
? vec2Snap(this.mousePosition, this.gridSnapScale)
|
|
: this.mousePosition;
|
|
this.dragging = true;
|
|
this.moved = false;
|
|
|
|
if (e.touches.length === 2) {
|
|
this.pinching = true;
|
|
}
|
|
|
|
this.pointerDown();
|
|
}
|
|
onTouchEnd(e: TouchEvent) {
|
|
this.pinching = false;
|
|
this.lastPinchLength = 0;
|
|
|
|
if (!e.touches?.length) {
|
|
this.dragging = false;
|
|
}
|
|
|
|
this.pointerUp();
|
|
}
|
|
|
|
onMouseWheel(e: WheelEvent) {
|
|
// TODO: zoom
|
|
// ev.preventDefault();
|
|
// this._mousex = ev.clientX;
|
|
// this._mousey = ev.clientY;
|
|
// const scaleX = (ev.clientX - this._posx) / this._zoom;
|
|
// const scaleY = (ev.clientY - this._posy) / this._zoom;
|
|
// ev.deltaY < 0 ? (this._zoom *= 1.2) : (this._zoom /= 1.2);
|
|
// this._zoom = clamp(this._zoom, this._minZoom, this._maxZoom);
|
|
// this._posx = ev.clientX - scaleX * this._zoom;
|
|
// this._posy = ev.clientY - scaleY * this._zoom;
|
|
// const realSize = this._zoom * this._size;
|
|
// this._posx = clamp(this._posx, this._cursorx - realSize, this._cursorx);
|
|
// this._posy = clamp(this._posy, this._cursory - realSize, this._cursory);
|
|
}
|
|
onPointerLeave() {
|
|
this.dragging = false;
|
|
}
|
|
|
|
onKeyDown(e: KeyboardEvent) {
|
|
if (e.key === 'z' && e.ctrlKey) {
|
|
this.history.undo();
|
|
this.manager.draw();
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:undo', {
|
|
detail: 'keyboard',
|
|
})
|
|
);
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:update', {
|
|
detail: {
|
|
event: 'undo',
|
|
},
|
|
})
|
|
);
|
|
}
|
|
|
|
if (e.key === 'y' && e.ctrlKey) {
|
|
this.history.redo();
|
|
this.manager.draw();
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:redo', {
|
|
detail: 'keyboard',
|
|
})
|
|
);
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:update', {
|
|
detail: { event: 'redo' },
|
|
})
|
|
);
|
|
}
|
|
|
|
if (e.key === 'Shift') {
|
|
this.holdShift = true;
|
|
}
|
|
}
|
|
|
|
onKeyUp(e: KeyboardEvent) {
|
|
if (e.key === 'Enter') {
|
|
if (this.drawingLine && this.selectedLayer) {
|
|
if (this.subTool === 'room') {
|
|
this.drawingLine.closed = true;
|
|
}
|
|
this.drawingLine.segments.splice(
|
|
this.drawingLine.segments.length - 1,
|
|
1
|
|
);
|
|
this.history.appendToHistory([
|
|
{
|
|
object: this.selectedLayer,
|
|
property: 'contents',
|
|
value: [...this.selectedLayer.contents].filter(
|
|
(item) => item !== this.drawingLine
|
|
),
|
|
} as History<typeof this.selectedLayer>,
|
|
{
|
|
object: this,
|
|
property: 'selectedObjects',
|
|
value: [],
|
|
},
|
|
]);
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:newobject', {
|
|
detail: this.drawingLine,
|
|
})
|
|
);
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:update', {
|
|
detail: { event: 'newobject', object: this.drawingLine },
|
|
})
|
|
);
|
|
this.drawingLine = null;
|
|
this.manager.draw();
|
|
}
|
|
}
|
|
|
|
if (e.key === 'Escape') {
|
|
if (this.selectedObjects.length === 0 && this.tool && !this.drawingLine) {
|
|
this.setTool(null);
|
|
}
|
|
|
|
this.selectObject(null);
|
|
|
|
if (this.drawingLine && this.selectedLayer) {
|
|
const indexOf = this.selectedLayer.contents.indexOf(this.drawingLine);
|
|
if (indexOf > -1) {
|
|
this.selectedLayer.contents.splice(indexOf, 1);
|
|
}
|
|
this.drawingLine = null;
|
|
this.manager.draw();
|
|
}
|
|
}
|
|
|
|
if (e.key === 'Shift') {
|
|
this.holdShift = false;
|
|
}
|
|
|
|
if (e.key === 'l') {
|
|
this.setTool('line');
|
|
}
|
|
|
|
if (e.key === 'Delete' || e.key === 'x') {
|
|
this.deleteSelection();
|
|
}
|
|
}
|
|
|
|
deleteSelection() {
|
|
if (!this.selectedObjects.length || !this.selectedLayer) return;
|
|
this.history.appendToHistory([
|
|
{
|
|
object: this.selectedLayer,
|
|
property: 'contents',
|
|
value: [...this.selectedLayer.contents],
|
|
} as History<typeof this.selectedLayer>,
|
|
]);
|
|
|
|
this.selectedLayer.contents = this.selectedLayer.contents.filter(
|
|
(item) => this.selectedObjects.indexOf(item) === -1
|
|
);
|
|
|
|
this.selectedObjects.length = 0;
|
|
this.manager.draw();
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:update', {
|
|
detail: { event: 'delete' },
|
|
})
|
|
);
|
|
}
|
|
|
|
private grabbedBezierControl() {
|
|
let clickedOnControl: BezierControl | null = null;
|
|
for (const bezier of this.bezierControls) {
|
|
for (const control of ['startControl', 'endControl']) {
|
|
const asType = control as 'startControl' | 'endControl';
|
|
if (
|
|
vec2InCircle(bezier[asType], this.mousePosition, this.selectError / 2)
|
|
) {
|
|
clickedOnControl = [bezier, asType, bezier[asType]];
|
|
}
|
|
}
|
|
}
|
|
return clickedOnControl;
|
|
}
|
|
|
|
private grabbedLineControl() {
|
|
let clickedOnControl: LineControl | null = null;
|
|
for (const line of this.lineControls) {
|
|
for (const control of ['start', 'end']) {
|
|
const asType = control as 'start' | 'end';
|
|
if (
|
|
line[asType] &&
|
|
vec2InCircle(
|
|
line[asType] as Vec2,
|
|
this.mousePosition,
|
|
this.selectError / 2
|
|
)
|
|
) {
|
|
clickedOnControl = [line, asType, line[asType] as Vec2];
|
|
}
|
|
}
|
|
}
|
|
return clickedOnControl;
|
|
}
|
|
|
|
private translate(offset: Vec2) {
|
|
for (const object of this.selectedObjects) {
|
|
if ((object as Line).segments) {
|
|
for (const segment of (object as Line).segments) {
|
|
if ((segment as BezierSegment).startControl) {
|
|
const bezier = segment as BezierSegment;
|
|
bezier.startControl = vec2Add(bezier.startControl, offset);
|
|
bezier.endControl = vec2Add(bezier.endControl, offset);
|
|
}
|
|
|
|
if (segment.start) {
|
|
segment.start = vec2Add(segment.start, offset);
|
|
}
|
|
|
|
segment.end = vec2Add(segment.end, offset);
|
|
}
|
|
}
|
|
}
|
|
this.manager.draw();
|
|
}
|
|
|
|
private pointerDown() {
|
|
if (this.drawingLine) return;
|
|
this.clickedOn = this.getMousedObject();
|
|
const bezierControl = this.grabbedBezierControl();
|
|
const lineControl = this.grabbedLineControl();
|
|
if (bezierControl) {
|
|
this.handlingBezier = bezierControl;
|
|
} else if (lineControl) {
|
|
this.handlingLine = lineControl;
|
|
}
|
|
}
|
|
|
|
private pointerUp() {
|
|
if (!this.moved && !this.handlingBezier && !this.handlingLine) {
|
|
if (this.tool === 'line') {
|
|
this.startLine();
|
|
} else if (!this.drawingLine) {
|
|
this.pick();
|
|
}
|
|
}
|
|
|
|
if (this.handlingBezier) {
|
|
this.history.appendToHistory([
|
|
{
|
|
object: this.handlingBezier[0],
|
|
property: this.handlingBezier[1],
|
|
value: this.handlingBezier[2],
|
|
},
|
|
]);
|
|
}
|
|
|
|
if (this.handlingLine) {
|
|
this.history.appendToHistory([
|
|
{
|
|
object: this.handlingLine[0],
|
|
property: this.handlingLine[1],
|
|
value: this.handlingLine[2],
|
|
},
|
|
]);
|
|
}
|
|
|
|
if (this.movingObject || this.handlingLine || this.handlingBezier) {
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:update', {
|
|
detail: {
|
|
event: 'line-move',
|
|
object: this.handlingLine || this.handlingBezier || this.clickedOn,
|
|
},
|
|
})
|
|
);
|
|
}
|
|
|
|
this.handlingBezier = null;
|
|
this.handlingLine = null;
|
|
this.clickedOn = null;
|
|
this.movingObject = false;
|
|
}
|
|
|
|
private startLine() {
|
|
if (!this.selectedLayer?.visible) return;
|
|
if (this.drawingLine) {
|
|
if (
|
|
vec2Equals(
|
|
this.mousePositionSnapped,
|
|
this.drawingLine.segments[0].start as Vec2
|
|
) ||
|
|
this.drawingLine.type === 'curve'
|
|
) {
|
|
if (this.drawingLine.type !== 'curve') {
|
|
this.drawingLine.segments.splice(
|
|
this.drawingLine.segments.length - 1,
|
|
1
|
|
);
|
|
}
|
|
this.history.appendToHistory([
|
|
{
|
|
object: this.selectedLayer,
|
|
property: 'contents',
|
|
value: [...this.selectedLayer.contents].filter(
|
|
(item) => item !== this.drawingLine
|
|
),
|
|
} as History<typeof this.selectedLayer>,
|
|
{
|
|
object: this,
|
|
property: 'selectedObjects',
|
|
value: [],
|
|
},
|
|
]);
|
|
this.drawingLine.closed = true;
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:newobject', {
|
|
detail: this.drawingLine,
|
|
})
|
|
);
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:update', {
|
|
detail: { event: 'newobject', object: this.drawingLine },
|
|
})
|
|
);
|
|
this.drawingLine = null;
|
|
return;
|
|
}
|
|
|
|
this.drawingLine.segments.push({
|
|
end: [...this.mousePositionSnapped],
|
|
});
|
|
return;
|
|
}
|
|
|
|
const newLineObject: Line = {
|
|
name: 'New Line',
|
|
type: this.subTool!,
|
|
visible: true,
|
|
selected: false,
|
|
closed: false,
|
|
color: this.lastColor,
|
|
width: this.subTool === 'curve' ? 2 : this.lastStrokeWidth,
|
|
segments: [
|
|
{
|
|
start: [...this.mousePositionSnapped],
|
|
end: [...this.mousePositionSnapped],
|
|
},
|
|
],
|
|
};
|
|
this.drawingLine = newLineObject;
|
|
this.selectedLayer.contents.unshift(newLineObject);
|
|
this.selectObject(this.drawingLine);
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:startdrawing', {
|
|
detail: newLineObject,
|
|
})
|
|
);
|
|
}
|
|
|
|
private pick() {
|
|
if (this.clickedOn && this.isSelected(this.clickedOn)) {
|
|
// Pick line segment
|
|
if ((this.clickedOn as Line).segments) {
|
|
const segment = this.getMousedLineSegment(this.clickedOn as Line);
|
|
}
|
|
}
|
|
this.selectObject(this.clickedOn, this.holdShift);
|
|
}
|
|
|
|
private dragEvent(x: number, y: number) {
|
|
this.moved = true;
|
|
|
|
const currentPos = this.gridSnap
|
|
? vec2Snap(this.mousePosition, this.gridSnapScale)
|
|
: this.mousePosition;
|
|
|
|
const rect = this.manager.canvas.getBoundingClientRect();
|
|
this.mousePosition = [x - rect.left, y - rect.top];
|
|
this.mousePositionSnapped = this.gridSnap
|
|
? vec2Snap(this.mousePosition, this.gridSnapScale)
|
|
: this.mousePosition;
|
|
|
|
let offset = vec2Sub(currentPos, this.mousePositionSnapped);
|
|
|
|
if (!this.selectedLayer) return;
|
|
|
|
if (this.tool) {
|
|
if (this.drawingLine) {
|
|
const lastSegment = this.drawingLine.segments.at(-1);
|
|
if (lastSegment) {
|
|
lastSegment.end = [...this.mousePositionSnapped];
|
|
}
|
|
}
|
|
this.manager.draw();
|
|
}
|
|
|
|
if (this.drawingLine) {
|
|
return;
|
|
}
|
|
|
|
if (this.handlingBezier) {
|
|
const [segment, property] = this.handlingBezier;
|
|
segment[property] = [...this.mousePositionSnapped];
|
|
this.manager.draw();
|
|
} else if (this.handlingLine) {
|
|
const [segment, property] = this.handlingLine;
|
|
segment[property] = [...this.mousePositionSnapped];
|
|
this.manager.draw();
|
|
} else if (this.clickedOn) {
|
|
if (!this.movingObject) {
|
|
// TODO: optimize history storage
|
|
this.history.appendToHistory([
|
|
{
|
|
object: this.selectedLayer,
|
|
property: 'contents',
|
|
value: JSON.parse(JSON.stringify(this.selectedLayer['contents'])),
|
|
},
|
|
]);
|
|
this.movingObject = true;
|
|
}
|
|
this.translate(vec2Inverse(offset));
|
|
}
|
|
}
|
|
|
|
private selectedEvent() {
|
|
this.canvas.dispatchEvent(
|
|
new CustomEvent('hpc:selectionchange', {
|
|
detail: this.selectedObjects,
|
|
})
|
|
);
|
|
}
|
|
}
|