aura-anim-iced 0.2.2

Iced integration for the aura typed animation runtime.
Documentation
//! Interactive button with tweened hover and spring press feedback.

use std::time::Instant;

use aura_anim_core::{
    Animatable, Motion, MotionRuntime, Spring, SpringConfig,
    timing::{Easing, Timing},
};
use iced::{
    Background, Border, Color, Element, Fill, Shadow, Subscription, Theme, Vector,
    widget::{column, container, mouse_area, row, text},
};

#[derive(Clone, Debug, Animatable)]
struct ButtonMotion {
    width: f32,
    height: f32,
    glow: f32,
    lift: f32,
    color: Color,
}

struct ButtonExample {
    runtime: MotionRuntime,
    button: Motion<ButtonMotion>,
    hovered: bool,
    clicks: u32,
}

#[derive(Clone, Copy, Debug)]
enum Message {
    Frame(Instant),
    Enter,
    Exit,
    Press,
    Release,
}

fn main() -> iced::Result {
    iced::application(
        ButtonExample::new,
        ButtonExample::update,
        ButtonExample::view,
    )
    .title("Aura Anim - Interactive Button")
    .theme(theme)
    .subscription(ButtonExample::subscription)
    .window_size((720.0, 440.0))
    .run()
}

impl ButtonExample {
    fn new() -> Self {
        let mut runtime = MotionRuntime::new();
        let button =
            runtime.motion_with(resting(), Timing::new(170.0).with_easing(Easing::EaseOut));

        Self {
            runtime,
            button,
            hovered: false,
            clicks: 0,
        }
    }

    fn update(&mut self, message: Message) {
        match message {
            Message::Frame(now) => aura_anim_iced::frame(&mut self.runtime, now),
            Message::Enter => {
                self.hovered = true;
                self.button.transition_to(hovered(), &mut self.runtime);
            }
            Message::Exit => {
                self.hovered = false;
                self.button.transition_to(resting(), &mut self.runtime);
            }
            Message::Press => {
                let current = self.button.value(&self.runtime);
                self.button.play(
                    Spring::new(
                        current,
                        pressed(),
                        SpringConfig {
                            stiffness: 380.0,
                            damping: 22.0,
                            ..SpringConfig::default()
                        },
                    ),
                    &mut self.runtime,
                );
            }
            Message::Release => {
                self.clicks += 1;
                let target = if self.hovered { hovered() } else { resting() };
                self.button.transition_to(target, &mut self.runtime);
            }
        }
    }

    fn subscription(&self) -> Subscription<Message> {
        aura_anim_iced::subscription(&self.runtime).map(Message::Frame)
    }

    fn view(&self) -> Element<'_, Message> {
        let motion = self.button.value_ref(&self.runtime);
        let color = motion.color;
        let surface = container(
            row![
                text("Run animation").size(16).color(Color::WHITE),
                text("->").size(18).color(Color::WHITE)
            ]
            .spacing(12),
        )
        .center_x(motion.width)
        .center_y(motion.height)
        .style(move |_| container::Style {
            background: Some(Background::Color(color)),
            border: Border::default()
                .rounded(8)
                .width(1)
                .color(Color::from_rgba(1.0, 1.0, 1.0, 0.22)),
            shadow: Shadow {
                color: Color::from_rgba(color.r, color.g, color.b, motion.glow),
                offset: Vector::new(0.0, 8.0 + motion.lift),
                blur_radius: 18.0 + motion.glow * 28.0,
            },
            ..container::Style::default()
        });

        let interactive = mouse_area(surface)
            .on_enter(Message::Enter)
            .on_exit(Message::Exit)
            .on_press(Message::Press)
            .on_release(Message::Release);

        container(
            column![
                text("Interactive button").size(32).color(Color::WHITE),
                text("Hover uses transition_to. Press replaces it with a spring from the current value.")
                    .size(14)
                    .color(Color::from_rgb8(169, 178, 207)),
                container(interactive)
                    .width(Fill)
                    .height(180)
                    .center_x(Fill)
                    .center_y(Fill),
                text(format!(
                    "{} click(s)  |  {:?}",
                    self.clicks,
                    self.button.state(&self.runtime)
                ))
                .size(13)
                .color(Color::from_rgb8(111, 226, 198))
            ]
            .spacing(18),
        )
        .width(Fill)
        .height(Fill)
        .padding(32)
        .style(|_| page_style())
        .into()
    }
}

fn resting() -> ButtonMotion {
    ButtonMotion {
        width: 190.0,
        height: 56.0,
        glow: 0.18,
        lift: 0.0,
        color: Color::from_rgb8(83, 100, 224),
    }
}

fn hovered() -> ButtonMotion {
    ButtonMotion {
        width: 206.0,
        height: 60.0,
        glow: 0.55,
        lift: 4.0,
        color: Color::from_rgb8(107, 91, 238),
    }
}

fn pressed() -> ButtonMotion {
    ButtonMotion {
        width: 182.0,
        height: 52.0,
        glow: 0.72,
        lift: 0.0,
        color: Color::from_rgb8(131, 78, 223),
    }
}

fn theme(_: &ButtonExample) -> Theme {
    Theme::TokyoNight
}

fn page_style() -> container::Style {
    container::Style::default()
        .background(Color::from_rgb8(18, 21, 34))
        .color(Color::WHITE)
}