// 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 const pointer = {x: undefined, y: undefined}; // Trace the path of the cursor const pointerHistory = []; const maxHistory = 50; const dropTargetMaxRange = 20; const drag = { start: {x: undefined, y: undefined, t: undefined}, element: undefined, placeholder: undefined, } function startDrag({element, x, y}) { drag.element = element; drag.start = {x, y, t: currentTime}; // We use a placeholder to represent the new position const {x: ox, y: oy, width, height} = element.el.getBoundingClientRect(); drag.placeholder = elements.add({ ...element, id: `${this.id}-placeholder`, classes: [...Array.from(element.el.classList.values()), "moving", "placeholder"], width, height, x, y, }); drag.element.el.classList.add("moving"); mainDiv.classList.add("dragging"); } function initializePointer() { mainDiv.addEventListener("pointermove", (e) => { const {clientX: x, clientY: y} = e; Object.assign(pointer, {x, y}); if (drag.start.t) { const displacement = { x: x - drag.start.x, y: y - drag.start.y, }; const {x: ox, y: oy} = drag.element.el.getBoundingClientRect(); 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 // } //}) } }); mainDiv.addEventListener("pointerup", (e) => { if (!drag.start.t) return; const {clientX: x, clientY: y} = e; // Displacement const d = { x: x - drag.start.x, y: y - drag.start.y, }; const {x: ox, y: oy, width, height} = drag.element.el.getBoundingClientRect(); drag.element.setPosition({ x: ox + d.x, y: oy + d.y}).setSize(width, height); drag.element.el.classList.remove("moving"); elements.remove(drag.placeholder.id); drag.start = {}; drag.placeholder = undefined; drag.element = undefined; mainDiv.classList.remove("dragging"); }); } function updatePointerHistory({decay} = {decay: true}) { if (pointer.x === undefined || pointer.y === undefined) return; if (!pointerHistory.length) { pointerHistory.push({...pointer, t: currentTime}); return; } const lastPointer = pointerHistory[pointerHistory.length - 1]; if (decay || pointer.x !== lastPointer.x || pointer.y !== lastPointer.y) { pointerHistory.push({...pointer, t: currentTime}); } while (pointerHistory.length > maxHistory) { pointerHistory.shift(); } } function drawPointerHistory() { if (pointerHistory.length < 2) return; for (let i = 1; i < pointerHistory.length; i++) { fgCtx.beginPath(); const opacity = i / pointerHistory.length; fgCtx.strokeStyle = `rgba(128, 0, 0, ${opacity})`; fgCtx.moveTo(pointerHistory[i - 1].x, pointerHistory[i - 1].y); fgCtx.lineTo(pointerHistory[i].x, pointerHistory[i].y); fgCtx.stroke(); } }