Lightningbeam/lightningbeam-ui/lightningbeam-core/src/layer.rs

960 lines
27 KiB
Rust

//! Layer system for Lightningbeam
//!
//! Layers organize objects and shapes, and contain animation data.
use crate::animation::AnimationData;
use crate::clip::ClipInstance;
use crate::effect_layer::EffectLayer;
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,
/// Visual effects layer
Effect,
}
/// 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<String>) -> 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<String>) -> 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
}
}
/// Tween type between keyframes
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TweenType {
/// Hold shapes until next keyframe (no interpolation)
None,
/// Shape tween — morph geometry between keyframes
Shape,
}
impl Default for TweenType {
fn default() -> Self {
TweenType::None
}
}
/// A keyframe containing all shapes at a point in time
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ShapeKeyframe {
/// Time in seconds
pub time: f64,
/// All shapes at this keyframe
pub shapes: Vec<Shape>,
/// What happens between this keyframe and the next
#[serde(default)]
pub tween_after: TweenType,
/// Clip instance IDs visible in this keyframe region.
/// Groups are only rendered in regions where they appear in the left keyframe.
#[serde(default)]
pub clip_instance_ids: Vec<Uuid>,
}
impl ShapeKeyframe {
/// Create a new empty keyframe at a given time
pub fn new(time: f64) -> Self {
Self {
time,
shapes: Vec::new(),
tween_after: TweenType::None,
clip_instance_ids: Vec::new(),
}
}
/// Create a keyframe with shapes
pub fn with_shapes(time: f64, shapes: Vec<Shape>) -> Self {
Self {
time,
shapes,
tween_after: TweenType::None,
clip_instance_ids: Vec::new(),
}
}
}
/// 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<Uuid, Shape>,
/// Shape instances (references to shapes with transforms)
pub shape_instances: Vec<ShapeInstance>,
/// Shape keyframes (sorted by time) — replaces shapes/shape_instances
#[serde(default)]
pub keyframes: Vec<ShapeKeyframe>,
/// Clip instances (references to vector clips with transforms)
/// VectorLayer can contain instances of VectorClips for nested compositions
pub clip_instances: Vec<ClipInstance>,
}
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<String>) -> Self {
Self {
layer: Layer::new(LayerType::Vector, name),
shapes: HashMap::new(),
shape_instances: Vec::new(),
keyframes: 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) ===
/// 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<F>(&mut self, id: &Uuid, f: F)
where
F: FnOnce(&mut ShapeInstance),
{
if let Some(object) = self.get_object_mut(id) {
f(object);
}
}
// === KEYFRAME METHODS ===
/// Find the keyframe at-or-before the given time
pub fn keyframe_at(&self, time: f64) -> Option<&ShapeKeyframe> {
// keyframes are sorted by time; find the last one <= time
let idx = self.keyframes.partition_point(|kf| kf.time <= time);
if idx > 0 {
Some(&self.keyframes[idx - 1])
} else {
None
}
}
/// Find the mutable keyframe at-or-before the given time
pub fn keyframe_at_mut(&mut self, time: f64) -> Option<&mut ShapeKeyframe> {
let idx = self.keyframes.partition_point(|kf| kf.time <= time);
if idx > 0 {
Some(&mut self.keyframes[idx - 1])
} else {
None
}
}
/// Find the index of a keyframe at the exact time (within tolerance)
pub fn keyframe_index_at_exact(&self, time: f64, tolerance: f64) -> Option<usize> {
self.keyframes.iter().position(|kf| (kf.time - time).abs() < tolerance)
}
/// Get shapes visible at a given time (from the keyframe at-or-before time)
pub fn shapes_at_time(&self, time: f64) -> &[Shape] {
match self.keyframe_at(time) {
Some(kf) => &kf.shapes,
None => &[],
}
}
/// Get the duration of the keyframe span starting at-or-before `time`.
/// Returns the time from `time` to the next keyframe, or to `fallback_end` if there is no next keyframe.
pub fn keyframe_span_duration(&self, time: f64, fallback_end: f64) -> f64 {
// Find the next keyframe after `time`
let next_idx = self.keyframes.partition_point(|kf| kf.time <= time);
let end = if next_idx < self.keyframes.len() {
self.keyframes[next_idx].time
} else {
fallback_end
};
(end - time).max(0.0)
}
/// Check if a clip instance (group) is visible at the given time.
/// Returns true if the keyframe at-or-before `time` contains `clip_instance_id`.
pub fn is_clip_instance_visible_at(&self, clip_instance_id: &Uuid, time: f64) -> bool {
self.keyframe_at(time)
.map_or(false, |kf| kf.clip_instance_ids.contains(clip_instance_id))
}
/// Get the visibility end time for a group clip instance starting from `time`.
/// A group is visible in regions bounded on the left by a keyframe that contains it
/// and on the right by any keyframe. Walks forward through keyframe regions,
/// extending as long as consecutive left-keyframes contain the clip instance.
/// If the last containing keyframe has no next keyframe (no right border),
/// the region is just one frame long.
pub fn group_visibility_end(&self, clip_instance_id: &Uuid, time: f64, frame_duration: f64) -> f64 {
let start_idx = self.keyframes.partition_point(|kf| kf.time <= time);
// start_idx is the index of the first keyframe AFTER time
// Walk forward: each keyframe that contains the group extends visibility to the NEXT keyframe
for idx in start_idx..self.keyframes.len() {
if !self.keyframes[idx].clip_instance_ids.contains(clip_instance_id) {
// This keyframe doesn't contain the group — it's the right border of the last region
return self.keyframes[idx].time;
}
// This keyframe contains the group — check if there's a next one to form a right border
}
// No more keyframes after the last containing one — last region is one frame
if let Some(last_kf) = self.keyframes.last() {
if last_kf.clip_instance_ids.contains(clip_instance_id) {
return last_kf.time + frame_duration;
}
}
time + frame_duration
}
/// Get mutable shapes at a given time
pub fn shapes_at_time_mut(&mut self, time: f64) -> Option<&mut Vec<Shape>> {
self.keyframe_at_mut(time).map(|kf| &mut kf.shapes)
}
/// Find a shape by ID within the keyframe active at the given time
pub fn get_shape_in_keyframe(&self, shape_id: &Uuid, time: f64) -> Option<&Shape> {
self.keyframe_at(time)
.and_then(|kf| kf.shapes.iter().find(|s| &s.id == shape_id))
}
/// Find a mutable shape by ID within the keyframe active at the given time
pub fn get_shape_in_keyframe_mut(&mut self, shape_id: &Uuid, time: f64) -> Option<&mut Shape> {
self.keyframe_at_mut(time)
.and_then(|kf| kf.shapes.iter_mut().find(|s| &s.id == shape_id))
}
/// Ensure a keyframe exists at the exact time, creating an empty one if needed.
/// Returns a mutable reference to the keyframe.
pub fn ensure_keyframe_at(&mut self, time: f64) -> &mut ShapeKeyframe {
let tolerance = 0.001;
if let Some(idx) = self.keyframe_index_at_exact(time, tolerance) {
return &mut self.keyframes[idx];
}
// Insert in sorted position
let insert_idx = self.keyframes.partition_point(|kf| kf.time < time);
self.keyframes.insert(insert_idx, ShapeKeyframe::new(time));
&mut self.keyframes[insert_idx]
}
/// Insert a new keyframe at time by copying shapes from the active keyframe.
/// Shape UUIDs are regenerated (no cross-keyframe identity).
/// If a keyframe already exists at the exact time, does nothing and returns it.
pub fn insert_keyframe_from_current(&mut self, time: f64) -> &mut ShapeKeyframe {
let tolerance = 0.001;
if let Some(idx) = self.keyframe_index_at_exact(time, tolerance) {
return &mut self.keyframes[idx];
}
// Clone shapes and clip instance IDs from the active keyframe
let (cloned_shapes, cloned_clip_ids) = self
.keyframe_at(time)
.map(|kf| {
let shapes: Vec<Shape> = kf.shapes
.iter()
.map(|s| {
let mut new_shape = s.clone();
new_shape.id = Uuid::new_v4();
new_shape
})
.collect();
let clip_ids = kf.clip_instance_ids.clone();
(shapes, clip_ids)
})
.unwrap_or_default();
let insert_idx = self.keyframes.partition_point(|kf| kf.time < time);
let mut kf = ShapeKeyframe::with_shapes(time, cloned_shapes);
kf.clip_instance_ids = cloned_clip_ids;
self.keyframes.insert(insert_idx, kf);
&mut self.keyframes[insert_idx]
}
/// Add a shape to the keyframe at the given time.
/// Creates a keyframe if none exists at that time.
pub(crate) fn add_shape_to_keyframe(&mut self, shape: Shape, time: f64) {
let kf = self.ensure_keyframe_at(time);
kf.shapes.push(shape);
}
/// Remove a shape from the keyframe at the given time.
/// Returns the removed shape if found.
pub(crate) fn remove_shape_from_keyframe(&mut self, shape_id: &Uuid, time: f64) -> Option<Shape> {
let kf = self.keyframe_at_mut(time)?;
let idx = kf.shapes.iter().position(|s| &s.id == shape_id)?;
Some(kf.shapes.remove(idx))
}
/// Remove a keyframe at the exact time (within tolerance).
/// Returns the removed keyframe if found.
pub(crate) fn remove_keyframe_at(&mut self, time: f64, tolerance: f64) -> Option<ShapeKeyframe> {
if let Some(idx) = self.keyframe_index_at_exact(time, tolerance) {
Some(self.keyframes.remove(idx))
} else {
None
}
}
}
/// Audio layer subtype - distinguishes sampled audio from MIDI
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AudioLayerType {
/// Sampled audio (WAV, MP3, etc.)
Sampled,
/// MIDI sequence
Midi,
}
impl Default for AudioLayerType {
fn default() -> Self {
AudioLayerType::Sampled
}
}
/// 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<ClipInstance>,
/// Audio layer subtype (sampled vs MIDI)
#[serde(default)]
pub audio_layer_type: AudioLayerType,
}
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 sampled audio layer
pub fn new(name: impl Into<String>) -> Self {
Self {
layer: Layer::new(LayerType::Audio, name),
clip_instances: Vec::new(),
audio_layer_type: AudioLayerType::Sampled,
}
}
/// Create a new sampled audio layer (explicit)
pub fn new_sampled(name: impl Into<String>) -> Self {
Self::new(name)
}
/// Create a new MIDI layer
pub fn new_midi(name: impl Into<String>) -> Self {
Self {
layer: Layer::new(LayerType::Audio, name),
clip_instances: Vec::new(),
audio_layer_type: AudioLayerType::Midi,
}
}
}
/// 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<ClipInstance>,
}
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<String>) -> 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),
Effect(EffectLayer),
}
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(),
AnyLayer::Effect(l) => l.id(),
}
}
fn name(&self) -> &str {
match self {
AnyLayer::Vector(l) => l.name(),
AnyLayer::Audio(l) => l.name(),
AnyLayer::Video(l) => l.name(),
AnyLayer::Effect(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),
AnyLayer::Effect(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(),
AnyLayer::Effect(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),
AnyLayer::Effect(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(),
AnyLayer::Effect(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),
AnyLayer::Effect(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(),
AnyLayer::Effect(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),
AnyLayer::Effect(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(),
AnyLayer::Effect(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),
AnyLayer::Effect(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(),
AnyLayer::Effect(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),
AnyLayer::Effect(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(),
AnyLayer::Effect(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),
AnyLayer::Effect(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(),
AnyLayer::Effect(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),
AnyLayer::Effect(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,
AnyLayer::Effect(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,
AnyLayer::Effect(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_basic_properties() {
let layer = Layer::new(LayerType::Vector, "Test");
assert_eq!(layer.name, "Test");
assert_eq!(layer.visible, true);
assert_eq!(layer.opacity, 1.0);
assert_eq!(layer.volume, 1.0);
assert_eq!(layer.muted, false);
assert_eq!(layer.soloed, false);
assert_eq!(layer.locked, false);
}
}