js-draw
Version:
Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.
126 lines (125 loc) • 5.86 kB
JavaScript
import { Vec2 } from '@js-draw/math';
export var PointerDevice;
(function (PointerDevice) {
PointerDevice[PointerDevice["Pen"] = 0] = "Pen";
PointerDevice[PointerDevice["Eraser"] = 1] = "Eraser";
PointerDevice[PointerDevice["Touch"] = 2] = "Touch";
PointerDevice[PointerDevice["PrimaryButtonMouse"] = 3] = "PrimaryButtonMouse";
PointerDevice[PointerDevice["RightButtonMouse"] = 4] = "RightButtonMouse";
PointerDevice[PointerDevice["Other"] = 5] = "Other";
})(PointerDevice || (PointerDevice = {}));
// Provides a snapshot containing information about a pointer. A Pointer
// object is immutable — it will not be updated when the pointer's information changes.
export default class Pointer {
constructor(
// The (x, y) position of the pointer relative to the top-left corner
// of the visible canvas.
screenPos,
// Position of the pointer relative to the top left corner of the drawing
// surface.
canvasPos, pressure, isPrimary, down, device,
// Unique ID for the pointer
id,
// Numeric timestamp (milliseconds, as from `performance.now()`).
timeStamp) {
this.screenPos = screenPos;
this.canvasPos = canvasPos;
this.pressure = pressure;
this.isPrimary = isPrimary;
this.down = down;
this.device = device;
this.id = id;
this.timeStamp = timeStamp;
}
/**
* Snaps this pointer to the nearest grid point (rounds the coordinates of this
* pointer based on the current zoom). Returns a new Pointer and does not modify
* this.
*/
snappedToGrid(viewport) {
const snappedCanvasPos = viewport.snapToGrid(this.canvasPos);
return this.withCanvasPosition(snappedCanvasPos, viewport);
}
// Snap this pointer to the X or Y axis (whichever is closer), where (0,0)
// is considered to be at `originPointScreen`.
// @internal
lockedToXYAxesScreen(originPointScreen, viewport) {
const current = this.screenPos;
const currentFromStart = current.minus(originPointScreen);
// Determine whether the last point was closer to being on the
// x- or y- axis.
const projOntoXAxis = Vec2.unitX.times(currentFromStart.x);
const projOntoYAxis = Vec2.unitY.times(currentFromStart.y);
let pos;
if (currentFromStart.dot(projOntoXAxis) > currentFromStart.dot(projOntoYAxis)) {
pos = projOntoXAxis;
}
else {
pos = projOntoYAxis;
}
pos = pos.plus(originPointScreen);
return this.withScreenPosition(pos, viewport);
}
/** @see {@link withCanvasPosition} */
withScreenPosition(screenPos, viewport) {
const canvasPos = viewport.screenToCanvas(screenPos);
return this.withCanvasPosition(canvasPos, viewport);
}
/** Returns a copy of this pointer with a changed timestamp. */
withTimestamp(timeStamp) {
return new Pointer(this.screenPos, this.canvasPos, this.pressure, this.isPrimary, this.down, this.device, this.id, timeStamp);
}
/**
* Returns a copy of this pointer with a new position. The screen position is determined
* by the given `canvasPos`.
*/
withCanvasPosition(canvasPos, viewport) {
const screenPos = viewport.canvasToScreen(canvasPos);
return new Pointer(screenPos, canvasPos, this.pressure, this.isPrimary, this.down, this.device, this.id, this.timeStamp);
}
// Creates a Pointer from a DOM event. If `relativeTo` is given, (0, 0) in screen coordinates is
// considered the top left of `relativeTo`.
static ofEvent(evt, isDown, viewport, relativeTo) {
let screenPos = Vec2.of(evt.clientX, evt.clientY);
if (relativeTo) {
const bbox = relativeTo.getBoundingClientRect();
screenPos = screenPos.minus(Vec2.of(bbox.left, bbox.top));
}
const pointerTypeToDevice = {
mouse: PointerDevice.PrimaryButtonMouse,
pen: PointerDevice.Pen,
touch: PointerDevice.Touch,
};
let device = pointerTypeToDevice[evt.pointerType] ?? PointerDevice.Other;
const eraserButtonMask = 0x20;
if (device === PointerDevice.Pen && (evt.buttons & eraserButtonMask) !== 0) {
device = PointerDevice.Eraser;
}
const timeStamp = evt.timeStamp;
const canvasPos = viewport.roundPoint(viewport.screenToCanvas(screenPos));
if (device === PointerDevice.PrimaryButtonMouse) {
if (evt.buttons & 0x2) {
device = PointerDevice.RightButtonMouse;
}
// Commented out: Mouse up events seem to not satisfy this condition on mouse up.
// else if (!(evt.buttons & 0x1)) {
// device = PointerDevice.Other;
//}
}
return new Pointer(screenPos, canvasPos, evt.pressure ?? null, evt.isPrimary, isDown, device, evt.pointerId, timeStamp);
}
// Create a new Pointer from a point on the canvas.
// Intended for unit tests.
static ofCanvasPoint(canvasPos, isDown, viewport, id = 0, device = PointerDevice.Pen, isPrimary = true, pressure = null, timeStamp = null) {
const screenPos = viewport.canvasToScreen(canvasPos);
timeStamp ??= performance.now();
return new Pointer(screenPos, canvasPos, pressure, isPrimary, isDown, device, id, timeStamp);
}
// Create a new Pointer from a point on the screen.
// Intended for unit tests.
static ofScreenPoint(screenPos, isDown, viewport, id = 0, device = PointerDevice.Pen, isPrimary = true, pressure = null, timeStamp = null) {
const canvasPos = viewport.screenToCanvas(screenPos);
timeStamp ??= performance.now();
return new Pointer(screenPos, canvasPos, pressure, isPrimary, isDown, device, id, timeStamp);
}
}