WIP timeline element adjust to seconds from ms

This commit is contained in:
Lee-Orr
2024-03-08 13:46:28 -05:00
parent 2419f82e09
commit 2e8a0fd798
@@ -22,7 +22,7 @@ import styles from "./anim-timeline.element.scss?inline";
// which would look something like: // which would look something like:
// ``` // ```
// @memo(["rangeStart", "rangeEnd", "_renderWidth"]) // @memo(["rangeStart", "rangeEnd", "_renderWidth"])
// msPerPx = new Computed(() => (this.rangeEnd - this.rangeStart) / this._renderWidth); // secondsPerPx = new Computed(() => (this.rangeEnd - this.rangeStart) / this._renderWidth);
// ``` // ```
// But that feels a little obtuse and overengineered. In this particular // But that feels a little obtuse and overengineered. In this particular
// case we're probably only saving on the order of nanoseconds, so I'm // case we're probably only saving on the order of nanoseconds, so I'm
@@ -54,19 +54,19 @@ class Computed extends Number {
export class AnimTimelineElement extends LitElement { export class AnimTimelineElement extends LitElement {
static override styles = unsafeCSS(styles); static override styles = unsafeCSS(styles);
/** The time (in ms) at the left edge of the timeline view */ /** The time (in seconds) at the left edge of the timeline view */
@property({ type: Number }) rangeStart = 0; @property({ type: Number }) rangeStart = 0;
/** The time (in ms) at the right edge of the timeline view */ /** The time (in seconds) at the right edge of the timeline view */
@property({ type: Number }) rangeEnd = 5_000; @property({ type: Number }) rangeEnd = 5;
/** The time (in ms) at the beginning of the active clip range */ /** The time (in seconds) at the beginning of the active clip range */
@property({ type: Number }) clipStart = 0; @property({ type: Number }) clipStart = 0;
/** The time (in ms) at the end of the active clip range */ /** The time (in seconds) at the end of the active clip range */
@property({ type: Number }) clipEnd = 15_000; @property({ type: Number }) clipEnd = 15;
/** The time (in ms) of the current play-head position */ /** The time (in seconds) of the current play-head position */
@property({ type: Number }) playHead = 0; @property({ type: Number }) playHead = 0;
/** Keyframe positions (in ms) */ /** Keyframe positions (in seconds) */
@property({ attribute: false }) keyframes: Record<string, number> = {}; @property({ attribute: false }) keyframes: Record<string, number> = {};
@state() _selectedKeyframe: string | null = null; @state() _selectedKeyframe: string | null = null;
@@ -74,7 +74,7 @@ export class AnimTimelineElement extends LitElement {
/** The width (in px) of the timeline view */ /** The width (in px) of the timeline view */
@state() _renderWidth!: number; @state() _renderWidth!: number;
/** The offset from 0ms of the left edge of the timeline view, in px */ /** The offset from 0seconds of the left edge of the timeline view, in px */
@state() _offsetX = 0; @state() _offsetX = 0;
/** The vertical scroll offset (in px) */ /** The vertical scroll offset (in px) */
@state() _offsetY = 0; @state() _offsetY = 0;
@@ -85,8 +85,8 @@ export class AnimTimelineElement extends LitElement {
*/ */
@state() _panning = false; @state() _panning = false;
/** How many ms are represented by each px of horizontal space. */ /** How many seconds are represented by each px of horizontal space. */
msPerPx = new Computed(() => (this.rangeEnd - this.rangeStart) / this._renderWidth); pxPerSecond = new Computed(() => (this.rangeEnd - this.rangeStart) / this._renderWidth);
#resizeObserver: ResizeObserver; #resizeObserver: ResizeObserver;
@@ -112,7 +112,7 @@ export class AnimTimelineElement extends LitElement {
|| changes.has("rangeEnd") || changes.has("rangeEnd")
|| changes.has("_renderWidth") || changes.has("_renderWidth")
) { ) {
this.msPerPx.invalidate(); this.pxPerSecond.invalidate();
} }
super.update(changes); super.update(changes);
@@ -158,7 +158,7 @@ export class AnimTimelineElement extends LitElement {
// FIXME: When zooming the timeline, the transform origin should be // FIXME: When zooming the timeline, the transform origin should be
// centered on the mouse cursor. Currently this does not work as // centered on the mouse cursor. Currently this does not work as
// intended, always centering on the 00:00 timestamp instead. // intended, always centering on the 00:00 timestamp instead.
const t = ((this._offsetX + origin) * this.msPerPx.get()) / currentRange; const t = ((this._offsetX + origin) * this.pxPerSecond.get()) / currentRange;
console.log(`t: ${t}`); console.log(`t: ${t}`);
console.log(`deltaStart: ${-deltaRange * t}`); console.log(`deltaStart: ${-deltaRange * t}`);
console.log(`deltaEnd: ${deltaRange * (1 - t)}`); console.log(`deltaEnd: ${deltaRange * (1 - t)}`);
@@ -184,24 +184,24 @@ export class AnimTimelineElement extends LitElement {
#pan(deltaPx: number): void { #pan(deltaPx: number): void {
this._offsetX += deltaPx; this._offsetX += deltaPx;
const msPerPx = this.msPerPx.get(); const pxPerSecond = this.pxPerSecond.get();
const rangeStart = this.rangeStart - deltaPx * msPerPx; const rangeStart = this.rangeStart - deltaPx * pxPerSecond;
const rangeEnd = this.rangeEnd - deltaPx * msPerPx; const rangeEnd = this.rangeEnd - deltaPx * pxPerSecond;
this.dispatchEvent(new CustomEvent("rangeStart-change", { detail: rangeStart })); this.dispatchEvent(new CustomEvent("rangeStart-change", { detail: rangeStart }));
this.dispatchEvent(new CustomEvent("rangeEnd-change", { detail: rangeEnd })); this.dispatchEvent(new CustomEvent("rangeEnd-change", { detail: rangeEnd }));
} }
#onPlayheadDrag(event: PointerEvent): void { #onPlayheadDrag(event: PointerEvent): void {
const deltaMs = event.movementX * this.msPerPx.get(); const deltaSeconds = event.movementX * this.pxPerSecond.get();
const updated = this.playHead + deltaMs; const updated = this.playHead + deltaSeconds;
this.dispatchEvent(new CustomEvent("playHead-change", { detail: updated })); this.dispatchEvent(new CustomEvent("playHead-change", { detail: updated }));
} }
#onKeyframeDrag(id: string, event: PointerEvent): void { #onKeyframeDrag(id: string, event: PointerEvent): void {
const deltaMs = event.movementX * this.msPerPx.get(); const deltaSeconds = event.movementX * this.pxPerSecond.get();
const updated = this.keyframes[id] + deltaMs; const updated = this.keyframes[id] + deltaSeconds;
studio.updateKeyframe(id, updated); studio.updateKeyframe(id, updated);
@@ -221,14 +221,14 @@ export class AnimTimelineElement extends LitElement {
} }
/** /**
* Format a time value (in ms) for displaying in the timeline view * Format a time value (in seconds) for displaying in the timeline view
*/ */
#timestamp(ms: number) { #timestamp(seconds: number) {
const sign = ms < 0 ? "-" : ""; const sign = seconds < 0 ? "-" : "";
ms = Math.abs(ms); seconds = Math.abs(seconds);
let s = Math.floor(ms / 1000); let s = Math.floor(seconds);
let frac = Math.round(ms % 1000); let frac = Math.floor(seconds * 1000 - s * 1000);
if (frac === 1000) { if (frac === 1000) {
frac = 0; frac = 0;
s += 1; s += 1;
@@ -252,28 +252,22 @@ export class AnimTimelineElement extends LitElement {
} }
/** /**
* Convert a time value (in ms) to a px offset from the left edge of the view * Convert a time value (in seconds) to a px offset from the left edge of the view
*/ */
#msToPx(ms: number): number { #secondsToPx(seconds: number): number {
return this._offsetX + ms / this.msPerPx.get(); return this._offsetX + seconds * this.pxPerSecond.get();
} }
protected override render = () => { protected override render = () => {
const msPerPx = this.msPerPx.get(); const pxPerSecond = this.pxPerSecond.get();
const range = this.rangeEnd - this.rangeStart; const range = this.rangeEnd - this.rangeStart;
let subdivsPerSec = 32; const subdivsCount = Math.floor(this._renderWidth / 64);
let subdivsCount = (range / 1000) * subdivsPerSec; const pxPerSubdiv = this._renderWidth / subdivsCount;
let pxPerSubdiv = this._renderWidth / subdivsCount; const secondsPerSubdiv = pxPerSecond / pxPerSubdiv;
while (pxPerSubdiv < 64) { console.log("Seconds per Subdiv: ", secondsPerSubdiv);
subdivsPerSec /= 2; const subdivs = new Array(subdivsCount + 2).fill(null);
subdivsCount = (range / 1000) * subdivsPerSec;
pxPerSubdiv = this._renderWidth / subdivsCount;
}
const msPerSubdiv = msPerPx * pxPerSubdiv;
const subdivs = new Array(Math.ceil(subdivsCount) + 2).fill(null);
const fracPxOffset = this._offsetX % pxPerSubdiv; const fracPxOffset = this._offsetX % pxPerSubdiv;
@@ -299,17 +293,17 @@ export class AnimTimelineElement extends LitElement {
> >
<div class="clip-region" <div class="clip-region"
style=${styleMap({ style=${styleMap({
left: `${this.#msToPx(this.clipStart)}px`, left: `${this.#secondsToPx(this.clipStart)}px`,
right: `${this._renderWidth - this.#msToPx(this.clipEnd)}px`, right: `${this._renderWidth - this.#secondsToPx(this.clipEnd)}px`,
})} })}
></div> ></div>
${subdivs.map((_, idx) => { ${subdivs.map((_, idx) => {
const pxOffset = (idx - 1) * pxPerSubdiv + fracPxOffset; const pxOffset = (idx - 1) * pxPerSubdiv + fracPxOffset;
const ms = (startingIdx + idx) * msPerSubdiv; const seconds = (startingIdx + idx) * secondsPerSubdiv;
const msFrac = Math.abs(Math.round(ms) % 1000); const secondsFrac = Math.abs(Math.round(seconds));
const strong = msFrac === 0; const strong = secondsFrac === 0;
return html` return html`
<div <div
@@ -327,7 +321,7 @@ export class AnimTimelineElement extends LitElement {
left: `${pxOffset + 8}px` left: `${pxOffset + 8}px`
})} })}
> >
${this.#timestamp(ms)} ${seconds}
</span> </span>
` : nothing} ` : nothing}
`; `;
@@ -338,7 +332,7 @@ export class AnimTimelineElement extends LitElement {
move: this.#onPlayheadDrag move: this.#onPlayheadDrag
})} })}
style=${styleMap({ style=${styleMap({
left: `${this.#msToPx(this.playHead)}px` left: `${this.#secondsToPx(this.playHead)}px`
})} })}
tabindex="-1" tabindex="-1"
> >
@@ -356,7 +350,7 @@ export class AnimTimelineElement extends LitElement {
selected: this._selectedKeyframe === id, selected: this._selectedKeyframe === id,
})} })}
style=${styleMap({ style=${styleMap({
left: `${this.#msToPx(keyframe)}px` left: `${this.#secondsToPx(keyframe)}px`
})} })}
tabindex="-1" tabindex="-1"
${drag({ ${drag({