import { Base } from "./base.js"; import { add, sub } from "./util.js"; export class Pointer extends Base { // Let's set up pointer tracking. We can use the main div as the pointer target. // Note that we may later add pointer handlers on layered elements position = { x: undefined, y: undefined }; // Trace the path of the cursor history = []; maxHistory = 50; dropTargetMaxRange = 20; drag = { start: { x: undefined, y: undefined, t: undefined }, element: undefined, placeholder: undefined, } startDrag({ element, x, y }) { this.drag.element = element; this.drag.start = { x, y, t: this.main.currentTime }; // We use a placeholder to represent the new position const { x: ox, y: oy, width, height } = element.el.getBoundingClientRect(); const o = { x: ox, y: oy }; this.drag.placeholder = this.main.elements.add({ ...element, id: `${this.id}-placeholder`, classes: [...Array.from(element.el.classList.values()), "moving", "placeholder"], position: o, size: { x: width, y: height }, }); this.drag.element.el.classList.add("moving"); this.main.div.classList.add("dragging"); } initialize() { this.main.div.addEventListener("pointermove", (e) => { const { clientX: x, clientY: y } = e; Object.assign(this.position, { x, y }); if (this.drag.start.t) { const displacement = { x: x - this.drag.start.x, y: y - this.drag.start.y, }; const { x: ox, y: oy } = this.drag.element.el.getBoundingClientRect(); this.drag.placeholder.setPosition({ x: ox + displacement.x, y: oy + displacement.y, }); // We can check here if we're near one or more drop targets to offer. // We should indicate which available drop target is currently active. // If there are multiple options, we can show them. // const placeholder; // const nearbyTargets = Array.from(elements.values()).filter(({el}) => { // const {x, y, width, height} = el.getBoundingClientRect(); // const linearDist = minLinearDist(el.getBoundingClientRect(), ); // if (linearDist <= dropTargetMaxRange) { // Visually activate the drop target // } //}) } }); this.main.div.addEventListener("pointerup", (e) => { if (!this.drag.start.t) return; const { clientX: x, clientY: y } = e; // Displacement const d = sub({ x, y }, this.drag.start); const { x: ox, y: oy, width, height } = this.drag.element.el.getBoundingClientRect(); const o = { x: ox, y: oy }; const size = { x: width, y: height }; this.drag.element.setPosition(add(o, d)).setSize(size); this.drag.element.el.classList.remove("moving"); this.main.elements.remove(this.drag.placeholder.id); this.drag.start = {}; this.drag.placeholder = undefined; this.drag.element = undefined; this.main.div.classList.remove("dragging"); }); } updateHistory({ decay } = { decay: true }) { if (this.position.x === undefined || this.position.y === undefined) return; if (!this.history.length) { this.history.push({ ...this.position, t: this.main.currentTime }); return; } const lastPointer = this.history[this.history.length - 1]; if (decay || this.position.x !== lastPointer.x || this.position.y !== lastPointer.y) { this.history.push({ ...this.position, t: this.main.currentTime }); } while (this.history.length > this.maxHistory) { this.history.shift(); } } drawHistory() { const { fgCtx } = this.main.canvases; if (this.history.length < 2) return; for (let i = 1; i < this.history.length; i++) { fgCtx.beginPath(); const opacity = i / this.history.length; fgCtx.lineWidth = "0.2"; fgCtx.strokeStyle = `rgba(128, 0, 0, ${opacity})`; fgCtx.moveTo(this.history[i - 1].x, this.history[i - 1].y); fgCtx.lineTo(this.history[i].x, this.history[i].y); fgCtx.stroke(); } } }