//! Layer system for Lightningbeam //! //! Layers organize objects and shapes, and contain animation data. use crate::animation::AnimationData; use crate::clip::ClipInstance; use crate::object::ShapeInstance; use crate::shape::Shape; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use uuid::Uuid; /// Layer type #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum LayerType { /// Vector graphics layer (shapes and objects) Vector, /// Audio track Audio, /// Video clip Video, /// Generic automation layer Automation, } /// Common trait for all layer types /// /// Provides uniform access to common layer properties across VectorLayer, /// AudioLayer, VideoLayer, and their wrapper AnyLayer enum. pub trait LayerTrait { // Identity fn id(&self) -> Uuid; fn name(&self) -> &str; fn set_name(&mut self, name: String); fn has_custom_name(&self) -> bool; fn set_has_custom_name(&mut self, custom: bool); // Visual properties fn visible(&self) -> bool; fn set_visible(&mut self, visible: bool); fn opacity(&self) -> f64; fn set_opacity(&mut self, opacity: f64); // Audio properties (all layers can affect audio through nesting) fn volume(&self) -> f64; fn set_volume(&mut self, volume: f64); fn muted(&self) -> bool; fn set_muted(&mut self, muted: bool); // Editor state fn soloed(&self) -> bool; fn set_soloed(&mut self, soloed: bool); fn locked(&self) -> bool; fn set_locked(&mut self, locked: bool); } /// Base layer structure #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Layer { /// Unique identifier pub id: Uuid, /// Layer type pub layer_type: LayerType, /// Layer name pub name: String, /// Whether the name was set by user (vs auto-generated) pub has_custom_name: bool, /// Whether the layer is visible pub visible: bool, /// Layer opacity (0.0 to 1.0) pub opacity: f64, /// Audio volume (1.0 = 100%, affects nested audio layers/clips) pub volume: f64, /// Audio mute state pub muted: bool, /// Solo state (for isolating layers) pub soloed: bool, /// Lock state (prevents editing) pub locked: bool, /// Animation data for this layer pub animation_data: AnimationData, } impl Layer { /// Create a new layer pub fn new(layer_type: LayerType, name: impl Into) -> Self { Self { id: Uuid::new_v4(), layer_type, name: name.into(), has_custom_name: false, // Auto-generated by default visible: true, opacity: 1.0, volume: 1.0, // 100% volume muted: false, soloed: false, locked: false, animation_data: AnimationData::new(), } } /// Create with a specific ID pub fn with_id(id: Uuid, layer_type: LayerType, name: impl Into) -> Self { Self { id, layer_type, name: name.into(), has_custom_name: false, visible: true, opacity: 1.0, volume: 1.0, muted: false, soloed: false, locked: false, animation_data: AnimationData::new(), } } /// Set visibility pub fn with_visibility(mut self, visible: bool) -> Self { self.visible = visible; self } } /// Vector layer containing shapes and objects #[derive(Clone, Debug, Serialize, Deserialize)] pub struct VectorLayer { /// Base layer properties pub layer: Layer, /// Shapes defined in this layer (indexed by UUID for O(1) lookup) pub shapes: HashMap, /// Shape instances (references to shapes with transforms) pub shape_instances: Vec, /// Clip instances (references to vector clips with transforms) /// VectorLayer can contain instances of VectorClips for nested compositions pub clip_instances: Vec, } impl LayerTrait for VectorLayer { fn id(&self) -> Uuid { self.layer.id } fn name(&self) -> &str { &self.layer.name } fn set_name(&mut self, name: String) { self.layer.name = name; } fn has_custom_name(&self) -> bool { self.layer.has_custom_name } fn set_has_custom_name(&mut self, custom: bool) { self.layer.has_custom_name = custom; } fn visible(&self) -> bool { self.layer.visible } fn set_visible(&mut self, visible: bool) { self.layer.visible = visible; } fn opacity(&self) -> f64 { self.layer.opacity } fn set_opacity(&mut self, opacity: f64) { self.layer.opacity = opacity; } fn volume(&self) -> f64 { self.layer.volume } fn set_volume(&mut self, volume: f64) { self.layer.volume = volume; } fn muted(&self) -> bool { self.layer.muted } fn set_muted(&mut self, muted: bool) { self.layer.muted = muted; } fn soloed(&self) -> bool { self.layer.soloed } fn set_soloed(&mut self, soloed: bool) { self.layer.soloed = soloed; } fn locked(&self) -> bool { self.layer.locked } fn set_locked(&mut self, locked: bool) { self.layer.locked = locked; } } impl VectorLayer { /// Create a new vector layer pub fn new(name: impl Into) -> Self { Self { layer: Layer::new(LayerType::Vector, name), shapes: HashMap::new(), shape_instances: Vec::new(), clip_instances: Vec::new(), } } /// Add a shape to this layer pub fn add_shape(&mut self, shape: Shape) -> Uuid { let id = shape.id; self.shapes.insert(id, shape); id } /// Add an object to this layer pub fn add_object(&mut self, object: ShapeInstance) -> Uuid { let id = object.id; self.shape_instances.push(object); id } /// Find a shape by ID pub fn get_shape(&self, id: &Uuid) -> Option<&Shape> { self.shapes.get(id) } /// Find a mutable shape by ID pub fn get_shape_mut(&mut self, id: &Uuid) -> Option<&mut Shape> { self.shapes.get_mut(id) } /// Find an object by ID pub fn get_object(&self, id: &Uuid) -> Option<&ShapeInstance> { self.shape_instances.iter().find(|o| &o.id == id) } /// Find a mutable object by ID pub fn get_object_mut(&mut self, id: &Uuid) -> Option<&mut ShapeInstance> { self.shape_instances.iter_mut().find(|o| &o.id == id) } // === MUTATION METHODS (pub(crate) - only accessible to action module) === /// Add a shape to this layer (internal, for actions only) /// /// This method is intentionally `pub(crate)` to ensure mutations /// only happen through the action system. pub(crate) fn add_shape_internal(&mut self, shape: Shape) -> Uuid { let id = shape.id; self.shapes.insert(id, shape); id } /// Add an object to this layer (internal, for actions only) /// /// This method is intentionally `pub(crate)` to ensure mutations /// only happen through the action system. pub(crate) fn add_object_internal(&mut self, object: ShapeInstance) -> Uuid { let id = object.id; self.shape_instances.push(object); id } /// Remove a shape from this layer (internal, for actions only) /// /// Returns the removed shape if found. /// This method is intentionally `pub(crate)` to ensure mutations /// only happen through the action system. pub(crate) fn remove_shape_internal(&mut self, id: &Uuid) -> Option { self.shapes.remove(id) } /// Remove an object from this layer (internal, for actions only) /// /// Returns the removed object if found. /// This method is intentionally `pub(crate)` to ensure mutations /// only happen through the action system. pub(crate) fn remove_object_internal(&mut self, id: &Uuid) -> Option { if let Some(index) = self.shape_instances.iter().position(|o| &o.id == id) { Some(self.shape_instances.remove(index)) } else { None } } /// Modify an object in place (internal, for actions only) /// /// Applies the given function to the object if found. /// This method is intentionally `pub(crate)` to ensure mutations /// only happen through the action system. pub fn modify_object_internal(&mut self, id: &Uuid, f: F) where F: FnOnce(&mut ShapeInstance), { if let Some(object) = self.get_object_mut(id) { f(object); } } } /// Audio layer containing audio clips #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AudioLayer { /// Base layer properties pub layer: Layer, /// Clip instances (references to audio clips) /// AudioLayer can contain instances of AudioClips (sampled or MIDI) pub clip_instances: Vec, } impl LayerTrait for AudioLayer { fn id(&self) -> Uuid { self.layer.id } fn name(&self) -> &str { &self.layer.name } fn set_name(&mut self, name: String) { self.layer.name = name; } fn has_custom_name(&self) -> bool { self.layer.has_custom_name } fn set_has_custom_name(&mut self, custom: bool) { self.layer.has_custom_name = custom; } fn visible(&self) -> bool { self.layer.visible } fn set_visible(&mut self, visible: bool) { self.layer.visible = visible; } fn opacity(&self) -> f64 { self.layer.opacity } fn set_opacity(&mut self, opacity: f64) { self.layer.opacity = opacity; } fn volume(&self) -> f64 { self.layer.volume } fn set_volume(&mut self, volume: f64) { self.layer.volume = volume; } fn muted(&self) -> bool { self.layer.muted } fn set_muted(&mut self, muted: bool) { self.layer.muted = muted; } fn soloed(&self) -> bool { self.layer.soloed } fn set_soloed(&mut self, soloed: bool) { self.layer.soloed = soloed; } fn locked(&self) -> bool { self.layer.locked } fn set_locked(&mut self, locked: bool) { self.layer.locked = locked; } } impl AudioLayer { /// Create a new audio layer pub fn new(name: impl Into) -> Self { Self { layer: Layer::new(LayerType::Audio, name), clip_instances: Vec::new(), } } } /// Video layer containing video clips #[derive(Clone, Debug, Serialize, Deserialize)] pub struct VideoLayer { /// Base layer properties pub layer: Layer, /// Clip instances (references to video clips) /// VideoLayer can contain instances of VideoClips pub clip_instances: Vec, } impl LayerTrait for VideoLayer { fn id(&self) -> Uuid { self.layer.id } fn name(&self) -> &str { &self.layer.name } fn set_name(&mut self, name: String) { self.layer.name = name; } fn has_custom_name(&self) -> bool { self.layer.has_custom_name } fn set_has_custom_name(&mut self, custom: bool) { self.layer.has_custom_name = custom; } fn visible(&self) -> bool { self.layer.visible } fn set_visible(&mut self, visible: bool) { self.layer.visible = visible; } fn opacity(&self) -> f64 { self.layer.opacity } fn set_opacity(&mut self, opacity: f64) { self.layer.opacity = opacity; } fn volume(&self) -> f64 { self.layer.volume } fn set_volume(&mut self, volume: f64) { self.layer.volume = volume; } fn muted(&self) -> bool { self.layer.muted } fn set_muted(&mut self, muted: bool) { self.layer.muted = muted; } fn soloed(&self) -> bool { self.layer.soloed } fn set_soloed(&mut self, soloed: bool) { self.layer.soloed = soloed; } fn locked(&self) -> bool { self.layer.locked } fn set_locked(&mut self, locked: bool) { self.layer.locked = locked; } } impl VideoLayer { /// Create a new video layer pub fn new(name: impl Into) -> Self { Self { layer: Layer::new(LayerType::Video, name), clip_instances: Vec::new(), } } } /// Unified layer enum for polymorphic handling #[derive(Clone, Debug, Serialize, Deserialize)] pub enum AnyLayer { Vector(VectorLayer), Audio(AudioLayer), Video(VideoLayer), } impl LayerTrait for AnyLayer { fn id(&self) -> Uuid { match self { AnyLayer::Vector(l) => l.id(), AnyLayer::Audio(l) => l.id(), AnyLayer::Video(l) => l.id(), } } fn name(&self) -> &str { match self { AnyLayer::Vector(l) => l.name(), AnyLayer::Audio(l) => l.name(), AnyLayer::Video(l) => l.name(), } } fn set_name(&mut self, name: String) { match self { AnyLayer::Vector(l) => l.set_name(name), AnyLayer::Audio(l) => l.set_name(name), AnyLayer::Video(l) => l.set_name(name), } } fn has_custom_name(&self) -> bool { match self { AnyLayer::Vector(l) => l.has_custom_name(), AnyLayer::Audio(l) => l.has_custom_name(), AnyLayer::Video(l) => l.has_custom_name(), } } fn set_has_custom_name(&mut self, custom: bool) { match self { AnyLayer::Vector(l) => l.set_has_custom_name(custom), AnyLayer::Audio(l) => l.set_has_custom_name(custom), AnyLayer::Video(l) => l.set_has_custom_name(custom), } } fn visible(&self) -> bool { match self { AnyLayer::Vector(l) => l.visible(), AnyLayer::Audio(l) => l.visible(), AnyLayer::Video(l) => l.visible(), } } fn set_visible(&mut self, visible: bool) { match self { AnyLayer::Vector(l) => l.set_visible(visible), AnyLayer::Audio(l) => l.set_visible(visible), AnyLayer::Video(l) => l.set_visible(visible), } } fn opacity(&self) -> f64 { match self { AnyLayer::Vector(l) => l.opacity(), AnyLayer::Audio(l) => l.opacity(), AnyLayer::Video(l) => l.opacity(), } } fn set_opacity(&mut self, opacity: f64) { match self { AnyLayer::Vector(l) => l.set_opacity(opacity), AnyLayer::Audio(l) => l.set_opacity(opacity), AnyLayer::Video(l) => l.set_opacity(opacity), } } fn volume(&self) -> f64 { match self { AnyLayer::Vector(l) => l.volume(), AnyLayer::Audio(l) => l.volume(), AnyLayer::Video(l) => l.volume(), } } fn set_volume(&mut self, volume: f64) { match self { AnyLayer::Vector(l) => l.set_volume(volume), AnyLayer::Audio(l) => l.set_volume(volume), AnyLayer::Video(l) => l.set_volume(volume), } } fn muted(&self) -> bool { match self { AnyLayer::Vector(l) => l.muted(), AnyLayer::Audio(l) => l.muted(), AnyLayer::Video(l) => l.muted(), } } fn set_muted(&mut self, muted: bool) { match self { AnyLayer::Vector(l) => l.set_muted(muted), AnyLayer::Audio(l) => l.set_muted(muted), AnyLayer::Video(l) => l.set_muted(muted), } } fn soloed(&self) -> bool { match self { AnyLayer::Vector(l) => l.soloed(), AnyLayer::Audio(l) => l.soloed(), AnyLayer::Video(l) => l.soloed(), } } fn set_soloed(&mut self, soloed: bool) { match self { AnyLayer::Vector(l) => l.set_soloed(soloed), AnyLayer::Audio(l) => l.set_soloed(soloed), AnyLayer::Video(l) => l.set_soloed(soloed), } } fn locked(&self) -> bool { match self { AnyLayer::Vector(l) => l.locked(), AnyLayer::Audio(l) => l.locked(), AnyLayer::Video(l) => l.locked(), } } fn set_locked(&mut self, locked: bool) { match self { AnyLayer::Vector(l) => l.set_locked(locked), AnyLayer::Audio(l) => l.set_locked(locked), AnyLayer::Video(l) => l.set_locked(locked), } } } impl AnyLayer { /// Get a reference to the base layer pub fn layer(&self) -> &Layer { match self { AnyLayer::Vector(l) => &l.layer, AnyLayer::Audio(l) => &l.layer, AnyLayer::Video(l) => &l.layer, } } /// Get a mutable reference to the base layer pub fn layer_mut(&mut self) -> &mut Layer { match self { AnyLayer::Vector(l) => &mut l.layer, AnyLayer::Audio(l) => &mut l.layer, AnyLayer::Video(l) => &mut l.layer, } } /// Get the layer ID pub fn id(&self) -> Uuid { self.layer().id } /// Get the layer name pub fn name(&self) -> &str { &self.layer().name } } #[cfg(test)] mod tests { use super::*; #[test] fn test_layer_creation() { let layer = Layer::new(LayerType::Vector, "Test Layer"); assert_eq!(layer.layer_type, LayerType::Vector); assert_eq!(layer.name, "Test Layer"); assert_eq!(layer.opacity, 1.0); } #[test] fn test_vector_layer() { let vector_layer = VectorLayer::new("My Layer"); assert_eq!(vector_layer.shapes.len(), 0); assert_eq!(vector_layer.shape_instances.len(), 0); } #[test] fn test_layer_time_range() { let layer = Layer::new(LayerType::Vector, "Test") .with_time_range(5.0, 15.0); assert_eq!(layer.duration(), 10.0); assert!(layer.contains_time(10.0)); assert!(!layer.contains_time(3.0)); assert!(!layer.contains_time(20.0)); } }