255 lines
7.1 KiB
TypeScript
255 lines
7.1 KiB
TypeScript
import {
|
|
BezierSegment,
|
|
LayerObject,
|
|
Line,
|
|
LineSegment,
|
|
Vec2,
|
|
} from '../interfaces';
|
|
import { BezierControl, LineControl } from '../types';
|
|
import { vec2InCircle, vec2Add, vec2Inverse } from '../utils';
|
|
import { CanvasToolBase } from './tool-base';
|
|
|
|
export class MoveTool extends CanvasToolBase<undefined> {
|
|
public name = 'move';
|
|
private bezierControls: BezierSegment[] = [];
|
|
private lineControls: LineSegment[] = [];
|
|
private handlingBezier: BezierControl | null = null;
|
|
private handlingLine: LineControl | null = null;
|
|
private movingObject = false;
|
|
private clickedOn?: LayerObject;
|
|
|
|
drawControls() {
|
|
this.bezierControls.length = 0;
|
|
this.lineControls.length = 0;
|
|
for (const object of this.manager.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
|
|
}
|
|
}
|
|
|
|
mouseDown(targetObject?: LayerObject | undefined): void {
|
|
this.clickedOn = targetObject;
|
|
const bezierControl = this.grabbedBezierControl();
|
|
const lineControl = this.grabbedLineControl();
|
|
if (bezierControl) {
|
|
this.handlingBezier = bezierControl;
|
|
} else if (lineControl) {
|
|
this.handlingLine = lineControl;
|
|
} else if (
|
|
targetObject &&
|
|
!this.manager.selectedObjects.includes(targetObject)
|
|
) {
|
|
this.manager.selectObject(targetObject);
|
|
}
|
|
}
|
|
|
|
mouseUp(moved?: boolean): void {
|
|
if (
|
|
!this.handlingBezier &&
|
|
!this.handlingLine &&
|
|
!this.movingObject &&
|
|
!moved
|
|
) {
|
|
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.movingObject = false;
|
|
this.clickedOn = undefined;
|
|
}
|
|
|
|
private pick() {
|
|
if (this.clickedOn && this.manager.isSelected(this.clickedOn)) {
|
|
// Pick line segment
|
|
if ((this.clickedOn as Line).segments) {
|
|
const segment = this.manager.getMousedLineSegment(
|
|
this.clickedOn as Line
|
|
);
|
|
}
|
|
}
|
|
this.manager.selectObject(this.clickedOn, this.manager.holdShift);
|
|
}
|
|
|
|
mouseMoved(mouse: Vec2, offset: Vec2, mouseAbsolute: Vec2): void {
|
|
super.mouseMoved(mouse, offset, mouseAbsolute);
|
|
if (!this.layer) return;
|
|
if (this.handlingBezier) {
|
|
const [segment, property] = this.handlingBezier;
|
|
segment[property] = [...mouse];
|
|
this.renderer.draw();
|
|
} else if (this.handlingLine) {
|
|
const [segment, property] = this.handlingLine;
|
|
segment[property] = [...mouse];
|
|
this.renderer.draw();
|
|
} else if (this.clickedOn) {
|
|
if (!this.movingObject) {
|
|
// TODO: optimize history storage
|
|
this.history.appendToHistory([
|
|
{
|
|
object: this.layer,
|
|
property: 'contents',
|
|
value: JSON.parse(JSON.stringify(this.layer['contents'])),
|
|
},
|
|
]);
|
|
this.movingObject = true;
|
|
}
|
|
this.translate(vec2Inverse(offset));
|
|
}
|
|
}
|
|
|
|
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.manager.selectError / 2, 0, 2 * Math.PI);
|
|
this.ctx.fill();
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(cp2x, cp2y, this.manager.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.manager.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.manager.selectError / 2,
|
|
0,
|
|
2 * Math.PI
|
|
);
|
|
this.ctx.fill();
|
|
}
|
|
}
|
|
|
|
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.manager.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.manager.selectError / 2
|
|
)
|
|
) {
|
|
clickedOnControl = [line, asType, line[asType] as Vec2];
|
|
}
|
|
}
|
|
}
|
|
return clickedOnControl;
|
|
}
|
|
|
|
private translate(offset: Vec2) {
|
|
for (const object of this.manager.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.renderer.draw();
|
|
}
|
|
}
|