Refactor to classes
This pattern is a natural fit for the js module system in the browser. This gets rid of globals. Instead, index.js creates an instance of Main, which then instantiates each of the specialized classes. We should probably do class inheritance for the setMain pattern. On the other hand it's currently only 3 lines of code, so we'd only be saving 1 LOC per class, while creating a new class, and it wouldn't really be simpler. If our classes take on more features, we can further refactor.
This commit is contained in:
parent
de46a13dfb
commit
7a9fb5ac0c
64
index.html
64
index.html
@ -12,68 +12,10 @@
|
|||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
<script src="./js/util.js"></script>
|
<!-- <script src="./js/util.js"></script>
|
||||||
<script src="./js/element.js"></script>
|
<script src="./js/element.js"></script>
|
||||||
<script src="./js/elements.js"></script>
|
<script src="./js/elements.js"></script>
|
||||||
<script src="./js/pointer.js"></script>
|
<script src="./js/pointer.js"></script>
|
||||||
<script src="./js/canvas.js"></script>
|
<script src="./js/canvas.js"></script>
|
||||||
<script src="./js/main.js"></script>
|
<script src="./js/main.js"></script> -->
|
||||||
<script defer>
|
<script defer type="module" src="./js/index.js"></script>
|
||||||
|
|
||||||
// Time-stepped to support animations;
|
|
||||||
// Can also run tasks queued by the UI, if we want;
|
|
||||||
// Could also support arbitrary scheduled operations.
|
|
||||||
|
|
||||||
// Let's create an ontology for the UI, which the render loop can reference
|
|
||||||
// What kinds of things we have?
|
|
||||||
// Text : Consists of some text content; could have formatting; could have title;
|
|
||||||
// May want to specialize it to support data or whatever...
|
|
||||||
// Connection : Like a line / arrow ; May have different kinds of connections such as
|
|
||||||
// Precedence / hierarchy tree; depencency graph;
|
|
||||||
// May have multiple start / end points (a.k.a. may be a hyperedge)
|
|
||||||
// Also worth thinking about matrices - outer products
|
|
||||||
// Might as well further consider tensor products
|
|
||||||
|
|
||||||
// So then maybe we have an output / display node; this could be displaying things such as
|
|
||||||
// Values of some parameters
|
|
||||||
// Graph of (historical trends of) parameters
|
|
||||||
// Results of logic computations / expression evaluation
|
|
||||||
|
|
||||||
// Utility elements to start, for displaying document time and pointer location
|
|
||||||
|
|
||||||
|
|
||||||
// Add utility elements
|
|
||||||
addElement({
|
|
||||||
id: "time",
|
|
||||||
classes: ["monospace"],
|
|
||||||
detail: () => {
|
|
||||||
const t = currentTime;
|
|
||||||
const s = t / 1000;
|
|
||||||
const m = s / 60;
|
|
||||||
const h = m / 60;
|
|
||||||
const d = h / 24;
|
|
||||||
const timeStr = [d % 60, h % 24, m % 60, s % 60].map(x => x.toFixed(0).padStart(2, "0")).join(":");
|
|
||||||
return `runtime: ${timeStr}`;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
addElement({
|
|
||||||
id: "pointer-info",
|
|
||||||
classes: ["monospace"],
|
|
||||||
detail: () => (["x", "y"]
|
|
||||||
.map(q => `${q}:${(pointer[q] ?? 0).toFixed(2).padStart(7, " ")}`)
|
|
||||||
.join(", ")
|
|
||||||
.replace(/ /g, " ")),
|
|
||||||
});
|
|
||||||
addElement({
|
|
||||||
classes: ["monospace"],
|
|
||||||
id: 'pointer-history-length',
|
|
||||||
detail: () => `ptr hist len: ${pointerHistory.length}`,
|
|
||||||
});
|
|
||||||
addElement({
|
|
||||||
summary: () => "Element Summary",
|
|
||||||
detail: () => "Element Detail<br> With multile lines<br> How about that?",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run main loop
|
|
||||||
main();
|
|
||||||
</script>
|
|
||||||
53
js/canvas.js
53
js/canvas.js
@ -1,53 +0,0 @@
|
|||||||
// Background canvas
|
|
||||||
|
|
||||||
let bgCanvas;
|
|
||||||
let fgCanvas;
|
|
||||||
let bgCtx;
|
|
||||||
let fgCtx;
|
|
||||||
|
|
||||||
function initializeCanvas() {
|
|
||||||
initializeBackground();
|
|
||||||
initializeForeground();
|
|
||||||
fullscreen();
|
|
||||||
window.addEventListener('resize', fullscreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeBackground() {
|
|
||||||
bgCanvas = document.createElement("canvas");
|
|
||||||
mainDiv.appendChild(bgCanvas);
|
|
||||||
bgCanvas.classList.add("fullscreen");
|
|
||||||
bgCanvas.classList.add("background");
|
|
||||||
bgCtx = bgCanvas.getContext("2d");
|
|
||||||
clearCanvas(bgCanvas, bgCtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeForeground() {
|
|
||||||
fgCanvas = document.createElement("canvas");
|
|
||||||
mainDiv.appendChild(fgCanvas);
|
|
||||||
fgCanvas.classList.add("fullscreen");
|
|
||||||
fgCanvas.classList.add("foreground");
|
|
||||||
fgCtx = fgCanvas.getContext("2d");
|
|
||||||
clearCanvas(fgCanvas, fgCtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearCanvas(canvas, ctx) {
|
|
||||||
if (!canvas && !ctx) {
|
|
||||||
clearCanvas(bgCanvas, bgCtx);
|
|
||||||
clearCanvas(fgCanvas, fgCtx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
if (ctx === bgCtx) {
|
|
||||||
bgCtx.fillStyle = '#ccc';
|
|
||||||
bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fullscreen() {
|
|
||||||
const dim = {
|
|
||||||
width: document.documentElement.clientWidth,
|
|
||||||
height: document.documentElement.clientHeight,
|
|
||||||
};
|
|
||||||
Object.assign(bgCanvas, dim);
|
|
||||||
Object.assign(fgCanvas, dim);
|
|
||||||
}
|
|
||||||
57
js/canvases.js
Normal file
57
js/canvases.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
export class Canvases {
|
||||||
|
bgCanvas;
|
||||||
|
fgCanvas;
|
||||||
|
bgCtx;
|
||||||
|
fgCtx;
|
||||||
|
|
||||||
|
setMain(main) {
|
||||||
|
this.main = main;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
this.initializeBackground();
|
||||||
|
this.initializeForeground();
|
||||||
|
this.fullscreen();
|
||||||
|
window.addEventListener('resize', () => this.fullscreen());
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeBackground() {
|
||||||
|
this.bgCanvas = document.createElement("canvas");
|
||||||
|
this.main.div.appendChild(this.bgCanvas);
|
||||||
|
this.bgCanvas.classList.add("fullscreen");
|
||||||
|
this.bgCanvas.classList.add("background");
|
||||||
|
this.bgCtx = this.bgCanvas.getContext("2d");
|
||||||
|
this.clear(this.bgCanvas, this.bgCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeForeground() {
|
||||||
|
this.fgCanvas = document.createElement("canvas");
|
||||||
|
this.main.div.appendChild(this.fgCanvas);
|
||||||
|
this.fgCanvas.classList.add("fullscreen");
|
||||||
|
this.fgCanvas.classList.add("foreground");
|
||||||
|
this.fgCtx = this.fgCanvas.getContext("2d");
|
||||||
|
this.clear(this.fgCanvas, this.fgCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(canvas, ctx) {
|
||||||
|
if (!canvas && !ctx) {
|
||||||
|
this.clear(this.bgCanvas, this.bgCtx);
|
||||||
|
this.clear(this.fgCanvas, this.fgCtx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
if (ctx === this.bgCtx) {
|
||||||
|
this.bgCtx.fillStyle = '#ccc';
|
||||||
|
this.bgCtx.fillRect(0, 0, this.bgCanvas.width, this.bgCanvas.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fullscreen() {
|
||||||
|
const dim = {
|
||||||
|
width: document.documentElement.clientWidth,
|
||||||
|
height: document.documentElement.clientHeight,
|
||||||
|
};
|
||||||
|
Object.assign(this.bgCanvas, dim);
|
||||||
|
Object.assign(this.fgCanvas, dim);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
class Element {
|
export class Element {
|
||||||
elements = undefined;
|
elements = undefined;
|
||||||
id = undefined;
|
id = undefined;
|
||||||
summary = () => "";
|
summary = () => "";
|
||||||
@ -6,7 +6,11 @@ class Element {
|
|||||||
el = undefined;
|
el = undefined;
|
||||||
classes = [];
|
classes = [];
|
||||||
|
|
||||||
constructor(elements, {id, summary, detail, classes, width, height, x, y}) {
|
setMain(main) {
|
||||||
|
this.main = main;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(elements, { id, summary, detail, classes, width, height, x, y }) {
|
||||||
this.elements = elements;
|
this.elements = elements;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.summary = summary;
|
this.summary = summary;
|
||||||
@ -23,7 +27,7 @@ class Element {
|
|||||||
this.setSize(width, height);
|
this.setSize(width, height);
|
||||||
}
|
}
|
||||||
if (x !== undefined && y !== undefined) {
|
if (x !== undefined && y !== undefined) {
|
||||||
this.setPosition({x, y});
|
this.setPosition({ x, y });
|
||||||
}
|
}
|
||||||
if (summary) {
|
if (summary) {
|
||||||
this.summaryEl = document.createElement("div");
|
this.summaryEl = document.createElement("div");
|
||||||
@ -39,12 +43,12 @@ class Element {
|
|||||||
// Handle pointer down to initiate drag
|
// Handle pointer down to initiate drag
|
||||||
this.el.addEventListener("pointerdown", (e) => {
|
this.el.addEventListener("pointerdown", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const {clientX: x, clientY: y} = e;
|
const { clientX: x, clientY: y } = e;
|
||||||
startDrag({element: this, x, y});
|
this.main.pointer.startDrag({ element: this, x, y });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setPosition({x, y}) {
|
setPosition({ x, y }) {
|
||||||
this.el.style.position = "absolute";
|
this.el.style.position = "absolute";
|
||||||
this.el.style.top = `${y}px`;
|
this.el.style.top = `${y}px`;
|
||||||
this.el.style.left = `${x}px`;
|
this.el.style.left = `${x}px`;
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
// globals: mainDiv, crypto
|
import { Element } from "./element.js";
|
||||||
class Elements {
|
|
||||||
|
export class Elements {
|
||||||
elements = new Map(); // id -> Element
|
elements = new Map(); // id -> Element
|
||||||
classes = [];
|
classes = [];
|
||||||
div = undefined;
|
div = undefined;
|
||||||
|
|
||||||
constructor({classes} = {}) {
|
constructor({ classes } = {}) {
|
||||||
this.classes = classes;
|
this.classes = classes;
|
||||||
this.div = document.createElement("div");
|
this.div = document.createElement("div");
|
||||||
this.div.classList.add("elements");
|
this.div.classList.add("elements");
|
||||||
@ -12,7 +13,10 @@ class Elements {
|
|||||||
for (const className of (classes ?? [])) {
|
for (const className of (classes ?? [])) {
|
||||||
this.div.classList.add(className);
|
this.div.classList.add(className);
|
||||||
}
|
}
|
||||||
mainDiv.appendChild(this.div);
|
}
|
||||||
|
setMain(main) {
|
||||||
|
this.main = main;
|
||||||
|
this.main.div.appendChild(this.div);
|
||||||
}
|
}
|
||||||
|
|
||||||
add(props) {
|
add(props) {
|
||||||
@ -29,7 +33,8 @@ class Elements {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create element
|
// Create element
|
||||||
const element = new Element(this, {...props, id});
|
const element = new Element(this, { ...props, id });
|
||||||
|
element.setMain(this.main);
|
||||||
// Append to div
|
// Append to div
|
||||||
this.div.appendChild(element.el);
|
this.div.appendChild(element.el);
|
||||||
// Add to collection
|
// Add to collection
|
||||||
@ -46,7 +51,7 @@ class Elements {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getElementFromDOM(target) {
|
getElementFromDOM(target) {
|
||||||
return Array.from(this.elements.values()).find(({el}) => el === target);
|
return Array.from(this.elements.values()).find(({ el }) => el === target);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAll() {
|
renderAll() {
|
||||||
|
|||||||
58
js/index.js
Normal file
58
js/index.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Time-stepped to support animations;
|
||||||
|
// Can also run tasks queued by the UI, if we want;
|
||||||
|
// Could also support arbitrary scheduled operations.
|
||||||
|
|
||||||
|
// Let's create an ontology for the UI, which the render loop can reference
|
||||||
|
// What kinds of things we have?
|
||||||
|
// Text : Consists of some text content; could have formatting; could have title;
|
||||||
|
// May want to specialize it to support data or whatever...
|
||||||
|
// Connection : Like a line / arrow ; May have different kinds of connections such as
|
||||||
|
// Precedence / hierarchy tree; depencency graph;
|
||||||
|
// May have multiple start / end points (a.k.a. may be a hyperedge)
|
||||||
|
// Also worth thinking about matrices - outer products
|
||||||
|
// Might as well further consider tensor products
|
||||||
|
|
||||||
|
// So then maybe we have an output / display node; this could be displaying things such as
|
||||||
|
// Values of some parameters
|
||||||
|
// Graph of (historical trends of) parameters
|
||||||
|
// Results of logic computations / expression evaluation
|
||||||
|
|
||||||
|
// Utility elements to start, for displaying document time and pointer location
|
||||||
|
|
||||||
|
import { Main } from "./main.js";
|
||||||
|
const main = new Main("mydeate-main");
|
||||||
|
|
||||||
|
// Add utility elements
|
||||||
|
main.addElement({
|
||||||
|
id: "time",
|
||||||
|
classes: ["monospace"],
|
||||||
|
detail: () => {
|
||||||
|
const t = main.currentTime;
|
||||||
|
const s = t / 1000;
|
||||||
|
const m = s / 60;
|
||||||
|
const h = m / 60;
|
||||||
|
const d = h / 24;
|
||||||
|
const timeStr = [d % 60, h % 24, m % 60, s % 60].map(x => x.toFixed(0).padStart(2, "0")).join(":");
|
||||||
|
return `runtime: ${timeStr}`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
main.addElement({
|
||||||
|
id: "pointer-info",
|
||||||
|
classes: ["monospace"],
|
||||||
|
detail: () => (["x", "y"]
|
||||||
|
.map(q => `${q}:${(main.pointer[q] ?? 0).toFixed(2).padStart(7, " ")}`)
|
||||||
|
.join(", ")
|
||||||
|
.replace(/ /g, " ")),
|
||||||
|
});
|
||||||
|
main.addElement({
|
||||||
|
classes: ["monospace"],
|
||||||
|
id: 'pointer-history-length',
|
||||||
|
detail: () => `ptr hist len: ${main.pointer.history.length}`,
|
||||||
|
});
|
||||||
|
main.addElement({
|
||||||
|
summary: () => "Element Summary",
|
||||||
|
detail: () => "Element Detail<br> With multile lines<br> How about that?",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run main loop
|
||||||
|
main.start();
|
||||||
60
js/main.js
60
js/main.js
@ -1,26 +1,44 @@
|
|||||||
const mainDiv = document.getElementById("mydeate-main");
|
import { Elements } from "./elements.js";
|
||||||
const elements = new Elements();
|
import { Pointer } from "./pointer.js";
|
||||||
// Initialize variables for main loop
|
import { Canvases } from "./canvases.js";
|
||||||
let run = true;
|
|
||||||
let currentTime;
|
|
||||||
|
|
||||||
function main() {
|
const pointer = new Pointer();
|
||||||
initializeCanvas();
|
|
||||||
initializePointer();
|
|
||||||
requestAnimationFrame(loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loop(elapsedTime) {
|
export class Main {
|
||||||
if (run) {
|
div = undefined; // Constructor finds this by id
|
||||||
currentTime = document.timeline.currentTime;
|
// Child objects we create
|
||||||
clearCanvas();
|
canvases = new Canvases();
|
||||||
updatePointerHistory({decay: true});
|
elements = new Elements();
|
||||||
drawPointerHistory();
|
pointer = new Pointer();
|
||||||
elements.renderAll();
|
// Initialize variables for main loop
|
||||||
|
run = true;
|
||||||
|
currentTime;
|
||||||
|
|
||||||
|
constructor(mainDivId) {
|
||||||
|
this.div = document.getElementById(mainDivId);
|
||||||
|
this.canvases.setMain(this);
|
||||||
|
this.elements.setMain(this);
|
||||||
|
this.pointer.setMain(this);
|
||||||
}
|
}
|
||||||
requestAnimationFrame(loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addElement(params) {
|
start() {
|
||||||
return elements.add(params);
|
this.canvases.initialize();
|
||||||
|
this.pointer.initialize();
|
||||||
|
requestAnimationFrame(() => this.loop());
|
||||||
|
}
|
||||||
|
|
||||||
|
loop(elapsedTime) {
|
||||||
|
if (this.run) {
|
||||||
|
this.currentTime = document.timeline.currentTime;
|
||||||
|
this.canvases.clear();
|
||||||
|
this.pointer.updateHistory({ decay: true });
|
||||||
|
this.pointer.drawHistory();
|
||||||
|
this.elements.renderAll();
|
||||||
|
}
|
||||||
|
requestAnimationFrame(() => this.loop());
|
||||||
|
}
|
||||||
|
|
||||||
|
addElement(params) {
|
||||||
|
return this.elements.add(params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
199
js/pointer.js
199
js/pointer.js
@ -1,104 +1,111 @@
|
|||||||
// Let's set up pointer tracking. We can use the main div as the pointer target.
|
export class Pointer {
|
||||||
// Note that we may later add pointer handlers on layered elements
|
setMain(main) {
|
||||||
const pointer = {x: undefined, y: undefined};
|
this.main = main;
|
||||||
// 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}) {
|
// Let's set up pointer tracking. We can use the main div as the pointer target.
|
||||||
drag.element = element;
|
// Note that we may later add pointer handlers on layered elements
|
||||||
drag.start = {x, y, t: currentTime};
|
position = { x: undefined, y: undefined };
|
||||||
// We use a placeholder to represent the new position
|
// Trace the path of the cursor
|
||||||
const {x: ox, y: oy, width, height} = element.el.getBoundingClientRect();
|
history = [];
|
||||||
drag.placeholder = elements.add({
|
maxHistory = 50;
|
||||||
...element,
|
dropTargetMaxRange = 20;
|
||||||
id: `${this.id}-placeholder`,
|
drag = {
|
||||||
classes: [...Array.from(element.el.classList.values()), "moving", "placeholder"],
|
start: { x: undefined, y: undefined, t: undefined },
|
||||||
width, height, x, y,
|
element: undefined,
|
||||||
});
|
placeholder: undefined,
|
||||||
drag.element.el.classList.add("moving");
|
}
|
||||||
mainDiv.classList.add("dragging");
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializePointer() {
|
startDrag({ element, x, y }) {
|
||||||
mainDiv.addEventListener("pointermove", (e) => {
|
this.drag.element = element;
|
||||||
const {clientX: x, clientY: y} = e;
|
this.drag.start = { x, y, t: this.main.currentTime };
|
||||||
Object.assign(pointer, {x, y});
|
// We use a placeholder to represent the new position
|
||||||
|
const { x: ox, y: oy, width, height } = element.el.getBoundingClientRect();
|
||||||
|
this.drag.placeholder = this.main.elements.add({
|
||||||
|
...element,
|
||||||
|
id: `${this.id}-placeholder`,
|
||||||
|
classes: [...Array.from(element.el.classList.values()), "moving", "placeholder"],
|
||||||
|
width, height, x, y,
|
||||||
|
});
|
||||||
|
this.drag.element.el.classList.add("moving");
|
||||||
|
this.main.div.classList.add("dragging");
|
||||||
|
}
|
||||||
|
|
||||||
if (drag.start.t) {
|
initialize() {
|
||||||
const displacement = {
|
this.main.div.addEventListener("pointermove", (e) => {
|
||||||
x: x - drag.start.x,
|
const { clientX: x, clientY: y } = e;
|
||||||
y: y - drag.start.y,
|
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 = {
|
||||||
|
x: x - this.drag.start.x,
|
||||||
|
y: y - this.drag.start.y,
|
||||||
};
|
};
|
||||||
const {x: ox, y: oy} = drag.element.el.getBoundingClientRect();
|
const { x: ox, y: oy, width, height } = this.drag.element.el.getBoundingClientRect();
|
||||||
drag.placeholder.setPosition({
|
this.drag.element.setPosition({ x: ox + d.x, y: oy + d.y }).setSize(width, height);
|
||||||
x: ox + displacement.x,
|
this.drag.element.el.classList.remove("moving");
|
||||||
y: oy + displacement.y,
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// We can check here if we're near one or more drop targets to offer.
|
updateHistory({ decay } = { decay: true }) {
|
||||||
// We should indicate which available drop target is currently active.
|
if (this.position.x === undefined || this.position.y === undefined) return;
|
||||||
// If there are multiple options, we can show them.
|
if (!this.history.length) {
|
||||||
// const placeholder;
|
this.history.push({ ...this.position, t: this.main.currentTime });
|
||||||
// const nearbyTargets = Array.from(elements.values()).filter(({el}) => {
|
return;
|
||||||
// const {x, y, width, height} = el.getBoundingClientRect();
|
|
||||||
// const linearDist = minLinearDist(el.getBoundingClientRect(), );
|
|
||||||
// if (linearDist <= dropTargetMaxRange) {
|
|
||||||
// Visually activate the drop target
|
|
||||||
// }
|
|
||||||
//})
|
|
||||||
}
|
}
|
||||||
});
|
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.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user