#animation #opengl #bsd #api #embedded-devices

rust-animation

OpenGL based Animation Toolkit written in Rust

19 releases

0.2.7 Nov 27, 2023
0.2.6 Nov 26, 2023
0.2.3 May 11, 2022
0.2.2 Dec 5, 2021
0.1.11 Nov 30, 2021

#81 in Graphics APIs

BSD-3-Clause

3MB
844 lines

rust-animation   Latest Version

alt easing_funcitions

rust-animation is an OpenGL-based graphics library written in Rust for creating hardware-accelerated user interfaces. It is designed to implement a simple animated UI for embedded devices, inspired by GNOME Clutter project and Apple Core Animation.

The library supports the following features:

  • 2D transforms: translate, scale, and rotate
  • Animations with easing functions
  • Flex UI
  • Various examples

Note that rust-animation is still in the early stages of development, so some features may be missing, and there may be bugs. Feel free to file any bugs.

Installation

To use rust-animation, you need to install Rust first:

If you're building rust-animation on Windows or Mac, you'll need to install cmake as well:

For Max OSX,

$ brew install cmake

Note: rust-animation has been tested on Ubuntu 20.04, Windows10, and Mac OSX.

There are several examples so you can build them as follows:

Examples

rust-animation includes several examples to help you get started. To build and run them, you can use the following commands:

easing_functions.rs

This example shows all the available easing functions.

$ cargo build --example easing_functions
$ target/debug/examples/easing_functions
  let mut play = Play::new(
    "Easing functions demo".to_string(),
    1920,
    1080,
    LayoutMode::UserDefine,
  );
  let mut stage = Actor::new("stage".to_string(), 1920, 1080, None);
  stage.set_visible(true);

  let easing_functions = vec![
    EasingFunction::EaseIn,
    EasingFunction::EaseInCubic,
    EasingFunction::EaseInOut,
    EasingFunction::EaseInOutCubic,
    EasingFunction::EaseInOutQuad,
    EasingFunction::EaseInOutQuart,
    EasingFunction::EaseInOutQuint,
    EasingFunction::EaseInQuad,
    EasingFunction::EaseInQuart,
    EasingFunction::EaseInQuint,
    EasingFunction::EaseOut,
    EasingFunction::EaseOutCubic,
    EasingFunction::EaseOutQuad,
    EasingFunction::EaseOutQuart,
    EasingFunction::EaseOutQuint,
    EasingFunction::Linear,
    EasingFunction::Step,
  ];
  let mut y = 0;
  let time = 5.0;
  let width = 63;
  let height = width;
  for i in 0..17 {
    let actor_name = format!("actor_{}", i + 1);
    let mut actor = Actor::new(actor_name.to_string(), width, height, None);
    actor.x = 0;
    actor.y = y;
    y += height as i32;
    actor.set_color(i as f32 / 18.0, i as f32 / 18.0, i as f32 / 18.0);

    let mut animation = Animation::new();
    animation.apply_translation_x(0, (1920 - width) as i32, time, easing_functions[i]);
    animation.apply_rotation(0, 360, time, EasingFunction::Linear);
    actor.set_animation(Some(animation));
    stage.add_sub_actor(actor);
  }
  play.add_stage(stage);

  while !window.should_close() {
    // events
    process_events(&mut window, &events);

    play.render();

    // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
    window.swap_buffers();
    glfw.poll_events();
  }

flex_ui.rs

alt flex_ui rust-animation is experimentally using Stretch to support Flex UI. You can apply a flex layout to actors using the Layout trait.

$ cargo build --example flex_ui
$ target/debug/examples/flex_ui

This example shows how to use the Layout trait to implement the flex layout using Stretch.


pub struct FlexLayout {
  name: String,
}

impl FlexLayout {
  pub fn new() -> Self {
    let mut flex_layout = FlexLayout {
      name: "flex_layout".to_string(),
    };

    println!("new FlexLayout {}", flex_layout.name);

    flex_layout
  }
}

