mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-11-02 18:08:19 +00:00
tracks move correctly
This commit is contained in:
@@ -1,21 +1,27 @@
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: grid;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #0006;
|
background: #0006;
|
||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(16px);
|
||||||
|
grid-template-columns: [sidebar-start] min-content [sidebar-end divider-start] 8px [divider-end timeline-start] 1fr [timeline-end];
|
||||||
|
padding: 8px;
|
||||||
|
row-gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar, .timeline { padding: 8px; }
|
|
||||||
.sidebar { padding-inline-end: 0; }
|
|
||||||
.timeline { padding-inline-start: 0; }
|
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
width: 256px;
|
width: 256px;
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
grid-column: sidebar;
|
||||||
|
grid-row: 1 / span var(--row-count);
|
||||||
|
grid-template-rows: subgrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
grid-column: timeline;
|
||||||
|
grid-row: 1 / span var(--row-count);
|
||||||
|
grid-template-rows: subgrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-keyframe {
|
.btn-keyframe {
|
||||||
@@ -58,6 +64,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
|
grid-column: divider;
|
||||||
|
grid-row: 1 / span var(--row-count);
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 0 0 8px;
|
flex: 0 0 8px;
|
||||||
margin: {
|
margin: {
|
||||||
@@ -83,6 +91,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline {
|
.tracklist {
|
||||||
flex: 1 0 0;
|
display: grid;
|
||||||
|
grid-template-columns: subgrid;
|
||||||
|
grid-row: 2 / span var(--track-count);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as studio from "@storyteller/studio";
|
import * as studio from "@storyteller/studio";
|
||||||
import { type CameraKeyframesEvent, type SeekTimeChanged, type UpdateAnimationTargets } from "@storyteller/studio";
|
import { type CameraKeyframesEvent, type SeekTimeChanged, type UpdateAnimationTargets } from "@storyteller/studio";
|
||||||
import { bind, drag, on } from "@storyteller/framework";
|
import { bind, drag, on } from "@storyteller/framework";
|
||||||
import { LitElement, type PropertyValues, html, unsafeCSS, TemplateResult } from "lit";
|
import { LitElement, type PropertyValues, html, unsafeCSS, TemplateResult, PropertyValueMap } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import { styleMap } from "lit/directives/style-map.js";
|
import { styleMap } from "lit/directives/style-map.js";
|
||||||
|
|
||||||
@@ -12,6 +12,12 @@ import "./anim-timeline.element";
|
|||||||
|
|
||||||
import styles from "./anim-editor.element.scss?inline";
|
import styles from "./anim-editor.element.scss?inline";
|
||||||
|
|
||||||
|
const TRACK_ICONS : Record<string, string> = {
|
||||||
|
camera: "video",
|
||||||
|
transform: "up-down-left-right",
|
||||||
|
light: "light"
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("sts-anim-editor")
|
@customElement("sts-anim-editor")
|
||||||
export class AnimEditorElement extends LitElement {
|
export class AnimEditorElement extends LitElement {
|
||||||
static override styles = unsafeCSS(styles);
|
static override styles = unsafeCSS(styles);
|
||||||
@@ -19,14 +25,25 @@ export class AnimEditorElement extends LitElement {
|
|||||||
@state() sidebarWidth = 256;
|
@state() sidebarWidth = 256;
|
||||||
|
|
||||||
@state() timelineStart = 0;
|
@state() timelineStart = 0;
|
||||||
@state() timelineEnd = 5_000;
|
@state() timelineEnd = 5;
|
||||||
|
|
||||||
@state() clipStart = 0;
|
@state() clipStart = 0;
|
||||||
@state() clipEnd = 15_000;
|
@state() clipEnd = 15_000;
|
||||||
|
|
||||||
@state() playHead = 0;
|
@state() playHead = 0;
|
||||||
@state() playing = false;
|
@state() playing = false;
|
||||||
@state() keyframes: Record<string, number> = {};
|
|
||||||
|
@state() tracks : Record<string, {name?: string, target_type: string }> = {};
|
||||||
|
|
||||||
|
@state() track_ordered : string[] = [];
|
||||||
|
|
||||||
|
@state() keyframes : Record<string, number[]> = {};
|
||||||
|
|
||||||
|
@state() selected_track : string | null = null;
|
||||||
|
|
||||||
|
trackCount(): number {
|
||||||
|
return Object.keys(this.tracks).length;
|
||||||
|
}
|
||||||
|
|
||||||
protected override update(changes: PropertyValues<this>): void {
|
protected override update(changes: PropertyValues<this>): void {
|
||||||
if (changes.has("playHead"))
|
if (changes.has("playHead"))
|
||||||
@@ -42,14 +59,26 @@ export class AnimEditorElement extends LitElement {
|
|||||||
this.playing = playing;
|
this.playing = playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@on("window:camera-keyframes")
|
|
||||||
onCameraKeyframeChanges({ detail: keyframes }: CameraKeyframesEvent): void {
|
|
||||||
this.keyframes = keyframes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@on("window:update-animation-targets")
|
@on("window:update-animation-targets")
|
||||||
onUpdateAnimationTargets({detail: { added, removed}}: UpdateAnimationTargets) : void {
|
onUpdateAnimationTargets({detail: { added, removed}}: UpdateAnimationTargets) : void {
|
||||||
|
let addedKeys = Object.keys(added);
|
||||||
|
if (addedKeys.length === 0 && removed.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Animation Targets Changed");
|
||||||
|
let tracks = {...this.tracks};
|
||||||
|
for (let track_entity of addedKeys) {
|
||||||
|
let track = added[track_entity];
|
||||||
|
tracks[track_entity] = track;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let track_entity of removed) {
|
||||||
|
delete tracks[track_entity];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.track_ordered = Object.keys(tracks);
|
||||||
|
|
||||||
|
this.tracks = tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSidebarResize(event: PointerEvent): void {
|
onSidebarResize(event: PointerEvent): void {
|
||||||
@@ -67,26 +96,12 @@ export class AnimEditorElement extends LitElement {
|
|||||||
prevKeyframe(): void {
|
prevKeyframe(): void {
|
||||||
let pos = this.playHead;
|
let pos = this.playHead;
|
||||||
|
|
||||||
const keyframes = Array.from(Object.values(this.keyframes)).sort((a, b) => b - a);
|
|
||||||
for (let keyframe of keyframes) {
|
|
||||||
pos = keyframe;
|
|
||||||
if (keyframe < this.playHead)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.playHead = pos;
|
this.playHead = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextKeyframe(): void {
|
nextKeyframe(): void {
|
||||||
let pos = this.playHead;
|
let pos = this.playHead;
|
||||||
|
|
||||||
const keyframes = Array.from(Object.values(this.keyframes)).sort((a, b) => a - b);
|
|
||||||
for (let keyframe of keyframes) {
|
|
||||||
pos = keyframe;
|
|
||||||
if (keyframe > this.playHead)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.playHead = pos;
|
this.playHead = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +110,7 @@ export class AnimEditorElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
togglePlaying(): void {
|
togglePlaying(): void {
|
||||||
|
console.log("toggling play");
|
||||||
if (this.playing) {
|
if (this.playing) {
|
||||||
studio.pause();
|
studio.pause();
|
||||||
} else {
|
} else {
|
||||||
@@ -102,10 +118,16 @@ export class AnimEditorElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override render = (): TemplateResult => html`
|
protected override render(): TemplateResult {
|
||||||
|
let track_count = {
|
||||||
|
'--track-count': `${this.trackCount()}`,
|
||||||
|
'--row-count': `calc(var(--track-count) + 1)`
|
||||||
|
};
|
||||||
|
return html`
|
||||||
<div class="sidebar"
|
<div class="sidebar"
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
width: `${this.sidebarWidth}px`,
|
width: `${this.sidebarWidth}px`,
|
||||||
|
...track_count
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<sts-toolbar>
|
<sts-toolbar>
|
||||||
@@ -151,26 +173,29 @@ export class AnimEditorElement extends LitElement {
|
|||||||
</sts-toolbar-group>
|
</sts-toolbar-group>
|
||||||
</sts-toolbar>
|
</sts-toolbar>
|
||||||
<div class="tracklist" role="list">
|
<div class="tracklist" role="list">
|
||||||
<sts-tree class="track"
|
${
|
||||||
role="listitem"
|
this.track_ordered.map((entity) => {
|
||||||
name="Camera"
|
let track = this.tracks[entity];
|
||||||
icon="video"
|
let name = track.name ?? track.target_type;
|
||||||
></sts-tree>
|
let icon = TRACK_ICONS[track.target_type] ?? "minus";
|
||||||
<sts-tree class="track"
|
return html!`<sts-tree class="track"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
name="Camera Target"
|
name=${name}
|
||||||
icon="crosshairs"
|
icon=${icon}
|
||||||
></sts-tree>
|
></sts-tree>`;
|
||||||
|
})
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider"
|
<div class="divider" style=${styleMap(track_count)}
|
||||||
${drag({
|
${drag({
|
||||||
move: this.onSidebarResize,
|
move: this.onSidebarResize,
|
||||||
})}
|
})}
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<sts-anim-timeline class="timeline"
|
<sts-anim-timeline class="timeline"
|
||||||
|
style=${styleMap(track_count)}
|
||||||
.rangeStart=${bind(this, "timelineStart")}
|
.rangeStart=${bind(this, "timelineStart")}
|
||||||
.rangeEnd=${bind(this, "timelineEnd")}
|
.rangeEnd=${bind(this, "timelineEnd")}
|
||||||
.clipStart=${this.clipStart}
|
.clipStart=${this.clipStart}
|
||||||
@@ -178,5 +203,7 @@ export class AnimEditorElement extends LitElement {
|
|||||||
.playHead=${bind(this, "playHead")}
|
.playHead=${bind(this, "playHead")}
|
||||||
.keyframes=${bind(this, "keyframes")}
|
.keyframes=${bind(this, "keyframes")}
|
||||||
></sts-anim-timeline>
|
></sts-anim-timeline>
|
||||||
`;
|
`
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -59,11 +59,6 @@ export class AnimTimelineElement extends LitElement {
|
|||||||
/** The time (in seconds) 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;
|
@property({ type: Number }) rangeEnd = 5;
|
||||||
|
|
||||||
/** The time (in seconds) at the beginning of the active clip range */
|
|
||||||
@property({ type: Number }) clipStart = 0;
|
|
||||||
/** The time (in seconds) at the end of the active clip range */
|
|
||||||
@property({ type: Number }) clipEnd = 15;
|
|
||||||
|
|
||||||
/** The time (in seconds) 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 seconds) */
|
/** Keyframe positions (in seconds) */
|
||||||
@@ -86,7 +81,7 @@ export class AnimTimelineElement extends LitElement {
|
|||||||
@state() _panning = false;
|
@state() _panning = false;
|
||||||
|
|
||||||
/** How many seconds are represented by each px of horizontal space. */
|
/** How many seconds are represented by each px of horizontal space. */
|
||||||
pxPerSecond = new Computed(() => (this.rangeEnd - this.rangeStart) / this._renderWidth);
|
secondsPerPx = new Computed(() => (this.rangeEnd - this.rangeStart) / this._renderWidth);
|
||||||
|
|
||||||
#resizeObserver: ResizeObserver;
|
#resizeObserver: ResizeObserver;
|
||||||
|
|
||||||
@@ -107,12 +102,13 @@ export class AnimTimelineElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override update(changes: PropertyValues<this>): void {
|
protected override update(changes: PropertyValues<this>): void {
|
||||||
|
console.log("Changes: ", changes);
|
||||||
if (
|
if (
|
||||||
changes.has("rangeStart")
|
changes.has("rangeStart")
|
||||||
|| changes.has("rangeEnd")
|
|| changes.has("rangeEnd")
|
||||||
|| changes.has("_renderWidth")
|
|| changes.has("_renderWidth")
|
||||||
) {
|
) {
|
||||||
this.pxPerSecond.invalidate();
|
this.secondsPerPx.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
super.update(changes);
|
super.update(changes);
|
||||||
@@ -158,7 +154,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.pxPerSecond.get()) / currentRange;
|
const t = ((this._offsetX + origin) * this.secondsPerPx.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)}`);
|
||||||
@@ -169,22 +165,22 @@ export class AnimTimelineElement extends LitElement {
|
|||||||
this.dispatchEvent(new CustomEvent("rangeEnd-change", { detail: rangeEnd }));
|
this.dispatchEvent(new CustomEvent("rangeEnd-change", { detail: rangeEnd }));
|
||||||
}
|
}
|
||||||
|
|
||||||
#onDragStart(): void {
|
onDragStart(): void {
|
||||||
this._panning = true;
|
this._panning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#onDragEnd(): void {
|
onDragEnd(): void {
|
||||||
this._panning = false;
|
this._panning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#onDrag(event: PointerEvent): void {
|
onDrag(event: PointerEvent): void {
|
||||||
this.#pan(event.movementX);
|
this.#pan(event.movementX);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pan(deltaPx: number): void {
|
#pan(deltaPx: number): void {
|
||||||
this._offsetX += deltaPx;
|
this._offsetX += deltaPx;
|
||||||
|
|
||||||
const pxPerSecond = this.pxPerSecond.get();
|
const pxPerSecond = this.secondsPerPx.get();
|
||||||
const rangeStart = this.rangeStart - deltaPx * pxPerSecond;
|
const rangeStart = this.rangeStart - deltaPx * pxPerSecond;
|
||||||
const rangeEnd = this.rangeEnd - deltaPx * pxPerSecond;
|
const rangeEnd = this.rangeEnd - deltaPx * pxPerSecond;
|
||||||
|
|
||||||
@@ -192,15 +188,18 @@ export class AnimTimelineElement extends LitElement {
|
|||||||
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 deltaSeconds = event.movementX * this.pxPerSecond.get();
|
let elementBounds = this.getBoundingClientRect();
|
||||||
const updated = this.playHead + deltaSeconds;
|
let relativeX = event.clientX - elementBounds.left;
|
||||||
|
let ratioX = Math.max(0, Math.min(relativeX / elementBounds.width, 1));
|
||||||
|
const seconds = (this.rangeEnd - this.rangeStart) * ratioX + this.rangeStart;
|
||||||
|
const updated = seconds;
|
||||||
|
|
||||||
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 deltaSeconds = event.movementX * this.pxPerSecond.get();
|
const deltaSeconds = event.movementX * this.secondsPerPx.get();
|
||||||
const updated = this.keyframes[id] + deltaSeconds;
|
const updated = this.keyframes[id] + deltaSeconds;
|
||||||
|
|
||||||
studio.updateKeyframe(id, updated);
|
studio.updateKeyframe(id, updated);
|
||||||
@@ -223,59 +222,65 @@ export class AnimTimelineElement extends LitElement {
|
|||||||
/**
|
/**
|
||||||
* Format a time value (in seconds) for displaying in the timeline view
|
* Format a time value (in seconds) for displaying in the timeline view
|
||||||
*/
|
*/
|
||||||
#timestamp(seconds: number) {
|
timestamp(seconds: number) {
|
||||||
const sign = seconds < 0 ? "-" : "";
|
const sign = seconds < 0 ? "-" : "";
|
||||||
seconds = Math.abs(seconds);
|
seconds = Math.abs(seconds);
|
||||||
|
|
||||||
let s = Math.floor(seconds);
|
let minutes = Math.floor(seconds / 60);
|
||||||
let frac = Math.floor(seconds * 1000 - s * 1000);
|
let secondsInMinute = seconds % 60;
|
||||||
if (frac === 1000) {
|
let flooredSeconds = Math.floor(secondsInMinute);
|
||||||
frac = 0;
|
let fraction = Math.floor(100 * (secondsInMinute - flooredSeconds));
|
||||||
s += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let m = 0;
|
|
||||||
if (s >= 60) {
|
|
||||||
m = Math.floor(s / 60);
|
|
||||||
s %= 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`${
|
return html`${
|
||||||
sign
|
sign
|
||||||
}${
|
}${
|
||||||
m.toString(10).padStart(2, "0")
|
minutes.toString(10).padStart(2, "0")
|
||||||
}:${
|
}:${
|
||||||
s.toString(10).padStart(2, "0")
|
flooredSeconds.toString(10).padStart(2, "0")
|
||||||
}<!--<span class="frac">.${
|
}.${
|
||||||
frac.toString(10).padEnd(3, "0")
|
fraction.toString(10).padStart(2, "0")
|
||||||
}</span>-->`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a time value (in seconds) 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
|
||||||
*/
|
*/
|
||||||
#secondsToPx(seconds: number): number {
|
secondsToPx(seconds: number): number {
|
||||||
return this._offsetX + seconds * this.pxPerSecond.get();
|
return this._offsetX + seconds * this.secondsPerPx.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override render = () => {
|
protected override render() {
|
||||||
const pxPerSecond = this.pxPerSecond.get();
|
const {
|
||||||
|
subdivision: subdivisionScale,
|
||||||
|
major: majorSubdivisionScale,
|
||||||
|
} = {
|
||||||
|
subdivision: 0.5,
|
||||||
|
major: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const scaledStart = this.rangeStart / subdivisionScale;
|
||||||
|
const scaledEnd = this.rangeEnd / subdivisionScale;
|
||||||
const range = this.rangeEnd - this.rangeStart;
|
const range = this.rangeEnd - this.rangeStart;
|
||||||
|
|
||||||
const subdivsCount = Math.floor(this._renderWidth / 64);
|
console.log("Scaled Start", scaledStart, "Scaled End", scaledEnd, "Range", range);
|
||||||
const pxPerSubdiv = this._renderWidth / subdivsCount;
|
|
||||||
const secondsPerSubdiv = pxPerSecond / pxPerSubdiv;
|
|
||||||
|
|
||||||
console.log("Seconds per Subdiv: ", secondsPerSubdiv);
|
let subdivisions : Array<{seconds: number, fraction: number, isMajor: boolean}> = [];
|
||||||
const subdivs = new Array(subdivsCount + 2).fill(null);
|
let currentOffset = scaledStart;
|
||||||
|
let currentOffsetSeconds = this.rangeStart;
|
||||||
|
|
||||||
const fracPxOffset = this._offsetX % pxPerSubdiv;
|
while (currentOffset < scaledEnd) {
|
||||||
|
const currentSeconds = currentOffsetSeconds;
|
||||||
|
const isMajor = currentOffsetSeconds % majorSubdivisionScale === 0;
|
||||||
|
subdivisions.push({ seconds: currentSeconds, fraction: currentSeconds / range, isMajor });
|
||||||
|
let nextSubdiv = Math.ceil(currentOffset);
|
||||||
|
if (Math.abs(nextSubdiv - currentOffset) < 0.1) {
|
||||||
|
nextSubdiv += 1;
|
||||||
|
}
|
||||||
|
currentOffset = nextSubdiv;
|
||||||
|
currentOffsetSeconds = currentOffset * subdivisionScale;
|
||||||
|
}
|
||||||
|
|
||||||
let startingIdx = -Math.floor(this._offsetX / pxPerSubdiv);
|
console.log("Subdivisions", subdivisions.length, subdivisions);
|
||||||
if (fracPxOffset < 0)
|
|
||||||
startingIdx -= 2;
|
|
||||||
else
|
|
||||||
startingIdx -= 1;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@@ -285,43 +290,37 @@ export class AnimTimelineElement extends LitElement {
|
|||||||
})}
|
})}
|
||||||
${drag({
|
${drag({
|
||||||
button: 1,
|
button: 1,
|
||||||
start: this.#onDragStart,
|
start: this.onDragStart,
|
||||||
move: this.#onDrag,
|
move: this.onDrag,
|
||||||
end: this.#onDragEnd,
|
end: this.onDragEnd,
|
||||||
})}
|
})}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div class="clip-region"
|
<div class="clip-region"
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
left: `${this.#secondsToPx(this.clipStart)}px`,
|
|
||||||
right: `${this._renderWidth - this.#secondsToPx(this.clipEnd)}px`,
|
|
||||||
})}
|
})}
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
${subdivs.map((_, idx) => {
|
${subdivisions.map(({ seconds, fraction, isMajor}, idx) => {
|
||||||
const pxOffset = (idx - 1) * pxPerSubdiv + fracPxOffset;
|
const secondsPercentage = fraction * 100;
|
||||||
const seconds = (startingIdx + idx) * secondsPerSubdiv;
|
|
||||||
|
|
||||||
const secondsFrac = Math.abs(Math.round(seconds));
|
|
||||||
const strong = secondsFrac === 0;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
hash: true,
|
hash: true,
|
||||||
strong,
|
strong: isMajor,
|
||||||
})}
|
})}
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
left: `${pxOffset}px`
|
left: `${secondsPercentage}%`
|
||||||
})}
|
})}
|
||||||
></div>
|
></div>
|
||||||
${strong ? html`
|
${true ? html`
|
||||||
<span class="timestamp"
|
<span class="timestamp"
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
left: `${pxOffset + 8}px`
|
left: `calc(${secondsPercentage}% + 8px)`
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
${seconds}
|
${this.timestamp(seconds)}
|
||||||
</span>
|
</span>
|
||||||
` : nothing}
|
` : nothing}
|
||||||
`;
|
`;
|
||||||
@@ -329,10 +328,10 @@ export class AnimTimelineElement extends LitElement {
|
|||||||
|
|
||||||
<div class="playhead"
|
<div class="playhead"
|
||||||
${drag({
|
${drag({
|
||||||
move: this.#onPlayheadDrag
|
move: this.onPlayheadDrag
|
||||||
})}
|
})}
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
left: `${this.#secondsToPx(this.playHead)}px`
|
left: `${100 * (this.playHead - this.rangeStart) / range}%`
|
||||||
})}
|
})}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
@@ -350,11 +349,11 @@ export class AnimTimelineElement extends LitElement {
|
|||||||
selected: this._selectedKeyframe === id,
|
selected: this._selectedKeyframe === id,
|
||||||
})}
|
})}
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
left: `${this.#secondsToPx(keyframe)}px`
|
left: `${this.secondsToPx(keyframe)}px`
|
||||||
})}
|
})}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
${drag({
|
${drag({
|
||||||
move: event => this.#onKeyframeDrag(id, event),
|
move: event => this.onKeyframeDrag(id, event),
|
||||||
})}
|
})}
|
||||||
@click=${() => this.selectKeyframe(id)}
|
@click=${() => this.selectKeyframe(id)}
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user