272 lines
9.3 KiB
Rust
272 lines
9.3 KiB
Rust
//! Demonstrates animation, easing functions.
|
|
|
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use zng::{
|
|
button,
|
|
color::{ColorScheme, Rgba},
|
|
image,
|
|
layout::{margin, offset, size},
|
|
prelude::*,
|
|
rule_line::RuleLine,
|
|
var::{
|
|
animation::{
|
|
self,
|
|
easing::{EasingFn, EasingTime},
|
|
},
|
|
ArcVar, VARS,
|
|
},
|
|
widget::{background_color, corner_radius, LineOrientation},
|
|
window::RenderMode,
|
|
};
|
|
|
|
use zng::view_process::prebuilt as view_process;
|
|
|
|
fn main() {
|
|
examples_util::print_info();
|
|
view_process::init();
|
|
zng::app::crash_handler::init_debug();
|
|
|
|
// let rec = examples_util::record_profile("animation");
|
|
|
|
// view_process::run_same_process(app_main);
|
|
app_main();
|
|
|
|
// rec.finish();
|
|
}
|
|
|
|
fn app_main() {
|
|
APP.defaults().run_window(async {
|
|
Window! {
|
|
title = "Animation Example";
|
|
padding = 10;
|
|
child_align = Align::CENTER;
|
|
child = example();
|
|
}
|
|
})
|
|
}
|
|
|
|
const FROM_COLOR: Rgba = web_colors::RED;
|
|
const TO_COLOR: Rgba = web_colors::GREEN;
|
|
const FPS: u32 = 60;
|
|
|
|
fn example() -> impl UiNode {
|
|
// VARS.animation_time_scale().set(0.5.fct());
|
|
VARS.frame_duration().set((1.0 / FPS as f32).secs());
|
|
|
|
let x = var(0.dip());
|
|
let color = var(FROM_COLOR);
|
|
|
|
// x.trace_value(move |v| {
|
|
// tracing::debug_span!("x", value = ?v, thread = "<x>").entered()
|
|
// })
|
|
// .perm();
|
|
|
|
let easing_mod = var(Txt::from("ease_out"));
|
|
|
|
Stack! {
|
|
direction = StackDirection::top_to_bottom();
|
|
spacing = 10;
|
|
children_align = Align::TOP;
|
|
children = ui_vec![
|
|
Container! {
|
|
id = "demo";
|
|
layout::width = 301;
|
|
widget::background = ruler();
|
|
margin = (0, 0, 40, 0);
|
|
child_align = Align::LEFT;
|
|
child = Wgt! {
|
|
id = "ball";
|
|
size = (40, 40);
|
|
corner_radius = 20;
|
|
background_color = color.clone();
|
|
|
|
layout::x = x.map(|x| x.clone() - 20.dip());
|
|
|
|
when *#gesture::is_hovered {
|
|
background_color = web_colors::LIME;
|
|
}
|
|
};
|
|
},
|
|
Stack! {
|
|
id = "mod-menu";
|
|
direction = StackDirection::left_to_right();
|
|
spacing = 2;
|
|
toggle::selector = toggle::Selector::single(easing_mod.clone());
|
|
children = {
|
|
let mode = |m: Txt| Toggle! {
|
|
child = Text!(m.clone());
|
|
value::<Txt> = m;
|
|
};
|
|
ui_vec![
|
|
mode(Txt::from("ease_in")),
|
|
mode(Txt::from("ease_out")),
|
|
mode(Txt::from("ease_in_out")),
|
|
mode(Txt::from("ease_out_in")),
|
|
mode(Txt::from("reverse")),
|
|
mode(Txt::from("reverse_out")),
|
|
]
|
|
}
|
|
},
|
|
Grid! {
|
|
id = "easing-menu";
|
|
spacing = 2;
|
|
columns = ui_vec![grid::Column!(1.lft()); 7];
|
|
auto_grow_fn = wgt_fn!(|_| grid::Row!(1.lft()));
|
|
button::style_fn = Style! { layout::padding = 3 };
|
|
cells = ui_vec![
|
|
ease_btn(&x, &color, "", EasingFn::Linear, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Quad, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Cubic, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Quart, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Quint, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Sine, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Expo, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Circ, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Back, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Elastic, &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::Bounce, &easing_mod),
|
|
ease_btn(&x, &color, "step_ceil(6)", EasingFn::custom(|t| easing::step_ceil(6, t)), &easing_mod),
|
|
ease_btn(&x, &color, "step_floor(6)", EasingFn::custom(|t| easing::step_floor(6, t)), &easing_mod),
|
|
ease_btn(&x, &color, "", EasingFn::None, &easing_mod),
|
|
]
|
|
},
|
|
Button! {
|
|
child = Text!("reset");
|
|
widget::foreground_highlight = {
|
|
offsets: -2,
|
|
widths: 1,
|
|
sides: web_colors::DARK_RED,
|
|
};
|
|
gesture::click_shortcut = shortcut![Escape];
|
|
on_click = hn!(x, color, |_| {
|
|
x.set(0);
|
|
color.set(FROM_COLOR);
|
|
});
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
fn ease_btn(l: &ArcVar<Length>, color: &ArcVar<Rgba>, name: &'static str, easing: EasingFn, easing_mod: &ArcVar<Txt>) -> impl UiNode {
|
|
let name = if name.is_empty() { formatx!("{easing:?}") } else { name.to_txt() };
|
|
let easing = easing_mod.map(clmv!(easing, |m| {
|
|
let f = match m.as_str() {
|
|
"ease_in" => easing.clone(),
|
|
"ease_out" => easing.clone().ease_out(),
|
|
"ease_in_out" => easing.clone().ease_in_out(),
|
|
"ease_out_in" => easing.clone().ease_out_in(),
|
|
"reverse" => easing.clone().reverse(),
|
|
"reverse_out" => easing.clone().reverse_out(),
|
|
_ => unreachable!(),
|
|
};
|
|
(m.clone(), f)
|
|
}));
|
|
let mut plot_cache = HashMap::new(); // to reuse rendered images
|
|
Button! {
|
|
grid::cell::at = grid::cell::AT_AUTO;
|
|
child = Stack! {
|
|
direction = StackDirection::top_to_bottom();
|
|
spacing = 2;
|
|
children_align = Align::TOP;
|
|
children = ui_vec![
|
|
Text!(name),
|
|
Image! {
|
|
img_scale_ppi = true;
|
|
img_loading_fn = wgt_fn!(|_| Wgt! {
|
|
size = (64, 64);
|
|
margin = 10;
|
|
});
|
|
source = easing.map(move |(name, f)| plot_cache.entry(name.clone()).or_insert_with(|| plot(f.clone())).clone());
|
|
},
|
|
]
|
|
};
|
|
on_click = hn!(l, color, |_| {
|
|
let f = easing.get().1;
|
|
l.set_ease(0, 300, 1.secs(), f.ease_fn()).perm();
|
|
color.set_ease(FROM_COLOR, TO_COLOR, 1.secs(), f.ease_fn()).perm();
|
|
});
|
|
}
|
|
}
|
|
fn plot(easing: EasingFn) -> ImageSource {
|
|
let size = (64, 64);
|
|
ImageSource::render_node(
|
|
RenderMode::Software,
|
|
clmv!(size, |_| {
|
|
let mut children = ui_vec![];
|
|
let color_t = animation::Transition::new(FROM_COLOR, TO_COLOR);
|
|
let fps_f = FPS as f32;
|
|
for i in 0..=FPS {
|
|
let x_fct = (i as f32 / fps_f).fct();
|
|
let x = size.0 * x_fct;
|
|
|
|
let y_fct = easing(EasingTime::new(x_fct));
|
|
let y = size.1 * (1.fct() - y_fct);
|
|
|
|
children.push(
|
|
Wgt! {
|
|
offset = (x, y);
|
|
size = (3, 3);
|
|
corner_radius = 2;
|
|
layout::translate = -1.5, -1.5;
|
|
background_color = color_t.sample(y_fct);
|
|
}
|
|
.boxed(),
|
|
)
|
|
}
|
|
|
|
image::IMAGE_RENDER.retain().set(true);
|
|
let meta_color = WINDOW.vars().actual_color_scheme().map(|t| match t {
|
|
ColorScheme::Light => rgba(0, 0, 0, 0.4),
|
|
ColorScheme::Dark => rgba(255, 255, 255, 0.4),
|
|
});
|
|
|
|
#[allow(clippy::precedence)]
|
|
children.push(
|
|
Text! {
|
|
txt = "v";
|
|
font_size = 12;
|
|
font_style = FontStyle::Italic;
|
|
font_color = meta_color.clone();
|
|
offset = (-3.dip() - 100.pct(), -3.dip());
|
|
}
|
|
.boxed(),
|
|
);
|
|
children.push(
|
|
Text! {
|
|
txt = "t";
|
|
font_size = 12;
|
|
font_style = FontStyle::Italic;
|
|
font_color = meta_color.clone();
|
|
offset = (size.0.dip() - 100.pct() - 3.dip(), size.1 - 3);
|
|
}
|
|
.boxed(),
|
|
);
|
|
Stack! {
|
|
children_align = Align::TOP_LEFT;
|
|
children;
|
|
size;
|
|
widget::border = (0, 0, 1, 1), meta_color.map_into();
|
|
margin = 10;
|
|
}
|
|
}),
|
|
)
|
|
}
|
|
|
|
fn ruler() -> impl UiNode {
|
|
Stack! {
|
|
children_align = Align::LEFT;
|
|
children = (0..=300).step_by(10)
|
|
.map(|x| RuleLine! {
|
|
orientation = LineOrientation::Vertical;
|
|
color = text::FONT_COLOR_VAR.map(|c| c.with_alpha(40.pct()));
|
|
layout::x = x.dip();
|
|
layout::height = if x % 100 == 0 { 52 } else if x % 50 == 0 { 22 } else { 12 };
|
|
}
|
|
.boxed())
|
|
.collect::<Vec<_>>(),
|
|
}
|
|
}
|