Timeline animation clips (#122)

This is a super initial version of animation clips in timeline.


https://github.com/storytold/storyteller-bevy/assets/10376144/b649162b-8758-4662-bfb9-6a492563b9f7


https://github.com/storytold/storyteller-bevy/assets/10376144/a5815685-bf52-4413-8828-d86051bf850a

---------

Co-authored-by: Danny McGee <dannymcgee@gmail.com>
This commit is contained in:
Lee-Orr
2024-04-05 02:17:06 -04:00
committed by GitHub
parent 0f4eda3e82
commit d2ae2f5744
8 changed files with 620 additions and 62 deletions
+24 -3
View File
@@ -149,7 +149,8 @@ export class AnimEditorElement extends LitElement
let min = Infinity
let max = -Infinity;
for (const keyframe of Object.keys(this.keyframes)) {
let time = this.keyframes[keyframe].time;
const current = this.keyframes[keyframe];
let time = current.time;
if (typeof time != "number") {
continue;
}
@@ -159,6 +160,12 @@ export class AnimEditorElement extends LitElement
if (time < min) {
min = time;
}
if ("duration" in current) {
const end_time = time + current.duration;
if (end_time > max) {
max = end_time
}
}
}
this.clipStart = Math.max(min, 0.);
this.clipEnd = Math.max(max, min);
@@ -210,13 +217,20 @@ export class AnimEditorElement extends LitElement
let closest_keyframe = -1 * Infinity;
for (const keyframe of Object.keys(this.keyframes)) {
const time = this.keyframes[keyframe]?.time;
const current = this.keyframes[keyframe];
const time = current?.time;
if (!time || typeof time != "number") {
continue;
}
if (time < pos && time > closest_keyframe) {
closest_keyframe = time;
}
if ("duration" in current) {
const end_time = time + current.duration;
if (end_time < pos && end_time > closest_keyframe) {
closest_keyframe = end_time;
}
}
}
if (closest_keyframe < 0) {
@@ -233,13 +247,20 @@ export class AnimEditorElement extends LitElement
let closest_keyframe = Infinity;
for (const keyframe of Object.keys(this.keyframes)) {
const time = this.keyframes[keyframe]?.time;
const current = this.keyframes[keyframe];
const time = current?.time;
if (!time || typeof time != "number") {
continue;
}
if (time > pos && time < closest_keyframe) {
closest_keyframe = time;
}
if ("duration" in current) {
const end_time = time + current.duration;
if (end_time > pos && end_time < closest_keyframe) {
closest_keyframe = end_time;
}
}
}
if (closest_keyframe > this.clipEnd) {
@@ -136,3 +136,27 @@
background: #0FF; // TODO
}
}
.clip {
position: absolute;
transform: translateX(-4.5px);
--clip-name: "";
height: 12px;
&::before {
content: var(--clip-name);
display: flex;
width: 100%;
height: 12px;
background: #AAA;
color: black;
font-size: 10px;
font-weight: bold;
justify-content: center;
align-items: center;
}
&.selected::before {
background: #0FF; // TODO
}
}
@@ -406,23 +406,50 @@ export class AnimTimelineElement extends LitElement {
([id]) => id,
([id, keyframe]) => {
let row = this.track_animation_target_ordered[keyframe.target];
return html`
<div id=${id}
class=${classMap({
keyframe: true,
selected: this._selectedKeyframe === id
})}
style=${styleMap({
left: `${100 * (keyframe.time - this.rangeStart) / range}%`,
gridRow: row + 2,
})}
tabindex="-1"
${drag({
move: event => this.onKeyframeDrag(id, event),
})}
@click=${() => this.selectKeyframe(id)}
></div>
`;
if ("clip" in keyframe) {
let clip = keyframe.clip;
let duration = keyframe.duration;
let time = keyframe.time;
return html`
<div id=${id}
class=${classMap({
clip: true,
selected: this._selectedKeyframe === id
})}
style=${styleMap({
left: `${100 * (time - this.rangeStart) / range}%`,
right: `${100 * (this.rangeEnd - (time + duration)) / range}%`,
gridRow: row + 2,
'--clip-name': `"${clip}"`,
})}
tabindex="-1"
${drag({
move: event => this.onKeyframeDrag(id, event),
})}
@click=${() => this.selectKeyframe(id)}
>
</div>
`;
} else {
return html`
<div id=${id}
class=${classMap({
keyframe: true,
selected: this._selectedKeyframe === id
})}
style=${styleMap({
left: `${100 * (keyframe.time - this.rangeStart) / range}%`,
gridRow: row + 2,
})}
tabindex="-1"
${drag({
move: event => this.onKeyframeDrag(id, event),
})}
@click=${() => this.selectKeyframe(id)}
></div>
`;
}
}
)}
</div>
+6
View File
@@ -9,6 +9,12 @@ $hierarchy-width: 290px;
--timeline-height: max-content;
}
.debug-mode {
.studio-scene {
z-index: 100;
}
}
.wrapper {
position: fixed;
top: 0;
+8 -1
View File
@@ -56,6 +56,8 @@ export class AppElement extends LitElement {
@state() resizingPanel = false;
@state() inspectorWidth = 320;
@state() debugMode = false;
@state() sidebars: Array<{ title: string, name: string}> = [{title: 'Hierarchy', name: 'hierarchy'}];
@state() currentSidebar : string = 'hierarchy';
@@ -103,6 +105,11 @@ export class AppElement extends LitElement {
});
}
@on("window:debug-mode-toggled")
onDebugModeToggled({detail: { debugModeActive }}: studio.DebugModeToggled ): void {
this.debugMode = debugModeActive;
}
onPanelResizeStart(): void {
this.resizingPanel = true;
}
@@ -133,7 +140,7 @@ export class AppElement extends LitElement {
protected override render() {
return html`
<sts-canvas-provider class="app-root">
<sts-canvas-provider class=${classMap({"app-root": true, "debug-mode": this.debugMode})}>
<sts-scene-data-provider>
<sts-overlay-provider>
<sts-iframe-messaging-bridge></sts-iframe-messaging-bridge>