Lightningbeam/lightningbeam-ui/lightningbeam-core/src/actions/set_keyframe.rs

148 lines
5.0 KiB
Rust

//! Set keyframe action
//!
//! For vector layers: creates a new ShapeKeyframe at the given time by copying
//! shapes from the current keyframe span (with new UUIDs).
//! For clip instances: adds AnimationData keyframes for transform properties.
use crate::action::Action;
use crate::animation::{AnimationCurve, AnimationTarget, Keyframe, TransformProperty};
use crate::document::Document;
use crate::layer::{AnyLayer, ShapeKeyframe};
use uuid::Uuid;
/// Undo info for a clip animation curve
struct ClipUndoEntry {
target: AnimationTarget,
old_keyframe: Option<Keyframe>,
curve_created: bool,
}
pub struct SetKeyframeAction {
layer_id: Uuid,
time: f64,
/// Clip instance IDs to keyframe (motion tweens)
clip_instance_ids: Vec<Uuid>,
/// Whether a shape keyframe was created by this action
shape_keyframe_created: bool,
/// The removed keyframe for rollback (if we created one)
removed_keyframe: Option<ShapeKeyframe>,
/// Clip animation undo entries
clip_undo_entries: Vec<ClipUndoEntry>,
}
impl SetKeyframeAction {
pub fn new(layer_id: Uuid, time: f64, clip_instance_ids: Vec<Uuid>) -> Self {
Self {
layer_id,
time,
clip_instance_ids,
shape_keyframe_created: false,
removed_keyframe: None,
clip_undo_entries: Vec::new(),
}
}
}
const TRANSFORM_PROPERTIES: &[TransformProperty] = &[
TransformProperty::X,
TransformProperty::Y,
TransformProperty::Rotation,
TransformProperty::ScaleX,
TransformProperty::ScaleY,
TransformProperty::SkewX,
TransformProperty::SkewY,
TransformProperty::Opacity,
];
fn transform_default(prop: &TransformProperty) -> f64 {
match prop {
TransformProperty::ScaleX | TransformProperty::ScaleY => 1.0,
TransformProperty::Opacity => 1.0,
_ => 0.0,
}
}
impl Action for SetKeyframeAction {
fn execute(&mut self, document: &mut Document) -> Result<(), String> {
self.clip_undo_entries.clear();
self.shape_keyframe_created = false;
let layer = document
.get_layer_mut(&self.layer_id)
.ok_or_else(|| format!("Layer {} not found", self.layer_id))?;
// For vector layers: create a shape keyframe
if let AnyLayer::Vector(vl) = layer {
// Check if a keyframe already exists at this exact time
let already_exists = vl.keyframe_index_at_exact(self.time, 0.001).is_some();
if !already_exists {
vl.insert_keyframe_from_current(self.time);
self.shape_keyframe_created = true;
}
// Add clip animation keyframes
for clip_id in &self.clip_instance_ids {
for prop in TRANSFORM_PROPERTIES {
let target = AnimationTarget::Object {
id: *clip_id,
property: *prop,
};
let default = transform_default(prop);
let value = vl.layer.animation_data.eval(&target, self.time, default);
let curve_created = vl.layer.animation_data.get_curve(&target).is_none();
if curve_created {
vl.layer
.animation_data
.set_curve(AnimationCurve::new(target.clone(), default));
}
let curve = vl.layer.animation_data.get_curve_mut(&target).unwrap();
let old_keyframe = curve.get_keyframe_at(self.time, 0.001).cloned();
curve.set_keyframe(Keyframe::linear(self.time, value));
self.clip_undo_entries.push(ClipUndoEntry {
target,
old_keyframe,
curve_created,
});
}
}
}
Ok(())
}
fn rollback(&mut self, document: &mut Document) -> Result<(), String> {
let layer = document
.get_layer_mut(&self.layer_id)
.ok_or_else(|| format!("Layer {} not found", self.layer_id))?;
if let AnyLayer::Vector(vl) = layer {
// Undo clip animation keyframes in reverse order
for entry in self.clip_undo_entries.drain(..).rev() {
if entry.curve_created {
vl.layer.animation_data.remove_curve(&entry.target);
} else if let Some(curve) = vl.layer.animation_data.get_curve_mut(&entry.target) {
curve.remove_keyframe(self.time, 0.001);
if let Some(old_kf) = entry.old_keyframe {
curve.set_keyframe(old_kf);
}
}
}
// Remove the shape keyframe if we created one
if self.shape_keyframe_created {
self.removed_keyframe = vl.remove_keyframe_at(self.time, 0.001);
self.shape_keyframe_created = false;
}
}
Ok(())
}
fn description(&self) -> String {
"New keyframe".to_string()
}
}