play/pause, update timeline

This commit is contained in:
Lee-Orr
2024-03-08 13:46:09 -05:00
parent 86f471de3c
commit 2419f82e09
5 changed files with 126 additions and 21 deletions
+24 -2
View File
@@ -1,5 +1,5 @@
import * as studio from "@storyteller/studio";
import { type CameraKeyframesEvent } from "@storyteller/studio";
import { type CameraKeyframesEvent, type SeekTimeChanged, type UpdateAnimationTargets } from "@storyteller/studio";
import { bind, drag, on } from "@storyteller/framework";
import { LitElement, type PropertyValues, html, unsafeCSS, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
@@ -25,6 +25,7 @@ export class AnimEditorElement extends LitElement {
@state() clipEnd = 15_000;
@state() playHead = 0;
@state() playing = false;
@state() keyframes: Record<string, number> = {};
protected override update(changes: PropertyValues<this>): void {
@@ -34,11 +35,23 @@ export class AnimEditorElement extends LitElement {
super.update(changes);
}
@on("window:seek-time-changed")
onSeekTimeChanged({ detail: {time, playing} }: SeekTimeChanged ): void {
console.log("Seek Time Changed", time, playing);
this.playHead = time;
this.playing = playing;
}
@on("window:camera-keyframes")
onCameraKeyframeChanges({ detail: keyframes }: CameraKeyframesEvent): void {
this.keyframes = keyframes;
}
@on("window:update-animation-targets")
onUpdateAnimationTargets({detail: { added, removed}}: UpdateAnimationTargets) : void {
}
onSidebarResize(event: PointerEvent): void {
this.sidebarWidth = Math.max(Math.min(this.sidebarWidth + event.movementX, 512), 128);
}
@@ -81,6 +94,14 @@ export class AnimEditorElement extends LitElement {
this.playHead = this.clipEnd;
}
togglePlaying(): void {
if (this.playing) {
studio.pause();
} else {
studio.play();
}
}
protected override render = (): TemplateResult => html`
<div class="sidebar"
style=${styleMap({
@@ -110,9 +131,10 @@ export class AnimEditorElement extends LitElement {
@click=${this.prevKeyframe}
></sts-button>
<sts-button class="play"
icon="play"
icon=${this.playing ? "pause" : "play" }
aria-label="Play"
title="Play"
@click=${this.togglePlaying}
></sts-button>
<sts-button
icon="fwd-step"
+1 -1
View File
@@ -46,7 +46,7 @@ export class AppElement extends LitElement {
@state() inspectorWidth = 320;
@state() hierarchyHeight?: number;
#enableAnimTimeline = false; // TODO
#enableAnimTimeline = true; // TODO
#inspectorRef = createRef<HTMLDivElement>();
#hierarchyRef = createRef<SceneHierarchyElement>();
+2
View File
@@ -12,6 +12,7 @@ import farCube from "@fortawesome/fontawesome-pro/svgs/regular/cube.svg?raw";
import farGlobe from "@fortawesome/fontawesome-pro/svgs/regular/globe.svg?raw";
import farUpDownLeftRight from "@fortawesome/fontawesome-pro/svgs/regular/arrows-up-down-left-right.svg?raw";
import fasPlay from "@fortawesome/fontawesome-pro/svgs/solid/play.svg?raw";
import fasPause from "@fortawesome/fontawesome-pro/svgs/solid/pause.svg?raw";
import fasFwdStep from "@fortawesome/fontawesome-pro/svgs/solid/forward-step.svg?raw";
import fasFwdFast from "@fortawesome/fontawesome-pro/svgs/solid/forward-fast.svg?raw";
import fasBwdStep from "@fortawesome/fontawesome-pro/svgs/solid/backward-step.svg?raw";
@@ -77,6 +78,7 @@ function renderSvg(iconId: string) {
"minus": () => fasMinus,
"globe": () => farGlobe,
"play": () => fasPlay,
"pause": () => fasPause,
"question": () => fasQuestion,
"rotate": () => farRotate,
"scale": () => farScale,
+96 -11
View File
@@ -23,14 +23,22 @@ impl Plugin for TimelinePlugin {
playing: false,
});
#[cfg(feature = "wasm")]
app.add_systems(
Update,
(
wasm::update_global_seek_time,
wasm::notify_seek_time_changes.map(bevy::utils::error),
),
app.add_systems(PreUpdate,
(process_timeline_events, update_tick).chain(),
);
#[cfg(feature = "wasm")]
{
app.add_systems(First,
wasm::update_global_seek_time);
app.add_systems(
Update,
(
wasm::notify_seek_time_changes.map(bevy::utils::error),
wasm::notify_updated_animation_targets.map(bevy::utils::error)
),
);
}
}
}
@@ -55,6 +63,7 @@ pub struct TimedAnimationClip {
start_time: f32,
duration: f32,
incoming_transition_duration: f32,
speed: f32,
target: Entity,
}
@@ -89,6 +98,7 @@ impl Default for TimedAnimationClip {
clip: Default::default(),
start_time: Default::default(),
duration: Default::default(),
speed: 1.0,
incoming_transition_duration: Default::default(),
target: Entity::PLACEHOLDER,
}
@@ -105,14 +115,31 @@ impl MapEntities for TimedAnimationClip {
#[reflect(Component)]
pub struct AnimationTarget;
fn process_timeline_events(mut e_timeline_event: EventReader<TimelineEvent>, mut r_global_seek_time: ResMut<GlobalSeekTime>) {
for event in e_timeline_event.read() {
match event {
TimelineEvent::Play => r_global_seek_time.playing = true,
TimelineEvent::Pause => r_global_seek_time.playing = false,
TimelineEvent::Seek(time) => r_global_seek_time.time = *time,
}
}
}
fn update_tick(mut r_global_seek_time: ResMut<GlobalSeekTime>, time: Res<Time>) {
if r_global_seek_time.playing {
r_global_seek_time.time += time.delta_seconds();
}
}
#[cfg(feature = "wasm")]
mod wasm {
use bevy::{prelude::*, window::PrimaryWindow};
use bevy::{prelude::*, utils::hashbrown::HashMap, window::PrimaryWindow};
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use crate::wasm::{StaticBuffer, WasmMessageBuffer};
use super::{GlobalSeekTime, TimelineEvent};
use super::{AnimationTarget, GlobalSeekTime, TimelineEvent};
static SEEK_TIME: WasmMessageBuffer<TimelineEvent> = WasmMessageBuffer::new();
@@ -138,26 +165,84 @@ mod wasm {
}
#[wasm_bindgen(typescript_custom_section)]
const SEEK_TIME_CHANGED_EVENT: &str = r#"
const TIMELINE_TYPES: &str = r#"
export interface SeekTimeChanged extends CustomEvent {
type: "seek-time-changed";
detail: number;
detail: {
time: number,
playing: bool
};
}
export interface UpdateAnimationTargets extends CustomEvent {
type: "update-animation-targets",
details: {
added: Record<string, { name?: string, target_type: 'camera' | 'transform' }>,
removed: string[]
}
}
declare global {
export interface GlobalEventHandlersEventMap {
"seek-time-changed": CameraKeyframesEvent;
"update-animation-targets": UpdateAnimationTargets;
}
}
"#;
const CAMERA : &str = "camera";
const TRANSFORM : &str = "transform";
#[derive(Serialize, Deserialize)]
struct AddedAnimationTargetRecordItem {
name: Option<String>,
target_type: String
}
#[derive(Serialize, Deserialize)]
struct UpdatedAnimationTargetsEvent {
added: HashMap<Entity, AddedAnimationTargetRecordItem>,
removed: Vec<Entity>
}
pub(super) fn notify_updated_animation_targets(
q_window: Query<&Window, With<PrimaryWindow>>,
q_cameras: Query<(Entity, Option<&Name>), (Added<AnimationTarget>, With<Camera>)>,
q_transforms: Query<(Entity, Option<&Name>), (Added<AnimationTarget>, With<Transform>, Without<Camera>)>,
mut removed: bevy::prelude::RemovedComponents<AnimationTarget>
) -> Result<(), String> {
let Ok(window) = q_window.get_single() else {
return Ok(());
};
let added = q_transforms.iter().map(|(entity, name)| {
(entity, AddedAnimationTargetRecordItem { name: name.map(|v| v.to_string()), target_type: TRANSFORM.to_owned()})
}).chain(q_cameras.iter().map(|(entity, name)| (entity, AddedAnimationTargetRecordItem { name: name.map(|v| v.to_string()), target_type: CAMERA.to_owned()}))).collect::<HashMap<_,_>>();
let removed = removed.read().collect::<Vec<_>>();
let event = UpdatedAnimationTargetsEvent { added, removed };
crate::wasm::dispatch_to_js(
window,
"update-animation-targets",
&serde_json::to_value(&event).map_err(|e| format!("{e}"))?,
)
}
pub(super) fn notify_seek_time_changes(
r_global_seek_time: Res<GlobalSeekTime>,
q_window: Query<&Window, With<PrimaryWindow>>,
) -> Result<(), String> {
if !r_global_seek_time.is_changed() {
return Ok(());
}
let Ok(window) = q_window.get_single() else {
return Ok(());
};
info!("Current Seek Time: {r_global_seek_time:?}");
crate::wasm::dispatch_to_js(
window,
+3 -7
View File
@@ -23,13 +23,7 @@ use wasm_bindgen::prelude::wasm_bindgen;
use wgpu::{TextureDescriptor, TextureUsages};
use crate::{
camera_controller::{CameraController, CameraControllerBundle},
change_tracking::{ChangeTracker, ChangeTrackingPlugin},
debug::DebugPoint,
headless::{is_headless, is_not_headless, HeadlessSettings},
math::{Combinable, Extrema},
screenshot::ScreenCapture,
MainCamera,
anim::timeline_animation::AnimationTarget, camera_controller::{CameraController, CameraControllerBundle}, change_tracking::{ChangeTracker, ChangeTrackingPlugin}, debug::DebugPoint, headless::{is_headless, is_not_headless, HeadlessSettings}, math::{Combinable, Extrema}, screenshot::ScreenCapture, MainCamera
};
pub struct ScenePlugin;
@@ -260,6 +254,7 @@ fn initialize(mut cmd: Commands) {
cmd.spawn((
Name::new("Main Camera"),
MainCamera,
AnimationTarget,
ChangeTracker,
Camera3dBundle {
transform: camera_xform,
@@ -316,6 +311,7 @@ fn initialiaze_headless(
cmd.spawn((
Name::new("Main Camera"),
MainCamera,
AnimationTarget,
Camera3dBundle {
transform: camera_xform,
tonemapping: Tonemapping::BlenderFilmic,