diff --git a/index.html b/index.html index e1ebb70..60d4e36 100644 --- a/index.html +++ b/index.html @@ -12,68 +12,10 @@ - + + \ No newline at end of file diff --git a/js/canvas.js b/js/canvas.js deleted file mode 100644 index f364f37..0000000 --- a/js/canvas.js +++ /dev/null @@ -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); -} diff --git a/js/canvases.js b/js/canvases.js new file mode 100644 index 0000000..22ab689 --- /dev/null +++ b/js/canvases.js @@ -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); + } +} \ No newline at end of file diff --git a/js/element.js b/js/element.js index 4c4a26e..e09c1f1 100644 --- a/js/element.js +++ b/js/element.js @@ -1,4 +1,4 @@ -class Element { +export class Element { elements = undefined; id = undefined; summary = () => ""; @@ -6,12 +6,16 @@ class Element { el = undefined; 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.id = id; this.summary = summary; this.detail = detail; - + // Create DOM element(s) this.el = document.createElement("div"); this.el.id = id; @@ -23,7 +27,7 @@ class Element { this.setSize(width, height); } if (x !== undefined && y !== undefined) { - this.setPosition({x, y}); + this.setPosition({ x, y }); } if (summary) { this.summaryEl = document.createElement("div"); @@ -35,28 +39,28 @@ class Element { this.detailEl.classList.add("detail"); this.el.appendChild(this.detailEl) } - + // Handle pointer down to initiate drag this.el.addEventListener("pointerdown", (e) => { e.preventDefault(); - const {clientX: x, clientY: y} = e; - startDrag({element: this, x, y}); + const { clientX: x, clientY: y } = e; + this.main.pointer.startDrag({ element: this, x, y }); }); } - - setPosition({x, y}) { + + setPosition({ x, y }) { this.el.style.position = "absolute"; this.el.style.top = `${y}px`; this.el.style.left = `${x}px`; return this; } - + setSize(width, height) { this.el.style.width = `${width}px`; this.el.style.height = `${height}px`; return this; } - + render() { if (this.summary) { this.summaryEl.innerHTML = this.summary(); diff --git a/js/elements.js b/js/elements.js index e0873f4..c88c205 100644 --- a/js/elements.js +++ b/js/elements.js @@ -1,10 +1,11 @@ -// globals: mainDiv, crypto -class Elements { +import { Element } from "./element.js"; + +export class Elements { elements = new Map(); // id -> Element classes = []; div = undefined; - - constructor({classes} = {}) { + + constructor({ classes } = {}) { this.classes = classes; this.div = document.createElement("div"); this.div.classList.add("elements"); @@ -12,7 +13,10 @@ class Elements { for (const className of (classes ?? [])) { this.div.classList.add(className); } - mainDiv.appendChild(this.div); + } + setMain(main) { + this.main = main; + this.main.div.appendChild(this.div); } add(props) { @@ -29,14 +33,15 @@ class Elements { } } // Create element - const element = new Element(this, {...props, id}); + const element = new Element(this, { ...props, id }); + element.setMain(this.main); // Append to div this.div.appendChild(element.el); // Add to collection this.elements.set(id, element); return element; } - + remove(id) { const element = this.elements.get(id); if (element) { @@ -44,11 +49,11 @@ class Elements { this.elements.delete(id); } } - + getElementFromDOM(target) { - return Array.from(this.elements.values()).find(({el}) => el === target); + return Array.from(this.elements.values()).find(({ el }) => el === target); } - + renderAll() { for (const element of this.elements.values()) { element.render(); diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000..c76be38 --- /dev/null +++ b/js/index.js @@ -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
With multile lines
How about that?", +}); + +// Run main loop +main.start(); \ No newline at end of file diff --git a/js/main.js b/js/main.js index 6ce3c82..570ae9a 100644 --- a/js/main.js +++ b/js/main.js @@ -1,26 +1,44 @@ -const mainDiv = document.getElementById("mydeate-main"); -const elements = new Elements(); -// Initialize variables for main loop -let run = true; -let currentTime; +import { Elements } from "./elements.js"; +import { Pointer } from "./pointer.js"; +import { Canvases } from "./canvases.js"; -function main() { - initializeCanvas(); - initializePointer(); - requestAnimationFrame(loop); -} +const pointer = new Pointer(); -function loop(elapsedTime) { - if (run) { - currentTime = document.timeline.currentTime; - clearCanvas(); - updatePointerHistory({decay: true}); - drawPointerHistory(); - elements.renderAll(); +export class Main { + div = undefined; // Constructor finds this by id + // Child objects we create + canvases = new Canvases(); + elements = new Elements(); + pointer = new Pointer(); + // 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) { - return elements.add(params); + start() { + 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); + } } \ No newline at end of file diff --git a/js/pointer.js b/js/pointer.js index c873eed..c14444e 100644 --- a/js/pointer.js +++ b/js/pointer.js @@ -1,104 +1,111 @@ -// 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, -} +export class Pointer { + setMain(main) { + this.main = main; + } -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"); -} + // 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, + } -function initializePointer() { - mainDiv.addEventListener("pointermove", (e) => { - const {clientX: x, clientY: y} = e; - Object.assign(pointer, {x, y}); + 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(); + 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) { - const displacement = { - x: x - drag.start.x, - y: y - drag.start.y, + 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 = { + x: x - this.drag.start.x, + y: y - this.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 - // } - //}) + const { x: ox, y: oy, width, height } = this.drag.element.el.getBoundingClientRect(); + this.drag.element.setPosition({ x: ox + d.x, y: oy + d.y }).setSize(width, height); + 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(); } - }); - - 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(); + 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(); + } } -} +} \ No newline at end of file