work on converting timeline to widgets
This commit is contained in:
parent
33896c693d
commit
b76dcc7a7e
257
src/main.js
257
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,104 +7235,32 @@ 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<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()
|
||||
}
|
||||
}
|
||||
}
|
||||
// layer.frames.forEach((frame, j) => {
|
||||
// if (!frame) return;
|
||||
// switch (frame.frameType) {
|
||||
// case "keyframe":
|
||||
|
||||
// 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<layer.frames.length; j++) {
|
||||
// const frameInfo = layer.getFrameValue(j)
|
||||
// if (frameInfo.valueAtN) {
|
||||
// ctx.fillStyle = foregroundColor;
|
||||
// drawBorderedRect(
|
||||
// ctx,
|
||||
|
|
@ -7342,8 +7283,23 @@ function renderLayers() {
|
|||
// 2 * Math.PI,
|
||||
// );
|
||||
// ctx.fill();
|
||||
// break;
|
||||
// case "normal":
|
||||
// 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,
|
||||
|
|
@ -7356,30 +7312,89 @@ function renderLayers() {
|
|||
// 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;
|
||||
// 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()
|
||||
// }
|
||||
// });
|
||||
} 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();
|
||||
// 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;
|
||||
// // 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);
|
||||
|
|
|
|||
199
src/widgets.js
199
src/widgets.js
|
|
@ -1,11 +1,17 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
const SCROLL = {
|
||||
HORIZONTAL: 1,
|
||||
VERTICAL: 2,
|
||||
}
|
||||
|
||||
class Widget {
|
||||
constructor(x, y) {
|
||||
|
|
@ -276,7 +282,6 @@ class ColorWidget extends Widget {
|
|||
ctx.fillRect(0, 0, this.width, this.height);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorSelectorWidget extends Widget {
|
||||
constructor(x, y, colorCvs) {
|
||||
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 {
|
||||
SCROLL,
|
||||
Widget,
|
||||
HueSelectionBar,
|
||||
SaturationValueSelectionGradient,
|
||||
AlphaSelectionBar,
|
||||
ColorWidget,
|
||||
ColorSelectorWidget
|
||||
ColorSelectorWidget,
|
||||
HBox, VBox,
|
||||
ScrollableWindow,
|
||||
ScrollableWindowHeaders,
|
||||
TimelineWindow
|
||||
};
|
||||
Loading…
Reference in New Issue