Skip to main content

rust_animation/
layer.rs

1// Copyright (c) 2021 Joone Hur <joone@chromium.org> All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5extern crate image;
6extern crate keyframe;
7
8use cgmath::{Deg, Matrix4, SquareMatrix, Vector3};
9use image::DynamicImage;
10use std::path::Path;
11
12use stretch::{
13  node::{Node, Stretch},
14  style::*,
15};
16
17use crate::animation::Animation;
18use crate::font::FontRenderer;
19
20#[repr(i32)]
21#[derive(Copy, Clone, Debug, PartialEq)]
22pub enum Key {
23  Space = 32,
24  Enter = 36,
25  Tab = 48,
26  Backspace = 51,
27  Escape = 53,
28  Right = 262,
29  Left = 263,
30  Down = 264,
31  Up = 265,
32}
33
34#[derive(Copy, Clone, Debug)]
35pub enum LayoutMode {
36  UserDefine,
37  Flex,
38}
39
40#[repr(C)]
41#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
42pub struct Vertex {
43  pub position: [f32; 3],
44  pub tex_coords: [f32; 2],
45}
46
47impl Vertex {
48  pub fn desc() -> wgpu::VertexBufferLayout<'static> {
49    wgpu::VertexBufferLayout {
50      array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
51      step_mode: wgpu::VertexStepMode::Vertex,
52      attributes: &[
53        wgpu::VertexAttribute {
54          offset: 0,
55          shader_location: 0,
56          format: wgpu::VertexFormat::Float32x3,
57        },
58        wgpu::VertexAttribute {
59          offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
60          shader_location: 1,
61          format: wgpu::VertexFormat::Float32x2,
62        },
63      ],
64    }
65  }
66}
67
68pub struct Layer {
69  pub name: String,
70  pub x: i32,
71  pub y: i32,
72  pub z: f32,
73  pub width: u32,
74  pub height: u32,
75  pub anchor_x: f32,
76  pub anchor_y: f32,
77  pub scale_x: f32,
78  pub scale_y: f32,
79  pub rotation: i32,
80  pub visible: bool,
81  color: [f32; 3],
82  pub opacity: f32, // CoreAnimation-style property
83  pub image_path: String,
84  pub sub_layer_list: Vec<Layer>,
85  pub(crate) vertex_buffer: Option<wgpu::Buffer>,
86  pub(crate) index_buffer: Option<wgpu::Buffer>,
87  pub(crate) texture: Option<wgpu::Texture>,
88  pub(crate) texture_view: Option<wgpu::TextureView>,
89  pub(crate) bind_group: Option<wgpu::BindGroup>,
90  pub animated: bool,
91  pub animation: Option<Animation>,
92  animations: std::collections::HashMap<String, Animation>, // CoreAnimation-style animations by key
93  event_handler: Option<Box<dyn EventHandler>>,
94  layout: Option<Box<dyn Layout>>,
95  pub(crate) focused_sub_layer: usize,
96  pub(crate) focused: bool,
97  pub needs_update: bool,
98  pub node: Option<Node>,   // for stretch only
99  pub style: Option<Style>, // for stretch only
100}
101
102pub trait EventHandler {
103  fn key_focus_in(&mut self, layer: &mut Layer);
104  fn key_focus_out(&mut self, layer: &mut Layer);
105  fn key_down(&mut self, key: Key, layer: &mut Layer);
106}
107
108pub trait Layout {
109  fn layout_sub_layers(
110    &mut self,
111    layer: &mut Layer,
112    parent_layer: Option<&Layer>,
113    stretch: &mut Option<Stretch>,
114  );
115  fn update_layout(&mut self, layer: &mut Layer, stretch: &mut Option<Stretch>);
116  fn finalize(&mut self);
117}
118
119impl Layer {
120  pub fn new(name: String, w: u32, h: u32, event_handler: Option<Box<dyn EventHandler>>) -> Self {
121    let layer = Layer {
122      name: name,
123      x: 0,
124      y: 0,
125      z: 0.0,
126      width: w,
127      height: h,
128      anchor_x: 0.5,
129      anchor_y: 0.5,
130      scale_x: 1.0,
131      scale_y: 1.0,
132      rotation: 0,
133      visible: true,
134      color: [1.0, 1.0, 1.0],
135      opacity: 1.0,
136      image_path: "".to_string(),
137      sub_layer_list: Vec::new(),
138      vertex_buffer: None,
139      index_buffer: None,
140      texture: None,
141      texture_view: None,
142      bind_group: None,
143      animated: false,
144      animation: None,
145      animations: std::collections::HashMap::new(),
146      event_handler: event_handler,
147      layout: None,
148      focused_sub_layer: 0,
149      focused: false,
150      needs_update: true,
151      node: None,
152      style: None,
153    };
154
155    layer
156  }
157
158  #[allow(unused_variables)]
159  pub fn init_buffers(&mut self, device: &wgpu::Device) {
160    // Skip buffer initialization during tests
161    #[cfg(test)]
162    {
163      return;
164    }
165
166    #[cfg(not(test))]
167    {
168      let vertices = [
169        Vertex {
170          position: [self.width as f32, self.height as f32, 0.0],
171          tex_coords: [1.0, 1.0],
172        }, // top right
173        Vertex {
174          position: [self.width as f32, 0.0, 0.0],
175          tex_coords: [1.0, 0.0],
176        }, // bottom right
177        Vertex {
178          position: [0.0, 0.0, 0.0],
179          tex_coords: [0.0, 0.0],
180        }, // bottom left
181        Vertex {
182          position: [0.0, self.height as f32, 0.0],
183          tex_coords: [0.0, 1.0],
184        }, // top left
185      ];
186
187      let indices: [u16; 6] = [0, 1, 3, 1, 2, 3];
188
189      use wgpu::util::DeviceExt;
190      self.vertex_buffer = Some(
191        device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
192          label: Some("Vertex Buffer"),
193          contents: bytemuck::cast_slice(&vertices),
194          usage: wgpu::BufferUsages::VERTEX,
195        }),
196      );
197
198      self.index_buffer = Some(
199        device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
200          label: Some("Index Buffer"),
201          contents: bytemuck::cast_slice(&indices),
202          usage: wgpu::BufferUsages::INDEX,
203        }),
204      );
205    }
206  }
207
208  pub fn set_color(&mut self, r: f32, g: f32, b: f32) {
209    self.color[0] = r;
210    self.color[1] = g;
211    self.color[2] = b;
212  }
213
214  pub fn set_text(&mut self, text: &str, device: &wgpu::Device, queue: &wgpu::Queue) {
215    let mut font_renderer: FontRenderer = FontRenderer::new("fonts/DejaVuSans.ttf".to_string());
216    let image = font_renderer.render(text);
217    let dynamic_image = DynamicImage::ImageRgba8(image);
218
219    dynamic_image.save("temp.png").unwrap();
220
221    self.image_path = "temp".to_string();
222    self.width = dynamic_image.width();
223    self.height = dynamic_image.height();
224
225    println!("width: {}, height: {}", self.width, self.height);
226
227    let rgba = dynamic_image.to_rgba8();
228    let dimensions = rgba.dimensions();
229
230    let texture_size = wgpu::Extent3d {
231      width: dimensions.0,
232      height: dimensions.1,
233      depth_or_array_layers: 1,
234    };
235
236    let texture = device.create_texture(&wgpu::TextureDescriptor {
237      label: Some("Text Texture"),
238      size: texture_size,
239      mip_level_count: 1,
240      sample_count: 1,
241      dimension: wgpu::TextureDimension::D2,
242      format: wgpu::TextureFormat::Rgba8UnormSrgb,
243      usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
244      view_formats: &[],
245    });
246
247    queue.write_texture(
248      wgpu::ImageCopyTexture {
249        texture: &texture,
250        mip_level: 0,
251        origin: wgpu::Origin3d::ZERO,
252        aspect: wgpu::TextureAspect::All,
253      },
254      &rgba,
255      wgpu::ImageDataLayout {
256        offset: 0,
257        bytes_per_row: Some(4 * dimensions.0),
258        rows_per_image: Some(dimensions.1),
259      },
260      texture_size,
261    );
262
263    let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
264    self.texture = Some(texture);
265    self.texture_view = Some(texture_view);
266  }
267
268  /// Set image path (for backward compatibility - actual texture loading requires wgpu context)
269  pub fn set_image(&mut self, path: String) {
270    self.image_path = path;
271  }
272
273  /// Load image with wgpu context
274  pub fn load_image_texture(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
275    if !self.image_path.is_empty() {
276      // Use format auto-detection to handle images with incorrect extensions
277      let img_result = image::ImageReader::open(&Path::new(&self.image_path))
278        .and_then(|reader| reader.with_guessed_format())
279        .map_err(image::ImageError::IoError)
280        .and_then(|reader| reader.decode());
281      
282      match img_result {
283        Ok(img) => {
284          let rgba = img.to_rgba8();
285          let dimensions = rgba.dimensions();
286
287          let texture_size = wgpu::Extent3d {
288            width: dimensions.0,
289            height: dimensions.1,
290            depth_or_array_layers: 1,
291          };
292
293          let texture = device.create_texture(&wgpu::TextureDescriptor {
294            label: Some("Image Texture"),
295            size: texture_size,
296            mip_level_count: 1,
297            sample_count: 1,
298            dimension: wgpu::TextureDimension::D2,
299            format: wgpu::TextureFormat::Rgba8UnormSrgb,
300            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
301            view_formats: &[],
302          });
303
304          queue.write_texture(
305            wgpu::ImageCopyTexture {
306              texture: &texture,
307              mip_level: 0,
308              origin: wgpu::Origin3d::ZERO,
309              aspect: wgpu::TextureAspect::All,
310            },
311            &rgba,
312            wgpu::ImageDataLayout {
313              offset: 0,
314              bytes_per_row: Some(4 * dimensions.0),
315              rows_per_image: Some(dimensions.1),
316            },
317            texture_size,
318          );
319
320          let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
321          self.texture = Some(texture);
322          self.texture_view = Some(texture_view);
323        }
324        Err(err) => println!("Fail to load a image {:?}", err),
325      }
326    }
327  }
328
329  pub fn set_layout(&mut self, layout: Option<Box<dyn Layout>>) {
330    self.layout = layout;
331  }
332
333  pub fn set_animation(&mut self, animation: Option<Animation>) {
334    self.animation = animation;
335  }
336
337  pub fn set_style(&mut self, style: Style) {
338    self.style = Some(style);
339  }
340
341  pub fn set_visible(&mut self, visible: bool) {
342    self.visible = visible;
343  }
344
345  /*pub fn update(&mut self) {
346    // Sort sub actors by z-axis
347    self.sub_layer_list.sort_by(|a, b| a.z.partial_cmp(&b.z).unwrap());
348  }*/
349
350  pub fn animate(&mut self) {
351    // Run legacy animation if present
352    if let Some(mut animation) = self.animation.take() {
353      animation.run(self);
354      self.animation = Some(animation);
355    }
356
357    // Run CoreAnimation-style animations
358    // Take the animations HashMap out temporarily
359    let mut animations = std::mem::take(&mut self.animations);
360    for (_key, animation) in animations.iter_mut() {
361      animation.run(self);
362    }
363    // Put it back
364    self.animations = animations;
365
366    for sub_layer in self.sub_layer_list.iter_mut() {
367      sub_layer.animate();
368    }
369  }
370
371  pub fn select_next_sub_layer(&mut self) {
372    if self.sub_layer_list.is_empty() {
373      return;
374    }
375    // no more next layer.
376    if self.focused_sub_layer < self.sub_layer_list.len() - 1 {
377      let prev_focused_sub_layer = self.focused_sub_layer;
378      self.focused_sub_layer += 1;
379      self.sub_layer_list[self.focused_sub_layer].set_focus(true);
380      self.sub_layer_list[prev_focused_sub_layer].set_focus(false);
381    }
382  }
383
384  pub fn select_prev_sub_layer(&mut self) {
385    if self.sub_layer_list.is_empty() {
386      return;
387    }
388    // ne more previous layer.
389    if self.focused_sub_layer == 0 {
390      return;
391    }
392    let prev_focused_sub_layer = self.focused_sub_layer;
393    self.focused_sub_layer -= 1;
394    self.sub_layer_list[self.focused_sub_layer].set_focus(true);
395    self.sub_layer_list[prev_focused_sub_layer].set_focus(false);
396  }
397
398  pub fn set_focus(&mut self, focused: bool) {
399    self.focused = focused;
400    if let Some(mut event_handler) = self.event_handler.take() {
401      //println!("set_focus {} {} ", self.name, focused);
402
403      if self.focused {
404        event_handler.key_focus_in(self);
405      } else {
406        event_handler.key_focus_out(self);
407      }
408      self.event_handler = Some(event_handler);
409    }
410  }
411
412  pub fn handle_input(&mut self, key: Key) {
413    for sub_layer in self.sub_layer_list.iter_mut() {
414      if sub_layer.focused {
415        sub_layer.handle_input(key);
416      }
417    }
418    if let Some(mut event_handler) = self.event_handler.take() {
419      event_handler.key_down(key, self);
420      self.event_handler = Some(event_handler);
421    }
422  }
423
424  pub fn layout_sub_layers(
425    &mut self,
426    parent_layer: Option<&Layer>,
427    stretch: &mut Option<Stretch>,
428  ) {
429    if let Some(mut layout) = self.layout.take() {
430      layout.layout_sub_layers(self, parent_layer, stretch);
431      self.layout = Some(layout); // Put back the layout
432    }
433
434    // Replace the sub_layer_list with an empty vector and take the original vector out
435    let mut sub_layer_list = std::mem::replace(&mut self.sub_layer_list, Vec::new());
436
437    // Iterate over the vector outside of the self structure
438    for sub_layer in &mut sub_layer_list {
439      // As we are outside of the self structure, we can now borrow self as immutable
440      sub_layer.layout_sub_layers(Some(self), stretch);
441    }
442
443    // Put back the original sub_layer_list
444    self.sub_layer_list = sub_layer_list;
445  }
446
447  pub fn update_layout(&mut self, stretch: &mut Option<Stretch>) {
448    if let Some(mut layout) = self.layout.take() {
449      layout.update_layout(self, stretch);
450      self.layout = Some(layout); // Put back the layout
451    }
452
453    for sub_layer in self.sub_layer_list.iter_mut() {
454      sub_layer.update_layout(stretch);
455    }
456  }
457
458  pub fn finalize_layout(&mut self) {
459    if let Some(ref mut layout) = self.layout {
460      layout.finalize();
461    }
462  }
463
464  pub fn model_matrix(&self) -> Matrix4<f32> {
465    let mut transform: Matrix4<f32> = Matrix4::identity();
466    transform = transform
467      * Matrix4::<f32>::from_translation(Vector3::new(self.x as f32, self.y as f32, self.z as f32));
468
469    // Handle rotation and scale.
470    // Move back to the original position.
471    transform = transform
472      * Matrix4::<f32>::from_translation(Vector3::new(
473        self.width as f32 * self.anchor_x,
474        self.height as f32 * self.anchor_y,
475        0.0,
476      ));
477
478    if self.rotation != 0 {
479      transform = transform * Matrix4::<f32>::from_angle_z(Deg(self.rotation as f32));
480    }
481
482    transform = transform * Matrix4::from_nonuniform_scale(self.scale_x, self.scale_y, 0.0);
483
484    // Move to the origin of coordinate.
485    transform = transform
486      * Matrix4::<f32>::from_translation(Vector3::new(
487        -(self.width as f32 * self.anchor_x),
488        -(self.height as f32 * self.anchor_y),
489        0.0,
490      ));
491
492    transform
493  }
494
495  pub fn render(&mut self, parent_model_matrix: Option<&Matrix4<f32>>, projection: &Matrix4<f32>) {
496    if !self.visible {
497      return;
498    }
499
500    let mut transform: Matrix4<f32> = self.model_matrix();
501    if let Some(parent_model_matrix) = parent_model_matrix {
502      transform = transform * parent_model_matrix;
503    }
504
505    // Rendering will be handled by the Play struct with wgpu
506    // This method now just updates the transform hierarchy
507
508    for sub_layer in self.sub_layer_list.iter_mut() {
509      if sub_layer.focused == false {
510        sub_layer.render(Some(&transform), projection);
511      }
512    }
513
514    // render the focused sub_layer at the end.
515    if !self.sub_layer_list.is_empty() {
516      self.sub_layer_list[self.focused_sub_layer].render(Some(&transform), projection);
517    }
518  }
519
520  pub fn add_sub_layer(&mut self, layer: Layer) {
521    self.sub_layer_list.push(layer);
522  }
523
524  // CoreAnimation-style API methods
525
526  /// Set position (CoreAnimation-style API)
527  pub fn set_position(&mut self, x: i32, y: i32) {
528    self.x = x;
529    self.y = y;
530  }
531
532  /// Get position as tuple (CoreAnimation-style API)
533  pub fn position(&self) -> (i32, i32) {
534    (self.x, self.y)
535  }
536
537  /// Set bounds (CoreAnimation-style API)
538  pub fn set_bounds(&mut self, width: u32, height: u32) {
539    self.width = width;
540    self.height = height;
541  }
542
543  /// Get bounds as tuple (CoreAnimation-style API)
544  pub fn bounds(&self) -> (u32, u32) {
545    (self.width, self.height)
546  }
547
548  /// Set opacity (CoreAnimation-style API)
549  pub fn set_opacity(&mut self, opacity: f32) {
550    self.opacity = opacity.max(0.0).min(1.0);
551  }
552
553  /// Set background color (CoreAnimation-style API)
554  pub fn set_background_color(&mut self, r: f32, g: f32, b: f32) {
555    self.set_color(r, g, b);
556  }
557
558  /// Get background color (CoreAnimation-style API)
559  pub fn background_color(&self) -> (f32, f32, f32) {
560    (self.color[0], self.color[1], self.color[2])
561  }
562
563  /// Add an animation for a specific key (CoreAnimation-style API)
564  pub fn add_animation(&mut self, animation: Animation, key: Option<&str>) {
565    if let Some(key_str) = key {
566      self.animations.insert(key_str.to_string(), animation);
567    } else {
568      // If no key provided, use the legacy animation field
569      self.animation = Some(animation);
570    }
571  }
572
573  /// Remove all animations (CoreAnimation-style API)
574  pub fn remove_all_animations(&mut self) {
575    self.animations.clear();
576    self.animation = None;
577  }
578
579  /// Remove animation for a specific key (CoreAnimation-style API)
580  pub fn remove_animation(&mut self, key: &str) {
581    self.animations.remove(key);
582  }
583
584  /// Add a sublayer (CoreAnimation-style API, alias for add_sub_layer)
585  pub fn add_sublayer(&mut self, layer: Layer) {
586    self.add_sub_layer(layer);
587  }
588
589  /// Get sublayers (CoreAnimation-style API)
590  pub fn sublayers(&self) -> &Vec<Layer> {
591    &self.sub_layer_list
592  }
593
594  /// Get mutable sublayers (CoreAnimation-style API)
595  pub fn sublayers_mut(&mut self) -> &mut Vec<Layer> {
596    &mut self.sub_layer_list
597  }
598
599  /// Create uniform buffer with transform matrix and color
600  pub fn create_uniform_buffer(
601    &self,
602    device: &wgpu::Device,
603    transform: &Matrix4<f32>,
604    projection: &Matrix4<f32>,
605  ) -> wgpu::Buffer {
606    use wgpu::util::DeviceExt;
607
608    #[repr(C)]
609    #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
610    struct Uniforms {
611      transform: [[f32; 4]; 4],
612      projection: [[f32; 4]; 4],
613      color: [f32; 4],
614      use_texture: u32,
615      _padding: [u32; 3],
616    }
617
618    let use_texture = if self.texture.is_some() { 1 } else { 0 };
619    
620    let uniforms = Uniforms {
621      transform: (*transform).into(),
622      projection: (*projection).into(),
623      color: [self.color[0], self.color[1], self.color[2], self.opacity],
624      use_texture,
625      _padding: [0; 3],
626    };
627
628    device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
629      label: Some("Uniform Buffer"),
630      contents: bytemuck::cast_slice(&[uniforms]),
631      usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
632    })
633  }
634
635  /// Get or create texture bind group
636  pub fn get_or_create_bind_group(
637    &mut self,
638    device: &wgpu::Device,
639    layout: &wgpu::BindGroupLayout,
640    sampler: &wgpu::Sampler,
641    default_texture_view: &wgpu::TextureView,
642  ) -> &wgpu::BindGroup {
643    if self.bind_group.is_none() {
644      let texture_view = self.texture_view.as_ref().unwrap_or(default_texture_view);
645      
646      self.bind_group = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
647        label: Some("Texture Bind Group"),
648        layout,
649        entries: &[
650          wgpu::BindGroupEntry {
651            binding: 0,
652            resource: wgpu::BindingResource::TextureView(texture_view),
653          },
654          wgpu::BindGroupEntry {
655            binding: 1,
656            resource: wgpu::BindingResource::Sampler(sampler),
657          },
658        ],
659      }));
660    }
661    
662    self.bind_group.as_ref().unwrap()
663  }
664}
665
666#[cfg(test)]
667mod tests {
668  use super::*;
669  use crate::animation::{Animation, EasingFunction};
670
671  #[test]
672  fn test_position_api() {
673    let mut layer = Layer::new("test".to_string(), 100, 100, None);
674    layer.set_position(50, 75);
675    let (x, y) = layer.position();
676    assert_eq!(x, 50);
677    assert_eq!(y, 75);
678  }
679
680  #[test]
681  fn test_bounds_api() {
682    let mut layer = Layer::new("test".to_string(), 100, 100, None);
683    layer.set_bounds(200, 150);
684    let (w, h) = layer.bounds();
685    assert_eq!(w, 200);
686    assert_eq!(h, 150);
687  }
688
689  #[test]
690  fn test_opacity_api() {
691    let mut layer = Layer::new("test".to_string(), 100, 100, None);
692    assert_eq!(layer.opacity, 1.0);
693
694    layer.set_opacity(0.5);
695    assert_eq!(layer.opacity, 0.5);
696
697    // Test clamping
698    layer.set_opacity(1.5);
699    assert_eq!(layer.opacity, 1.0);
700
701    layer.set_opacity(-0.5);
702    assert_eq!(layer.opacity, 0.0);
703  }
704
705  #[test]
706  fn test_background_color_api() {
707    let mut layer = Layer::new("test".to_string(), 100, 100, None);
708    layer.set_background_color(0.5, 0.6, 0.7);
709    let (r, g, b) = layer.background_color();
710    assert_eq!(r, 0.5);
711    assert_eq!(g, 0.6);
712    assert_eq!(b, 0.7);
713  }
714
715  #[test]
716  fn test_add_animation_with_key() {
717    let mut layer = Layer::new("test".to_string(), 100, 100, None);
718    let mut animation = Animation::with_key_path("position.x");
719    animation.duration = 2.0;
720    animation.timing_function = Some(EasingFunction::Linear);
721
722    layer.add_animation(animation, Some("moveX"));
723    assert_eq!(layer.animations.len(), 1);
724    assert!(layer.animations.contains_key("moveX"));
725  }
726
727  #[test]
728  fn test_remove_animation() {
729    let mut layer = Layer::new("test".to_string(), 100, 100, None);
730    let animation1 = Animation::with_key_path("position.x");
731    let animation2 = Animation::with_key_path("opacity");
732
733    layer.add_animation(animation1, Some("anim1"));
734    layer.add_animation(animation2, Some("anim2"));
735    assert_eq!(layer.animations.len(), 2);
736
737    layer.remove_animation("anim1");
738    assert_eq!(layer.animations.len(), 1);
739    assert!(!layer.animations.contains_key("anim1"));
740    assert!(layer.animations.contains_key("anim2"));
741  }
742
743  #[test]
744  fn test_remove_all_animations() {
745    let mut layer = Layer::new("test".to_string(), 100, 100, None);
746    let animation1 = Animation::with_key_path("position.x");
747    let animation2 = Animation::with_key_path("opacity");
748    let animation3 = Animation::new();
749
750    layer.add_animation(animation1, Some("anim1"));
751    layer.add_animation(animation2, Some("anim2"));
752    layer.set_animation(Some(animation3));
753
754    assert_eq!(layer.animations.len(), 2);
755    assert!(layer.animation.is_some());
756
757    layer.remove_all_animations();
758    assert_eq!(layer.animations.len(), 0);
759    assert!(layer.animation.is_none());
760  }
761
762  #[test]
763  fn test_sublayers_api() {
764    let mut parent = Layer::new("parent".to_string(), 200, 200, None);
765    let child1 = Layer::new("child1".to_string(), 50, 50, None);
766    let child2 = Layer::new("child2".to_string(), 50, 50, None);
767
768    parent.add_sublayer(child1);
769    parent.add_sublayer(child2);
770
771    let sublayers = parent.sublayers();
772    assert_eq!(sublayers.len(), 2);
773    assert_eq!(sublayers[0].name, "child1");
774    assert_eq!(sublayers[1].name, "child2");
775  }
776
777  #[test]
778  fn test_backward_compatibility() {
779    let mut layer = Layer::new("test".to_string(), 100, 100, None);
780
781    // Old way of setting position
782    layer.x = 50;
783    layer.y = 75;
784    assert_eq!(layer.x, 50);
785    assert_eq!(layer.y, 75);
786
787    // Old way of creating animation
788    let mut animation = Animation::new();
789    animation.apply_translation_x(0, 100, 1.0, EasingFunction::Linear);
790    layer.set_animation(Some(animation));
791
792    assert!(layer.animation.is_some());
793  }
794}