js-draw
Version:
Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.
155 lines (154 loc) • 6.82 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RotateTransformer = exports.ResizeTransformer = exports.DragTransformer = void 0;
const math_1 = require("@js-draw/math");
const Viewport_1 = __importDefault(require("../../Viewport"));
const types_1 = require("./types");
class DragTransformer {
constructor(editor, selection) {
this.editor = editor;
this.selection = selection;
}
onDragStart(startPoint) {
this.selection.setTransform(math_1.Mat33.identity);
this.dragStartPoint = startPoint;
}
onDragUpdate(canvasPos) {
const delta = this.editor.viewport.roundPoint(canvasPos.minus(this.dragStartPoint));
this.selection.setTransform(math_1.Mat33.translation(delta));
}
onDragEnd() {
return this.selection.finalizeTransform();
}
}
exports.DragTransformer = DragTransformer;
class ResizeTransformer {
constructor(editor, selection) {
this.editor = editor;
this.selection = selection;
this.mode = types_1.ResizeMode.Both;
}
onDragStart(startPoint, mode) {
this.selection.setTransform(math_1.Mat33.identity);
this.mode = mode;
this.dragStartPoint = startPoint;
this.computeOriginAndScaleRate();
}
computeOriginAndScaleRate() {
// Store the index of the furthest corner from startPoint. We'll use that
// to determine where the transform considers (0, 0) (where we scale from).
const selectionRect = this.selection.preTransformRegion;
const selectionBoxCorners = selectionRect.corners;
let largestDistSquared = 0;
for (let i = 0; i < selectionBoxCorners.length; i++) {
const currentCorner = selectionBoxCorners[i];
const distSquaredToCurrent = this.dragStartPoint.minus(currentCorner).magnitudeSquared();
if (distSquaredToCurrent > largestDistSquared) {
largestDistSquared = distSquaredToCurrent;
this.transformOrigin = currentCorner;
}
}
// Determine whether moving the mouse to the right increases or decreases the width.
let widthScaleRate = 1;
let heightScaleRate = 1;
if (this.transformOrigin.x > selectionRect.center.x) {
widthScaleRate = -1;
}
if (this.transformOrigin.y > selectionRect.center.y) {
heightScaleRate = -1;
}
this.scaleRate = math_1.Vec2.of(widthScaleRate, heightScaleRate);
}
onDragUpdate(canvasPos) {
const canvasDelta = canvasPos.minus(this.dragStartPoint);
const origWidth = this.selection.preTransformRegion.width;
const origHeight = this.selection.preTransformRegion.height;
let scale = math_1.Vec2.of(1, 1);
if (this.mode === types_1.ResizeMode.HorizontalOnly) {
const newWidth = origWidth + canvasDelta.x * this.scaleRate.x;
scale = math_1.Vec2.of(newWidth / origWidth, scale.y);
}
if (this.mode === types_1.ResizeMode.VerticalOnly) {
const newHeight = origHeight + canvasDelta.y * this.scaleRate.y;
scale = math_1.Vec2.of(scale.x, newHeight / origHeight);
}
if (this.mode === types_1.ResizeMode.Both) {
const delta = Math.abs(canvasDelta.x) > Math.abs(canvasDelta.y) ? canvasDelta.x : canvasDelta.y;
const newWidth = origWidth + delta;
scale = math_1.Vec2.of(newWidth / origWidth, newWidth / origWidth);
}
// Round: If this isn't done, scaling can create numbers with long decimal representations.
// long decimal representations => large file sizes.
scale = scale.map((component) => Viewport_1.default.roundScaleRatio(component, 2));
if (scale.x !== 0 && scale.y !== 0) {
const origin = this.editor.viewport.roundPoint(this.transformOrigin);
this.selection.setTransform(math_1.Mat33.scaling2D(scale, origin));
}
}
onDragEnd() {
return this.selection.finalizeTransform();
}
}
exports.ResizeTransformer = ResizeTransformer;
class RotateTransformer {
constructor(editor, selection) {
this.editor = editor;
this.selection = selection;
this.startAngle = 0;
this.targetRotation = 0;
this.maximumDistFromStart = 0;
}
getAngle(canvasPoint) {
const selectionCenter = this.selection.preTransformRegion.center;
const offset = canvasPoint.minus(selectionCenter);
return offset.angle();
}
roundAngle(angle) {
// Round angles to the nearest 16th of a turn
const roundingFactor = 16 / 2 / Math.PI;
return Math.round(angle * roundingFactor) / roundingFactor;
}
onDragStart(startPoint) {
this.startPoint = startPoint;
this.selection.setTransform(math_1.Mat33.identity);
this.startAngle = this.getAngle(startPoint);
this.targetRotation = 0;
// Used to determine whether the user clicked or not.
this.maximumDistFromStart = 0;
this.startTime = performance.now();
}
setRotationTo(angle) {
// Transform in canvas space
const canvasSelCenter = this.editor.viewport.roundPoint(this.selection.preTransformRegion.center);
const unrounded = math_1.Mat33.zRotation(angle);
const roundedRotationTransform = unrounded.mapEntries((entry) => Viewport_1.default.roundScaleRatio(entry));
const fullRoundedTransform = math_1.Mat33.translation(canvasSelCenter)
.rightMul(roundedRotationTransform)
.rightMul(math_1.Mat33.translation(canvasSelCenter.times(-1)));
this.selection.setTransform(fullRoundedTransform);
}
onDragUpdate(canvasPos) {
this.targetRotation = this.roundAngle(this.getAngle(canvasPos) - this.startAngle);
this.setRotationTo(this.targetRotation);
const distFromStart = canvasPos.minus(this.startPoint).magnitude();
if (distFromStart > this.maximumDistFromStart) {
this.maximumDistFromStart = distFromStart;
}
}
onDragEnd() {
// Anything with motion less than this is considered a click
const clickThresholdDist = 10;
const clickThresholdTime = 0.4; // s
const dragTimeSeconds = (performance.now() - this.startTime) / 1000;
if (dragTimeSeconds < clickThresholdTime &&
this.maximumDistFromStart < clickThresholdDist &&
this.targetRotation === 0) {
this.setRotationTo(-Math.PI / 2);
}
return this.selection.finalizeTransform();
}
}
exports.RotateTransformer = RotateTransformer;