rust-animation 

rust-animation is an OpenGL-based graphics library written in Rust for creating hardware-accelerated user interfaces. It is designed to implement simple, animated UIs for embedded devices, inspired by the GNOME Clutter project and Apple Core Animation.
Table of Contents
Features
- 2D Transforms: Apply translate, scale, and rotate transformations to layers
- Rich Animation System: Support for multiple easing functions (Linear, EaseIn, EaseOut, EaseInOut, and various polynomial variants)
- Flex Layout: CSS Flexbox-like layout system using the Stretch library
- Hardware Acceleration: OpenGL-based rendering for high performance
- RALayer Hierarchy: Support for nested layers with parent-child relationships
- Event Handling: Built-in event system for keyboard input and focus management
- Image Support: Load and display images as textures
- Text Rendering: Font rendering capabilities for displaying text
Note: rust-animation is in early development. Some features may be incomplete or have bugs. Please report any issues you encounter.
Prerequisites
Before using rust-animation, ensure you have the following installed:
Required
Platform-Specific Requirements
Ubuntu/Debian
sudo apt-get update
sudo apt-get install build-essential cmake pkg-config
macOS
brew install cmake
Windows
- Install CMake from the official website
- Ensure you have the Visual Studio Build Tools or Visual Studio installed
Tested Platforms
- Ubuntu 20.04 and later
- Windows 10 and later
- macOS (Intel and Apple Silicon)
Installation
Using as a Library
Add rust-animation to your Cargo.toml:
[dependencies]
rust-animation = "0.2.7"
Building from Source
Clone the repository and build:
git clone https://github.com/joone/rust-animation.git
cd rust-animation
cargo build --release
Quick Start
Here's a minimal example to get started:
use rust_animation::{layer::RALayer, animation::Animation, play::Play};
use rust_animation::layer::LayoutMode;
use keyframe::EasingFunction;
fn main() {
let mut play = Play::new(
"My First Animation".to_string(),
800,
600,
LayoutMode::UserDefine,
);
let mut stage = RALayer::new("stage".to_string(), 800, 600, None);
stage.set_visible(true);
let mut layer = RALayer::new("my_layer".to_string(), 100, 100, None);
layer.x = 50;
layer.y = 50;
layer.set_color(1.0, 0.0, 0.0);
let mut animation = Animation::new();
animation.apply_translation_x(50, 400, 2.0, EasingFunction::EaseInOut);
layer.set_animation(Some(animation));
stage.add_sub_actor(layer);
play.add_stage(stage);
}
For complete working examples, see the Examples section below.
Examples
rust-animation includes several examples to demonstrate its capabilities. All examples can be run using cargo:
cargo run --example <example_name>
Easing Functions
Example file: easing_functions.rs
Demonstrates all available easing functions with visual animations.
Run:
cargo run --example easing_functions
What it does: Creates 17 animated layers, each using a different easing function, moving horizontally across the screen while rotating.
Key concepts demonstrated:
- Multiple easing functions (Linear, EaseIn, EaseOut, EaseInOut, and polynomial variants)
- Combining multiple animations (translation + rotation)
- RALayer positioning and coloring
Code snippet:
let mut play = Play::new(
"Easing functions demo".to_string(),
1920,
1080,
LayoutMode::UserDefine,
);
let mut stage = RALayer::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 layer_name = format!("layer_{}", i + 1);
let mut layer = RALayer::new(actor_name.to_string(), width, height, None);
layer.x = 0;
layer.y = y;
y += height as i32;
layer.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);
layer.set_animation(Some(animation));
stage.add_sub_actor(layer);
}
play.add_stage(stage);
while !window.should_close() {
process_events(&mut window, &events);
play.render();
window.swap_buffers();
glfw.poll_events();
}
Flex UI
Example file: flex_ui.rs

