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

1053 lines
31 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Layer system for Lightningbeam
//!
//! Layers organize objects and shapes, and contain animation data.
use crate::animation::AnimationData;
use crate::clip::ClipInstance;
use crate::dcel::Dcel;
use crate::effect_layer::EffectLayer;
use crate::object::ShapeInstance;
use crate::raster_layer::RasterLayer;
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,
/// Group layer containing child layers (e.g. video + audio)
Group,
/// Raster pixel-buffer painting layer
Raster,
}
/// 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);
}
fn default_input_gain() -> f64 { 1.0 }
/// 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,
/// Input gain for recording (1.0 = unity, range 0.04.0)
#[serde(default = "default_input_gain")]
pub input_gain: 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
input_gain: 1.0,
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,
input_gain: 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 vector artwork as a DCEL planar subdivision.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ShapeKeyframe {
/// Time in seconds
pub time: f64,
/// DCEL planar subdivision containing all vector artwork
pub dcel: Dcel,
/// 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,
dcel: Dcel::new(),
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,
/// Legacy shapes — kept for old .beam file compat, not written to new files.
#[serde(default, skip_serializing)]
pub shapes: HashMap<Uuid, Shape>,
/// Legacy shape instances — kept for old .beam file compat, not written to new files.
#[serde(default, skip_serializing)]
pub shape_instances: Vec<ShapeInstance>,
/// Shape keyframes (sorted by time)
#[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 the DCEL at a given time (from the keyframe at-or-before time)
pub fn dcel_at_time(&self, time: f64) -> Option<&Dcel> {
self.keyframe_at(time).map(|kf| &kf.dcel)
}
/// Get a mutable DCEL at a given time
pub fn dcel_at_time_mut(&mut self, time: f64) -> Option<&mut Dcel> {
self.keyframe_at_mut(time).map(|kf| &mut kf.dcel)
}
/// 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
}
// Shape-based methods removed — use DCEL methods instead.
// - shapes_at_time_mut → dcel_at_time_mut
// - get_shape_in_keyframe → use DCEL vertex/edge/face accessors
// - get_shape_in_keyframe_mut → use DCEL vertex/edge/face accessors
/// 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 cloning the DCEL from the active keyframe.
/// 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 DCEL and clip instance IDs from the active keyframe
let (cloned_dcel, cloned_clip_ids) = self
.keyframe_at(time)
.map(|kf| {
(kf.dcel.clone(), kf.clip_instance_ids.clone())
})
.unwrap_or_else(|| (Dcel::new(), Vec::new()));
let insert_idx = self.keyframes.partition_point(|kf| kf.time < time);
let mut kf = ShapeKeyframe::new(time);
kf.dcel = cloned_dcel;
kf.clip_instance_ids = cloned_clip_ids;
self.keyframes.insert(insert_idx, kf);
&mut self.keyframes[insert_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>,
/// When true, the live webcam feed is shown in the stage for this
/// layer (at times when no clip instance is active).
#[serde(default)]
pub camera_enabled: bool,
}
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(),
camera_enabled: false,
}
}
}
/// Group layer containing child layers (e.g. video + audio).
/// Collapsible in the timeline; when collapsed shows a merged clip view.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GroupLayer {
/// Base layer properties
pub layer: Layer,
/// Child layers in this group (typically one VideoLayer + one AudioLayer)
pub children: Vec<AnyLayer>,
/// Whether the group is expanded in the timeline
#[serde(default = "default_true")]
pub expanded: bool,
}
fn default_true() -> bool {
true
}
impl LayerTrait for GroupLayer {
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 GroupLayer {
/// Create a new group layer
pub fn new(name: impl Into<String>) -> Self {
Self {
layer: Layer::new(LayerType::Group, name),
children: Vec::new(),
expanded: true,
}
}
/// Add a child layer to this group
pub fn add_child(&mut self, layer: AnyLayer) {
self.children.push(layer);
}
/// Get clip instances from all child layers as (child_layer_id, &ClipInstance) pairs
pub fn all_child_clip_instances(&self) -> Vec<(Uuid, &ClipInstance)> {
let mut result = Vec::new();
for child in &self.children {
let child_id = child.id();
let instances: &[ClipInstance] = match child {
AnyLayer::Audio(l) => &l.clip_instances,
AnyLayer::Video(l) => &l.clip_instances,
AnyLayer::Vector(l) => &l.clip_instances,
AnyLayer::Effect(l) => &l.clip_instances,
AnyLayer::Group(_) => &[], // no nested groups
AnyLayer::Raster(_) => &[], // raster layers have no clip instances
};
for ci in instances {
result.push((child_id, ci));
}
}
result
}
}
/// Unified layer enum for polymorphic handling
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AnyLayer {
Vector(VectorLayer),
Audio(AudioLayer),
Video(VideoLayer),
Effect(EffectLayer),
Group(GroupLayer),
Raster(RasterLayer),
}
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(),
AnyLayer::Group(l) => l.id(),
AnyLayer::Raster(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(),
AnyLayer::Group(l) => l.name(),
AnyLayer::Raster(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),
AnyLayer::Group(l) => l.set_name(name),
AnyLayer::Raster(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(),
AnyLayer::Group(l) => l.has_custom_name(),
AnyLayer::Raster(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),
AnyLayer::Group(l) => l.set_has_custom_name(custom),
AnyLayer::Raster(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(),
AnyLayer::Group(l) => l.visible(),
AnyLayer::Raster(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),
AnyLayer::Group(l) => l.set_visible(visible),
AnyLayer::Raster(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(),
AnyLayer::Group(l) => l.opacity(),
AnyLayer::Raster(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),
AnyLayer::Group(l) => l.set_opacity(opacity),
AnyLayer::Raster(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(),
AnyLayer::Group(l) => l.volume(),
AnyLayer::Raster(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),
AnyLayer::Group(l) => l.set_volume(volume),
AnyLayer::Raster(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(),
AnyLayer::Group(l) => l.muted(),
AnyLayer::Raster(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),
AnyLayer::Group(l) => l.set_muted(muted),
AnyLayer::Raster(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(),
AnyLayer::Group(l) => l.soloed(),
AnyLayer::Raster(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),
AnyLayer::Group(l) => l.set_soloed(soloed),
AnyLayer::Raster(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(),
AnyLayer::Group(l) => l.locked(),
AnyLayer::Raster(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),
AnyLayer::Group(l) => l.set_locked(locked),
AnyLayer::Raster(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,
AnyLayer::Group(l) => &l.layer,
AnyLayer::Raster(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,
AnyLayer::Group(l) => &mut l.layer,
AnyLayer::Raster(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);
}
}