From b76dcc7a7e4e0809e767d31ac79f20101ca293f9 Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Thu, 16 Jan 2025 21:40:05 -0500 Subject: [PATCH] work on converting timeline to widgets --- src/main.js | 327 ++++++++++---------- src/widgets.js | 805 ++++++++++++++++++++++++++++++------------------- 2 files changed, 669 insertions(+), 463 deletions(-) diff --git a/src/main.js b/src/main.js index d321f7c..d125171 100644 --- a/src/main.js +++ b/src/main.js @@ -60,7 +60,7 @@ import { shadow, } from "./styles.js"; import { Icon } from "./icon.js"; -import { AlphaSelectionBar, ColorSelectorWidget, ColorWidget, HueSelectionBar, SaturationValueSelectionGradient, Widget } from "./widgets.js"; +import { AlphaSelectionBar, ColorSelectorWidget, ColorWidget, HueSelectionBar, SaturationValueSelectionGradient, TimelineWindow, Widget } from "./widgets.js"; const { writeTextFile: writeTextFile, readTextFile: readTextFile, @@ -6302,6 +6302,9 @@ function timeline() { let timeline_cvs = document.createElement("canvas"); timeline_cvs.className = "timeline"; + // Start building widget hierarchy + timeline_cvs.timelinewindow = new TimelineWindow(0, 0, context) + // Load icons for show/hide layer timeline_cvs.icons = {}; timeline_cvs.icons.volume_up_fill = new Icon("assets/volume-up-fill.svg"); @@ -6359,6 +6362,8 @@ function timeline() { 0, Math.min(maxScroll, timeline_cvs.offsetY + deltaY), ); + timeline_cvs.timelinewindow.offsetX = -timeline_cvs.offsetX + timeline_cvs.timelinewindow.offsetY = -timeline_cvs.offsetY const currentTime = Date.now(); if (currentTime - lastResizeTime > throttleIntervalMs) { @@ -7139,6 +7144,14 @@ function renderLayers() { ctx.fillRect(0, 0, width, height); ctx.lineWidth = 1; + + ctx.save() + ctx.translate(layerWidth, gutterHeight) + canvas.timelinewindow.width = width - layerWidth + canvas.timelinewindow.height = height - gutterHeight + canvas.timelinewindow.draw(ctx) + ctx.restore() + // Draw timeline top ctx.save(); ctx.save(); @@ -7222,164 +7235,166 @@ function renderLayers() { labelColor, ); ctx.restore(); - ctx.save(); - ctx.beginPath(); - ctx.rect(layerWidth, i * layerHeight, width, layerHeight); - ctx.clip(); - ctx.translate(layerWidth - offsetX, i * layerHeight); - // Draw empty frames - for (let j = Math.floor(offsetX / frameWidth); j < frameCount; j++) { - ctx.fillStyle = (j + 1) % 5 == 0 ? shade : backgroundColor; - drawBorderedRect( - ctx, - j * frameWidth, - 0, - frameWidth, - layerHeight, - shadow, - highlight, - shadow, - shadow, - ); - } - // Draw existing frames - if (layer instanceof Layer) { - for (let j=0; j { - // if (!frame) return; - // switch (frame.frameType) { - // case "keyframe": - // ctx.fillStyle = foregroundColor; - // drawBorderedRect( - // ctx, - // j * frameWidth, - // 0, - // frameWidth, - // layerHeight, - // highlight, - // shadow, - // shadow, - // shadow, - // ); - // ctx.fillStyle = "#111"; - // ctx.beginPath(); - // ctx.arc( - // (j + 0.5) * frameWidth, - // layerHeight * 0.75, - // frameWidth * 0.25, - // 0, - // 2 * Math.PI, - // ); - // ctx.fill(); - // break; - // case "normal": - // ctx.fillStyle = foregroundColor; - // drawBorderedRect( - // ctx, - // j * frameWidth, - // 0, - // frameWidth, - // layerHeight, - // highlight, - // shadow, - // backgroundColor, - // backgroundColor, - // ); - // break; - // case "motion": - // ctx.fillStyle = "#7a00b3"; - // ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight); - // break; - // case "shape": - // ctx.fillStyle = "#9bff9b"; - // ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight); - // break; - // } - // }); - } else if (layer instanceof AudioLayer) { - // TODO: split waveform into chunks - for (let i in layer.sounds) { - let sound = layer.sounds[i]; - // layerTrack.appendChild(sound.img) - ctx.drawImage(sound.img, 0, 0); - } - } - // if (context.activeObject.currentFrameNum) - ctx.restore(); + + // ctx.save(); + // ctx.beginPath(); + // ctx.rect(layerWidth, i * layerHeight, width, layerHeight); + // ctx.clip(); + // ctx.translate(layerWidth - offsetX, i * layerHeight); + // // Draw empty frames + // for (let j = Math.floor(offsetX / frameWidth); j < frameCount; j++) { + // ctx.fillStyle = (j + 1) % 5 == 0 ? shade : backgroundColor; + // drawBorderedRect( + // ctx, + // j * frameWidth, + // 0, + // frameWidth, + // layerHeight, + // shadow, + // highlight, + // shadow, + // shadow, + // ); + // } + // // Draw existing frames + // if (layer instanceof Layer) { + // for (let j=0; j { + // // if (!frame) return; + // // switch (frame.frameType) { + // // case "keyframe": + // // ctx.fillStyle = foregroundColor; + // // drawBorderedRect( + // // ctx, + // // j * frameWidth, + // // 0, + // // frameWidth, + // // layerHeight, + // // highlight, + // // shadow, + // // shadow, + // // shadow, + // // ); + // // ctx.fillStyle = "#111"; + // // ctx.beginPath(); + // // ctx.arc( + // // (j + 0.5) * frameWidth, + // // layerHeight * 0.75, + // // frameWidth * 0.25, + // // 0, + // // 2 * Math.PI, + // // ); + // // ctx.fill(); + // // break; + // // case "normal": + // // ctx.fillStyle = foregroundColor; + // // drawBorderedRect( + // // ctx, + // // j * frameWidth, + // // 0, + // // frameWidth, + // // layerHeight, + // // highlight, + // // shadow, + // // backgroundColor, + // // backgroundColor, + // // ); + // // break; + // // case "motion": + // // ctx.fillStyle = "#7a00b3"; + // // ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight); + // // break; + // // case "shape": + // // ctx.fillStyle = "#9bff9b"; + // // ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight); + // // break; + // // } + // // }); + // } else if (layer instanceof AudioLayer) { + // // TODO: split waveform into chunks + // for (let i in layer.sounds) { + // let sound = layer.sounds[i]; + // // layerTrack.appendChild(sound.img) + // ctx.drawImage(sound.img, 0, 0); + // } + // } + // // if (context.activeObject.currentFrameNum) + // ctx.restore(); i++; } ctx.restore(); + // Draw highlighted frame ctx.save(); ctx.translate(layerWidth - offsetX, -offsetY); diff --git a/src/widgets.js b/src/widgets.js index e0b56d3..1664b09 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -1,340 +1,531 @@ -import { clamp, drawCheckerboardBackground, hslToRgb, hsvToRgb, rgbToHex } from "./utils.js" +import { backgroundColor, foregroundColor, frameWidth, highlight, layerHeight, shade, shadow } from "./styles.js"; +import { clamp, drawBorderedRect, drawCheckerboardBackground, hslToRgb, hsvToRgb, rgbToHex } from "./utils.js" function growBoundingBox(bboxa, bboxb) { - bboxa.x.min = Math.min(bboxa.x.min, bboxb.x.min); - bboxa.y.min = Math.min(bboxa.y.min, bboxb.y.min); - bboxa.x.max = Math.max(bboxa.x.max, bboxb.x.max); - bboxa.y.max = Math.max(bboxa.y.max, bboxb.y.max); - } + bboxa.x.min = Math.min(bboxa.x.min, bboxb.x.min); + bboxa.y.min = Math.min(bboxa.y.min, bboxb.y.min); + bboxa.x.max = Math.max(bboxa.x.max, bboxb.x.max); + bboxa.y.max = Math.max(bboxa.y.max, bboxb.y.max); +} + +const SCROLL = { + HORIZONTAL: 1, + VERTICAL: 2, +} class Widget { - constructor(x, y) { - this._globalEvents = new Set() - this.x = x - this.y = y - this.scale_x = 1 - this.scale_y = 1 - this.rotation = 0 - this.children = [] + constructor(x, y) { + this._globalEvents = new Set() + this.x = x + this.y = y + this.scale_x = 1 + this.scale_y = 1 + this.rotation = 0 + this.children = [] + } + handleMouseEvent(eventType, x, y) { + for (let child of this.children) { + // Adjust for translation + const dx = x - child.x; + const dy = y - child.y; + + // Apply inverse rotation + const cosTheta = Math.cos(child.rotation); + const sinTheta = Math.sin(child.rotation); + + // Rotate coordinates to child's local space + const rotatedX = dx * cosTheta + dy * sinTheta; + const rotatedY = -dx * sinTheta + dy * cosTheta; + + // First, perform hit test using original (global) coordinates + if (child.hitTest(rotatedX, rotatedY) || child._globalEvents.has(eventType)) { + child.handleMouseEvent(eventType, rotatedX, rotatedY); + } } - handleMouseEvent(eventType, x, y) { - for (let child of this.children) { - // Adjust for translation - const dx = x - child.x; - const dy = y - child.y; - - // Apply inverse rotation - const cosTheta = Math.cos(child.rotation); - const sinTheta = Math.sin(child.rotation); - - // Rotate coordinates to child's local space - const rotatedX = dx * cosTheta + dy * sinTheta; - const rotatedY = -dx * sinTheta + dy * cosTheta; - - // First, perform hit test using original (global) coordinates - if (child.hitTest(rotatedX, rotatedY) || child._globalEvents.has(eventType)) { - child.handleMouseEvent(eventType, rotatedX, rotatedY); - } - } - const eventTypes = [ - "mousedown", - "mousemove", - "mouseup", - "dblclick" - ] - if (eventTypes.indexOf(eventType)!=-1) { - if (typeof(this[eventType]) == "function") { - this[eventType](x, y) - } - } + const eventTypes = [ + "mousedown", + "mousemove", + "mouseup", + "dblclick" + ] + if (eventTypes.indexOf(eventType)!=-1) { + if (typeof(this[eventType]) == "function") { + this[eventType](x, y) + } } - hitTest(x, y) { - // if ((x >= this.x) && (x <= this.x+this.width) && - // (y >= this.y) && (y <= this.y+this.height)) { - if ((x>=0) && (x <= this.width) && (y >= 0) && (y <= this.height)) { - return true - } - return false + } + hitTest(x, y) { + // if ((x >= this.x) && (x <= this.x+this.width) && + // (y >= this.y) && (y <= this.y+this.height)) { + if ((x>=0) && (x <= this.width) && (y >= 0) && (y <= this.height)) { + return true } - bbox() { - let bbox; - if (this.children.length > 0) { - if (!bbox) { - bbox = structuredClone(this.children[0].bbox()); - } - for (let child of this.children) { - growBoundingBox(bbox, child.bbox()); - } - } - if (bbox == undefined) { - bbox = { x: { min: 0, max: 0 }, y: { min: 0, max: 0 } }; - } - bbox.x.max *= this.scale_x; - bbox.y.max *= this.scale_y; - bbox.x.min += this.x; - bbox.x.max += this.x; - bbox.y.min += this.y; - bbox.y.max += this.y; - return bbox; + return false + } + bbox() { + let bbox; + if (this.children.length > 0) { + if (!bbox) { + bbox = structuredClone(this.children[0].bbox()); + } + for (let child of this.children) { + growBoundingBox(bbox, child.bbox()); + } } - draw(ctx) { - for (let child of this.children) { - const transform = ctx.getTransform() - ctx.translate(child.x, child.y) - ctx.scale(child.scale_x, child.scale_y) - ctx.rotate(child.rotation) - child.draw(ctx) - ctx.setTransform(transform) - } + if (bbox == undefined) { + bbox = { x: { min: 0, max: 0 }, y: { min: 0, max: 0 } }; } + bbox.x.max *= this.scale_x; + bbox.y.max *= this.scale_y; + bbox.x.min += this.x; + bbox.x.max += this.x; + bbox.y.min += this.y; + bbox.y.max += this.y; + return bbox; + } + draw(ctx) { + for (let child of this.children) { + const transform = ctx.getTransform() + ctx.translate(child.x, child.y) + ctx.scale(child.scale_x, child.scale_y) + ctx.rotate(child.rotation) + child.draw(ctx) + ctx.setTransform(transform) + } + } } class HueSelectionBar extends Widget { - constructor(width, height, x, y, colorCvs) { - super(x, y) - this.width = width - this.height = height - this.colorCvs = colorCvs + constructor(width, height, x, y, colorCvs) { + super(x, y) + this.width = width + this.height = height + this.colorCvs = colorCvs + } + + draw(ctx) { + const [h, s, v] = this.colorCvs.currentHSV + const hueGradient = ctx.createImageData(this.width, this.height); + const data = hueGradient.data; + for (let i = 0; i < data.length; i += 4) { + const x = ((i / 4) % this.width) / this.width; + const y = Math.floor(i / 4 / this.height); + const rgb = hslToRgb(x, 1, 0.5); + data[i + 0] = rgb.r; + data[i + 1] = rgb.g; + data[i + 2] = rgb.b; + data[i + 3] = 255; } + const transform = ctx.getTransform(); + ctx.putImageData(hueGradient, transform.e, transform.f); + // draw pointer + ctx.beginPath(); + ctx.rect( + h * this.width - 2, + 0, + 4, + this.height, + ); + ctx.strokeStyle = "white"; + ctx.stroke(); + } + updateColorFromMouse(x, y) { + let [h, s, v] = this.colorCvs.currentHSV + x = clamp(x / this.width); + let rgb = hsvToRgb(x, s, v); + let alpha = this.colorCvs.currentColor.slice(7, 9) || "ff"; + this.colorCvs.currentColor = rgbToHex(rgb.r, rgb.g, rgb.b) + alpha; + this.colorCvs.currentHSV = [x, s, v] + this.colorCvs.currentAlpha = alpha + } + mousedown(x, y) { + this._globalEvents.add("mousemove") + this._globalEvents.add("mouseup") - draw(ctx) { - const [h, s, v] = this.colorCvs.currentHSV - const hueGradient = ctx.createImageData(this.width, this.height); - const data = hueGradient.data; - for (let i = 0; i < data.length; i += 4) { - const x = ((i / 4) % this.width) / this.width; - const y = Math.floor(i / 4 / this.height); - const rgb = hslToRgb(x, 1, 0.5); - data[i + 0] = rgb.r; - data[i + 1] = rgb.g; - data[i + 2] = rgb.b; - data[i + 3] = 255; - } - const transform = ctx.getTransform(); - ctx.putImageData(hueGradient, transform.e, transform.f); - // draw pointer - ctx.beginPath(); - ctx.rect( - h * this.width - 2, - 0, - 4, - this.height, - ); - ctx.strokeStyle = "white"; - ctx.stroke(); - } - updateColorFromMouse(x, y) { - let [h, s, v] = this.colorCvs.currentHSV - x = clamp(x / this.width); - let rgb = hsvToRgb(x, s, v); - let alpha = this.colorCvs.currentColor.slice(7, 9) || "ff"; - this.colorCvs.currentColor = rgbToHex(rgb.r, rgb.g, rgb.b) + alpha; - this.colorCvs.currentHSV = [x, s, v] - this.colorCvs.currentAlpha = alpha - } - mousedown(x, y) { - this._globalEvents.add("mousemove") - this._globalEvents.add("mouseup") - - this.updateColorFromMouse(x, y) - this.clicked = true; - } - mousemove(x, y) { - if (this.clicked) { - this.updateColorFromMouse(x, y) - } - } - mouseup(x, y) { - this._globalEvents.delete("mousemove") - this._globalEvents.delete("mouseup") - this.clicked = false + this.updateColorFromMouse(x, y) + this.clicked = true; + } + mousemove(x, y) { + if (this.clicked) { + this.updateColorFromMouse(x, y) } + } + mouseup(x, y) { + this._globalEvents.delete("mousemove") + this._globalEvents.delete("mouseup") + this.clicked = false + } } class SaturationValueSelectionGradient extends Widget { - constructor(width, height, x, y, colorCvs) { - super(x, y) - this.width = width - this.height = height - this.colorCvs = colorCvs + constructor(width, height, x, y, colorCvs) { + super(x, y) + this.width = width + this.height = height + this.colorCvs = colorCvs + } + draw(ctx) { + let mainGradient = ctx.createImageData(this.width, this.height); + let data = mainGradient.data; + // let { h, s, v } = hexToHsv(colorCvs.currentColor); + let [h, s, v] = this.colorCvs.currentHSV + for (let i = 0; i < data.length; i += 4) { + let x = ((i / 4) % this.width) / this.width; + let y = Math.floor(i / 4 / this.height) / this.height; + let hue = h; + let rgb = hsvToRgb(hue, x, 1 - y); + data[i + 0] = rgb.r; + data[i + 1] = rgb.g; + data[i + 2] = rgb.b; + data[i + 3] = 255; } - draw(ctx) { - let mainGradient = ctx.createImageData(this.width, this.height); - let data = mainGradient.data; - // let { h, s, v } = hexToHsv(colorCvs.currentColor); - let [h, s, v] = this.colorCvs.currentHSV - for (let i = 0; i < data.length; i += 4) { - let x = ((i / 4) % this.width) / this.width; - let y = Math.floor(i / 4 / this.height) / this.height; - let hue = h; - let rgb = hsvToRgb(hue, x, 1 - y); - data[i + 0] = rgb.r; - data[i + 1] = rgb.g; - data[i + 2] = rgb.b; - data[i + 3] = 255; - } - const transform = ctx.getTransform(); - ctx.putImageData(mainGradient, transform.e, transform.f); - // draw pointer - ctx.beginPath(); - ctx.arc( - s * this.width, - (1 - v) * this.height, - 3, - 0, - 2 * Math.PI, - ); - ctx.strokeStyle = "white"; - ctx.stroke(); - } - updateColorFromMouse(x, y) { - const [h, s, v] = this.colorCvs.currentHSV - const _x = clamp(x / this.width); - const _y = clamp(y / this.height); - const rgb = hsvToRgb(h, _x, 1 - _y); - const alpha = this.colorCvs.currentColor.slice(7, 9) || "ff"; - this.colorCvs.currentColor = rgbToHex(rgb.r, rgb.g, rgb.b) + alpha; - this.colorCvs.currentHSV = [h, _x, 1 - _y] - this.colorCvs.currentAlpha = alpha - } - - mousedown(x, y) { - this._globalEvents.add("mousemove") - this._globalEvents.add("mouseup") - this.updateColorFromMouse(x, y) - this.clicked = true; - } - mousemove(x, y) { - if (this.clicked) { - this.updateColorFromMouse(x, y) - } - } - mouseup(x, y) { - this._globalEvents.delete("mousemove") - this._globalEvents.delete("mouseup") - this.clicked = false + const transform = ctx.getTransform(); + ctx.putImageData(mainGradient, transform.e, transform.f); + // draw pointer + ctx.beginPath(); + ctx.arc( + s * this.width, + (1 - v) * this.height, + 3, + 0, + 2 * Math.PI, + ); + ctx.strokeStyle = "white"; + ctx.stroke(); + } + updateColorFromMouse(x, y) { + const [h, s, v] = this.colorCvs.currentHSV + const _x = clamp(x / this.width); + const _y = clamp(y / this.height); + const rgb = hsvToRgb(h, _x, 1 - _y); + const alpha = this.colorCvs.currentColor.slice(7, 9) || "ff"; + this.colorCvs.currentColor = rgbToHex(rgb.r, rgb.g, rgb.b) + alpha; + this.colorCvs.currentHSV = [h, _x, 1 - _y] + this.colorCvs.currentAlpha = alpha + } + + mousedown(x, y) { + this._globalEvents.add("mousemove") + this._globalEvents.add("mouseup") + this.updateColorFromMouse(x, y) + this.clicked = true; + } + mousemove(x, y) { + if (this.clicked) { + this.updateColorFromMouse(x, y) } + } + mouseup(x, y) { + this._globalEvents.delete("mousemove") + this._globalEvents.delete("mouseup") + this.clicked = false + } } class AlphaSelectionBar extends Widget { - constructor(width, height, x, y, colorCvs) { - super(x, y) - this.width = width - this.height = height - this.colorCvs = colorCvs - } - - draw(ctx) { - drawCheckerboardBackground(ctx, 0, 0, this.width, this.height, 10); - // Vertical gradient - const gradient = ctx.createLinearGradient( 0, 0, 0, this.height); - gradient.addColorStop(0, `${this.colorCvs.currentColor.slice(0, 7)}ff`); // Full color at the top - gradient.addColorStop(1, `${this.colorCvs.currentColor.slice(0, 7)}00`); - ctx.fillStyle = gradient; - ctx.fillRect(0, 0, this.width, this.height); - let alpha = - parseInt(this.colorCvs.currentColor.slice(7, 9) || "ff", 16) / 255; - // draw pointer - ctx.beginPath(); - ctx.rect(0, (1 - alpha) * this.height - 2, this.width, 4); - ctx.strokeStyle = "white"; - ctx.stroke(); - } - updateColorFromMouse(x, y) { - y = 1 - y / this.height; - const alpha = Math.round(clamp(y) * 255).toString(16); - this.colorCvs.currentColor = `${this.colorCvs.currentColor.slice(0, 7)}${alpha}`; - this.colorCvs.currentAlpha = alpha - } - mousedown(x, y) { - this._globalEvents.add("mousemove") - this._globalEvents.add("mouseup") - this.updateColorFromMouse(x, y) - this.clicked = true; - } - mousemove(x, y) { - if (this.clicked) { - this.updateColorFromMouse(x, y) - } - } - mouseup(x, y) { - this._globalEvents.delete("mousemove") - this._globalEvents.delete("mouseup") - this.clicked = false + constructor(width, height, x, y, colorCvs) { + super(x, y) + this.width = width + this.height = height + this.colorCvs = colorCvs + } + + draw(ctx) { + drawCheckerboardBackground(ctx, 0, 0, this.width, this.height, 10); + // Vertical gradient + const gradient = ctx.createLinearGradient( 0, 0, 0, this.height); + gradient.addColorStop(0, `${this.colorCvs.currentColor.slice(0, 7)}ff`); // Full color at the top + gradient.addColorStop(1, `${this.colorCvs.currentColor.slice(0, 7)}00`); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, this.width, this.height); + let alpha = + parseInt(this.colorCvs.currentColor.slice(7, 9) || "ff", 16) / 255; + // draw pointer + ctx.beginPath(); + ctx.rect(0, (1 - alpha) * this.height - 2, this.width, 4); + ctx.strokeStyle = "white"; + ctx.stroke(); + } + updateColorFromMouse(x, y) { + y = 1 - y / this.height; + const alpha = Math.round(clamp(y) * 255).toString(16); + this.colorCvs.currentColor = `${this.colorCvs.currentColor.slice(0, 7)}${alpha}`; + this.colorCvs.currentAlpha = alpha + } + mousedown(x, y) { + this._globalEvents.add("mousemove") + this._globalEvents.add("mouseup") + this.updateColorFromMouse(x, y) + this.clicked = true; + } + mousemove(x, y) { + if (this.clicked) { + this.updateColorFromMouse(x, y) } + } + mouseup(x, y) { + this._globalEvents.delete("mousemove") + this._globalEvents.delete("mouseup") + this.clicked = false + } } class ColorWidget extends Widget { - constructor(width, height, x, y, colorCvs) { - super(x, y) - this.width = width - this.height = height - this.colorCvs = colorCvs - } - draw(ctx) { - drawCheckerboardBackground(ctx, 0, 0, this.width, this.height, 10); - ctx.fillStyle = this.colorCvs.currentColor; - ctx.fillRect(0, 0, this.width, this.height); - } + constructor(width, height, x, y, colorCvs) { + super(x, y) + this.width = width + this.height = height + this.colorCvs = colorCvs + } + draw(ctx) { + drawCheckerboardBackground(ctx, 0, 0, this.width, this.height, 10); + ctx.fillStyle = this.colorCvs.currentColor; + ctx.fillRect(0, 0, this.width, this.height); + } } - class ColorSelectorWidget extends Widget { - constructor(x, y, colorCvs) { - super(x, y) - this.colorCvs = colorCvs - const padding = 10; - const gradwidth = 25; - const ccwidth = 300; - const mainSize = ccwidth - (3 * padding + gradwidth); - this.children = [ - new ColorWidget( - colorCvs.width - 2 * padding, - 50, - padding, - padding, - colorCvs - ), - new HueSelectionBar( - mainSize, - gradwidth, - padding, - 3 * padding + 50 + mainSize, colorCvs - ), - new SaturationValueSelectionGradient( - mainSize, - mainSize, - padding, - 2 * padding + 50, - colorCvs - ), - new AlphaSelectionBar( - gradwidth, - mainSize, - colorCvs.width - (padding + gradwidth), - 2 * padding + 50, - colorCvs - ) - ] - } - draw(ctx) { - const darkMode = - window.matchMedia && - window.matchMedia("(prefers-color-scheme: dark)").matches; - ctx.lineWidth = 2; - if (darkMode) { - ctx.fillStyle = "#333"; - } else { - ctx.fillStyle = "#ccc"; //TODO - } - ctx.fillRect(0, 0, this.colorCvs.width, this.colorCvs.height); - super.draw(ctx) + constructor(x, y, colorCvs) { + super(x, y) + this.colorCvs = colorCvs + const padding = 10; + const gradwidth = 25; + const ccwidth = 300; + const mainSize = ccwidth - (3 * padding + gradwidth); + this.children = [ + new ColorWidget( + colorCvs.width - 2 * padding, + 50, + padding, + padding, + colorCvs + ), + new HueSelectionBar( + mainSize, + gradwidth, + padding, + 3 * padding + 50 + mainSize, colorCvs + ), + new SaturationValueSelectionGradient( + mainSize, + mainSize, + padding, + 2 * padding + 50, + colorCvs + ), + new AlphaSelectionBar( + gradwidth, + mainSize, + colorCvs.width - (padding + gradwidth), + 2 * padding + 50, + colorCvs + ) + ] + } + draw(ctx) { + const darkMode = + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches; + ctx.lineWidth = 2; + if (darkMode) { + ctx.fillStyle = "#333"; + } else { + ctx.fillStyle = "#ccc"; //TODO } + ctx.fillRect(0, 0, this.colorCvs.width, this.colorCvs.height); + super.draw(ctx) + } } +class HBox extends Widget { + constructor(x, y) { + super(x, y) + this.width = 0; + this.height = 0; + } + add(child) { + child.x = this.width + child.y = 0 + this.children.push(child) + this.width += child.width + } +} +class VBox extends Widget { + constructor(x, y) { + super(x, y) + this.width = 0; + this.height = 0; + } + add(child) { + child.x = 0 + child.y = this.height + this.children.push(child) + this.height += child.height + } +} + +class ScrollableWindowHeaders extends Widget { + constructor(x, y, scrollableWindow, scrollDirection, headers) { + this.scrollableWindow = scrollableWindow + this.children = [this.scrollableWindow] + if (scrollDirection & SCROLL.HORIZONTAL) { + this.vbox = new VBox(0, headers.y) + this.children.push(this.vbox) + } + if (scrollDirection & SCROLL.VERTICAL) { + this.hbox = new HBox(0, headers.y) + this.children.push(this.hbox) + } + } + wheel(dx, dy) { + + } +} + +class ScrollableWindow extends Widget { + constructor(x, y) { + super(x, y) + this.offsetX = 0 + this.offsetY = 0 + } + draw(ctx) { + ctx.save() + ctx.beginPath() + ctx.rect(0, 0, this.width, this.height) + ctx.clip() + ctx.translate(this.offsetX, this.offsetY) + this.drawContents(ctx) + ctx.restore() + } + drawContents(ctx) {} +} + +class TimelineWindow extends ScrollableWindow { + constructor(x, y, context) { + super(x, y) + this.context = context + this.width = 100 + this.height = 100 + } + drawContents(ctx) { + const startFrame = Math.floor(-this.offsetX / frameWidth) + const frameCount = (this.width / frameWidth) + 1 + for (let k = this.context.activeObject.allLayers.length - 1; k >= 0; k--) { + let layer = this.context.activeObject.allLayers[k]; + // if (layer instanceof Layer) { + if (layer.frames) { + // Draw background + for (let j = startFrame; j < startFrame + frameCount; j++) { + ctx.fillStyle = (j + 1) % 5 == 0 ? shade : backgroundColor; + drawBorderedRect( + ctx, + j * frameWidth, + 0, + frameWidth, + layerHeight, + shadow, + highlight, + shadow, + shadow, + ); + } + // Draw frames + for (let j=0; j