mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-29 09:08:58 +00:00
add deletion of keyframes
This commit is contained in:
@@ -101,6 +101,10 @@ export class AnimEditorElement extends LitElement {
|
|||||||
keyframes[keyframe] = added[keyframe];
|
keyframes[keyframe] = added[keyframe];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let keyframe of removed) {
|
||||||
|
delete keyframes[keyframe];
|
||||||
|
}
|
||||||
|
|
||||||
this.keyframes = keyframes;
|
this.keyframes = keyframes;
|
||||||
|
|
||||||
let min = Infinity
|
let min = Infinity
|
||||||
|
|||||||
@@ -11,14 +11,13 @@ use bevy::{
|
|||||||
|
|
||||||
use bevy_tweening::{
|
use bevy_tweening::{
|
||||||
lens::{TransformPositionLens, TransformRotationLens, TransformScaleLens},
|
lens::{TransformPositionLens, TransformRotationLens, TransformScaleLens},
|
||||||
Animator, EaseMethod, Sequence, Targetable, Tracks, Tween,
|
Animator, EaseMethod, Sequence, Targetable, Tracks, Tween, TweenCompleted,
|
||||||
TweenCompleted,
|
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use space_editor::space_prefab::editor_registry::EditorRegistryExt;
|
use space_editor::space_prefab::editor_registry::EditorRegistryExt;
|
||||||
|
|
||||||
use crate::interaction::Selectable;
|
use crate::interaction::{MarkedForDeletion, Selectable};
|
||||||
|
|
||||||
pub struct TimelinePlugin;
|
pub struct TimelinePlugin;
|
||||||
|
|
||||||
@@ -53,6 +52,7 @@ impl Plugin for TimelinePlugin {
|
|||||||
timeline_bound_component_animator_system::<Transform>,
|
timeline_bound_component_animator_system::<Transform>,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
app.add_systems(PostUpdate, generate_transform_animation);
|
app.add_systems(PostUpdate, generate_transform_animation);
|
||||||
|
|
||||||
@@ -77,6 +77,8 @@ impl Plugin for TimelinePlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MIN_KEYFRAME_DISTANCE: f32 = 0.01;
|
||||||
|
|
||||||
#[derive(Event, Clone, Copy, Debug)]
|
#[derive(Event, Clone, Copy, Debug)]
|
||||||
pub enum TimelineInteractionEvent {
|
pub enum TimelineInteractionEvent {
|
||||||
Play,
|
Play,
|
||||||
@@ -88,7 +90,7 @@ pub enum TimelineInteractionEvent {
|
|||||||
pub enum KeyframeInteractionEvent {
|
pub enum KeyframeInteractionEvent {
|
||||||
RecordKeyframe { target: Entity },
|
RecordKeyframe { target: Entity },
|
||||||
MoveKeyframeTime { target: Entity, time: f32 },
|
MoveKeyframeTime { target: Entity, time: f32 },
|
||||||
AddEntityTrack { target: Entity }
|
AddEntityTrack { target: Entity },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource, Clone, Copy, Debug, Default, Reflect, Serialize, Deserialize)]
|
#[derive(Resource, Clone, Copy, Debug, Default, Reflect, Serialize, Deserialize)]
|
||||||
@@ -227,29 +229,50 @@ fn process_keyframe_events(
|
|||||||
r_global_seek_time: Res<GlobalSeekTime>,
|
r_global_seek_time: Res<GlobalSeekTime>,
|
||||||
q_animation_targets: Query<(Entity, &Transform), With<AnimationTarget>>,
|
q_animation_targets: Query<(Entity, &Transform), With<AnimationTarget>>,
|
||||||
q_potential_targets: Query<Entity, (With<Transform>, Without<AnimationTarget>)>,
|
q_potential_targets: Query<Entity, (With<Transform>, Without<AnimationTarget>)>,
|
||||||
mut q_keyframes: Query<&mut Keyframe>,
|
mut q_keyframes: Query<(Entity, &mut Keyframe)>,
|
||||||
) {
|
) {
|
||||||
for event in e_timeline_event.read() {
|
for event in e_timeline_event.read() {
|
||||||
info!("Processing Keyframe Event {event:?}");
|
info!("Processing Keyframe Event {event:?}");
|
||||||
match event {
|
match event {
|
||||||
KeyframeInteractionEvent::RecordKeyframe { target } => {
|
KeyframeInteractionEvent::RecordKeyframe { target } => {
|
||||||
if let Ok((target, transform)) = q_animation_targets.get(*target) {
|
if let Ok((target, transform)) = q_animation_targets.get(*target) {
|
||||||
commands.spawn((
|
let mut existing = None;
|
||||||
SpatialBundle {
|
for (entity, keyframe) in q_keyframes.iter_mut() {
|
||||||
transform: *transform,
|
if keyframe.target == target
|
||||||
..Default::default()
|
&& (keyframe.time - r_global_seek_time.time).abs()
|
||||||
},
|
< MIN_KEYFRAME_DISTANCE
|
||||||
Selectable,
|
{
|
||||||
Keyframe {
|
existing = Some(entity);
|
||||||
target,
|
break;
|
||||||
time: r_global_seek_time.time,
|
}
|
||||||
},
|
}
|
||||||
TransformKeyframe::default(),
|
if let Some(existing) = existing {
|
||||||
));
|
commands.entity(existing).insert((
|
||||||
|
*transform,
|
||||||
|
TransformKeyframe::default(),
|
||||||
|
Keyframe {
|
||||||
|
target,
|
||||||
|
time: r_global_seek_time.time,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
commands.spawn((
|
||||||
|
SpatialBundle {
|
||||||
|
transform: *transform,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Selectable,
|
||||||
|
Keyframe {
|
||||||
|
target,
|
||||||
|
time: r_global_seek_time.time,
|
||||||
|
},
|
||||||
|
TransformKeyframe::default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyframeInteractionEvent::MoveKeyframeTime { target, time } => {
|
KeyframeInteractionEvent::MoveKeyframeTime { target, time } => {
|
||||||
if let Ok(mut keyframe) = q_keyframes.get_mut(*target) {
|
if let Ok((_, mut keyframe)) = q_keyframes.get_mut(*target) {
|
||||||
keyframe.time = *time;
|
keyframe.time = *time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +283,7 @@ fn process_keyframe_events(
|
|||||||
} else {
|
} else {
|
||||||
warn!("Can't add animation target to entity");
|
warn!("Can't add animation target to entity");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,11 +291,16 @@ fn process_keyframe_events(
|
|||||||
fn generate_transform_animation(
|
fn generate_transform_animation(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
changed: Query<&Keyframe, (With<TransformKeyframe>, Changed<Keyframe>)>,
|
changed: Query<&Keyframe, (With<TransformKeyframe>, Changed<Keyframe>)>,
|
||||||
keyframes: Query<(&Keyframe, &Transform, &TransformKeyframe, Entity)>,
|
keyframes: Query<(&Keyframe, &Transform, &TransformKeyframe, Entity), Without<MarkedForDeletion>>,
|
||||||
|
removed: Query<&Keyframe, With<MarkedForDeletion>>,
|
||||||
) {
|
) {
|
||||||
let changed_targets = changed
|
let changed_targets = changed
|
||||||
.iter()
|
.iter()
|
||||||
.map(|keyframe| keyframe.target)
|
.map(|keyframe| keyframe.target)
|
||||||
|
.chain(removed.iter().map(|keyframe| {
|
||||||
|
info!("Processing a removed keyframe {keyframe:?}");
|
||||||
|
keyframe.target
|
||||||
|
}))
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
let keyframes = keyframes
|
let keyframes = keyframes
|
||||||
.iter()
|
.iter()
|
||||||
@@ -467,13 +495,11 @@ mod wasm {
|
|||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
scene::SceneElement,
|
interaction::MarkedForDeletion, scene::SceneElement, wasm::{FromStr, StaticBuffer, WasmMessageBuffer}
|
||||||
wasm::{FromStr, StaticBuffer, WasmMessageBuffer},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
AnimationTarget, EaseType, GlobalSeekTime, Keyframe, KeyframeInteractionEvent,
|
AnimationTarget, EaseType, GlobalSeekTime, Keyframe, KeyframeInteractionEvent, TimedAnimationClipAsset, TimelineInteractionEvent, TransformKeyframe
|
||||||
TimedAnimationClipAsset, TimelineInteractionEvent, TransformKeyframe,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static SEEK_TIME: WasmMessageBuffer<TimelineInteractionEvent> = WasmMessageBuffer::new();
|
static SEEK_TIME: WasmMessageBuffer<TimelineInteractionEvent> = WasmMessageBuffer::new();
|
||||||
@@ -522,9 +548,7 @@ mod wasm {
|
|||||||
let Ok(target) = Entity::from_str(&entity) else {
|
let Ok(target) = Entity::from_str(&entity) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
KEYFRAME_EVENT.write_message(vec![KeyframeInteractionEvent::AddEntityTrack {
|
KEYFRAME_EVENT.write_message(vec![KeyframeInteractionEvent::AddEntityTrack { target }]);
|
||||||
target
|
|
||||||
}]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn update_global_seek_time(
|
pub(super) fn update_global_seek_time(
|
||||||
@@ -734,7 +758,7 @@ mod wasm {
|
|||||||
(Entity, &Keyframe, &TimedAnimationClipAsset),
|
(Entity, &Keyframe, &TimedAnimationClipAsset),
|
||||||
Or<(Changed<Keyframe>, Changed<TimedAnimationClipAsset>)>,
|
Or<(Changed<Keyframe>, Changed<TimedAnimationClipAsset>)>,
|
||||||
>,
|
>,
|
||||||
mut removed: bevy::prelude::RemovedComponents<Keyframe>,
|
removed: Query<Entity, With<MarkedForDeletion>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let Ok(window) = q_window.get_single() else {
|
let Ok(window) = q_window.get_single() else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -775,7 +799,7 @@ mod wasm {
|
|||||||
}))
|
}))
|
||||||
.collect::<Result<HashMap<_, _>, String>>()?;
|
.collect::<Result<HashMap<_, _>, String>>()?;
|
||||||
|
|
||||||
let removed = removed.read().map(|v| format!("{v:?}")).collect::<Vec<_>>();
|
let removed = removed.iter().map(|v| format!("{v:?}")).collect::<Vec<_>>();
|
||||||
|
|
||||||
if added.is_empty() && removed.is_empty() {
|
if added.is_empty() && removed.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ impl Plugin for SelectionPlugin {
|
|||||||
PostUpdate,
|
PostUpdate,
|
||||||
draw_selection_outline.run_if(resource_changed_or_removed::<Selection>()),
|
draw_selection_outline.run_if(resource_changed_or_removed::<Selection>()),
|
||||||
)
|
)
|
||||||
|
.add_systems(First, finalize_delete)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
OnEnter(SceneState::Active),
|
OnEnter(SceneState::Active),
|
||||||
(apply_deferred, tag_scene_elements)
|
(apply_deferred, tag_scene_elements)
|
||||||
@@ -158,6 +159,9 @@ fn update_selection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct MarkedForDeletion;
|
||||||
|
|
||||||
fn process_delete_inputs(
|
fn process_delete_inputs(
|
||||||
mut cmd: Commands,
|
mut cmd: Commands,
|
||||||
mut er_input: EventReader<InputAction>,
|
mut er_input: EventReader<InputAction>,
|
||||||
@@ -172,7 +176,7 @@ fn process_delete_inputs(
|
|||||||
}) {
|
}) {
|
||||||
let entity = **r_selection;
|
let entity = **r_selection;
|
||||||
|
|
||||||
cmd.entity(entity).despawn_recursive();
|
cmd.entity(entity).insert(MarkedForDeletion);
|
||||||
cmd.remove_resource::<Selection>();
|
cmd.remove_resource::<Selection>();
|
||||||
ew_changes.send(NewChange {
|
ew_changes.send(NewChange {
|
||||||
change: Arc::new(RemovedEntity { entity }),
|
change: Arc::new(RemovedEntity { entity }),
|
||||||
@@ -180,6 +184,15 @@ fn process_delete_inputs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finalize_delete(
|
||||||
|
mut cmd: Commands,
|
||||||
|
q: Query<Entity, With<MarkedForDeletion>>
|
||||||
|
) {
|
||||||
|
for entity in &q {
|
||||||
|
cmd.entity(entity).despawn_recursive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_selection_outline(
|
fn draw_selection_outline(
|
||||||
mut cmd: Commands,
|
mut cmd: Commands,
|
||||||
q_selected: Query<&Children, With<Selected>>,
|
q_selected: Query<&Children, With<Selected>>,
|
||||||
|
|||||||
Reference in New Issue
Block a user