Demonstrates CSS Flexbox-like layout capabilities using the Stretch library.
Run:
cargo run --example flex_ui
What it does: Creates a responsive layout with multiple containers, each demonstrating different flexbox alignment properties (FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, SpaceEvenly).
Key concepts demonstrated:
- Flex layout system
- Custom layout implementation using the
Layout trait
- Justify content and align items properties
- Nested layers with flex positioning
Code snippet:
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_layers(
&mut self,
layer: &mut RALayer,
parent_layer: Option<&RALayer>,
stretch: &mut Option<Stretch>,
) {
println!("run layout_sub_layer for FlexLayout {}", self.name);
if let Some(stretch_obj) = stretch {
if let Some(style_obj) = layer.style {
layer.node = Some(stretch_obj.new_node(style_obj, vec![]).unwrap());
} else {
layer.node = Some(
stretch_obj
.new_node(
Style {
size: Size {
width: Dimension::Points(layer.width as f32),
height: Dimension::Points(layer.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!("layer name {}", layer.name);
if let Some(parent_layer) = parent_layer {
if !parent_layer.node.is_none() && !layer.node.is_none() {
match stretch_obj.add_child(parent_layer.node.unwrap(), layer.node.unwrap()) {
Ok(()) => {
println!(
" stretch node is added {} {}",
parent_layer.name, layer.name
)
}
Err(..) => {}
}
}
}
}
}
fn update_layout(&mut self, layer: &mut RALayer, stretch: &mut Option<Stretch>) {
if let Some(stretch_obj) = stretch {
if !layer.node.is_none() {
let layout = stretch_obj.layout(layer.node.unwrap()).unwrap();
layer.x = layout.location.x as i32;
layer.y = layout.location.y as i32;
println!(
"run update_layout for FlexLayout {} = {},{}",
layer.name, layer.x, layer.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 = RALayer::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 layer_name = format!("layer_{}", i + 1);
let mut layer = RALayer::new(actor_name.to_string(), width, height, None);
layer.set_color(i as f32 / 6.0, i as f32 / 6.0, i as f32 / 6.0);
layer.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 = RALayer::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())));
layer.add_sub_actor(sub_actor);
}
layer.set_layout(Some(Box::new(FlexLayout::new())));
stage.add_sub_actor(layer);
}
stage.set_layout(Some(Box::new(FlexLayout::new())));
play.add_stage(stage);
while !window.should_close() {
process_events(&mut window, &events);
play.render();
window.swap_buffers();
glfw.poll_events();
}
}
Basic Animation
Example file: ani.rs
Demonstrates basic animation features including transforms and image loading.
Run:
cargo run --example ani
What it does: Shows multiple animations running simultaneously - scaling, translating, and rotating layers, including image-based layers and colored shapes with nested sub-layers.
Key concepts demonstrated:
- Multiple simultaneous animations (scale, translate, rotate)
- Loading and animating images
- Nested layer hierarchies
- Different easing functions
Code snippet:
let mut play = Play::new(
"Animation test".to_string(),
1920,
1080,
LayoutMode::UserDefine,
);
let mut stage = RALayer::new("stage".to_string(), 1920, 1080, None);
stage.set_visible(true);
let mut layer_1 = RALayer::new("layer_1".to_string(), 400, 225, None);
layer_1.x = 100;
layer_1.y = 100;
layer_1.set_image("examples/splash.png".to_string());
let mut animation_1 = Animation::new();
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);
layer_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);
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 layer_3 = Play::new_actor("layer_3".to_string(), 50, 50, None);
layer_3.x = 10;
layer_3.y = 10;
layer_3.set_color(1.0, 0.0, 0.0);
actor_2.add_sub_actor(layer_3);
stage.add_sub_actor(layer_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
Example file: picture_viewer.rs
Demonstrates event handling and custom user-defined layouts.
Run:
cargo run --example picture_viewer
What it does: Creates a thumbnail grid viewer with keyboard navigation and focus animations. Currently implements thumbnail view functionality.
Key concepts demonstrated:
- Custom event handlers (EventHandler trait)
- Keyboard input handling (arrow keys for navigation)
- Focus management (key_focus_in/out events)
- Custom layout implementation (Layout trait)
- Grid-based positioning
Note: This example is a work in progress. Currently, only the thumbnail view is fully functional.
Code snippet:
pub struct RALayerEvent {
name: String,
}
impl RALayerEvent {
pub fn new() -> Self {
RALayerEvent {
name: "layer_event".to_string(),
}
}
}
impl EventHandler for RALayerEvent {
fn key_focus_in(&mut self, layer: &mut RALayer) {
let mut animation = Animation::new();
animation.apply_scale(1.0, 1.1, 0.3, EasingFunction::EaseInOut);
layer.set_animation(Some(animation));
}
fn key_focus_out(&mut self, layer: &mut RALayer) {
layer.scale_x = 1.0;
layer.scale_y = 1.0;
}
fn key_down(&mut self, key: rust_animation::layer::Key, layer: &mut RALayer) {
if key == rust_animation::layer::Key::Right {
layer.select_next_sub_actor();
} else if key == rust_animation::layer::Key::Left {
layer.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_layers(
&mut self,
layer: &mut RALayer,
parent_layer: Option<&RALayer>,
stretch: &mut Option<Stretch>,
) {
println!("layout_sub_layer {}", self.name);
let mut index: i32 = 0;
for sub_actor in layer.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, layer: &mut RALayer, stretch: &mut Option<Stretch>) {
println!("update_layout {}", self.name);
}
fn finalize(&mut self) {
println!("finalize {}", self.name);
}
}
API Overview
Core Concepts
Play: The main container and render manager
- Manages the rendering loop
- Holds one or more stages
- Handles projection matrices and OpenGL setup
RALayer: Visual elements in the scene graph
- Can have position (x, y, z), size (width, height)
- Supports transforms: translate, scale, rotate
- Can have colors or textures
- Supports nested hierarchies (parent-child relationships)
- Can have animations, event handlers, and custom layouts
Animation: Defines time-based property changes
- Apply transformations over time with easing functions
- Supports: translation (x, y), scaling, rotation
- Multiple animations can run simultaneously on one layer
Easing Functions: Control animation timing curves
- Linear, Step
- EaseIn, EaseOut, EaseInOut (sine-based)
- Quad, Cubic, Quart, Quint variants (polynomial-based)
Main APIs
let play = Play::new(name, width, height, layout_mode);
let mut layer = RALayer::new(name, width, height, event_handler);
layer.x = x;
layer.y = y;
layer.set_color(r, g, b);
layer.set_image(path);
let mut animation = Animation::new();
animation.apply_translation_x(from, to, duration, easing);
animation.apply_translation_y(from, to, duration, easing);
animation.apply_scale(from, to, duration, easing);
animation.apply_rotation(from_deg, to_deg, duration, easing);
layer.set_animation(Some(animation));
parent_layer.add_sub_actor(child_actor);
stage.add_sub_actor(layer);
play.add_stage(stage);
play.render();
Contributing
Contributions are welcome! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature)
- Commit your changes (
git commit -m 'Add some amazing feature')
- Push to the branch (
git push origin feature/amazing-feature)
- Open a Pull Request
Development Guidelines
- Run
cargo fmt before committing to ensure consistent code style
- Use the provided
run-check-style.sh script for formatting
- Write examples to demonstrate new features
- Update documentation for API changes
Releasing
For information about publishing new versions to crates.io, see RELEASING.md.
Reporting Issues
Found a bug or have a feature request? Please open an issue with:
- Clear description of the problem/feature
- Steps to reproduce (for bugs)
- Expected vs. actual behavior
- System information (OS, Rust version)
License
This project is licensed under the BSD-3-Clause License - see the LICENSE file for details.
Acknowledgments
This project was inspired by:
Dependencies
rust-animation uses several excellent open-source libraries:
- cgmath - Linear algebra and mathematics for graphics
- gl - OpenGL bindings
- image - Image encoding and decoding
- keyframe - Keyframe animation library
- stretch - Flexbox layout engine
- ab_glyph - Font rendering
- glfw - Window and OpenGL context creation (examples only)
Author: Joone Hur
Repository: https://github.com/joone/rust-animation