cacao/examples/animation.rs
2022-08-26 10:40:31 -04:00

178 lines
5.6 KiB
Rust

//! This example builds on the AutoLayout example, but adds in animation
//! via `AnimationContext`. Views and layout anchors have special proxy objects that can be cloned
//! into handlers, enabling basic animation support within `AnimationContext`.
//!
//! This one is a bit kludgier than some other examples, but the comments throughout this should
//! clarify why that is.
use cacao::color::Color;
use cacao::layout::{Layout, LayoutConstraint, LayoutConstraintAnimatorProxy};
use cacao::view::{View, ViewAnimatorProxy};
use cacao::appkit::menu::Menu;
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate};
use cacao::appkit::{AnimationContext, App, AppDelegate};
use cacao::appkit::{Event, EventMask, EventMonitor};
struct BasicApp {
window: Window<AppWindow>
}
impl AppDelegate for BasicApp {
fn did_finish_launching(&self) {
App::set_menu(Menu::standard());
App::activate();
self.window.show();
}
fn should_terminate_after_last_window_closed(&self) -> bool {
true
}
}
/// This map is the four different animation frames that we display, per view type.
/// Why do we have this here?
///
/// Well, it's because there's no random number generator in the standard library, and I really
/// dislike when examples need crates attached to 'em.
///
/// The basic mapping logic is this: each entry is a view's frame(s), and each frame is an array
/// of:
///
/// [top, left, width, height, alpha]
///
/// We then treat each frame index as follows:
///
/// w: 0
/// a: 1
/// s: 2
/// d: 3
const ANIMATIONS: [[[f64; 5]; 4]; 3] = [
// Blue
[
[44., 16., 100., 100., 1.],
[128., 84., 144., 124., 1.],
[32., 32., 44., 44., 0.7],
[328., 157., 200., 200., 0.7]
],
// Red
[
[44., 132., 100., 100., 1.],
[40., 47., 80., 64., 0.7],
[84., 220., 600., 109., 1.0],
[48., 600., 340., 44., 0.7]
],
// Green
[
[44., 248., 100., 100., 1.],
[420., 232., 420., 244., 0.7],
[310., 440., 150., 238., 0.7],
[32., 32., 44., 44., 1.]
]
];
/// A helper method for generating frame constraints that we want to be animating.
fn apply_styles(view: &View, parent: &View, background_color: Color, animation_table_index: usize) -> [LayoutConstraint; 4] {
view.set_background_color(background_color);
view.layer.set_corner_radius(16.);
parent.add_subview(view);
let animation = ANIMATIONS[animation_table_index][0];
[
view.top.constraint_equal_to(&parent.top).offset(animation[0]),
view.left.constraint_equal_to(&parent.left).offset(animation[1]),
view.width.constraint_equal_to_constant(animation[2]),
view.height.constraint_equal_to_constant(animation[3])
]
}
#[derive(Default)]
struct AppWindow {
content: View,
blue: View,
red: View,
green: View,
key_monitor: Option<EventMonitor>
}
impl WindowDelegate for AppWindow {
const NAME: &'static str = "WindowDelegate";
fn did_load(&mut self, window: Window) {
window.set_title("Animation Example (Use W/A/S/D to change state!)");
window.set_minimum_content_size(300., 300.);
window.set_content_view(&self.content);
let blue_frame = apply_styles(&self.blue, &self.content, Color::SystemBlue, 0);
let red_frame = apply_styles(&self.red, &self.content, Color::SystemRed, 1);
let green_frame = apply_styles(&self.green, &self.content, Color::SystemGreen, 2);
let alpha_animators = [&self.blue, &self.red, &self.green]
.iter()
.map(|view| view.animator.clone())
.collect::<Vec<ViewAnimatorProxy>>();
let constraint_animators = [blue_frame, red_frame, green_frame]
.iter()
.map(|frame| {
LayoutConstraint::activate(frame);
vec![
frame[0].animator.clone(),
frame[1].animator.clone(),
frame[2].animator.clone(),
frame[3].animator.clone(),
]
})
.collect::<Vec<Vec<LayoutConstraintAnimatorProxy>>>();
// Monitor key change events for w/a/s/d, and then animate each view to their correct
// frame and alpha value.
self.key_monitor = Some(Event::local_monitor(EventMask::KeyDown, move |evt| {
let characters = evt.characters();
let animation_index = match characters.as_ref() {
"w" => 0,
"a" => 1,
"s" => 2,
"d" => 3,
_ => 4
};
if animation_index == 4 {
return None;
}
let alpha_animators = alpha_animators.clone();
let constraint_animators = constraint_animators.clone();
AnimationContext::run(move |_ctx| {
alpha_animators.iter().enumerate().for_each(move |(index, view)| {
let animation = ANIMATIONS[index][animation_index];
view.set_alpha(animation[4]);
});
constraint_animators.iter().enumerate().for_each(move |(index, frame)| {
let animation = ANIMATIONS[index][animation_index];
frame[0].set_offset(animation[0]);
frame[1].set_offset(animation[1]);
frame[2].set_offset(animation[2]);
frame[3].set_offset(animation[3]);
});
});
None
}));
}
}
fn main() {
App::new("com.test.window", BasicApp {
window: Window::with(WindowConfig::default(), AppWindow::default())
})
.run();
}