mod shared;
use aura_anim_iced::{
AnimationRuntime, AnimationTargetId, BehaviorRule, Easing, EffectSnapshot, PropertyTransition,
Timing, WIDTH, iced_ext,
};
use iced::{
Background, Border, Color, Element, Length, Subscription, Task, Theme,
alignment::{Horizontal, Vertical},
widget::{Space, button, column, container, row, stack, text},
};
use std::time::Instant;
const INITIAL_WIDTH: f32 = 90.0;
const MEDIUM_WIDTH: f32 = 240.0;
const WIDE_WIDTH: f32 = 420.0;
const TRANSITION_MS: f64 = 1_800.0;
const BEHAVIOR_SUMMARY: &str = "Each button sets a new target width. The bar animates from the width currently rendered on screen to that selected target.";
fn main() -> iced::Result {
iced::application(Demo::default, Demo::update, Demo::view)
.title(title)
.subscription(Demo::subscription)
.run()
}
fn title(_: &Demo) -> String {
String::from("aura-anim-iced behavior width")
}
#[derive(Debug, Clone, Copy)]
enum Message {
SetWidth(f32),
AnimationTick(Instant),
}
#[derive(Debug)]
struct Demo {
runtime: AnimationRuntime,
width_target: AnimationTargetId,
width_transition: PropertyTransition<aura_anim_iced::property::Scalar>,
effects: EffectSnapshot,
target_width: f32,
}
impl Default for Demo {
fn default() -> Self {
let mut runtime = AnimationRuntime::new();
let width_target = AnimationTargetId::new();
let rule = BehaviorRule::new(WIDTH)
.with_timing(Timing::new(TRANSITION_MS).with_easing(Easing::EaseOut));
let mut width_transition = rule.bind(width_target);
width_transition.transition_to(&mut runtime, INITIAL_WIDTH);
Self {
runtime,
width_target,
width_transition,
effects: width_effects(INITIAL_WIDTH),
target_width: INITIAL_WIDTH,
}
}
}
impl Demo {
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::SetWidth(width) => self.set_width(width),
Message::AnimationTick(tick_instant) => {
let tick = iced_ext::update_tick(&mut self.runtime, tick_instant);
let effects = aura_anim_iced::tick_effect_snapshot_for(&tick, self.width_target);
if !effects.is_empty() {
self.effects = shared::merge_effects(&self.effects, &effects);
}
self.width_transition.handle_completion(&self.runtime);
}
}
Task::none()
}
fn subscription(&self) -> Subscription<Message> {
iced_ext::subscription(&self.runtime, Message::AnimationTick)
}
fn view(&self) -> Element<'_, Message> {
let width = self.effects.width.unwrap_or(INITIAL_WIDTH);
let controls = row![
width_button("Narrow", INITIAL_WIDTH, self.target_width),
width_button("Medium", MEDIUM_WIDTH, self.target_width),
width_button("Wide", WIDE_WIDTH, self.target_width),
]
.spacing(12)
.align_y(Vertical::Center);
container(
column![
text(BEHAVIOR_SUMMARY)
.width(Length::Fixed(WIDE_WIDTH))
.size(15)
.color(Color::from_rgb(0.70, 0.79, 0.84)),
controls,
width_stage(width, self.target_width),
row![
text(format!("Current {width:.0}px"))
.size(16)
.color(Color::from_rgb(0.78, 0.86, 0.90)),
text(format!("Target {:.0}px", self.target_width))
.size(16)
.color(Color::from_rgb(0.48, 0.70, 0.76)),
]
.spacing(24)
.align_y(Vertical::Center),
]
.spacing(16)
.align_x(Horizontal::Center),
)
.width(Length::Fill)
.height(Length::Fill)
.padding(48)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into()
}
fn set_width(&mut self, width: f32) {
let visual = self.effects.width.unwrap_or(INITIAL_WIDTH);
self.target_width = width;
self.width_transition
.transition_from_visual(&mut self.runtime, visual, width);
}
}
#[allow(clippy::float_cmp)]
fn width_button(label: &'static str, width: f32, current: f32) -> Element<'static, Message> {
button(text(label))
.on_press_maybe((width != current).then_some(Message::SetWidth(width)))
.into()
}
fn width_stage(width: f32, target_width: f32) -> Element<'static, Message> {
stack![
width_outline(target_width),
container(text("Width").size(18).color(Color::WHITE))
.width(Length::Fixed(width))
.height(Length::Fixed(72.0))
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.style(bar_style),
]
.width(Length::Fixed(WIDE_WIDTH))
.height(Length::Fixed(72.0))
.into()
}
fn width_outline(width: f32) -> Element<'static, Message> {
container(Space::new())
.width(Length::Fixed(width))
.height(Length::Fixed(72.0))
.style(target_style)
.into()
}
fn width_effects(width: f32) -> EffectSnapshot {
EffectSnapshot {
width: Some(width),
..EffectSnapshot::default()
}
}
fn bar_style(_theme: &Theme) -> container::Style {
container::Style {
text_color: Some(Color::WHITE),
background: Some(Background::Color(Color::from_rgb(0.18, 0.35, 0.42))),
border: Border {
color: Color::from_rgb(0.50, 0.78, 0.82),
width: 1.0,
radius: 10.0.into(),
},
..container::Style::default()
}
}
fn target_style(_theme: &Theme) -> container::Style {
container::Style {
background: Some(Background::Color(Color::from_rgba(0.50, 0.78, 0.82, 0.12))),
border: Border {
color: Color::from_rgb(0.50, 0.78, 0.82),
width: 1.0,
radius: 10.0.into(),
},
..container::Style::default()
}
}
#[cfg(test)]
mod tests {
use super::BEHAVIOR_SUMMARY;
#[test]
fn behavior_summary_describes_current_and_target_widths() {
assert!(BEHAVIOR_SUMMARY.contains("target width"));
assert!(BEHAVIOR_SUMMARY.contains("currently rendered"));
assert!(BEHAVIOR_SUMMARY.contains("selected target"));
}
}