work on converting timeline to widgets

This commit is contained in:
Skyler Lehmkuhl 2025-01-16 21:40:05 -05:00
parent 33896c693d
commit b76dcc7a7e
2 changed files with 669 additions and 463 deletions

View File

@ -60,7 +60,7 @@ import {
shadow, shadow,
} from "./styles.js"; } from "./styles.js";
import { Icon } from "./icon.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 { const {
writeTextFile: writeTextFile, writeTextFile: writeTextFile,
readTextFile: readTextFile, readTextFile: readTextFile,
@ -6302,6 +6302,9 @@ function timeline() {
let timeline_cvs = document.createElement("canvas"); let timeline_cvs = document.createElement("canvas");
timeline_cvs.className = "timeline"; timeline_cvs.className = "timeline";
// Start building widget hierarchy
timeline_cvs.timelinewindow = new TimelineWindow(0, 0, context)
// Load icons for show/hide layer // Load icons for show/hide layer
timeline_cvs.icons = {}; timeline_cvs.icons = {};
timeline_cvs.icons.volume_up_fill = new Icon("assets/volume-up-fill.svg"); timeline_cvs.icons.volume_up_fill = new Icon("assets/volume-up-fill.svg");
@ -6359,6 +6362,8 @@ function timeline() {
0, 0,
Math.min(maxScroll, timeline_cvs.offsetY + deltaY), 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(); const currentTime = Date.now();
if (currentTime - lastResizeTime > throttleIntervalMs) { if (currentTime - lastResizeTime > throttleIntervalMs) {
@ -7139,6 +7144,14 @@ function renderLayers() {
ctx.fillRect(0, 0, width, height); ctx.fillRect(0, 0, width, height);
ctx.lineWidth = 1; 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 // Draw timeline top
ctx.save(); ctx.save();
ctx.save(); ctx.save();
@ -7222,164 +7235,166 @@ function renderLayers() {
labelColor, labelColor,
); );
ctx.restore(); ctx.restore();
ctx.save();
ctx.beginPath(); // ctx.save();
ctx.rect(layerWidth, i * layerHeight, width, layerHeight); // ctx.beginPath();
ctx.clip(); // ctx.rect(layerWidth, i * layerHeight, width, layerHeight);
ctx.translate(layerWidth - offsetX, i * layerHeight); // ctx.clip();
// Draw empty frames // ctx.translate(layerWidth - offsetX, i * layerHeight);
for (let j = Math.floor(offsetX / frameWidth); j < frameCount; j++) { // // Draw empty frames
ctx.fillStyle = (j + 1) % 5 == 0 ? shade : backgroundColor; // for (let j = Math.floor(offsetX / frameWidth); j < frameCount; j++) {
drawBorderedRect( // ctx.fillStyle = (j + 1) % 5 == 0 ? shade : backgroundColor;
ctx, // drawBorderedRect(
j * frameWidth, // ctx,
0, // j * frameWidth,
frameWidth, // 0,
layerHeight, // frameWidth,
shadow, // layerHeight,
highlight, // shadow,
shadow, // highlight,
shadow, // shadow,
); // shadow,
} // );
// Draw existing frames // }
if (layer instanceof Layer) { // // Draw existing frames
for (let j=0; j<layer.frames.length; j++) { // if (layer instanceof Layer) {
const frameInfo = layer.getFrameValue(j) // for (let j=0; j<layer.frames.length; j++) {
if (frameInfo.valueAtN) { // const frameInfo = layer.getFrameValue(j)
ctx.fillStyle = foregroundColor; // if (frameInfo.valueAtN) {
drawBorderedRect( // ctx.fillStyle = foregroundColor;
ctx, // drawBorderedRect(
j * frameWidth, // ctx,
0, // j * frameWidth,
frameWidth, // 0,
layerHeight, // frameWidth,
highlight, // layerHeight,
shadow, // highlight,
shadow, // shadow,
shadow, // shadow,
); // shadow,
ctx.fillStyle = "#111"; // );
ctx.beginPath(); // ctx.fillStyle = "#111";
ctx.arc( // ctx.beginPath();
(j + 0.5) * frameWidth, // ctx.arc(
layerHeight * 0.75, // (j + 0.5) * frameWidth,
frameWidth * 0.25, // layerHeight * 0.75,
0, // frameWidth * 0.25,
2 * Math.PI, // 0,
); // 2 * Math.PI,
ctx.fill(); // );
if (frameInfo.valueAtN.keyTypes.has("motion")) { // ctx.fill();
ctx.strokeStyle = "#7a00b3"; // if (frameInfo.valueAtN.keyTypes.has("motion")) {
ctx.lineWidth = 2; // ctx.strokeStyle = "#7a00b3";
ctx.beginPath() // ctx.lineWidth = 2;
ctx.moveTo(j*frameWidth, layerHeight*0.25) // ctx.beginPath()
ctx.lineTo((j+1)*frameWidth, layerHeight*0.25) // ctx.moveTo(j*frameWidth, layerHeight*0.25)
ctx.stroke() // ctx.lineTo((j+1)*frameWidth, layerHeight*0.25)
} // ctx.stroke()
if (frameInfo.valueAtN.keyTypes.has("shape")) { // }
ctx.strokeStyle = "#9bff9b"; // if (frameInfo.valueAtN.keyTypes.has("shape")) {
ctx.lineWidth = 2; // ctx.strokeStyle = "#9bff9b";
ctx.beginPath() // ctx.lineWidth = 2;
ctx.moveTo(j*frameWidth, layerHeight*0.35) // ctx.beginPath()
ctx.lineTo((j+1)*frameWidth, layerHeight*0.35) // ctx.moveTo(j*frameWidth, layerHeight*0.35)
ctx.stroke() // ctx.lineTo((j+1)*frameWidth, layerHeight*0.35)
} // ctx.stroke()
} else if (frameInfo.prev && frameInfo.next) { // }
ctx.fillStyle = foregroundColor; // } else if (frameInfo.prev && frameInfo.next) {
drawBorderedRect( // ctx.fillStyle = foregroundColor;
ctx, // drawBorderedRect(
j * frameWidth, // ctx,
0, // j * frameWidth,
frameWidth, // 0,
layerHeight, // frameWidth,
highlight, // layerHeight,
shadow, // highlight,
backgroundColor, // shadow,
backgroundColor, // backgroundColor,
); // backgroundColor,
if (frameInfo.prev.keyTypes.has("motion")) { // );
ctx.strokeStyle = "#7a00b3"; // if (frameInfo.prev.keyTypes.has("motion")) {
ctx.lineWidth = 2; // ctx.strokeStyle = "#7a00b3";
ctx.beginPath() // ctx.lineWidth = 2;
ctx.moveTo(j*frameWidth, layerHeight*0.25) // ctx.beginPath()
ctx.lineTo((j+1)*frameWidth, layerHeight*0.25) // ctx.moveTo(j*frameWidth, layerHeight*0.25)
ctx.stroke() // ctx.lineTo((j+1)*frameWidth, layerHeight*0.25)
} // ctx.stroke()
if (frameInfo.prev.keyTypes.has("shape")) { // }
ctx.strokeStyle = "#9bff9b"; // if (frameInfo.prev.keyTypes.has("shape")) {
ctx.lineWidth = 2; // ctx.strokeStyle = "#9bff9b";
ctx.beginPath() // ctx.lineWidth = 2;
ctx.moveTo(j*frameWidth, layerHeight*0.35) // ctx.beginPath()
ctx.lineTo((j+1)*frameWidth, layerHeight*0.35) // ctx.moveTo(j*frameWidth, layerHeight*0.35)
ctx.stroke() // ctx.lineTo((j+1)*frameWidth, layerHeight*0.35)
} // ctx.stroke()
} // }
} // }
// layer.frames.forEach((frame, j) => { // }
// if (!frame) return; // // layer.frames.forEach((frame, j) => {
// switch (frame.frameType) { // // if (!frame) return;
// case "keyframe": // // switch (frame.frameType) {
// ctx.fillStyle = foregroundColor; // // case "keyframe":
// drawBorderedRect( // // ctx.fillStyle = foregroundColor;
// ctx, // // drawBorderedRect(
// j * frameWidth, // // ctx,
// 0, // // j * frameWidth,
// frameWidth, // // 0,
// layerHeight, // // frameWidth,
// highlight, // // layerHeight,
// shadow, // // highlight,
// shadow, // // shadow,
// shadow, // // shadow,
// ); // // shadow,
// ctx.fillStyle = "#111"; // // );
// ctx.beginPath(); // // ctx.fillStyle = "#111";
// ctx.arc( // // ctx.beginPath();
// (j + 0.5) * frameWidth, // // ctx.arc(
// layerHeight * 0.75, // // (j + 0.5) * frameWidth,
// frameWidth * 0.25, // // layerHeight * 0.75,
// 0, // // frameWidth * 0.25,
// 2 * Math.PI, // // 0,
// ); // // 2 * Math.PI,
// ctx.fill(); // // );
// break; // // ctx.fill();
// case "normal": // // break;
// ctx.fillStyle = foregroundColor; // // case "normal":
// drawBorderedRect( // // ctx.fillStyle = foregroundColor;
// ctx, // // drawBorderedRect(
// j * frameWidth, // // ctx,
// 0, // // j * frameWidth,
// frameWidth, // // 0,
// layerHeight, // // frameWidth,
// highlight, // // layerHeight,
// shadow, // // highlight,
// backgroundColor, // // shadow,
// backgroundColor, // // backgroundColor,
// ); // // backgroundColor,
// break; // // );
// case "motion": // // break;
// ctx.fillStyle = "#7a00b3"; // // case "motion":
// ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight); // // ctx.fillStyle = "#7a00b3";
// break; // // ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight);
// case "shape": // // break;
// ctx.fillStyle = "#9bff9b"; // // case "shape":
// ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight); // // ctx.fillStyle = "#9bff9b";
// break; // // ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight);
// } // // break;
// }); // // }
} else if (layer instanceof AudioLayer) { // // });
// TODO: split waveform into chunks // } else if (layer instanceof AudioLayer) {
for (let i in layer.sounds) { // // TODO: split waveform into chunks
let sound = layer.sounds[i]; // for (let i in layer.sounds) {
// layerTrack.appendChild(sound.img) // let sound = layer.sounds[i];
ctx.drawImage(sound.img, 0, 0); // // layerTrack.appendChild(sound.img)
} // ctx.drawImage(sound.img, 0, 0);
} // }
// if (context.activeObject.currentFrameNum) // }
ctx.restore(); // // if (context.activeObject.currentFrameNum)
// ctx.restore();
i++; i++;
} }
ctx.restore(); ctx.restore();
// Draw highlighted frame // Draw highlighted frame
ctx.save(); ctx.save();
ctx.translate(layerWidth - offsetX, -offsetY); ctx.translate(layerWidth - offsetX, -offsetY);

View File

@ -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) { function growBoundingBox(bboxa, bboxb) {
bboxa.x.min = Math.min(bboxa.x.min, bboxb.x.min); bboxa.x.min = Math.min(bboxa.x.min, bboxb.x.min);
bboxa.y.min = Math.min(bboxa.y.min, bboxb.y.min); bboxa.y.min = Math.min(bboxa.y.min, bboxb.y.min);
bboxa.x.max = Math.max(bboxa.x.max, bboxb.x.max); bboxa.x.max = Math.max(bboxa.x.max, bboxb.x.max);
bboxa.y.max = Math.max(bboxa.y.max, bboxb.y.max); bboxa.y.max = Math.max(bboxa.y.max, bboxb.y.max);
} }
const SCROLL = {
HORIZONTAL: 1,
VERTICAL: 2,
}
class Widget { class Widget {
constructor(x, y) { constructor(x, y) {
this._globalEvents = new Set() this._globalEvents = new Set()
this.x = x this.x = x
this.y = y this.y = y
this.scale_x = 1 this.scale_x = 1
this.scale_y = 1 this.scale_y = 1
this.rotation = 0 this.rotation = 0
this.children = [] 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) { const eventTypes = [
for (let child of this.children) { "mousedown",
// Adjust for translation "mousemove",
const dx = x - child.x; "mouseup",
const dy = y - child.y; "dblclick"
]
// Apply inverse rotation if (eventTypes.indexOf(eventType)!=-1) {
const cosTheta = Math.cos(child.rotation); if (typeof(this[eventType]) == "function") {
const sinTheta = Math.sin(child.rotation); this[eventType](x, y)
}
// 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)
}
}
} }
hitTest(x, y) { }
// if ((x >= this.x) && (x <= this.x+this.width) && hitTest(x, y) {
// (y >= this.y) && (y <= this.y+this.height)) { // if ((x >= this.x) && (x <= this.x+this.width) &&
if ((x>=0) && (x <= this.width) && (y >= 0) && (y <= this.height)) { // (y >= this.y) && (y <= this.y+this.height)) {
return true if ((x>=0) && (x <= this.width) && (y >= 0) && (y <= this.height)) {
} return true
return false
} }
bbox() { return false
let bbox; }
if (this.children.length > 0) { bbox() {
if (!bbox) { let bbox;
bbox = structuredClone(this.children[0].bbox()); if (this.children.length > 0) {
} if (!bbox) {
for (let child of this.children) { bbox = structuredClone(this.children[0].bbox());
growBoundingBox(bbox, child.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;
} }
draw(ctx) { if (bbox == undefined) {
for (let child of this.children) { bbox = { x: { min: 0, max: 0 }, y: { min: 0, max: 0 } };
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)
}
} }
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 { class HueSelectionBar extends Widget {
constructor(width, height, x, y, colorCvs) { constructor(width, height, x, y, colorCvs) {
super(x, y) super(x, y)
this.width = width this.width = width
this.height = height this.height = height
this.colorCvs = colorCvs 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) { this.updateColorFromMouse(x, y)
const [h, s, v] = this.colorCvs.currentHSV this.clicked = true;
const hueGradient = ctx.createImageData(this.width, this.height); }
const data = hueGradient.data; mousemove(x, y) {
for (let i = 0; i < data.length; i += 4) { if (this.clicked) {
const x = ((i / 4) % this.width) / this.width; this.updateColorFromMouse(x, y)
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
} }
}
mouseup(x, y) {
this._globalEvents.delete("mousemove")
this._globalEvents.delete("mouseup")
this.clicked = false
}
} }
class SaturationValueSelectionGradient extends Widget { class SaturationValueSelectionGradient extends Widget {
constructor(width, height, x, y, colorCvs) { constructor(width, height, x, y, colorCvs) {
super(x, y) super(x, y)
this.width = width this.width = width
this.height = height this.height = height
this.colorCvs = colorCvs 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) { const transform = ctx.getTransform();
let mainGradient = ctx.createImageData(this.width, this.height); ctx.putImageData(mainGradient, transform.e, transform.f);
let data = mainGradient.data; // draw pointer
// let { h, s, v } = hexToHsv(colorCvs.currentColor); ctx.beginPath();
let [h, s, v] = this.colorCvs.currentHSV ctx.arc(
for (let i = 0; i < data.length; i += 4) { s * this.width,
let x = ((i / 4) % this.width) / this.width; (1 - v) * this.height,
let y = Math.floor(i / 4 / this.height) / this.height; 3,
let hue = h; 0,
let rgb = hsvToRgb(hue, x, 1 - y); 2 * Math.PI,
data[i + 0] = rgb.r; );
data[i + 1] = rgb.g; ctx.strokeStyle = "white";
data[i + 2] = rgb.b; ctx.stroke();
data[i + 3] = 255; }
} updateColorFromMouse(x, y) {
const transform = ctx.getTransform(); const [h, s, v] = this.colorCvs.currentHSV
ctx.putImageData(mainGradient, transform.e, transform.f); const _x = clamp(x / this.width);
// draw pointer const _y = clamp(y / this.height);
ctx.beginPath(); const rgb = hsvToRgb(h, _x, 1 - _y);
ctx.arc( const alpha = this.colorCvs.currentColor.slice(7, 9) || "ff";
s * this.width, this.colorCvs.currentColor = rgbToHex(rgb.r, rgb.g, rgb.b) + alpha;
(1 - v) * this.height, this.colorCvs.currentHSV = [h, _x, 1 - _y]
3, this.colorCvs.currentAlpha = alpha
0, }
2 * Math.PI,
); mousedown(x, y) {
ctx.strokeStyle = "white"; this._globalEvents.add("mousemove")
ctx.stroke(); this._globalEvents.add("mouseup")
} this.updateColorFromMouse(x, y)
updateColorFromMouse(x, y) { this.clicked = true;
const [h, s, v] = this.colorCvs.currentHSV }
const _x = clamp(x / this.width); mousemove(x, y) {
const _y = clamp(y / this.height); if (this.clicked) {
const rgb = hsvToRgb(h, _x, 1 - _y); this.updateColorFromMouse(x, 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
} }
}
mouseup(x, y) {
this._globalEvents.delete("mousemove")
this._globalEvents.delete("mouseup")
this.clicked = false
}
} }
class AlphaSelectionBar extends Widget { class AlphaSelectionBar extends Widget {
constructor(width, height, x, y, colorCvs) { constructor(width, height, x, y, colorCvs) {
super(x, y) super(x, y)
this.width = width this.width = width
this.height = height this.height = height
this.colorCvs = colorCvs this.colorCvs = colorCvs
} }
draw(ctx) { draw(ctx) {
drawCheckerboardBackground(ctx, 0, 0, this.width, this.height, 10); drawCheckerboardBackground(ctx, 0, 0, this.width, this.height, 10);
// Vertical gradient // Vertical gradient
const gradient = ctx.createLinearGradient( 0, 0, 0, this.height); 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(0, `${this.colorCvs.currentColor.slice(0, 7)}ff`); // Full color at the top
gradient.addColorStop(1, `${this.colorCvs.currentColor.slice(0, 7)}00`); gradient.addColorStop(1, `${this.colorCvs.currentColor.slice(0, 7)}00`);
ctx.fillStyle = gradient; ctx.fillStyle = gradient;
ctx.fillRect(0, 0, this.width, this.height); ctx.fillRect(0, 0, this.width, this.height);
let alpha = let alpha =
parseInt(this.colorCvs.currentColor.slice(7, 9) || "ff", 16) / 255; parseInt(this.colorCvs.currentColor.slice(7, 9) || "ff", 16) / 255;
// draw pointer // draw pointer
ctx.beginPath(); ctx.beginPath();
ctx.rect(0, (1 - alpha) * this.height - 2, this.width, 4); ctx.rect(0, (1 - alpha) * this.height - 2, this.width, 4);
ctx.strokeStyle = "white"; ctx.strokeStyle = "white";
ctx.stroke(); ctx.stroke();
} }
updateColorFromMouse(x, y) { updateColorFromMouse(x, y) {
y = 1 - y / this.height; y = 1 - y / this.height;
const alpha = Math.round(clamp(y) * 255).toString(16); const alpha = Math.round(clamp(y) * 255).toString(16);
this.colorCvs.currentColor = `${this.colorCvs.currentColor.slice(0, 7)}${alpha}`; this.colorCvs.currentColor = `${this.colorCvs.currentColor.slice(0, 7)}${alpha}`;
this.colorCvs.currentAlpha = alpha this.colorCvs.currentAlpha = alpha
} }
mousedown(x, y) { mousedown(x, y) {
this._globalEvents.add("mousemove") this._globalEvents.add("mousemove")
this._globalEvents.add("mouseup") this._globalEvents.add("mouseup")
this.updateColorFromMouse(x, y) this.updateColorFromMouse(x, y)
this.clicked = true; this.clicked = true;
} }
mousemove(x, y) { mousemove(x, y) {
if (this.clicked) { if (this.clicked) {
this.updateColorFromMouse(x, y) this.updateColorFromMouse(x, y)
}
}
mouseup(x, y) {
this._globalEvents.delete("mousemove")
this._globalEvents.delete("mouseup")
this.clicked = false
} }
}
mouseup(x, y) {
this._globalEvents.delete("mousemove")
this._globalEvents.delete("mouseup")
this.clicked = false
}
} }
class ColorWidget extends Widget { class ColorWidget extends Widget {
constructor(width, height, x, y, colorCvs) { constructor(width, height, x, y, colorCvs) {
super(x, y) super(x, y)
this.width = width this.width = width
this.height = height this.height = height
this.colorCvs = colorCvs this.colorCvs = colorCvs
} }
draw(ctx) { draw(ctx) {
drawCheckerboardBackground(ctx, 0, 0, this.width, this.height, 10); drawCheckerboardBackground(ctx, 0, 0, this.width, this.height, 10);
ctx.fillStyle = this.colorCvs.currentColor; ctx.fillStyle = this.colorCvs.currentColor;
ctx.fillRect(0, 0, this.width, this.height); ctx.fillRect(0, 0, this.width, this.height);
} }
} }
class ColorSelectorWidget extends Widget { class ColorSelectorWidget extends Widget {
constructor(x, y, colorCvs) { constructor(x, y, colorCvs) {
super(x, y) super(x, y)
this.colorCvs = colorCvs this.colorCvs = colorCvs
const padding = 10; const padding = 10;
const gradwidth = 25; const gradwidth = 25;
const ccwidth = 300; const ccwidth = 300;
const mainSize = ccwidth - (3 * padding + gradwidth); const mainSize = ccwidth - (3 * padding + gradwidth);
this.children = [ this.children = [
new ColorWidget( new ColorWidget(
colorCvs.width - 2 * padding, colorCvs.width - 2 * padding,
50, 50,
padding, padding,
padding, padding,
colorCvs colorCvs
), ),
new HueSelectionBar( new HueSelectionBar(
mainSize, mainSize,
gradwidth, gradwidth,
padding, padding,
3 * padding + 50 + mainSize, colorCvs 3 * padding + 50 + mainSize, colorCvs
), ),
new SaturationValueSelectionGradient( new SaturationValueSelectionGradient(
mainSize, mainSize,
mainSize, mainSize,
padding, padding,
2 * padding + 50, 2 * padding + 50,
colorCvs colorCvs
), ),
new AlphaSelectionBar( new AlphaSelectionBar(
gradwidth, gradwidth,
mainSize, mainSize,
colorCvs.width - (padding + gradwidth), colorCvs.width - (padding + gradwidth),
2 * padding + 50, 2 * padding + 50,
colorCvs colorCvs
) )
] ]
} }
draw(ctx) { draw(ctx) {
const darkMode = const darkMode =
window.matchMedia && window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches; window.matchMedia("(prefers-color-scheme: dark)").matches;
ctx.lineWidth = 2; ctx.lineWidth = 2;
if (darkMode) { if (darkMode) {
ctx.fillStyle = "#333"; ctx.fillStyle = "#333";
} else { } else {
ctx.fillStyle = "#ccc"; //TODO ctx.fillStyle = "#ccc"; //TODO
}
ctx.fillRect(0, 0, this.colorCvs.width, this.colorCvs.height);
super.draw(ctx)
} }
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<layer.frames.length; j++) {
const frameInfo = layer.getFrameValue(j)
if (frameInfo.valueAtN) {
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();
if (frameInfo.valueAtN.keyTypes.has("motion")) {
ctx.strokeStyle = "#7a00b3";
ctx.lineWidth = 2;
ctx.beginPath()
ctx.moveTo(j*frameWidth, layerHeight*0.25)
ctx.lineTo((j+1)*frameWidth, layerHeight*0.25)
ctx.stroke()
}
if (frameInfo.valueAtN.keyTypes.has("shape")) {
ctx.strokeStyle = "#9bff9b";
ctx.lineWidth = 2;
ctx.beginPath()
ctx.moveTo(j*frameWidth, layerHeight*0.35)
ctx.lineTo((j+1)*frameWidth, layerHeight*0.35)
ctx.stroke()
}
} else if (frameInfo.prev && frameInfo.next) {
ctx.fillStyle = foregroundColor;
drawBorderedRect(
ctx,
j * frameWidth,
0,
frameWidth,
layerHeight,
highlight,
shadow,
backgroundColor,
backgroundColor,
);
if (frameInfo.prev.keyTypes.has("motion")) {
ctx.strokeStyle = "#7a00b3";
ctx.lineWidth = 2;
ctx.beginPath()
ctx.moveTo(j*frameWidth, layerHeight*0.25)
ctx.lineTo((j+1)*frameWidth, layerHeight*0.25)
ctx.stroke()
}
if (frameInfo.prev.keyTypes.has("shape")) {
ctx.strokeStyle = "#9bff9b";
ctx.lineWidth = 2;
ctx.beginPath()
ctx.moveTo(j*frameWidth, layerHeight*0.35)
ctx.lineTo((j+1)*frameWidth, layerHeight*0.35)
ctx.stroke()
}
}
}
// } else if (layer instanceof AudioLayer) {
} else if (layer.sounds) {
// TODO: split waveform into chunks
for (let i in layer.sounds) {
let sound = layer.sounds[i];
ctx.drawImage(sound.img, 0, 0);
}
}
ctx.translate(0,layerHeight)
}
}
mousedown(x, y) {
}
}
export { export {
Widget, SCROLL,
HueSelectionBar, Widget,
SaturationValueSelectionGradient, HueSelectionBar,
AlphaSelectionBar, SaturationValueSelectionGradient,
ColorWidget, AlphaSelectionBar,
ColorSelectorWidget ColorWidget,
ColorSelectorWidget,
HBox, VBox,
ScrollableWindow,
ScrollableWindowHeaders,
TimelineWindow
}; };