impl Layout for FlexLayout {
  fn layout_sub_actors(
    &mut self,
    actor: &mut Actor,
    parent_actor: Option<&Actor>,
    stretch: &mut Option<Stretch>,
  ) {
    println!("run layout_sub_layer for FlexLayout {}", self.name);
    if let Some(stretch_obj) = stretch {
      if let Some(style_obj) = actor.style {
        actor.node = Some(stretch_obj.new_node(style_obj, vec![]).unwrap());
      } else {
        //println!("default style: {}: {},{}", self.name, self.width, self.height);
        actor.node = Some(
          stretch_obj
            .new_node(
              Style {
                size: Size {
                  width: Dimension::Points(actor.width as f32),
                  height: Dimension::Points(actor.height as f32),
                },
                margin: Rect {
                  start: Dimension::Points(2.0),
                  end: Dimension::Points(2.0),
                  top: Dimension::Points(2.0),
                  bottom: Dimension::Points(2.0),
                  ..Default::default()
                },
                ..Default::default()
              },
              vec![],
            )
            .unwrap(),
        );
      }

      println!("actor name {}", actor.name);

      if let Some(parent_actor) = parent_actor {
        if !parent_actor.node.is_none() && !actor.node.is_none() {
          match stretch_obj.add_child(parent_actor.node.unwrap(), actor.node.unwrap()) {
            Ok(()) => {
              println!(
                " stretch node  is added {} {}",
                parent_actor.name, actor.name
              )
            }
            Err(..) => {}
          }
        }
      }
    }

    //self.update_layout(actor, stretch);
  }

  fn update_layout(&mut self, actor: &mut Actor, stretch: &mut Option<Stretch>) {
    if let Some(stretch_obj) = stretch {
      if !actor.node.is_none() {
        let layout = stretch_obj.layout(actor.node.unwrap()).unwrap();
        actor.x = layout.location.x as i32;
        actor.y = layout.location.y as i32;
        println!(
          "run update_layout for FlexLayout {} = {},{}",
          actor.name, actor.x, actor.y
        );
      }
    }
  }

  fn finalize(&mut self) {
    println!("finalize {}", self.name);
  }
}

fn main() {
  let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
  glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3));
  glfw.window_hint(glfw::WindowHint::OpenGlProfile(
    glfw::OpenGlProfileHint::Core,
  ));
  #[cfg(target_os = "macos")]
  glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true));

  let (mut window, events) = glfw
    .create_window(1920, 1080, "Flex UI demo", glfw::WindowMode::Windowed)
    .expect("Failed to create GLFW window.");

  window.set_key_polling(true);
  window.make_current();
  window.set_framebuffer_size_polling(true);

  gl::load_with(|symbol| window.get_proc_address(symbol) as *const _);

  let mut play = Play::new("Flex UI test".to_string(), 1920, 1080, LayoutMode::Flex);
  let mut stage = Actor::new("stage".to_string(), 1920, 1080, None);
  stage.set_style(Style {
    size: Size {
      width: Dimension::Points(1920.0),
      height: Dimension::Points(1080.0),
    },
    justify_content: JustifyContent::Center,
    flex_direction: FlexDirection::Column,
    align_items: AlignItems::Center,
    margin: Rect {
      start: Dimension::Points(1.0),
      end: Dimension::Points(1.0),
      top: Dimension::Points(1.0),
      bottom: Dimension::Points(1.0),
      ..Default::default()
    },
    ..Default::default()
  });
  stage.set_visible(true);

  let justify_content = vec![
    JustifyContent::FlexStart,
    JustifyContent::FlexEnd,
    JustifyContent::Center,
    JustifyContent::SpaceBetween,
    JustifyContent::SpaceAround,
    JustifyContent::SpaceEvenly,
  ];
  let width = 1500;
  let height = 108;
  for i in 0..6 {
    let actor_name = format!("actor_{}", i + 1);
    let mut actor = Actor::new(actor_name.to_string(), width, height, None);
    actor.set_color(i as f32 / 6.0, i as f32 / 6.0, i as f32 / 6.0);
    actor.set_style(Style {
      size: Size {
        width: Dimension::Points(width as f32),
        height: Dimension::Points(height as f32),
      },
      justify_content: justify_content[i],
      align_items: AlignItems::Center,
      margin: Rect {
        start: Dimension::Points(1.0),
        end: Dimension::Points(1.0),
        top: Dimension::Points(1.0),
        bottom: Dimension::Points(1.0),
        ..Default::default()
      },
      padding: Rect {
        start: Dimension::Points(2.0),
        end: Dimension::Points(2.0),
        ..Default::default()
      },
      ..Default::default()
    });
    for j in 0..10 {
      let mut sub_actor = Actor::new(
        format!("actor_{}_{}", i + 1, j + 1).to_string(),
        100,
        100,
        None,
      );
      sub_actor.set_color(1.0, j as f32 / 10.0, j as f32 / 10.0);
      sub_actor.set_layout(Some(Box::new(FlexLayout::new())));
      actor.add_sub_actor(sub_actor);
    }
    actor.set_layout(Some(Box::new(FlexLayout::new())));
    stage.add_sub_actor(actor);
  }

  stage.set_layout(Some(Box::new(FlexLayout::new())));
  play.add_stage(stage);

  //play.set_stage_needs_layout(&"stage".to_string());

  while !window.should_close() {
    // events
    process_events(&mut window, &events);

    play.render();

    // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
    window.swap_buffers();
    glfw.poll_events();
  }
}

