move & update keyframes

This commit is contained in:
Lee-Orr
2024-03-14 18:48:03 -04:00
parent baa9431c39
commit 7c7435d2cc
6 changed files with 502 additions and 134 deletions
@@ -34,7 +34,11 @@
background: #7A2C34E3;
color: #FFF;
&:hover {
&.disabled {
opacity: 0.25;
}
&:not(.disabled):hover {
background: #90363F;
}
}
@@ -95,4 +99,21 @@
display: grid;
grid-template-columns: subgrid;
grid-row: 2 / span var(--track-count);
.track {
&:hover {
background: #0000001F;
}
&.selected {
background: #7A2C34E3;
color: #FFF;
font-weight: 500;
&:hover {
background: #90363F !important;
}
}
}
}
+59 -16
View File
@@ -1,5 +1,5 @@
import * as studio from "@storyteller/studio";
import { type CameraKeyframesEvent, type SeekTimeChanged, type UpdateAnimationTargets } from "@storyteller/studio";
import type { EntitySelectEvent, SeekTimeChanged, UpdateAnimationTargets } from "@storyteller/studio";
import { bind, drag, on } from "@storyteller/framework";
import { LitElement, type PropertyValues, html, unsafeCSS, TemplateResult, PropertyValueMap } from "lit";
import { customElement, state } from "lit/decorators.js";
@@ -37,9 +37,9 @@ export class AnimEditorElement extends LitElement {
@state() track_ordered : string[] = [];
@state() keyframes : Record<string, number[]> = {};
@state() keyframes : Record<string, Keyframe> = {};
@state() selected_track : string | null = null;
@state() selected_track : string | false = false;
trackCount(): number {
return Object.keys(this.tracks).length;
@@ -81,12 +81,39 @@ export class AnimEditorElement extends LitElement {
this.tracks = tracks;
}
@on("window:update-keyframes")
onUpdateKeyframes({detail: {added, removed}}: studio.UpdateKeyframes): void {
let addedKeyframes = Object.keys(added);
if (addedKeyframes.length === 0 && removed.length === 0) {
return;
}
console.log("Keyframes Changed");
let keyframes = {...this.keyframes};
for (let keyframe of addedKeyframes) {
keyframes[keyframe] = added[keyframe];
}
this.keyframes = keyframes;
}
@on("window:entity-select")
onEntitySelected({ detail: id }: EntitySelectEvent): void {
if (id && this.tracks[id]) {
this.selected_track = id;
} else {
this.selected_track = false;
}
}
onSidebarResize(event: PointerEvent): void {
this.sidebarWidth = Math.max(Math.min(this.sidebarWidth + event.movementX, 512), 128);
}
addKeyframe(): void {
studio.addCameraKeyframe(this.playHead);
if (this.selected_track) {
studio.recordKeyframe([this.selected_track]);
}
}
jumpToStart(): void {
@@ -123,6 +150,31 @@ export class AnimEditorElement extends LitElement {
'--track-count': `${this.trackCount()}`,
'--row-count': `calc(var(--track-count) + 1)`
};
const track_order_hashed : Record<string, number> = {};
for (const [index, track] of this.track_ordered.entries()) {
track_order_hashed[track] = index;
}
const tracklist = this.track_ordered.map((entity) => {
let track = this.tracks[entity];
let name = track.name ?? track.target_type;
let icon = TRACK_ICONS[track.target_type] ?? "minus";
return html`
<sts-tree class=${`track ` + (this.selected_track === entity ? `selected` : ``)}
role="listitem"
name=${name}
icon=${icon}
@click=${() => {
studio.select(entity);
}}
></sts-tree>
`;
});
const none_selected = !this.selected_track;
return html`
<div class="sidebar"
style=${styleMap({
@@ -136,6 +188,7 @@ export class AnimEditorElement extends LitElement {
icon="key"
aria-label="Add Keyframe"
title="Add Keyframe"
?disabled=${none_selected}
@click=${this.addKeyframe}
></sts-button>
</sts-toolbar-group>
@@ -173,18 +226,7 @@ export class AnimEditorElement extends LitElement {
</sts-toolbar-group>
</sts-toolbar>
<div class="tracklist" role="list">
${
this.track_ordered.map((entity) => {
let track = this.tracks[entity];
let name = track.name ?? track.target_type;
let icon = TRACK_ICONS[track.target_type] ?? "minus";
return html!`<sts-tree class="track"
role="listitem"
name=${name}
icon=${icon}
></sts-tree>`;
})
}
${tracklist}
</div>
</div>
@@ -202,6 +244,7 @@ export class AnimEditorElement extends LitElement {
.clipEnd=${this.clipEnd}
.playHead=${bind(this, "playHead")}
.keyframes=${bind(this, "keyframes")}
.track_ordered=${track_order_hashed}
></sts-anim-timeline>
`
};
@@ -1,6 +1,9 @@
:host {
position: relative;
overflow: hidden;
display: grid;
grid-template-rows: subgrid;
grid-row: 1 / span var(--row-count);
}
*:focus-visible {
@@ -8,21 +11,18 @@
}
.wrapper {
position: absolute;
position: relative;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: subgrid;
inset: 0;
grid-row: 1 / span var(--row-count);
&.panning {
cursor: grabbing;
}
}
.clip-region {
position: absolute;
top: 0;
bottom: 0;
background: #0004;
}
.hash {
position: absolute;
top: 0;
@@ -37,7 +37,7 @@
.timestamp {
position: absolute;
top: 8px;
grid-row: 1;
color: #FFF6;
font: {
family: "Fira Sans", sans-serif;
@@ -100,8 +100,7 @@
.keyframe {
position: absolute;
top: 50%;
transform: translateX(-4.5px) translateY(-4.5px);
transform: translateX(-4.5px);
&::before {
content: "";
@@ -62,7 +62,8 @@ export class AnimTimelineElement extends LitElement {
/** The time (in seconds) of the current play-head position */
@property({ type: Number }) playHead = 0;
/** Keyframe positions (in seconds) */
@property({ attribute: false }) keyframes: Record<string, number> = {};
@property({ attribute: false }) keyframes: Record<string, studio.Keyframe> = {};
@property({ attribute: false }) track_ordered: Record<string, number> = {}
@state() _selectedKeyframe: string | null = null;
@@ -131,8 +132,9 @@ export class AnimTimelineElement extends LitElement {
}
if (Array.from(Object.keys(this.keyframes)).includes(id)) {
let keyframe = this.keyframes[id];
this._selectedKeyframe = id;
this.dispatchEvent(new CustomEvent("playHead-change", { detail: this.keyframes[id]! }));
studio.seekTime(keyframe.time);
} else {
this._selectedKeyframe = null;
}
@@ -190,20 +192,12 @@ export class AnimTimelineElement extends LitElement {
}
onKeyframeDrag(id: string, event: PointerEvent): void {
const deltaSeconds = event.movementX * this.secondsPerPx.get();
const updated = this.keyframes[id] + deltaSeconds;
let elementBounds = this.getBoundingClientRect();
let relativeX = event.clientX - elementBounds.left;
let ratioX = Math.max(0, Math.min(relativeX / elementBounds.width, 1));
const time = (this.rangeEnd - this.rangeStart) * ratioX + this.rangeStart;
studio.updateKeyframe(id, updated);
const updatedKeyframes = Object.fromEntries(
Object
.entries(this.keyframes)
.map(entry => entry[0] === id
? [id, updated]
: entry
)
);
this.dispatchEvent(new CustomEvent("keyframes-change", { detail: updatedKeyframes }));
studio.moveKeyframeTime(id, time);
}
#updateRenderWidth(): void {
@@ -296,10 +290,6 @@ export class AnimTimelineElement extends LitElement {
})}
tabindex="-1"
>
<div class="clip-region"
style=${styleMap({
})}
></div>
${subdivisions.map(({ seconds, fraction, isMajor}, idx) => {
const secondsPercentage = fraction * 100;
@@ -342,14 +332,17 @@ export class AnimTimelineElement extends LitElement {
${repeat(
Object.entries(this.keyframes),
([id]) => id,
([id, keyframe]) => html`
([id, keyframe]) => {
let row = this.track_ordered[keyframe.target];
return html`
<div id=${id}
class=${classMap({
keyframe: true,
selected: this._selectedKeyframe === id,
selected: this._selectedKeyframe === id
})}
style=${styleMap({
left: `${100 * (keyframe - this.rangeStart) / range}%`
left: `${100 * (keyframe.time - this.rangeStart) / range}%`,
gridRow: row + 2,
})}
tabindex="-1"
${drag({
@@ -357,7 +350,8 @@ export class AnimTimelineElement extends LitElement {
})}
@click=${() => this.selectKeyframe(id)}
></div>
`
`;
}
)}
</div>
`;