aura-anim-iced 0.2.2

Iced integration for the aura typed animation runtime.
Documentation
//! Visual Spring example with live retargeting.

use std::time::Instant;

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

#[derive(Clone, Debug, Animatable)]
struct SpringValue {
    x: f32,
    size: f32,
}

struct SpringExample {
    runtime: MotionRuntime,
    value: Motion<SpringValue>,
    target_right: bool,
    bouncy: bool,
}

#[derive(Clone, Copy, Debug)]
enum Message {
    Frame(Instant),
    Retarget,
    ToggleDamping,
}

fn main() -> iced::Result {
    iced::application(
        SpringExample::new,
        SpringExample::update,
        SpringExample::view,
    )
    .title("Aura Anim - Spring")
    .theme(theme)
    .subscription(SpringExample::subscription)
    .window_size((820.0, 470.0))
    .run()
}

impl SpringExample {
    fn new() -> Self {
        let mut runtime = MotionRuntime::new();
        let value = runtime.motion(SpringValue {
            x: 28.0,
            size: 58.0,
        });
        let mut example = Self {
            runtime,
            value,
            target_right: false,
            bouncy: true,
        };
        example.retarget();
        example
    }

    fn retarget(&mut self) {
        self.target_right = !self.target_right;
        let current = self.value.value(&self.runtime);
        let target = if self.target_right {
            SpringValue {
                x: 620.0,
                size: 84.0,
            }
        } else {
            SpringValue {
                x: 28.0,
                size: 58.0,
            }
        };
        let damping = if self.bouncy { 11.0 } else { 30.0 };
        self.value.play(
            Spring::new(
                current,
                target,
                SpringConfig {
                    stiffness: 210.0,
                    damping,
                    mass: 1.0,
                    epsilon: 0.001,
                },
            ),
            &mut self.runtime,
        );
    }

    fn update(&mut self, message: Message) {
        match message {
            Message::Frame(now) => aura_anim_iced::frame(&mut self.runtime, now),
            Message::Retarget => self.retarget(),
            Message::ToggleDamping => {
                self.bouncy = !self.bouncy;
                self.retarget();
            }
        }
    }

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

    fn view(&self) -> Element<'_, Message> {
        let value = self.value.value_ref(&self.runtime);
        let color = if self.bouncy {
            Color::from_rgb8(255, 124, 159)
        } else {
            Color::from_rgb8(83, 216, 190)
        };
        let body = container("")
            .width(value.size)
            .height(value.size)
            .style(move |_| container::Style {
                background: Some(Background::Color(color)),
                border: Border::default().rounded(value.size / 2.0),
                shadow: Shadow {
                    color: Color::from_rgba(color.r, color.g, color.b, 0.55),
                    offset: Vector::ZERO,
                    blur_radius: 28.0,
                },
                ..container::Style::default()
            });
        let stage = container(row![
            container("").width(value.x),
            body,
            container("").width(Fill)
        ])
        .width(Fill)
        .height(142)
        .padding([28, 0])
        .style(|_| stage_style());

        container(
            column![
                text("Spring").size(34).color(Color::WHITE),
                text("Retarget while moving to preserve continuous, physically simulated motion.")
                    .size(14)
                    .color(Color::from_rgb8(174, 182, 211)),
                stage,
                row![
                    button("Retarget").on_press(Message::Retarget),
                    button(if self.bouncy {
                        "Use critical damping"
                    } else {
                        "Use bouncy damping"
                    })
                    .on_press(Message::ToggleDamping)
                ]
                .spacing(12),
                text(format!(
                    "{:?}  x: {:.1}  size: {:.1}  damping: {}",
                    self.value.state(&self.runtime),
                    value.x,
                    value.size,
                    if self.bouncy { 11 } else { 30 }
                ))
                .size(13)
                .color(color)
            ]
            .spacing(20),
        )
        .width(Fill)
        .height(Fill)
        .padding(34)
        .style(|_| page_style())
        .into()
    }
}

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

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

fn stage_style() -> container::Style {
    container::Style {
        background: Some(Background::Color(Color::from_rgb8(27, 32, 50))),
        border: Border::default()
            .rounded(8)
            .width(1)
            .color(Color::from_rgb8(61, 70, 99)),
        ..container::Style::default()
    }
}