ani.rs

$ cargo build --example ani
$ target/debug/examples/ani

This example shows the basic animation features.

  let mut play = Play::new(
    "Animation test".to_string(),
    1920,
    1080,
    LayoutMode::UserDefine,
  );
  let mut stage = Actor::new("stage".to_string(), 1920, 1080, None);
  stage.set_visible(true);

  let mut actor_1 = Actor::new("actor_1".to_string(), 400, 225, None);
  actor_1.x = 100;
  actor_1.y = 100;
  actor_1.set_image("examples/splash.png".to_string());

  let mut animation_1 = Animation::new();

  // 1X -> 2X for 5 sec.
  let time = 5.0;
  animation_1.apply_scale(1.0, 2.0, time, EasingFunction::Linear);
  animation_1.apply_translation_x(100, 1000, time, EasingFunction::EaseInOut);
  animation_1.apply_translation_y(100, 300, time, EasingFunction::EaseInOut);
  animation_1.apply_rotation(0, 360, time, EasingFunction::EaseInOut);
  actor_1.set_animation(Some(animation_1));

  let mut actor_2 = Play::new_actor("actor_2".to_string(), 120, 120, None);
  actor_2.x = 100;
  actor_2.y = 100;
  actor_2.scale_x = 1.5;
  actor_2.scale_y = 1.5;
  actor_2.set_color(0.0, 0.0, 1.0);
  // 0 degree -> 360 degree for 5 sec

  let mut animation_2 = Animation::new();
  animation_2.apply_rotation(0, 360, 5.0, EasingFunction::EaseInOut);
  actor_2.set_animation(Some(animation_2));

  let mut actor_3 = Play::new_actor("actor_3".to_string(), 50, 50, None);
  actor_3.x = 10;
  actor_3.y = 10;
  actor_3.set_color(1.0, 0.0, 0.0);
  actor_2.add_sub_actor(actor_3);

  stage.add_sub_actor(actor_1);
  stage.add_sub_actor(actor_2);

  play.add_stage(stage);

  while !window.should_close() {
    process_events(&mut window, &events);
    play.render();
    window.swap_buffers();
    glfw.poll_events();
  }

picture_viewer.rs

This example is still work in progress. The thumbnail view only works.

$ cargo build --example picture_viewer
$ target/debug/examples/picture_viewer

This example shows how to handle events and user-defined layout. More event handler methods would be added.


pub struct ActorEvent {
  name: String,
}

impl ActorEvent {
  pub fn new() -> Self {
    ActorEvent {
      name: "actor_event".to_string(),
    }
  }
}

impl EventHandler for ActorEvent {
  fn key_focus_in(&mut self, actor: &mut Actor) {
    let mut animation = Animation::new();
    animation.apply_scale(1.0, 1.1, 0.3, EasingFunction::EaseInOut);
    actor.set_animation(Some(animation));
  }

  fn key_focus_out(&mut self, actor: &mut Actor) {
    actor.scale_x = 1.0;
    actor.scale_y = 1.0;
  }

  fn key_down(&mut self, key: rust_animation::actor::Key, actor: &mut Actor) {
    if key == rust_animation::actor::Key::Right {
      // right cursor
      actor.select_next_sub_actor();
    } else if key == rust_animation::actor::Key::Left {
      // left cursor
      actor.select_prev_sub_actor();
    }
  }
}

pub struct ActorLayout {
  name: String,
  cur_x: i32,
}

impl ActorLayout {
  pub fn new() -> Self {
    ActorLayout {
      name: "actor_layout".to_string(),
      cur_x: 0,
    }
  }
}

impl Layout for ActorLayout {
  fn layout_sub_actors(
    &mut self,
    actor: &mut Actor,
    parent_actor: Option<&Actor>,
    stretch: &mut Option<Stretch>,
  ) {
    println!("layout_sub_layer {}", self.name);
    let mut index: i32 = 0;
    for sub_actor in actor.sub_actor_list.iter_mut() {
      self.cur_x += sub_actor.width as i32;
      sub_actor.x = index % 5 * IMAGE_WIDTH as i32;
      let col = index / 5;
      sub_actor.y = col * IMAGE_HEIGHT as i32;
      index += 1;
    }
  }

  fn update_layout(&mut self, actor: &mut Actor, stretch: &mut Option<Stretch>) {
    println!("update_layout {}", self.name);
  }

  fn finalize(&mut self) {
    println!("finalize {}", self.name);
  }
}

Dependencies

~15MB
~103K SLoC