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,104 +7235,32 @@ 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,
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()
}
}
}
// layer.frames.forEach((frame, j) => {
// if (!frame) return;
// switch (frame.frameType) {
// case "keyframe":
// ctx.fillStyle = foregroundColor; // ctx.fillStyle = foregroundColor;
// drawBorderedRect( // drawBorderedRect(
// ctx, // ctx,
@ -7342,8 +7283,23 @@ function renderLayers() {
// 2 * Math.PI, // 2 * Math.PI,
// ); // );
// ctx.fill(); // ctx.fill();
// break; // if (frameInfo.valueAtN.keyTypes.has("motion")) {
// case "normal": // 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; // ctx.fillStyle = foregroundColor;
// drawBorderedRect( // drawBorderedRect(
// ctx, // ctx,
@ -7356,30 +7312,89 @@ function renderLayers() {
// backgroundColor, // backgroundColor,
// backgroundColor, // backgroundColor,
// ); // );
// break; // if (frameInfo.prev.keyTypes.has("motion")) {
// case "motion": // ctx.strokeStyle = "#7a00b3";
// ctx.fillStyle = "#7a00b3"; // ctx.lineWidth = 2;
// ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight); // ctx.beginPath()
// break; // ctx.moveTo(j*frameWidth, layerHeight*0.25)
// case "shape": // ctx.lineTo((j+1)*frameWidth, layerHeight*0.25)
// ctx.fillStyle = "#9bff9b"; // ctx.stroke()
// ctx.fillRect(j * frameWidth, 0, frameWidth, layerHeight);
// break;
// } // }
// }); // if (frameInfo.prev.keyTypes.has("shape")) {
} else if (layer instanceof AudioLayer) { // ctx.strokeStyle = "#9bff9b";
// TODO: split waveform into chunks // ctx.lineWidth = 2;
for (let i in layer.sounds) { // ctx.beginPath()
let sound = layer.sounds[i]; // ctx.moveTo(j*frameWidth, layerHeight*0.35)
// layerTrack.appendChild(sound.img) // ctx.lineTo((j+1)*frameWidth, layerHeight*0.35)
ctx.drawImage(sound.img, 0, 0); // ctx.stroke()
} // }
} // }
// if (context.activeObject.currentFrameNum) // }
ctx.restore(); // // layer.frames.forEach((frame, 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++; 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,4 +1,5 @@
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);
@ -7,6 +8,11 @@ function growBoundingBox(bboxa, bboxb) {
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()
@ -276,7 +282,6 @@ class ColorWidget extends Widget {
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)
@ -330,11 +335,197 @@ class ColorSelectorWidget extends Widget {
} }
} }
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 {
SCROLL,
Widget, Widget,
HueSelectionBar, HueSelectionBar,
SaturationValueSelectionGradient, SaturationValueSelectionGradient,
AlphaSelectionBar, AlphaSelectionBar,
ColorWidget, ColorWidget,
ColorSelectorWidget ColorSelectorWidget,
HBox, VBox,
ScrollableWindow,
ScrollableWindowHeaders,
TimelineWindow
}; };