{"id":16271321,"url":"https://github.com/joone/rust-animation","last_synced_at":"2025-08-18T15:05:09.765Z","repository":{"id":57664881,"uuid":"428858664","full_name":"joone/rust-animation","owner":"joone","description":"OpenGL based animation toolkit written in Rust","archived":false,"fork":false,"pushed_at":"2024-12-25T22:28:12.000Z","size":3253,"stargazers_count":24,"open_issues_count":6,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-06-13T06:10:03.759Z","etag":null,"topics":["animation","opengl","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-11-17T00:35:07.000Z","updated_at":"2025-03-12T01:43:37.000Z","dependencies_parsed_at":"2023-11-29T09:31:36.818Z","dependency_job_id":"4dc105fe-8ef8-41f8-9b53-13d0af95837a","html_url":"https://github.com/joone/rust-animation","commit_stats":{"total_commits":102,"total_committers":1,"mean_commits":102.0,"dds":0.0,"last_synced_commit":"7e604e4d6d04916cb97cd34e18c5389284031247"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/joone/rust-animation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joone%2Frust-animation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joone%2Frust-animation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joone%2Frust-animation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joone%2Frust-animation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joone","download_url":"https://codeload.github.com/joone/rust-animation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joone%2Frust-animation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271011789,"owners_count":24684409,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-18T02:00:08.743Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","opengl","rust"],"created_at":"2024-10-10T18:13:16.416Z","updated_at":"2025-08-18T15:05:09.738Z","avatar_url":"https://github.com/joone.png","language":"Rust","readme":"# rust-animation \u0026emsp;  [![Latest Version]][crates.io] \n[Latest Version]: https://img.shields.io/crates/v/rust-animation.svg\n[crates.io]: https://crates.io/crates/rust-animation\n\n![alt easing_funcitions](https://github.com/joone/rust-animation/blob/main/examples/easing_functions.gif?raw=true)\n\nrust-animation is an OpenGL-based graphics library written in Rust for creating hardware-accelerated user interfaces.\nIt is designed to implement a simple animated UI for embedded devices, inspired by [GNOME Clutter project](https://en.wikipedia.org/wiki/Clutter_(software)) and [Apple Core Animation](https://en.wikipedia.org/wiki/Core_Animation).\n\nThe library supports the following features:\n\n* 2D transforms: translate, scale, and rotate\n* Animations with easing functions\n* [Flex Layout](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)\n* Various examples\n\nNote 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.\n\n# Installation\nTo use rust-animation, you need to install Rust first:\n* https://www.rust-lang.org/tools/install\n\nIf you're building rust-animation on Windows or Mac, you'll need to install cmake as well:\n\nFor Max OSX,\n```\n$ brew install cmake\n```\nNote: rust-animation has been tested on Ubuntu 20.04, Windows10, and Mac OSX.\n\nThere are several examples so you can build them as follows:\n\n\n# Examples\nrust-animation includes several examples to help you get started. To build and run them, you can use the following commands:\n\n## easing_functions.rs\nThis example shows all the available easing functions.\n```\n$ cargo build --example easing_functions\n$ target/debug/examples/easing_functions\n```\n```rust\n  let mut play = Play::new(\n    \"Easing functions demo\".to_string(),\n    1920,\n    1080,\n    LayoutMode::UserDefine,\n  );\n  let mut stage = Actor::new(\"stage\".to_string(), 1920, 1080, None);\n  stage.set_visible(true);\n\n  let easing_functions = vec![\n    EasingFunction::EaseIn,\n    EasingFunction::EaseInCubic,\n    EasingFunction::EaseInOut,\n    EasingFunction::EaseInOutCubic,\n    EasingFunction::EaseInOutQuad,\n    EasingFunction::EaseInOutQuart,\n    EasingFunction::EaseInOutQuint,\n    EasingFunction::EaseInQuad,\n    EasingFunction::EaseInQuart,\n    EasingFunction::EaseInQuint,\n    EasingFunction::EaseOut,\n    EasingFunction::EaseOutCubic,\n    EasingFunction::EaseOutQuad,\n    EasingFunction::EaseOutQuart,\n    EasingFunction::EaseOutQuint,\n    EasingFunction::Linear,\n    EasingFunction::Step,\n  ];\n  let mut y = 0;\n  let time = 5.0;\n  let width = 63;\n  let height = width;\n  for i in 0..17 {\n    let actor_name = format!(\"actor_{}\", i + 1);\n    let mut actor = Actor::new(actor_name.to_string(), width, height, None);\n    actor.x = 0;\n    actor.y = y;\n    y += height as i32;\n    actor.set_color(i as f32 / 18.0, i as f32 / 18.0, i as f32 / 18.0);\n\n    let mut animation = Animation::new();\n    animation.apply_translation_x(0, (1920 - width) as i32, time, easing_functions[i]);\n    animation.apply_rotation(0, 360, time, EasingFunction::Linear);\n    actor.set_animation(Some(animation));\n    stage.add_sub_actor(actor);\n  }\n  play.add_stage(stage);\n\n  while !window.should_close() {\n    // events\n    process_events(\u0026mut window, \u0026events);\n\n    play.render();\n\n    // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)\n    window.swap_buffers();\n    glfw.poll_events();\n  }\n```\n## flex_ui.rs\n![alt flex_ui](https://github.com/joone/rust-animation/blob/main/examples/flex_ui.png?raw=true)\nrust-animation is experimentally using [Stretch](https://github.com/vislyhq/stretch) to support Flex UI. You can apply a flex layout to actors using the Layout trait.\n\n```\n$ cargo build --example flex_ui\n$ target/debug/examples/flex_ui\n```\n\nThis example shows how to use the Layout trait to implement the flex layout using Stretch.\n```rust\n\npub struct FlexLayout {\n  name: String,\n}\n\nimpl FlexLayout {\n  pub fn new() -\u003e Self {\n    let mut flex_layout = FlexLayout {\n      name: \"flex_layout\".to_string(),\n    };\n\n    println!(\"new FlexLayout {}\", flex_layout.name);\n\n    flex_layout\n  }\n}\n\nimpl Layout for FlexLayout {\n  fn layout_sub_actors(\n    \u0026mut self,\n    actor: \u0026mut Actor,\n    parent_actor: Option\u003c\u0026Actor\u003e,\n    stretch: \u0026mut Option\u003cStretch\u003e,\n  ) {\n    println!(\"run layout_sub_layer for FlexLayout {}\", self.name);\n    if let Some(stretch_obj) = stretch {\n      if let Some(style_obj) = actor.style {\n        actor.node = Some(stretch_obj.new_node(style_obj, vec![]).unwrap());\n      } else {\n        //println!(\"default style: {}: {},{}\", self.name, self.width, self.height);\n        actor.node = Some(\n          stretch_obj\n            .new_node(\n              Style {\n                size: Size {\n                  width: Dimension::Points(actor.width as f32),\n                  height: Dimension::Points(actor.height as f32),\n                },\n                margin: Rect {\n                  start: Dimension::Points(2.0),\n                  end: Dimension::Points(2.0),\n                  top: Dimension::Points(2.0),\n                  bottom: Dimension::Points(2.0),\n                  ..Default::default()\n                },\n                ..Default::default()\n              },\n              vec![],\n            )\n            .unwrap(),\n        );\n      }\n\n      println!(\"actor name {}\", actor.name);\n\n      if let Some(parent_actor) = parent_actor {\n        if !parent_actor.node.is_none() \u0026\u0026 !actor.node.is_none() {\n          match stretch_obj.add_child(parent_actor.node.unwrap(), actor.node.unwrap()) {\n            Ok(()) =\u003e {\n              println!(\n                \" stretch node  is added {} {}\",\n                parent_actor.name, actor.name\n              )\n            }\n            Err(..) =\u003e {}\n          }\n        }\n      }\n    }\n\n    //self.update_layout(actor, stretch);\n  }\n\n  fn update_layout(\u0026mut self, actor: \u0026mut Actor, stretch: \u0026mut Option\u003cStretch\u003e) {\n    if let Some(stretch_obj) = stretch {\n      if !actor.node.is_none() {\n        let layout = stretch_obj.layout(actor.node.unwrap()).unwrap();\n        actor.x = layout.location.x as i32;\n        actor.y = layout.location.y as i32;\n        println!(\n          \"run update_layout for FlexLayout {} = {},{}\",\n          actor.name, actor.x, actor.y\n        );\n      }\n    }\n  }\n\n  fn finalize(\u0026mut self) {\n    println!(\"finalize {}\", self.name);\n  }\n}\n\nfn main() {\n  let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();\n  glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3));\n  glfw.window_hint(glfw::WindowHint::OpenGlProfile(\n    glfw::OpenGlProfileHint::Core,\n  ));\n  #[cfg(target_os = \"macos\")]\n  glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true));\n\n  let (mut window, events) = glfw\n    .create_window(1920, 1080, \"Flex UI demo\", glfw::WindowMode::Windowed)\n    .expect(\"Failed to create GLFW window.\");\n\n  window.set_key_polling(true);\n  window.make_current();\n  window.set_framebuffer_size_polling(true);\n\n  gl::load_with(|symbol| window.get_proc_address(symbol) as *const _);\n\n  let mut play = Play::new(\"Flex UI test\".to_string(), 1920, 1080, LayoutMode::Flex);\n  let mut stage = Actor::new(\"stage\".to_string(), 1920, 1080, None);\n  stage.set_style(Style {\n    size: Size {\n      width: Dimension::Points(1920.0),\n      height: Dimension::Points(1080.0),\n    },\n    justify_content: JustifyContent::Center,\n    flex_direction: FlexDirection::Column,\n    align_items: AlignItems::Center,\n    margin: Rect {\n      start: Dimension::Points(1.0),\n      end: Dimension::Points(1.0),\n      top: Dimension::Points(1.0),\n      bottom: Dimension::Points(1.0),\n      ..Default::default()\n    },\n    ..Default::default()\n  });\n  stage.set_visible(true);\n\n  let justify_content = vec![\n    JustifyContent::FlexStart,\n    JustifyContent::FlexEnd,\n    JustifyContent::Center,\n    JustifyContent::SpaceBetween,\n    JustifyContent::SpaceAround,\n    JustifyContent::SpaceEvenly,\n  ];\n  let width = 1500;\n  let height = 108;\n  for i in 0..6 {\n    let actor_name = format!(\"actor_{}\", i + 1);\n    let mut actor = Actor::new(actor_name.to_string(), width, height, None);\n    actor.set_color(i as f32 / 6.0, i as f32 / 6.0, i as f32 / 6.0);\n    actor.set_style(Style {\n      size: Size {\n        width: Dimension::Points(width as f32),\n        height: Dimension::Points(height as f32),\n      },\n      justify_content: justify_content[i],\n      align_items: AlignItems::Center,\n      margin: Rect {\n        start: Dimension::Points(1.0),\n        end: Dimension::Points(1.0),\n        top: Dimension::Points(1.0),\n        bottom: Dimension::Points(1.0),\n        ..Default::default()\n      },\n      padding: Rect {\n        start: Dimension::Points(2.0),\n        end: Dimension::Points(2.0),\n        ..Default::default()\n      },\n      ..Default::default()\n    });\n    for j in 0..10 {\n      let mut sub_actor = Actor::new(\n        format!(\"actor_{}_{}\", i + 1, j + 1).to_string(),\n        100,\n        100,\n        None,\n      );\n      sub_actor.set_color(1.0, j as f32 / 10.0, j as f32 / 10.0);\n      sub_actor.set_layout(Some(Box::new(FlexLayout::new())));\n      actor.add_sub_actor(sub_actor);\n    }\n    actor.set_layout(Some(Box::new(FlexLayout::new())));\n    stage.add_sub_actor(actor);\n  }\n\n  stage.set_layout(Some(Box::new(FlexLayout::new())));\n  play.add_stage(stage);\n\n  //play.set_stage_needs_layout(\u0026\"stage\".to_string());\n\n  while !window.should_close() {\n    // events\n    process_events(\u0026mut window, \u0026events);\n\n    play.render();\n\n    // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)\n    window.swap_buffers();\n    glfw.poll_events();\n  }\n}\n```\n## ani.rs\n```\n$ cargo build --example ani\n$ target/debug/examples/ani\n```\nThis example shows the basic animation features.\n\n```rust\n  let mut play = Play::new(\n    \"Animation test\".to_string(),\n    1920,\n    1080,\n    LayoutMode::UserDefine,\n  );\n  let mut stage = Actor::new(\"stage\".to_string(), 1920, 1080, None);\n  stage.set_visible(true);\n\n  let mut actor_1 = Actor::new(\"actor_1\".to_string(), 400, 225, None);\n  actor_1.x = 100;\n  actor_1.y = 100;\n  actor_1.set_image(\"examples/splash.png\".to_string());\n\n  let mut animation_1 = Animation::new();\n\n  // 1X -\u003e 2X for 5 sec.\n  let time = 5.0;\n  animation_1.apply_scale(1.0, 2.0, time, EasingFunction::Linear);\n  animation_1.apply_translation_x(100, 1000, time, EasingFunction::EaseInOut);\n  animation_1.apply_translation_y(100, 300, time, EasingFunction::EaseInOut);\n  animation_1.apply_rotation(0, 360, time, EasingFunction::EaseInOut);\n  actor_1.set_animation(Some(animation_1));\n\n  let mut actor_2 = Play::new_actor(\"actor_2\".to_string(), 120, 120, None);\n  actor_2.x = 100;\n  actor_2.y = 100;\n  actor_2.scale_x = 1.5;\n  actor_2.scale_y = 1.5;\n  actor_2.set_color(0.0, 0.0, 1.0);\n  // 0 degree -\u003e 360 degree for 5 sec\n\n  let mut animation_2 = Animation::new();\n  animation_2.apply_rotation(0, 360, 5.0, EasingFunction::EaseInOut);\n  actor_2.set_animation(Some(animation_2));\n\n  let mut actor_3 = Play::new_actor(\"actor_3\".to_string(), 50, 50, None);\n  actor_3.x = 10;\n  actor_3.y = 10;\n  actor_3.set_color(1.0, 0.0, 0.0);\n  actor_2.add_sub_actor(actor_3);\n\n  stage.add_sub_actor(actor_1);\n  stage.add_sub_actor(actor_2);\n\n  play.add_stage(stage);\n\n  while !window.should_close() {\n    process_events(\u0026mut window, \u0026events);\n    play.render();\n    window.swap_buffers();\n    glfw.poll_events();\n  }\n```\n\n## picture_viewer.rs\nThis example is still work in progress. The thumbnail view only works.\n```\n$ cargo build --example picture_viewer\n$ target/debug/examples/picture_viewer\n```\nThis example shows how to handle events and user-defined layout. More event handler methods would be added.\n\n```rust\n\npub struct ActorEvent {\n  name: String,\n}\n\nimpl ActorEvent {\n  pub fn new() -\u003e Self {\n    ActorEvent {\n      name: \"actor_event\".to_string(),\n    }\n  }\n}\n\nimpl EventHandler for ActorEvent {\n  fn key_focus_in(\u0026mut self, actor: \u0026mut Actor) {\n    let mut animation = Animation::new();\n    animation.apply_scale(1.0, 1.1, 0.3, EasingFunction::EaseInOut);\n    actor.set_animation(Some(animation));\n  }\n\n  fn key_focus_out(\u0026mut self, actor: \u0026mut Actor) {\n    actor.scale_x = 1.0;\n    actor.scale_y = 1.0;\n  }\n\n  fn key_down(\u0026mut self, key: rust_animation::actor::Key, actor: \u0026mut Actor) {\n    if key == rust_animation::actor::Key::Right {\n      // right cursor\n      actor.select_next_sub_actor();\n    } else if key == rust_animation::actor::Key::Left {\n      // left cursor\n      actor.select_prev_sub_actor();\n    }\n  }\n}\n\npub struct ActorLayout {\n  name: String,\n  cur_x: i32,\n}\n\nimpl ActorLayout {\n  pub fn new() -\u003e Self {\n    ActorLayout {\n      name: \"actor_layout\".to_string(),\n      cur_x: 0,\n    }\n  }\n}\n\nimpl Layout for ActorLayout {\n  fn layout_sub_actors(\n    \u0026mut self,\n    actor: \u0026mut Actor,\n    parent_actor: Option\u003c\u0026Actor\u003e,\n    stretch: \u0026mut Option\u003cStretch\u003e,\n  ) {\n    println!(\"layout_sub_layer {}\", self.name);\n    let mut index: i32 = 0;\n    for sub_actor in actor.sub_actor_list.iter_mut() {\n      self.cur_x += sub_actor.width as i32;\n      sub_actor.x = index % 5 * IMAGE_WIDTH as i32;\n      let col = index / 5;\n      sub_actor.y = col * IMAGE_HEIGHT as i32;\n      index += 1;\n    }\n  }\n\n  fn update_layout(\u0026mut self, actor: \u0026mut Actor, stretch: \u0026mut Option\u003cStretch\u003e) {\n    println!(\"update_layout {}\", self.name);\n  }\n\n  fn finalize(\u0026mut self) {\n    println!(\"finalize {}\", self.name);\n  }\n}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoone%2Frust-animation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoone%2Frust-animation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoone%2Frust-animation/lists"}