├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── README_zh.md ├── derive ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── examples ├── animated-splash.rs ├── color-example.rs ├── make-animative.rs ├── map-example.rs └── size-example.rs ├── images ├── animated-splash.gif ├── color-example.gif └── size-example.gif └── src ├── core ├── animatable.rs ├── animation │ ├── boxed.rs │ ├── cache.rs │ ├── chain.rs │ ├── delay.rs │ ├── key_frame.rs │ ├── map.rs │ ├── mod.rs │ ├── parallel.rs │ ├── primitive.rs │ ├── repeat.rs │ ├── scale.rs │ ├── seek.rs │ ├── step.rs │ └── take.rs ├── clock.rs ├── easing.rs ├── mod.rs ├── options.rs ├── timeline.rs └── utils.rs ├── iced.rs ├── lib.rs └── local ├── animator.rs ├── mod.rs └── timeline.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test and Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest, macOS-latest] 18 | rust: [stable, beta] 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Install Rust toolchain 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: ${{ matrix.rust }} 25 | components: rustfmt 26 | override: true 27 | #- name: Install build deps 28 | # run: sudo apt-get install gperf libfreetype-dev libX11 libXcursor libXrandr libXi 29 | - name: Verify versions 30 | run: rustc --version && rustup --version && cargo --version 31 | - name: Cargo Build 32 | run: cargo build --verbose 33 | - name: Build examples 34 | run: cargo build --examples 35 | - name: Run tests 36 | run: cargo test --verbose 37 | - name: Check code style 38 | run: cargo fmt -- --check 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anim" 3 | version = "0.1.4" 4 | description = "A framework independent animation library for rust, works nicely with Iced and the others" 5 | readme = "README.md" 6 | license = "MIT" 7 | edition = "2018" 8 | homepage = "https://github.com/Joylei/anim-rs" 9 | repository = "https://github.com/Joylei/anim-rs.git" 10 | documentation = "https://docs.rs/crate/anim/" 11 | keywords = ["animation", "iced"] 12 | categories = ["visualization", "gui", "graphics"] 13 | authors = ["joylei "] 14 | 15 | [workspace] 16 | members = [".", "derive"] 17 | 18 | [features] 19 | default = ["local", "iced-backend", "derive"] 20 | local = ["parking_lot"] 21 | iced-backend = ["iced_native"] 22 | derive = ["anim-derive"] 23 | 24 | [dependencies] 25 | anim-derive = { path = "./derive", optional = true, version = "0.1" } 26 | dyn-clone = "1" 27 | iced_native = { version = "0.4", optional = true, default-features = false } 28 | parking_lot = { version = "0.11", optional = true } 29 | 30 | [dev-dependencies] 31 | iced = { version = "0.3", features = ["tokio", "canvas"] } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, Joylei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [EN](./README.md) | [中文](./README_zh.md) 2 | 3 | # anim 4 | [![Test and Build](https://github.com/joylei/anim-rs/workflows/Test%20and%20Build/badge.svg?branch=master)](https://github.com/joylei/anim-rs/actions?query=workflow%3A%22Test+and+Build%22) 5 | [![Documentation](https://docs.rs/anim/badge.svg)](https://docs.rs/anim) 6 | [![Crates.io](https://img.shields.io/crates/v/anim.svg)](https://crates.io/crates/anim) 7 | [![License](https://img.shields.io/crates/l/anim.svg)](https://github.com/joylei/anim-rs/blob/master/LICENSE) 8 | 9 | A framework independent animation library for rust, works nicely with [Iced](https://github.com/hecrj/iced) and the others. 10 | 11 | ## Showcase 12 | 13 |
14 | 15 | ![Color&Opacity Animation Example](./images/color-example.gif) 16 | 17 | ![Size Animation Example](./images/size-example.gif) 18 | 19 | ![Raindrop Splash Animation](./images/animated-splash.gif) 20 | 21 |
22 | 23 | ## How to install? 24 | 25 | Include `anim` in your `Cargo.toml` dependencies: 26 | 27 | ```toml 28 | [dependencies] 29 | anim = "0.1" 30 | ``` 31 | 32 | Note: `anim` turns on `iced-backend` feature by default. You need to disable default features if you do not work with `iced`. 33 | 34 | ```toml 35 | [dependencies] 36 | anim = { version="0.1", default-features = false } 37 | ``` 38 | 39 | ## How to use? 40 | 41 | There are 3 important concepts in `anim`: 42 | - `Animatable` 43 | Types derived from `Animatable` means that its values can be calculated based on timing progress, with which you can create `Animation` objects. 44 | 45 | - `Animation` 46 | The `Animation` generates values based on its timing progress. You can construct a big `Animation` from small ones. 47 | 48 | - `Timeline` 49 | With `Timeline` you can control your animations' lifetime. 50 | 51 | --- 52 | 53 | For simple scenarios, you just need `Options`. 54 | 55 | ```rust 56 | use anim::{Options, Timeline, Animation, easing}; 57 | ``` 58 | 59 | Then, build and start your animation: 60 | 61 | ```rust 62 | use std::time::Duration; 63 | use anim::{Options, Timeline, Animation, easing}; 64 | 65 | let mut timeline = Options::new(20,100).easing(easing::bounce_ease()) 66 | .duration(Duration::from_millis(300)) 67 | .begin_animation(); 68 | 69 | loop { 70 | let status = timeline.update(); 71 | if status.is_completed() { 72 | break; 73 | } 74 | println!("animated value: {}", timeline.value()); 75 | } 76 | ``` 77 | 78 | For complex scenarios, please look at [examples](./examples/) to gain some ideas. 79 | 80 | 81 | ## How to run the examples? 82 | 83 | ### Example #1: `color-example` 84 | 85 | This example shows you color animations: 86 | 87 | ```sh 88 | cargo run --release --example color-example 89 | ``` 90 | 91 | ### Example #2: `size-example` 92 | 93 | This example shows you size animations: 94 | 95 | ```sh 96 | cargo run --release --example size-example 97 | ``` 98 | 99 | ### Example #3: `animated-splash` 100 | 101 | This example shows you rain dop splash animations: 102 | 103 | ```sh 104 | cargo run --release --example animated-splash 105 | ``` 106 | 107 | ## License 108 | 109 | MIT 110 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | [EN](./README.md) | [中文](./README_zh.md) 2 | 3 | # anim 4 | [![Test and Build](https://github.com/joylei/anim-rs/workflows/Test%20and%20Build/badge.svg?branch=master)](https://github.com/joylei/anim-rs/actions?query=workflow%3A%22Test+and+Build%22) 5 | [![Documentation](https://docs.rs/anim/badge.svg)](https://docs.rs/anim) 6 | [![Crates.io](https://img.shields.io/crates/v/anim.svg)](https://crates.io/crates/anim) 7 | [![License](https://img.shields.io/crates/l/anim.svg)](https://github.com/joylei/anim-rs/blob/master/LICENSE) 8 | 9 | 纯Rust语言编写的动画库,可以配合[Iced](https://github.com/hecrj/iced)和其它GUI框架工作。 10 | 11 | ## 演示 12 | 13 |
14 | 15 | ![Color&Opacity Animation Example](./images/color-example.gif) 16 | 17 | ![Size Animation Example](./images/size-example.gif) 18 | 19 | ![Raindrop Splash Animation](./images/animated-splash.gif) 20 | 21 |
22 | 23 | ## 怎么安装 24 | 25 | 添加 `anim` 到 cargo 项目中: 26 | 27 | ```toml 28 | [dependencies] 29 | anim = "0.1" 30 | ``` 31 | 32 | 注意: `anim` 默认打开了 `iced-backend` 特性支持。你可以关掉这个特性: 33 | 34 | ```toml 35 | [dependencies] 36 | anim = { version="0.1", default-features = false } 37 | ``` 38 | 39 | ## 怎么使用 40 | 41 | `anim` 有三个重要的概念: 42 | - `Animatable` 43 | 任何类型实现了 `Animatable`,那么意味着可以根据时间进度计算它的值。通过它可以创建`Animation`对象。 44 | 45 | - `Animation` 46 | `Animation` 根据时间进度产生新的值。可以通过组合 `Animation` 构建更大的`Animation` 对象。 47 | 48 | - `Timeline` 49 | 通过`Timeline`控制动画的生命周期。 50 | 51 | --- 52 | 53 | 简单场景,只需要使用 `Options`。 54 | 55 | ```rust 56 | use anim::{Options, Timeline, Animation, easing}; 57 | ``` 58 | 59 | 然后创建和运行动画: 60 | 61 | ```rust 62 | use std::time::Duration; 63 | use anim::{Options, Timeline, Animation, easing}; 64 | 65 | let mut timeline = Options::new(20,100).easing(easing::bounce_ease()) 66 | .duration(Duration::from_millis(300)) 67 | .begin_animation(); 68 | 69 | loop { 70 | let status = timeline.update(); 71 | if status.is_completed() { 72 | break; 73 | } 74 | println!("animated value: {}", timeline.value()); 75 | } 76 | ``` 77 | 78 | 更复杂的场景可以参考[示例代码](./examples/)。 79 | 80 | 81 | ## 示例 82 | 83 | ### 示例 #1: `color-example` 84 | 85 | 这个示例演示颜色的动画: 86 | 87 | ```sh 88 | cargo run --release --example color-example 89 | ``` 90 | 91 | ### 示例 #2: `size-example` 92 | 93 | 这个示例演示尺寸的动画: 94 | 95 | ```sh 96 | cargo run --release --example size-example 97 | ``` 98 | 99 | ### 示例 #3: `animated-splash` 100 | 101 | 这个示例演示雨水溅落的动画: 102 | 103 | ```sh 104 | cargo run --release --example animated-splash 105 | ``` 106 | 107 | ## 开源协议 108 | 109 | MIT 110 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anim-derive" 3 | version = "0.1.0" 4 | description = "macros for anim" 5 | readme = "README.md" 6 | license = "MIT" 7 | edition = "2018" 8 | homepage = "https://github.com/Joylei/anim-rs" 9 | repository = "https://github.com/Joylei/anim-rs.git" 10 | documentation = "https://docs.rs/crate/anim/" 11 | keywords = ["animation", "iced"] 12 | categories = ["visualization", "gui", "graphics"] 13 | authors = ["joylei "] 14 | 15 | [lib] 16 | name = "anim_derive" 17 | path = "src/lib.rs" 18 | proc-macro = true 19 | 20 | [dependencies] 21 | proc-macro-crate = "1" 22 | proc-macro2 = "1" 23 | proc-quote = "0.4" 24 | syn = { version = "1", features = ["full", "parsing"] } -------------------------------------------------------------------------------- /derive/README.md: -------------------------------------------------------------------------------- 1 | # anim-derive 2 | macros for `anim` 3 | 4 | ## License 5 | MIT -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro::TokenStream; 4 | use proc_macro2::Span; 5 | use proc_macro_crate::{crate_name, FoundCrate}; 6 | use proc_quote::quote; 7 | use syn::parse_macro_input; 8 | use syn::DeriveInput; 9 | use syn::{Data, DataStruct, Fields, Ident, Type}; 10 | 11 | /// the macro derives `anim::Animatable` for you automatically. 12 | #[proc_macro_derive(Animatable, attributes(tag))] 13 | pub fn animatable_derive(input: TokenStream) -> TokenStream { 14 | let input = parse_macro_input!(input as DeriveInput); 15 | expand_derive(input) 16 | .unwrap_or_else(syn::Error::into_compile_error) 17 | .into() 18 | } 19 | 20 | fn expand_derive(input: DeriveInput) -> syn::Result { 21 | let anim = get_crate()?; 22 | let fields = get_fields(input.data) 23 | .unwrap() 24 | .iter() 25 | .map(|(field_name, _)| { 26 | Ok(quote! { 27 | res.#field_name = #anim::Animatable::animate(&self.#field_name,&to.#field_name, time); 28 | }) 29 | }) 30 | .collect::>()?; 31 | let st_name = input.ident; 32 | 33 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 34 | Ok(quote! { 35 | impl #impl_generics #anim::Animatable for #st_name #ty_generics #where_clause 36 | { 37 | #[inline] 38 | fn animate(&self, to: &Self, time: f64) -> Self{ 39 | let mut res = self.clone(); 40 | #fields 41 | res 42 | } 43 | } 44 | }) 45 | } 46 | 47 | fn get_crate() -> syn::Result { 48 | let anim = match crate_name("anim") { 49 | Ok(found) => match found { 50 | FoundCrate::Itself => Ident::new("crate", Span::call_site()), 51 | FoundCrate::Name(name) => Ident::new(&name, Span::call_site()), 52 | }, 53 | Err(_) => Ident::new("crate", Span::call_site()), 54 | }; 55 | Ok(anim) 56 | } 57 | 58 | fn get_fields(data: Data) -> syn::Result> { 59 | let fields = match data { 60 | Data::Struct(DataStruct { 61 | fields: Fields::Named(fields), 62 | .. 63 | }) => fields.named, 64 | _ => panic!("this derive macro only works on structs with named fields"), 65 | }; 66 | let items = fields 67 | .into_iter() 68 | .map(|f| { 69 | let field_name = f.ident.unwrap(); 70 | let ty = f.ty; 71 | (field_name, ty) 72 | }) 73 | .collect(); 74 | 75 | Ok(items) 76 | } 77 | -------------------------------------------------------------------------------- /examples/animated-splash.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | // https://www.flutterclutter.dev/flutter/tutorials/beautiful-animated-splash-screen/2020/1108/ 8 | use anim::{easing, timeline::Status, Animatable, Animation, Options, Timeline}; 9 | use iced::{ 10 | canvas::{self, Cursor, Geometry}, 11 | Align, Application, Button, Canvas, Clipboard, Color, Column, Command, Container, Element, 12 | HorizontalAlignment, Length, Point, Rectangle, Subscription, Text, VerticalAlignment, 13 | }; 14 | use iced_native::button; 15 | use std::time::Duration; 16 | 17 | #[cfg(windows)] 18 | #[link(name = "Winmm")] 19 | extern "system" { 20 | fn timeBeginPeriod(uPeriod: u32) -> i32; 21 | } 22 | 23 | fn main() { 24 | #[cfg(windows)] 25 | unsafe { 26 | timeBeginPeriod(1) 27 | }; 28 | State::run(Default::default()).unwrap(); 29 | } 30 | 31 | #[derive(Debug, Clone)] 32 | enum Message { 33 | /// animation frame 34 | Tick, 35 | Click1, 36 | Click2, 37 | Click3, 38 | } 39 | 40 | struct State { 41 | timeline: Timeline, 42 | painter: AnimationPainter, 43 | btn_run1: button::State, 44 | btn_run2: button::State, 45 | btn_run3: button::State, 46 | } 47 | 48 | impl Application for State { 49 | type Executor = iced::executor::Default; 50 | type Flags = (); 51 | type Message = self::Message; 52 | 53 | fn new(_flags: ()) -> (Self, Command) { 54 | let app = Self { 55 | timeline: raindrop_animation2().begin_animation(), 56 | painter: Default::default(), 57 | btn_run1: Default::default(), 58 | btn_run2: Default::default(), 59 | btn_run3: Default::default(), 60 | }; 61 | (app, Command::none()) 62 | } 63 | 64 | fn title(&self) -> String { 65 | "Raindrop splash example".to_owned() 66 | } 67 | 68 | fn update(&mut self, message: Self::Message, _clipboard: &mut Clipboard) -> Command { 69 | match message { 70 | Message::Tick => { 71 | self.timeline.update(); 72 | self.painter.model = self.timeline.value(); 73 | self.painter.cache.clear(); 74 | } 75 | Message::Click1 => { 76 | self.timeline = raindrop_animation().begin_animation(); 77 | } 78 | Message::Click2 => { 79 | self.timeline = raindrop_animation2().begin_animation(); 80 | } 81 | Message::Click3 => { 82 | self.timeline = raindrop_animation3().begin_animation(); 83 | } 84 | } 85 | Command::none() 86 | } 87 | 88 | fn view(&mut self) -> iced::Element<'_, Self::Message> { 89 | let status = self.timeline.status(); 90 | let content: Element = if status == Status::Completed { 91 | Column::new() 92 | .spacing(10) 93 | .width(Length::Shrink) 94 | .height(Length::Shrink) 95 | .align_items(Align::Center) 96 | .push( 97 | Text::new("Animation completed") 98 | .horizontal_alignment(HorizontalAlignment::Center) 99 | .vertical_alignment(VerticalAlignment::Center) 100 | .width(Length::Shrink) 101 | .height(Length::Shrink), 102 | ) 103 | .push( 104 | Button::new( 105 | &mut self.btn_run1, 106 | Text::new("Run Again with method 1?") 107 | .horizontal_alignment(HorizontalAlignment::Center) 108 | .vertical_alignment(VerticalAlignment::Center), 109 | ) 110 | .on_press(Message::Click1), 111 | ) 112 | .push( 113 | Button::new( 114 | &mut self.btn_run2, 115 | Text::new("Run Again with method 2?") 116 | .horizontal_alignment(HorizontalAlignment::Center) 117 | .vertical_alignment(VerticalAlignment::Center), 118 | ) 119 | .on_press(Message::Click2), 120 | ) 121 | .push( 122 | Button::new( 123 | &mut self.btn_run3, 124 | Text::new("Run Again with method 3?") 125 | .horizontal_alignment(HorizontalAlignment::Center) 126 | .vertical_alignment(VerticalAlignment::Center), 127 | ) 128 | .on_press(Message::Click3), 129 | ) 130 | .into() 131 | } else { 132 | Canvas::new(&mut self.painter) 133 | .width(Length::Fill) 134 | .height(Length::Fill) 135 | .into() 136 | }; 137 | Container::new(content) 138 | .align_x(iced::Align::Center) 139 | .align_y(iced::Align::Center) 140 | .width(Length::Fill) 141 | .height(Length::Fill) 142 | .style(style::Container) 143 | .into() 144 | } 145 | 146 | fn subscription(&self) -> Subscription { 147 | let status = self.timeline.status(); 148 | if status.is_animating() { 149 | const FPS: f32 = 60.0; 150 | iced::time::every(Duration::from_secs_f32(1.0 / FPS)).map(|_tick| Message::Tick) 151 | } else { 152 | iced::Subscription::none() 153 | } 154 | } 155 | } 156 | 157 | #[derive(Default)] 158 | struct AnimationPainter { 159 | model: Raindrop, 160 | cache: canvas::Cache, 161 | } 162 | 163 | impl canvas::Program for AnimationPainter { 164 | fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec { 165 | let item = self.cache.draw(bounds.size(), |frame| { 166 | if self.model.drop_visible { 167 | //use circle here instead of raindrop 168 | let center = Point::new(bounds.width / 2.0, bounds.height * self.model.drop_pos); 169 | let path = canvas::Path::circle(center, self.model.drop_size as f32); 170 | frame.fill(&path, Color::WHITE); 171 | } else { 172 | let center = frame.center(); 173 | let max_radius = frame.width().max(frame.height()); 174 | let radius = max_radius * self.model.hole_size; 175 | //out circle 176 | let path = canvas::Path::circle(center, radius); 177 | frame.fill( 178 | &path, 179 | Color { 180 | a: 0.6, 181 | ..Color::WHITE 182 | }, 183 | ); 184 | //inner circle 185 | let path = canvas::Path::circle(center, radius / 2.0); 186 | frame.fill( 187 | &path, 188 | Color { 189 | a: 0.1, 190 | ..Color::WHITE 191 | }, 192 | ); 193 | } 194 | }); 195 | 196 | vec![item] 197 | } 198 | } 199 | 200 | #[derive(Clone)] 201 | struct Raindrop { 202 | drop_size: f32, 203 | drop_pos: f32, 204 | drop_visible: bool, 205 | hole_size: f32, 206 | } 207 | 208 | impl Default for Raindrop { 209 | fn default() -> Self { 210 | Self { 211 | drop_size: 0.0, 212 | drop_pos: 0.0, 213 | drop_visible: true, 214 | hole_size: 0.0, 215 | } 216 | } 217 | } 218 | 219 | const MAX_DROP_SIZE: f32 = 20.0; 220 | const MAX_DROP_POS: f32 = 0.5; 221 | const MAX_HOLE_SIZE: f32 = 1.0; 222 | 223 | // staged animations, demo of chained animations 224 | fn raindrop_animation() -> impl Animation { 225 | let duration = Duration::from_millis(3000); 226 | let stage1 = Options::new(0.0, MAX_DROP_SIZE) 227 | .duration(duration.mul_f64(0.2)) 228 | .build() 229 | .map(|size| Raindrop { 230 | drop_size: size, 231 | drop_visible: true, 232 | drop_pos: 0.0, 233 | hole_size: 0.0, 234 | }); 235 | let stage2 = Options::new(0.0, MAX_DROP_POS) 236 | .duration(duration.mul_f64(0.3)) 237 | .easing(easing::quad_ease()) 238 | .build() 239 | .map(move |pos| Raindrop { 240 | drop_size: MAX_DROP_SIZE, 241 | drop_visible: true, 242 | drop_pos: pos, 243 | hole_size: 0.0, 244 | }); 245 | let stage3 = Options::new(0.0, MAX_HOLE_SIZE) 246 | .duration(duration.mul_f64(0.5)) 247 | .easing(easing::quad_ease()) 248 | .build() 249 | .map(move |size| Raindrop { 250 | drop_size: MAX_DROP_SIZE, 251 | drop_visible: false, 252 | drop_pos: MAX_DROP_POS, 253 | hole_size: size, 254 | }); 255 | stage1.chain(stage2).chain(stage3) 256 | } 257 | 258 | //demo of delay and parallel 259 | fn raindrop_animation2() -> impl Animation { 260 | let duration = Duration::from_millis(3000); 261 | let drop_size = Options::new(0.0, MAX_DROP_SIZE) 262 | .duration(duration.mul_f64(0.2)) 263 | .build(); 264 | 265 | let drop_pos = Options::new(0.0, MAX_DROP_POS) 266 | .duration(duration.mul_f64(0.3)) 267 | .easing(easing::quad_ease()) 268 | .build() 269 | .delay(duration.mul_f64(0.2)); 270 | 271 | //linear 272 | let drop_visible = anim::builder::linear(duration).map(|t| if t <= 0.5 { true } else { false }); 273 | 274 | let hole_size = Options::new(0.0, MAX_HOLE_SIZE) 275 | .duration(duration.mul_f64(0.5)) 276 | .easing(easing::quad_ease()) 277 | .build() 278 | .delay(duration.mul_f64(0.5)); 279 | 280 | drop_size 281 | .zip(drop_pos) 282 | .zip(drop_visible) 283 | .zip(hole_size) 284 | .map( 285 | |(((drop_size, drop_pos), drop_visible), hole_size)| Raindrop { 286 | drop_size, 287 | drop_pos, 288 | drop_visible, 289 | hole_size, 290 | }, 291 | ) 292 | } 293 | 294 | /// demo key-frames, requires Raindrop animatable 295 | impl Animatable for Raindrop { 296 | fn animate(&self, to: &Self, time: f64) -> Self { 297 | let drop_size = self.drop_size.animate(&to.drop_size, time); 298 | let drop_pos = self.drop_pos.animate(&to.drop_pos, time); 299 | let drop_visible = self.drop_visible.animate(&to.drop_visible, time); 300 | let hole_size = self.hole_size.animate(&to.hole_size, time); 301 | Self { 302 | drop_size, 303 | drop_pos, 304 | drop_visible, 305 | hole_size, 306 | } 307 | } 308 | } 309 | fn raindrop_animation3() -> impl Animation { 310 | use anim::KeyFrame; 311 | let duration = Duration::from_millis(3000); 312 | anim::builder::key_frames(vec![ 313 | KeyFrame::default(), 314 | KeyFrame::new(Raindrop { 315 | drop_size: MAX_DROP_SIZE, 316 | ..Default::default() 317 | }) 318 | .by_percent(0.2), 319 | KeyFrame::new(Raindrop { 320 | drop_size: MAX_DROP_SIZE, 321 | drop_pos: MAX_DROP_POS, 322 | drop_visible: false, 323 | ..Default::default() 324 | }) 325 | .by_percent(0.5) 326 | .easing(easing::quad_ease()), 327 | KeyFrame::new(Raindrop { 328 | drop_size: MAX_DROP_SIZE, 329 | drop_pos: MAX_DROP_POS, 330 | drop_visible: false, 331 | hole_size: MAX_HOLE_SIZE, 332 | }) 333 | .by_duration(duration) 334 | .easing(easing::quad_ease()), 335 | ]) 336 | } 337 | 338 | mod style { 339 | use iced::{container, Color}; 340 | 341 | pub struct Container; 342 | impl container::StyleSheet for Container { 343 | fn style(&self) -> container::Style { 344 | container::Style { 345 | background: Color::from_rgb8(255, 0, 0).into(), 346 | text_color: Color::WHITE.into(), 347 | ..Default::default() 348 | } 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /examples/color-example.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anim::{Options, Timeline}; 8 | use iced::{ 9 | button, Align, Application, Button, Clipboard, Color, Command, Container, HorizontalAlignment, 10 | Length, Row, Subscription, Text, VerticalAlignment, 11 | }; 12 | use std::time::Duration; 13 | 14 | fn main() { 15 | State::run(Default::default()).unwrap(); 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | enum Message { 20 | Idle, 21 | Tick, 22 | } 23 | 24 | struct State { 25 | btn_color: button::State, 26 | btn_opacity: button::State, 27 | timeline: Timeline<(Color, Color)>, 28 | } 29 | 30 | impl Application for State { 31 | type Executor = iced::executor::Default; 32 | type Flags = (); 33 | type Message = self::Message; 34 | 35 | fn new(_flags: ()) -> (Self, Command) { 36 | let from = (Color::from_rgb8(0, 0, 255), Color::from_rgba8(0, 0, 0, 0.1)); 37 | let to = ( 38 | Color::from_rgb8(255, 0, 255), 39 | Color::from_rgba8(0, 0, 0, 1.0), 40 | ); 41 | let app = Self { 42 | btn_color: Default::default(), 43 | btn_opacity: Default::default(), 44 | timeline: Options::new(from, to) 45 | .duration(Duration::from_secs(2)) 46 | .auto_reverse(true) 47 | .forever() 48 | .begin_animation(), 49 | }; 50 | (app, Command::none()) 51 | } 52 | 53 | fn title(&self) -> String { 54 | "Color animation example".to_owned() 55 | } 56 | 57 | fn update(&mut self, message: Self::Message, _clipboard: &mut Clipboard) -> Command { 58 | match message { 59 | Message::Tick => { 60 | self.timeline.update(); 61 | } 62 | _ => {} 63 | } 64 | Command::none() 65 | } 66 | 67 | fn view(&mut self) -> iced::Element<'_, Self::Message> { 68 | let (btn_color, btn_opacity) = self.timeline.value(); 69 | let btn_color = Button::new( 70 | &mut self.btn_color, 71 | Text::new("color changes") 72 | .horizontal_alignment(HorizontalAlignment::Center) 73 | .vertical_alignment(VerticalAlignment::Center), 74 | ) 75 | .style(style::Button(btn_color)) 76 | .padding(20) 77 | .on_press(Message::Idle); 78 | 79 | let btn_opacity = Button::new( 80 | &mut self.btn_opacity, 81 | Text::new("opacity changes") 82 | .horizontal_alignment(HorizontalAlignment::Center) 83 | .vertical_alignment(VerticalAlignment::Center), 84 | ) 85 | .style(style::Button(btn_opacity)) 86 | .padding(20) 87 | .on_press(Message::Idle); 88 | 89 | let row = Row::new() 90 | .padding(10) 91 | .align_items(Align::Center) 92 | .push(btn_color) 93 | .push(btn_opacity); 94 | 95 | Container::new(row) 96 | .align_x(iced::Align::Center) 97 | .align_y(iced::Align::Center) 98 | .width(Length::Fill) 99 | .height(Length::Fill) 100 | .into() 101 | } 102 | 103 | fn subscription(&self) -> Subscription { 104 | const FPS: f32 = 60.0; 105 | iced::time::every(Duration::from_secs_f32(1.0 / FPS)).map(|_tick| Message::Tick) 106 | } 107 | } 108 | 109 | mod style { 110 | use iced::{button, Color}; 111 | 112 | pub struct Button(pub Color); 113 | impl button::StyleSheet for Button { 114 | fn active(&self) -> button::Style { 115 | button::Style { 116 | background: Some(self.0.into()), 117 | text_color: Color::WHITE, 118 | ..Default::default() 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /examples/make-animative.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anim::{timeline::Status, Animatable, Options, Timeline}; 8 | use std::time::Duration; 9 | 10 | /// make it animatable, do not forget to derive Clone 11 | #[derive(Clone, Debug, Animatable)] 12 | struct MyModel { 13 | a: f32, //animatable 14 | b: i64, //animatable 15 | } 16 | 17 | // once it's animatable, you can use it with anim::timeline::Options; 18 | 19 | fn main() { 20 | let from = MyModel { a: 0.0, b: 32 }; 21 | let to = MyModel { a: 100.0, b: 100 }; 22 | let mut timeline: Timeline<_> = Options::new(from, to) 23 | .duration(Duration::from_secs(2)) 24 | .times(1.5) 25 | .into(); 26 | 27 | println!("start animation"); 28 | timeline.begin(); 29 | 30 | loop { 31 | let status = timeline.update(); 32 | if status == Status::Completed { 33 | break; 34 | } 35 | let value = timeline.value(); 36 | println!("animated: {:?}", value); 37 | } 38 | let value = timeline.value(); 39 | println!("animated: {:?}", value); 40 | } 41 | -------------------------------------------------------------------------------- /examples/map-example.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anim::{ 8 | easing, 9 | timeline::{self, Status}, 10 | Animation, Options, Timeline, 11 | }; 12 | use iced::{ 13 | button, Application, Button, Clipboard, Command, Container, HorizontalAlignment, Length, Size, 14 | Subscription, Text, VerticalAlignment, 15 | }; 16 | use std::time::Duration; 17 | 18 | fn main() { 19 | State::run(Default::default()).unwrap(); 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | enum Message { 24 | Idle, 25 | /// animation frame 26 | Tick, 27 | } 28 | 29 | struct State { 30 | btn_test: button::State, 31 | timeline: Timeline<(Length, Length)>, 32 | } 33 | 34 | impl Application for State { 35 | type Executor = iced::executor::Default; 36 | type Flags = (); 37 | type Message = self::Message; 38 | 39 | fn new(_flags: ()) -> (Self, Command) { 40 | let app = Self { 41 | btn_test: Default::default(), 42 | timeline: Options::new(Size::new(130.0, 30.0), Size::new(500.0, 200.0)) 43 | .duration(Duration::from_secs(2)) 44 | .auto_reverse(true) 45 | .easing(easing::bounce_ease()) 46 | .times(3.0) 47 | .build() 48 | .map(|size| { 49 | ( 50 | Length::Units(size.width as u16), 51 | Length::Units(size.height as u16), 52 | ) 53 | }) 54 | .into(), 55 | }; 56 | (app, Command::none()) 57 | } 58 | 59 | fn title(&self) -> String { 60 | "Map example".to_owned() 61 | } 62 | 63 | fn update(&mut self, message: Self::Message, _clipboard: &mut Clipboard) -> Command { 64 | match message { 65 | Message::Tick => { 66 | let status = self.timeline.status(); 67 | match status { 68 | timeline::Status::Idle => { 69 | self.timeline.begin(); 70 | } 71 | _ => {} 72 | } 73 | self.timeline.update(); 74 | } 75 | _ => {} 76 | } 77 | Command::none() 78 | } 79 | 80 | fn view(&mut self) -> iced::Element<'_, Self::Message> { 81 | let size = self.timeline.value(); 82 | let status = self.timeline.status(); 83 | let button = Button::new( 84 | &mut self.btn_test, 85 | Text::new(if status == Status::Completed { 86 | "stopped" 87 | } else { 88 | "size changes" 89 | }) 90 | .horizontal_alignment(HorizontalAlignment::Center) 91 | .vertical_alignment(VerticalAlignment::Center) 92 | .width(Length::Fill) 93 | .height(Length::Fill), 94 | ) 95 | .style(style::Button) 96 | .width(size.0) 97 | .height(size.1) 98 | .on_press(Message::Idle); 99 | 100 | Container::new(button) 101 | .align_x(iced::Align::Center) 102 | .align_y(iced::Align::Center) 103 | .width(Length::Fill) 104 | .height(Length::Fill) 105 | .into() 106 | } 107 | 108 | fn subscription(&self) -> Subscription { 109 | const FPS: f32 = 60.0; 110 | iced::time::every(Duration::from_secs_f32(1.0 / FPS)).map(|_tick| Message::Tick) 111 | } 112 | } 113 | 114 | mod style { 115 | use iced::{button, Color}; 116 | 117 | pub struct Button; 118 | impl button::StyleSheet for Button { 119 | fn active(&self) -> button::Style { 120 | button::Style { 121 | background: Some(Color::BLACK.into()), 122 | text_color: Color::WHITE, 123 | ..Default::default() 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /examples/size-example.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use anim::{easing, timeline::Status, Options, Timeline}; 8 | use iced::{ 9 | button, Align, Application, Button, Clipboard, Column, Command, Container, HorizontalAlignment, 10 | Length, Row, Size, Subscription, Text, VerticalAlignment, 11 | }; 12 | use std::time::Duration; 13 | 14 | fn main() { 15 | State::run(Default::default()).unwrap(); 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | enum Message { 20 | Idle, 21 | /// animation frame 22 | Tick, 23 | Start, 24 | Pause, 25 | Stop, 26 | } 27 | 28 | struct State { 29 | btn_start: button::State, 30 | btn_pause: button::State, 31 | btn_stop: button::State, 32 | btn_test: button::State, 33 | timeline: Timeline, 34 | } 35 | 36 | impl Application for State { 37 | type Executor = iced::executor::Default; 38 | type Flags = (); 39 | type Message = self::Message; 40 | 41 | fn new(_flags: ()) -> (Self, Command) { 42 | let mut timeline: Timeline<_> = 43 | Options::new(Size::new(130.0, 30.0), Size::new(500.0, 200.0)) 44 | .duration(Duration::from_secs(2)) 45 | .auto_reverse(true) 46 | .easing(easing::bounce_ease()) 47 | .forever() 48 | .into(); 49 | timeline.begin(); 50 | let app = Self { 51 | btn_start: Default::default(), 52 | btn_pause: Default::default(), 53 | btn_stop: Default::default(), 54 | btn_test: Default::default(), 55 | timeline, 56 | }; 57 | (app, Command::none()) 58 | } 59 | 60 | fn title(&self) -> String { 61 | "Size animation example".to_owned() 62 | } 63 | 64 | fn update(&mut self, message: Self::Message, _clipboard: &mut Clipboard) -> Command { 65 | match message { 66 | Message::Tick => { 67 | self.timeline.update(); 68 | } 69 | Message::Start => { 70 | let status = self.timeline.status(); 71 | if status == Status::Paused { 72 | self.timeline.resume(); 73 | } else { 74 | self.timeline.begin(); 75 | } 76 | } 77 | Message::Pause => { 78 | self.timeline.pause(); 79 | } 80 | Message::Stop => { 81 | self.timeline.stop(); 82 | } 83 | _ => {} 84 | } 85 | Command::none() 86 | } 87 | 88 | fn view(&mut self) -> iced::Element<'_, Self::Message> { 89 | let status = self.timeline.status(); 90 | let size = self.timeline.value(); 91 | //eprintln!("{:?}", status); 92 | let controls = Row::new() 93 | .spacing(10) 94 | .push(button_optional( 95 | &mut self.btn_start, 96 | "Start", 97 | if status != Status::Animating { 98 | Message::Start.into() 99 | } else { 100 | None 101 | }, 102 | )) 103 | .push(button_optional( 104 | &mut self.btn_pause, 105 | "Pause", 106 | if status == Status::Animating { 107 | Message::Pause.into() 108 | } else { 109 | None 110 | }, 111 | )) 112 | .push(button_optional( 113 | &mut self.btn_stop, 114 | "Stop", 115 | if status == Status::Animating || status == Status::Paused { 116 | Message::Stop.into() 117 | } else { 118 | None 119 | }, 120 | )); 121 | 122 | let content = Column::new() 123 | .spacing(20) 124 | .align_items(Align::Start) 125 | .width(Length::Fill) 126 | .height(Length::Fill) 127 | .push(controls) 128 | .push( 129 | Container::new( 130 | Button::new( 131 | &mut self.btn_test, 132 | Text::new("size changes") 133 | .horizontal_alignment(HorizontalAlignment::Center) 134 | .vertical_alignment(VerticalAlignment::Center) 135 | .width(Length::Fill) 136 | .height(Length::Fill), 137 | ) 138 | .style(style::Button) 139 | .width(Length::Units(size.width as u16)) 140 | .height(Length::Units(size.height as u16)) 141 | .on_press(Message::Idle), 142 | ) 143 | .align_x(Align::Center) 144 | .align_y(Align::Center) 145 | .width(Length::Fill) 146 | .height(Length::Fill), 147 | ); 148 | 149 | Container::new(content) 150 | .align_x(iced::Align::Center) 151 | .align_y(iced::Align::Start) 152 | .padding(10) 153 | .width(Length::Fill) 154 | .height(Length::Fill) 155 | .into() 156 | } 157 | 158 | fn subscription(&self) -> Subscription { 159 | let status = self.timeline.status(); 160 | if status.is_animating() { 161 | const FPS: f32 = 60.0; 162 | iced::time::every(Duration::from_secs_f32(1.0 / FPS)).map(|_tick| Message::Tick) 163 | } else { 164 | iced::Subscription::none() 165 | } 166 | } 167 | } 168 | 169 | fn button_optional<'a, Message: Clone>( 170 | state: &'a mut button::State, 171 | label: &str, 172 | on_press: Option, 173 | ) -> Button<'a, Message> { 174 | let mut btn = Button::new(state, Text::new(label)); 175 | if let Some(on_press) = on_press { 176 | btn = btn.on_press(on_press); 177 | } 178 | btn 179 | } 180 | 181 | mod style { 182 | use iced::{button, Color}; 183 | 184 | pub struct Button; 185 | impl button::StyleSheet for Button { 186 | fn active(&self) -> button::Style { 187 | button::Style { 188 | background: Some(Color::BLACK.into()), 189 | text_color: Color::WHITE, 190 | ..Default::default() 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /images/animated-splash.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joylei/anim-rs/6be3e0d42959104fb6d717c8ca77b7006e629d36/images/animated-splash.gif -------------------------------------------------------------------------------- /images/color-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joylei/anim-rs/6be3e0d42959104fb6d717c8ca77b7006e629d36/images/color-example.gif -------------------------------------------------------------------------------- /images/size-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joylei/anim-rs/6be3e0d42959104fb6d717c8ca77b7006e629d36/images/size-example.gif -------------------------------------------------------------------------------- /src/core/animatable.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | #![allow(non_snake_case)] 8 | 9 | use std::marker::PhantomData; 10 | 11 | /// generates output values based on its timing progress 12 | /// 13 | /// see [`crate::Timeline`] 14 | /// 15 | /// Types derives [`Animatable`]: 16 | /// - `bool` 17 | /// - `i8` 18 | /// - `u8` 19 | /// - `i16` 20 | /// - `u16` 21 | /// - `i32` 22 | /// - `u32` 23 | /// - `i64` 24 | /// - `u64` 25 | /// - `f32` 26 | /// - `f64` 27 | /// - `i128` 28 | /// - `u168` 29 | /// - `Unit` 30 | /// - `Tuple` 31 | /// - `char` 32 | /// - `Option` where `T:Animatable` 33 | /// - `PhantomData` 34 | /// - `[T;N]` where `T:Animatable` 35 | pub trait Animatable: Sized + Clone { 36 | /// generates output values based on its timing progress 37 | fn animate(&self, to: &Self, time: f64) -> Self; 38 | } 39 | 40 | //-------- primitives ----------- 41 | macro_rules! impl_primitive { 42 | ($ty:ident) => { 43 | impl Animatable for $ty { 44 | #[inline] 45 | fn animate(&self, to: &Self, time: f64) -> Self { 46 | if time == 0.0 { 47 | return *self; 48 | } 49 | if (1.0 - time).abs() < f64::EPSILON { 50 | return *to; 51 | } 52 | if self == to { 53 | return *self; 54 | } 55 | crate::utils::check_time(time); 56 | let v = (*self as f64) * (1.0 - time) + (*to as f64) * time; 57 | if *to >= *self { 58 | (v + 0.5) as Self 59 | } else { 60 | (v - 0.5) as Self 61 | } 62 | } 63 | } 64 | }; 65 | ($ty:ident, float) => { 66 | impl Animatable for $ty { 67 | #[inline] 68 | fn animate(&self, to: &Self, time: f64) -> Self { 69 | if time == 0.0 { 70 | return *self; 71 | } 72 | if (1.0 - time).abs() < f64::EPSILON { 73 | return *to; 74 | } 75 | if (self - to).abs() < $ty::EPSILON { 76 | return *self; 77 | } 78 | crate::utils::check_time(time); 79 | // from + (to-from) * time 80 | let v = (*self as f64) * (1.0 - time) + (*to as f64) * time; 81 | v as Self 82 | } 83 | } 84 | }; 85 | } 86 | 87 | impl_primitive!(u8); 88 | impl_primitive!(u16); 89 | impl_primitive!(u32); 90 | impl_primitive!(u64); 91 | impl_primitive!(u128); 92 | impl_primitive!(usize); 93 | impl_primitive!(i8); 94 | impl_primitive!(i16); 95 | impl_primitive!(i32); 96 | impl_primitive!(i64); 97 | impl_primitive!(i128); 98 | impl_primitive!(isize); 99 | impl_primitive!(f32, float); 100 | impl_primitive!(f64, float); 101 | 102 | impl Animatable for bool { 103 | #[inline] 104 | fn animate(&self, to: &Self, time: f64) -> Self { 105 | if time < 1.0 { 106 | *self 107 | } else { 108 | *to 109 | } 110 | } 111 | } 112 | 113 | impl Animatable for char { 114 | #[inline] 115 | fn animate(&self, to: &Self, time: f64) -> Self { 116 | if self == to { 117 | return *self; 118 | } 119 | 120 | let from_idx = *self as u32; 121 | let to_idx = *to as u32; 122 | let idx = from_idx.animate(&to_idx, time); 123 | let n = if from_idx > to_idx { 124 | from_idx - idx 125 | } else { 126 | idx - from_idx 127 | }; 128 | let mut rng = *self..=*to; 129 | match rng.nth(n as usize) { 130 | Some(c) => c, 131 | None => *self, 132 | } 133 | } 134 | } 135 | 136 | impl Animatable for () { 137 | #[inline] 138 | fn animate(&self, _to: &Self, _time: f64) -> Self {} 139 | } 140 | 141 | impl Animatable for PhantomData { 142 | #[inline] 143 | fn animate(&self, _to: &Self, _time: f64) -> Self { 144 | Default::default() 145 | } 146 | } 147 | 148 | impl Animatable for Option { 149 | #[inline] 150 | fn animate(&self, to: &Self, time: f64) -> Self { 151 | match (self, to) { 152 | (Some(a), Some(b)) => Some(a.animate(b, time)), 153 | _ => None, 154 | } 155 | } 156 | } 157 | 158 | impl Animatable for [T; N] { 159 | #[inline] 160 | fn animate(&self, to: &Self, time: f64) -> Self { 161 | let mut res = self.clone(); 162 | self.iter() 163 | .zip(to.iter()) 164 | .zip(res.iter_mut()) 165 | .for_each(|((a, b), c)| *c = a.animate(b, time)); 166 | res 167 | } 168 | } 169 | 170 | //-------- tuples ----------- 171 | 172 | macro_rules! impl_tuple { 173 | ($($n:tt $name:ident)+) => { 174 | impl<'de, $($name,)+> Animatable for ($($name,)+) 175 | where 176 | $($name: Animatable,)+ 177 | { 178 | #[inline] 179 | fn animate(&self, to: &Self, time: f64) -> Self 180 | { 181 | $( 182 | let $name = Animatable::animate(&self.$n, &to.$n, time); 183 | )+ 184 | ($($name,)+) 185 | } 186 | } 187 | } 188 | } 189 | 190 | impl_tuple!(0 T0); 191 | impl_tuple!(0 T0 1 T1); 192 | impl_tuple!(0 T0 1 T1 2 T2); 193 | impl_tuple!(0 T0 1 T1 2 T2 3 T3); 194 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4); 195 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5); 196 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6); 197 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7); 198 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8); 199 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9); 200 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10); 201 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11); 202 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12); 203 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13); 204 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14); 205 | impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14 15 T15); 206 | 207 | #[cfg(test)] 208 | mod test { 209 | use crate::Animatable; 210 | 211 | #[test] 212 | fn test_bool() { 213 | let v = false.animate(&true, 0.0); 214 | assert!(v == false); 215 | 216 | let v = false.animate(&true, 0.5); 217 | assert!(v == false); 218 | 219 | let v = false.animate(&true, 1.0); 220 | assert!(v == true); 221 | 222 | let v = true.animate(&true, 0.3); 223 | assert!(v == true); 224 | 225 | let v = false.animate(&false, 0.2); 226 | assert!(v == false); 227 | } 228 | 229 | #[test] 230 | fn test_char() { 231 | let v = 'a'.animate(&'e', 0.0); 232 | assert_eq!(v, 'a'); 233 | 234 | let v = 'a'.animate(&'e', 0.5); 235 | assert_eq!(v, 'c'); 236 | 237 | let v = 'a'.animate(&'e', 0.555); 238 | assert_eq!(v, 'c'); 239 | 240 | let v = 'a'.animate(&'e', 1.0); 241 | assert_eq!(v, 'e'); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/core/animation/boxed.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use std::{fmt, time::Duration}; 9 | 10 | /// wrapper for boxed [`Animation`] 11 | pub struct Boxed(Box>); 12 | 13 | impl Boxed { 14 | /// construct [`Boxed`] 15 | #[inline] 16 | pub(crate) fn new(src: F) -> Self 17 | where 18 | F: Animation + 'static, 19 | { 20 | Self(Box::new(src)) 21 | } 22 | } 23 | 24 | impl BaseAnimation for Boxed { 25 | type Item = T; 26 | #[inline] 27 | fn duration(&self) -> Option { 28 | self.0.duration() 29 | } 30 | 31 | #[inline] 32 | fn animate(&self, elapsed: Duration) -> Self::Item { 33 | self.0.animate(elapsed) 34 | } 35 | } 36 | 37 | impl fmt::Debug for Boxed { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | write!(f, "BoxedAnimation") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/core/animation/cache.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use std::{cell::RefCell, time::Duration}; 9 | 10 | /// caches animated value, reducing computing while not animating. 11 | /// you might want to use it at the end of the animation chains. 12 | #[derive(Debug)] 13 | pub struct Cache 14 | where 15 | T: Animation, 16 | T::Item: Clone, 17 | { 18 | src: T, 19 | cell: RefCell>, 20 | } 21 | 22 | impl Cache 23 | where 24 | T: Animation, 25 | T::Item: Clone, 26 | { 27 | #[inline] 28 | pub(super) fn new(src: T) -> Self { 29 | Self { 30 | src, 31 | cell: Default::default(), 32 | } 33 | } 34 | } 35 | 36 | impl BaseAnimation for Cache 37 | where 38 | T: Animation, 39 | T::Item: Clone, 40 | { 41 | type Item = T::Item; 42 | 43 | #[inline] 44 | fn duration(&self) -> Option { 45 | self.src.duration() 46 | } 47 | 48 | #[inline] 49 | fn animate(&self, mut elapsed: Duration) -> Self::Item { 50 | if let Some(duration) = self.duration() { 51 | if elapsed > duration { 52 | //finished 53 | elapsed = duration; 54 | } 55 | } 56 | 57 | if let Some((time, value)) = &*self.cell.borrow() { 58 | if time == &elapsed { 59 | return value.clone(); 60 | } 61 | } 62 | let value = self.src.animate(elapsed); 63 | { 64 | let cell = &mut *self.cell.borrow_mut(); 65 | *cell = Some((elapsed, value.clone())); 66 | } 67 | value 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/core/animation/chain.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use std::time::Duration; 9 | 10 | /// chained animations, runs in orders 11 | #[derive(Debug, Clone)] 12 | pub struct Chain { 13 | first: A, 14 | second: B, 15 | } 16 | 17 | impl Chain { 18 | #[inline] 19 | pub(super) fn new(first: A, second: B) -> Self { 20 | Self { first, second } 21 | } 22 | } 23 | 24 | impl BaseAnimation for Chain 25 | where 26 | A: Animation, 27 | B: Animation, 28 | { 29 | type Item = A::Item; 30 | 31 | #[inline] 32 | fn duration(&self) -> Option { 33 | if let Some(first) = self.first.duration() { 34 | if let Some(second) = self.second.duration() { 35 | return Some(first + second); 36 | } 37 | } 38 | None 39 | } 40 | 41 | #[inline] 42 | fn animate(&self, elapsed: Duration) -> Self::Item { 43 | if let Some(first) = self.first.duration() { 44 | if elapsed >= first { 45 | return self.second.animate(elapsed - first); 46 | } 47 | } 48 | self.first.animate(elapsed) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/core/animation/delay.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use crate::core::DURATION_ZERO; 9 | use std::time::Duration; 10 | 11 | /// delay your animation for a specified time; negative delay has no effect 12 | #[derive(Debug, Clone)] 13 | pub struct Delay { 14 | src: T, 15 | delay: Duration, 16 | } 17 | 18 | impl Delay { 19 | #[inline] 20 | pub(super) fn new(src: T, delay: Duration) -> Self { 21 | Self { src, delay } 22 | } 23 | 24 | #[inline] 25 | fn delay(&self) -> Duration { 26 | if self.delay > DURATION_ZERO { 27 | self.delay 28 | } else { 29 | DURATION_ZERO 30 | } 31 | } 32 | } 33 | 34 | impl BaseAnimation for Delay { 35 | type Item = T::Item; 36 | #[inline] 37 | fn duration(&self) -> Option { 38 | self.src.duration().map(|d| self.delay() + d) 39 | } 40 | 41 | #[inline] 42 | fn animate(&self, elapsed: Duration) -> Self::Item { 43 | let delay = self.delay(); 44 | let elapsed = if elapsed > delay { 45 | elapsed - delay 46 | } else { 47 | DURATION_ZERO 48 | }; 49 | self.src.animate(elapsed) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/core/animation/key_frame.rs: -------------------------------------------------------------------------------- 1 | use crate::{easing, Animatable, DEFAULT_ANIMATION_DURATION, DURATION_ZERO}; 2 | use std::fmt; 3 | use std::time::Duration; 4 | 5 | use super::BaseAnimation; 6 | 7 | /// key time 8 | #[derive(Debug, Clone, Copy)] 9 | pub enum KeyTime { 10 | /// by duration 11 | Duration(Duration), 12 | /// by percent 13 | Percent(f32), 14 | } 15 | 16 | impl Default for KeyTime { 17 | #[inline] 18 | fn default() -> Self { 19 | KeyTime::Duration(DURATION_ZERO) 20 | } 21 | } 22 | 23 | impl From for KeyTime { 24 | #[inline] 25 | fn from(duration: Duration) -> Self { 26 | Self::Duration(duration) 27 | } 28 | } 29 | 30 | impl From for KeyTime { 31 | #[inline] 32 | fn from(percent: f32) -> Self { 33 | assert!((0.0..=1.0).contains(&percent)); 34 | Self::Percent(percent) 35 | } 36 | } 37 | 38 | /// key-frame 39 | pub struct KeyFrame { 40 | /// value of key-frame 41 | pub value: T, 42 | /// key-time of key-frame 43 | pub key_time: KeyTime, 44 | easing: Box, 45 | } 46 | 47 | impl KeyFrame { 48 | /// create key-frame 49 | #[inline] 50 | pub fn new(value: T) -> Self { 51 | Self { 52 | value, 53 | key_time: DURATION_ZERO.into(), 54 | easing: Box::new(easing::linear()), 55 | } 56 | } 57 | 58 | /// create key-frame 59 | #[inline] 60 | pub fn new_with_key_time(value: T, key_time: KeyTime) -> Self { 61 | Self { 62 | value, 63 | key_time, 64 | easing: Box::new(easing::linear()), 65 | } 66 | } 67 | 68 | /// set value 69 | #[inline] 70 | pub fn value(mut self, value: T) -> Self { 71 | self.value = value; 72 | self 73 | } 74 | 75 | /// set key time 76 | #[inline] 77 | pub fn key_time(mut self, key_time: KeyTime) -> Self { 78 | self.key_time = key_time; 79 | self 80 | } 81 | 82 | /// set key time 83 | /// 84 | /// panics if percent<0 or percent>1 85 | #[inline] 86 | pub fn by_percent(mut self, percent: f32) -> Self { 87 | self.key_time = percent.into(); 88 | self 89 | } 90 | 91 | /// set key time 92 | #[inline] 93 | pub fn by_duration(mut self, duration: Duration) -> Self { 94 | self.key_time = duration.into(); 95 | self 96 | } 97 | 98 | /// set easing function 99 | #[inline] 100 | pub fn easing(mut self, func: impl easing::Function + Clone + 'static) -> Self { 101 | self.easing = Box::new(func); 102 | self 103 | } 104 | } 105 | 106 | impl Default for KeyFrame { 107 | #[inline] 108 | fn default() -> Self { 109 | Self { 110 | value: Default::default(), 111 | key_time: Default::default(), 112 | easing: Box::new(easing::linear()), 113 | } 114 | } 115 | } 116 | 117 | impl Clone for KeyFrame { 118 | fn clone(&self) -> Self { 119 | Self { 120 | value: self.value.clone(), 121 | key_time: self.key_time, 122 | easing: dyn_clone::clone_box(&*self.easing), 123 | } 124 | } 125 | } 126 | 127 | impl fmt::Debug for KeyFrame { 128 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 129 | f.debug_struct("KeyFrame") 130 | .field("value", &self.value) 131 | .field("key_time", &self.key_time) 132 | .field("easing", &"???") 133 | .finish() 134 | } 135 | } 136 | 137 | struct KeyFrameInner { 138 | value: T, 139 | key_time: Duration, 140 | easing: Box, 141 | } 142 | 143 | impl KeyFrameInner { 144 | fn cvt_from(src: KeyFrame, duration: &Duration) -> Option { 145 | match src.key_time { 146 | KeyTime::Duration(duration) => Some(KeyFrameInner { 147 | value: src.value, 148 | key_time: duration, 149 | easing: src.easing, 150 | }), 151 | KeyTime::Percent(percent) => { 152 | // filter out invalid values 153 | assert!((0.0..=1.0).contains(&percent)); 154 | Some(KeyFrameInner { 155 | value: src.value, 156 | key_time: duration.mul_f32(percent), 157 | easing: src.easing, 158 | }) 159 | } 160 | } 161 | } 162 | } 163 | 164 | impl Clone for KeyFrameInner { 165 | fn clone(&self) -> Self { 166 | Self { 167 | value: self.value.clone(), 168 | key_time: self.key_time, 169 | easing: dyn_clone::clone_box(&*self.easing), 170 | } 171 | } 172 | } 173 | 174 | impl fmt::Debug for KeyFrameInner { 175 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 176 | f.debug_struct("KeyFrame") 177 | .field("value", &self.value) 178 | .field("key_time", &self.key_time) 179 | .field("easing", &"???") 180 | .finish() 181 | } 182 | } 183 | 184 | #[derive(Debug, Clone, Default)] 185 | pub struct KeyFrameAnimation { 186 | key_frames: Vec>, 187 | duration: Duration, 188 | } 189 | 190 | impl KeyFrameAnimation { 191 | #[inline] 192 | pub(super) fn builder(key_frames: Vec>) -> Builder { 193 | Builder { key_frames } 194 | } 195 | } 196 | 197 | impl BaseAnimation for KeyFrameAnimation { 198 | type Item = T; 199 | 200 | #[inline] 201 | fn duration(&self) -> Option { 202 | Some(self.duration) 203 | } 204 | 205 | #[inline] 206 | fn animate(&self, elapsed: Duration) -> Self::Item { 207 | if elapsed < self.duration { 208 | let mut last = None; 209 | for item in self.key_frames.iter() { 210 | if item.key_time <= elapsed { 211 | last = Some(item); 212 | continue; 213 | } 214 | if let Some(last) = last { 215 | let delta = elapsed - last.key_time; 216 | let total = item.key_time - last.key_time; 217 | let time = delta.as_secs_f64() / total.as_secs_f64(); 218 | let time = item.easing.ease(time); 219 | return last.value.animate(&item.value, time); 220 | } else { 221 | return item.value.clone(); 222 | } 223 | } 224 | } 225 | let item = self.key_frames.last().unwrap(); 226 | item.value.clone() 227 | } 228 | } 229 | 230 | pub struct Builder { 231 | key_frames: Vec>, 232 | } 233 | 234 | impl Builder { 235 | #[allow(unused)] 236 | #[inline] 237 | pub fn push(mut self, item: KeyFrame) -> Self { 238 | self.key_frames.push(item); 239 | self 240 | } 241 | 242 | #[inline] 243 | pub fn build(self) -> KeyFrameAnimation { 244 | //find max duration, so we can sort frames later 245 | let max_duration = self 246 | .key_frames 247 | .iter() 248 | .filter_map(|v| match v.key_time { 249 | KeyTime::Duration(duration) => Some(duration), 250 | KeyTime::Percent(_) => None, 251 | }) 252 | .max() 253 | .unwrap_or(DEFAULT_ANIMATION_DURATION); 254 | 255 | //dbg!(max_duration); 256 | 257 | //sort key frames 258 | let mut key_frames: Vec<_> = self 259 | .key_frames 260 | .into_iter() 261 | .filter_map(|frame| KeyFrameInner::cvt_from(frame, &max_duration)) 262 | .collect(); 263 | assert!(!key_frames.is_empty()); 264 | key_frames.sort_by_key(|x| x.key_time); 265 | KeyFrameAnimation { 266 | key_frames, 267 | duration: max_duration, 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/core/animation/map.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use std::time::Duration; 9 | 10 | /// map from one type to another 11 | #[derive(Debug, Clone)] 12 | pub struct Map 13 | where 14 | Source: Animation, 15 | F: Fn(Source::Item) -> T, 16 | { 17 | src: Source, 18 | f: F, 19 | } 20 | 21 | impl Map 22 | where 23 | Source: Animation, 24 | F: Fn(Source::Item) -> T, 25 | { 26 | #[inline] 27 | pub(super) fn new(src: Source, f: F) -> Self { 28 | Self { src, f } 29 | } 30 | } 31 | 32 | impl BaseAnimation for Map 33 | where 34 | Source: Animation, 35 | F: Fn(Source::Item) -> T, 36 | { 37 | type Item = T; 38 | 39 | #[inline] 40 | fn duration(&self) -> Option { 41 | self.src.duration() 42 | } 43 | 44 | #[inline] 45 | fn animate(&self, elapsed: Duration) -> Self::Item { 46 | let v = self.src.animate(elapsed); 47 | (self.f)(v) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/core/animation/mod.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | mod boxed; 8 | mod cache; 9 | mod chain; 10 | mod delay; 11 | mod key_frame; 12 | mod map; 13 | mod parallel; 14 | mod primitive; 15 | mod repeat; 16 | mod scale; 17 | mod seek; 18 | mod step; 19 | mod take; 20 | 21 | use crate::{easing, Animatable, Options, RepeatBehavior, Timeline}; 22 | 23 | pub use self::key_frame::{KeyFrame, KeyTime}; 24 | pub use self::seek::SeekFrom; 25 | pub use self::step::Cursor; 26 | pub use self::step::StepAnimation; 27 | use self::{scale::Scale, step::Infinite}; 28 | pub(crate) use boxed::Boxed; 29 | pub(crate) use cache::Cache; 30 | pub(crate) use chain::Chain; 31 | pub(crate) use delay::Delay; 32 | pub(crate) use key_frame::KeyFrameAnimation; 33 | pub(crate) use map::Map; 34 | pub(crate) use parallel::Parallel; 35 | pub(crate) use primitive::Primitive; 36 | pub(crate) use repeat::Repeat; 37 | pub(crate) use seek::Seek; 38 | use std::time::Duration; 39 | pub(crate) use take::Take; 40 | 41 | /// build a linear animation(x=t), with which you can get normalized time between 0-1 42 | /// 43 | /// ## Example 44 | /// ```rust 45 | /// use std::time::Duration; 46 | /// use anim::{Animation,builder::linear}; 47 | /// 48 | /// let timeline = linear(Duration::from_millis(2000)) 49 | /// .map(|t| if t>0.5 { true } else { false }) 50 | /// .begin_animation(); 51 | /// ``` 52 | #[inline] 53 | pub fn linear(duration: Duration) -> impl Animation + Clone { 54 | Options::new(0.0, 1.0) 55 | .auto_reverse(false) 56 | .easing(easing::linear()) 57 | .duration(duration) 58 | .build() 59 | } 60 | 61 | /// build a constant animation, which will output constant values 62 | #[inline] 63 | pub fn constant(value: T, duration: Duration) -> impl Animation + Clone { 64 | Options::new(true, true) 65 | .duration(duration) 66 | .build() 67 | .map(move |_| value.clone()) 68 | } 69 | 70 | /// build key frames animation 71 | /// 72 | /// - requires at least one frame 73 | /// - default duration is one second if not specified in any of the frames 74 | #[inline] 75 | pub fn key_frames( 76 | frames: impl Into>>, 77 | ) -> impl Animation + Clone { 78 | KeyFrameAnimation::builder(frames.into()).build() 79 | } 80 | 81 | /// infinite or finite steps 82 | /// 83 | /// see [`Cursor`] 84 | #[inline] 85 | pub fn steps(src: T, interval: Duration) -> StepAnimation { 86 | StepAnimation::new(src).interval(interval) 87 | } 88 | 89 | /// infinite steps 90 | /// 91 | /// ## Example 92 | /// ```rust 93 | /// use std::time::Duration; 94 | /// use anim::{Animation, builder::steps_infinite}; 95 | /// 96 | /// #[derive(Debug)] 97 | /// enum Action { 98 | /// Stand, 99 | /// Step1, 100 | /// Step2, 101 | /// Run, 102 | /// } 103 | /// 104 | /// let steps = steps_infinite(|i| { 105 | /// if i == 0 { 106 | /// return Action::Stand; 107 | /// } 108 | /// match (i-1) % 3 { 109 | /// 0 => Action::Step1, 110 | /// 1 => Action::Step2, 111 | /// _ => Action::Run, 112 | /// } 113 | /// },Duration::from_millis(40)); 114 | /// let timeline = steps.begin_animation(); 115 | /// //... 116 | /// ``` 117 | #[inline] 118 | pub fn steps_infinite T, T>( 119 | f: F, 120 | interval: Duration, 121 | ) -> StepAnimation> { 122 | let src = Infinite::new(f); 123 | StepAnimation::new(src).interval(interval) 124 | } 125 | 126 | /// A crate-private base trait, 127 | pub trait BaseAnimation { 128 | /// animated value 129 | type Item; 130 | 131 | /// the animation lasts for how long; `None` means it's never finished 132 | fn duration(&self) -> Option; 133 | 134 | /// outputs animated value based on the progressing time 135 | fn animate(&self, elapsed: Duration) -> Self::Item; 136 | } 137 | 138 | /// your animation, which outputs animated value based on the progressing time. 139 | /// 140 | /// Simply, you can think it as an [`Iterator`]. The difference is that an [`Animation`] 141 | /// always output some values. 142 | pub trait Animation: BaseAnimation { 143 | /// always delay for specified time when play current animation; negative delay has no effect 144 | #[inline] 145 | fn delay(self, delay: Duration) -> Delay 146 | where 147 | Self: Sized, 148 | { 149 | Delay::new(self, delay) 150 | } 151 | 152 | /// always delay for specified time when play current animation 153 | #[inline] 154 | fn delay_ms(self, millis: u64) -> Delay 155 | where 156 | Self: Sized, 157 | { 158 | Delay::new(self, Duration::from_millis(millis)) 159 | } 160 | 161 | /// always move forward for specified time when play current animation 162 | /// 163 | /// just a simple wrap on [`Animation::seek`] 164 | #[inline] 165 | fn skip(self, progress: Duration) -> Seek 166 | where 167 | Self: Sized, 168 | { 169 | Seek::new(self, SeekFrom::Begin(progress)) 170 | } 171 | 172 | /// always move forward for specified time when play current animation 173 | /// 174 | /// ## panic 175 | /// - panics if percent < -1.0 or percent > 1.0 176 | /// - panics if current animation lasts indefinitely while seeking from end or by percent 177 | #[inline] 178 | fn seek(self, seek: SeekFrom) -> Seek 179 | where 180 | Self: Sized, 181 | { 182 | Seek::new(self, seek) 183 | } 184 | 185 | /// always move forward for specified time when play current animation 186 | /// 187 | /// just a simple wrap on [`Animation::seek`] 188 | /// 189 | /// ## panic 190 | /// - panics if percent < -1.0 or percent > 1.0 191 | /// - panics if current animation lasts indefinitely 192 | #[inline] 193 | fn seek_by(self, percent: f32) -> Seek 194 | where 195 | Self: Sized, 196 | { 197 | Seek::new(self, SeekFrom::Percent(percent)) 198 | } 199 | 200 | /// map from one type to another 201 | #[inline] 202 | fn map(self, f: F) -> Map 203 | where 204 | Self: Sized, 205 | F: Fn(Self::Item) -> T, 206 | { 207 | Map::new(self, f) 208 | } 209 | 210 | /// chain two animations, play in the chained order 211 | #[inline] 212 | fn chain(self, other: Other) -> Chain 213 | where 214 | Self: Sized, 215 | Other: Animation, 216 | { 217 | Chain::new(self, other) 218 | } 219 | 220 | /// take specified duration 221 | #[inline] 222 | fn take(self, duration: Duration) -> Take 223 | where 224 | Self: Sized, 225 | { 226 | Take::new(self, duration) 227 | } 228 | 229 | /// speed up or slow down you animation 230 | /// 231 | /// scale | effect 232 | /// ------|------- 233 | /// =0.0 | your animation's duration becomes zero 234 | /// <1.0 | speed up your animation 235 | /// >1.0 | slow down your animation 236 | /// <0.0 | panics 237 | /// 238 | /// see [`Animation::speed_up`] 239 | #[inline] 240 | fn scale(self, scale: f32) -> Scale 241 | where 242 | Self: Sized, 243 | { 244 | Scale::new(self, scale) 245 | } 246 | 247 | /// speed up or slow down you animation 248 | /// 249 | /// ratio | effect 250 | /// -----|-------- 251 | /// >1.0 | speed up your animation 252 | /// <1.0 | slow down your animation 253 | /// <=0.0 | panics 254 | /// 255 | /// see [`Animation::scale`] 256 | #[inline] 257 | fn speed_up(self, ratio: f32) -> Scale 258 | where 259 | Self: Sized, 260 | { 261 | assert!(ratio > 0.0); 262 | let scale = 1.0 / ratio; 263 | Scale::new(self, scale) 264 | } 265 | 266 | /// repeat animations with specified strategies 267 | /// 268 | /// panics if count<0 269 | #[inline] 270 | fn repeat(self, repeat: RepeatBehavior) -> Repeat 271 | where 272 | Self: Sized, 273 | { 274 | Repeat::new(self, repeat) 275 | } 276 | 277 | /// repeat your animation for specified times 278 | /// 279 | /// see [`Animation::repeat`] 280 | /// 281 | /// ## panic 282 | /// panics if count<0 283 | #[inline] 284 | fn times(self, count: f32) -> Repeat 285 | where 286 | Self: Sized, 287 | { 288 | Repeat::new(self, RepeatBehavior::Count(count)) 289 | } 290 | 291 | // repeat your animation indefinitely 292 | /// 293 | /// see [`Animation::repeat`] 294 | #[inline] 295 | fn forever(self) -> Repeat 296 | where 297 | Self: Sized, 298 | { 299 | self.cycle() 300 | } 301 | 302 | // repeat your animation indefinitely 303 | /// 304 | /// see [`Animation::repeat`] 305 | #[inline] 306 | fn cycle(self) -> Repeat 307 | where 308 | Self: Sized, 309 | { 310 | Repeat::new(self, RepeatBehavior::Forever) 311 | } 312 | 313 | /// parallel animations, play at the same time until the longest one finishes 314 | #[inline] 315 | fn parallel(self, other: Other) -> Parallel 316 | where 317 | Self: Sized, 318 | Other: Animation, 319 | { 320 | Parallel::new(self, other) 321 | } 322 | 323 | /// parallel animations, play at the same time until the longest one finishes. 324 | /// 325 | /// alias for [`Animation::parallel()`] 326 | #[inline] 327 | fn zip(self, other: Other) -> Parallel 328 | where 329 | Self: Sized, 330 | Other: Animation, 331 | { 332 | Parallel::new(self, other) 333 | } 334 | 335 | /// caches animated value, reducing computing while not animating. 336 | /// you might want to use it at the end of the animation chains 337 | #[inline] 338 | fn cached(self) -> Cache 339 | where 340 | Self: Sized, 341 | Self::Item: Clone, 342 | { 343 | Cache::new(self) 344 | } 345 | 346 | /// into boxed animation 347 | #[inline] 348 | fn boxed(self) -> Boxed 349 | where 350 | Self: Sized + 'static, 351 | { 352 | Boxed::new(self) 353 | } 354 | 355 | /// build [`Timeline`] 356 | #[inline] 357 | fn to_timeline(self) -> Timeline 358 | where 359 | Self: Sized + 'static, 360 | Self::Item: 'static, 361 | { 362 | Timeline::new(self) 363 | } 364 | 365 | /// build [`Timeline`] and start to play the animation 366 | #[inline] 367 | fn begin_animation(self) -> Timeline 368 | where 369 | Self: Sized + 'static, 370 | Self::Item: 'static, 371 | { 372 | let mut timeline = Timeline::new(self); 373 | timeline.begin(); 374 | timeline 375 | } 376 | } 377 | 378 | impl Animation for T {} 379 | 380 | pub trait AnimationClone: Animation + Clone {} 381 | 382 | impl AnimationClone for T {} 383 | 384 | // ----- private ----- 385 | 386 | // helper 387 | pub(crate) trait IsFinished { 388 | fn is_finished(&self, elapsed: Duration) -> bool; 389 | } 390 | 391 | impl IsFinished for T { 392 | #[inline] 393 | fn is_finished(&self, elapsed: Duration) -> bool { 394 | self.duration().map(|d| elapsed >= d).unwrap_or_default() 395 | } 396 | } 397 | 398 | #[cfg(test)] 399 | mod test { 400 | use super::*; 401 | use crate::core::{easing, Options, DURATION_ZERO}; 402 | 403 | #[test] 404 | fn test_constant() { 405 | let animation = constant(1.0, Duration::from_millis(200)); 406 | let v = animation.animate(DURATION_ZERO); 407 | assert_eq!(v, 1.0); 408 | let v = animation.animate(Duration::from_secs(10)); 409 | assert_eq!(v, 1.0); 410 | } 411 | 412 | #[test] 413 | fn test_primitive() { 414 | let animation = Options::new(0.0, 1.0) 415 | .easing(easing::linear()) 416 | .duration(Duration::from_millis(1000)) 417 | .auto_reverse(false) 418 | .build(); 419 | 420 | let v = animation.animate(DURATION_ZERO); 421 | assert_eq!(v, 0.0); 422 | 423 | let v = animation.animate(Duration::from_millis(500)); 424 | assert_eq!(v, 0.5); 425 | 426 | let v = animation.animate(Duration::from_millis(1000)); 427 | assert_eq!(v, 1.0); 428 | 429 | let v = animation.animate(Duration::from_millis(1100)); 430 | assert_eq!(v, 1.0); 431 | } 432 | 433 | #[test] 434 | fn test_primitive_const() { 435 | let animation = Options::new(1.0, 1.0) 436 | .easing(easing::linear()) 437 | .duration(Duration::from_millis(1000)) 438 | .auto_reverse(false) 439 | .build(); 440 | 441 | let v = animation.animate(DURATION_ZERO); 442 | assert_eq!(v, 1.0); 443 | 444 | let v = animation.animate(Duration::from_millis(500)); 445 | assert_eq!(v, 1.0); 446 | 447 | let v = animation.animate(Duration::from_millis(1000)); 448 | assert_eq!(v, 1.0); 449 | 450 | let v = animation.animate(Duration::from_millis(1100)); 451 | assert_eq!(v, 1.0); 452 | } 453 | 454 | #[test] 455 | fn test_primitive_duration_zero() { 456 | let animation = Options::new(1.0, 2.0) 457 | .easing(easing::linear()) 458 | .duration(DURATION_ZERO) 459 | .auto_reverse(false) 460 | .build(); 461 | 462 | let v = animation.animate(DURATION_ZERO); 463 | assert_eq!(v, 1.0); 464 | 465 | let v = animation.animate(Duration::from_millis(500)); 466 | assert_eq!(v, 1.0); 467 | 468 | let v = animation.animate(Duration::from_millis(1000)); 469 | assert_eq!(v, 1.0); 470 | 471 | let v = animation.animate(Duration::from_millis(1100)); 472 | assert_eq!(v, 1.0); 473 | } 474 | 475 | #[test] 476 | fn test_primitive_reverse() { 477 | let animation = Options::new(0.0, 1.0) 478 | .easing(easing::linear()) 479 | .duration(Duration::from_millis(1000)) 480 | .auto_reverse(true) 481 | .build(); 482 | let v = animation.animate(DURATION_ZERO); 483 | assert_eq!(v, 0.0); 484 | 485 | let v = animation.animate(Duration::from_millis(250)); 486 | assert_eq!(v, 0.5); 487 | 488 | let v = animation.animate(Duration::from_millis(500)); 489 | assert_eq!(v, 1.0); 490 | 491 | let v = animation.animate(Duration::from_millis(750)); 492 | assert_eq!(v, 0.5); 493 | 494 | let v = animation.animate(Duration::from_millis(1000)); 495 | assert_eq!(v, 0.0); 496 | 497 | let v = animation.animate(Duration::from_millis(1100)); 498 | assert_eq!(v, 0.0); 499 | } 500 | 501 | #[test] 502 | fn test_primitive_repeat() { 503 | let animation = Options::new(0.0, 1.0) 504 | .easing(easing::linear()) 505 | .duration(Duration::from_millis(1000)) 506 | .times(2.0) 507 | .auto_reverse(false) 508 | .build(); 509 | 510 | let v = animation.animate(DURATION_ZERO); 511 | assert_eq!(v, 0.0); 512 | 513 | let v = animation.animate(Duration::from_millis(500)); 514 | assert_eq!(v, 0.5); 515 | 516 | let v = animation.animate(Duration::from_millis(1000)); 517 | assert_eq!(v, 1.0); 518 | 519 | let v = animation.animate(Duration::from_millis(1500)); 520 | assert_eq!(v, 0.5); 521 | 522 | let v = animation.animate(Duration::from_millis(2000)); 523 | assert_eq!(v, 1.0); 524 | 525 | let v = animation.animate(Duration::from_millis(2100)); 526 | assert_eq!(v, 1.0); 527 | } 528 | 529 | #[test] 530 | fn test_primitive_skip() { 531 | let animation = Options::new(0.0, 1.0) 532 | .easing(easing::linear()) 533 | .duration(Duration::from_millis(1000)) 534 | .auto_reverse(false) 535 | .skip(Duration::from_millis(500)) 536 | .build(); 537 | 538 | let v = animation.animate(DURATION_ZERO); 539 | assert_eq!(v, 0.5); 540 | 541 | let v = animation.animate(Duration::from_millis(250)); 542 | assert_eq!(v, 0.75); 543 | 544 | let v = animation.animate(Duration::from_millis(500)); 545 | assert_eq!(v, 1.0); 546 | 547 | let v = animation.animate(Duration::from_millis(1100)); 548 | assert_eq!(v, 1.0); 549 | } 550 | 551 | #[test] 552 | fn test_primitive_delay() { 553 | let animation = Options::new(0.0, 1.0) 554 | .easing(easing::linear()) 555 | .duration(Duration::from_millis(1000)) 556 | .auto_reverse(false) 557 | .delay(Duration::from_millis(500)) 558 | .build(); 559 | 560 | let v = animation.animate(DURATION_ZERO); 561 | assert_eq!(v, 0.0); 562 | 563 | let v = animation.animate(Duration::from_millis(250)); 564 | assert_eq!(v, 0.0); 565 | 566 | let v = animation.animate(Duration::from_millis(500)); 567 | assert_eq!(v, 0.0); 568 | 569 | let v = animation.animate(Duration::from_millis(1000)); 570 | assert_eq!(v, 0.5); 571 | 572 | let v = animation.animate(Duration::from_millis(1500)); 573 | assert_eq!(v, 1.0); 574 | 575 | let v = animation.animate(Duration::from_millis(1700)); 576 | assert_eq!(v, 1.0); 577 | } 578 | 579 | #[test] 580 | fn test_map() { 581 | let animation = Options::new(0.0, 1.0) 582 | .easing(easing::linear()) 583 | .duration(Duration::from_millis(1000)) 584 | .auto_reverse(false) 585 | .build() 586 | .map(|v| v * 2.0); 587 | 588 | let v = animation.animate(DURATION_ZERO); 589 | assert_eq!(v, 0.0); 590 | 591 | let v = animation.animate(Duration::from_millis(500)); 592 | assert_eq!(v, 1.0); 593 | 594 | let v = animation.animate(Duration::from_millis(1000)); 595 | assert_eq!(v, 2.0); 596 | 597 | let v = animation.animate(Duration::from_millis(1100)); 598 | assert_eq!(v, 2.0); 599 | } 600 | 601 | #[test] 602 | fn test_skip() { 603 | let animation = Options::new(0.0, 1.0) 604 | .easing(easing::linear()) 605 | .duration(Duration::from_millis(1000)) 606 | .auto_reverse(false) 607 | .build() 608 | .skip(Duration::from_millis(500)); 609 | 610 | let v = animation.animate(DURATION_ZERO); 611 | assert_eq!(v, 0.5); 612 | 613 | let v = animation.animate(Duration::from_millis(250)); 614 | assert_eq!(v, 0.75); 615 | 616 | let v = animation.animate(Duration::from_millis(500)); 617 | assert_eq!(v, 1.0); 618 | 619 | let v = animation.animate(Duration::from_millis(1000)); 620 | assert_eq!(v, 1.0); 621 | } 622 | 623 | #[test] 624 | fn test_seek_from_end() { 625 | let animation = Options::new(0.0, 1.0) 626 | .easing(easing::linear()) 627 | .duration(Duration::from_millis(1000)) 628 | .auto_reverse(false) 629 | .build() 630 | .seek(SeekFrom::End(Duration::from_millis(500))); 631 | 632 | let v = animation.animate(DURATION_ZERO); 633 | assert_eq!(v, 0.5); 634 | 635 | let v = animation.animate(Duration::from_millis(250)); 636 | assert_eq!(v, 0.75); 637 | 638 | let v = animation.animate(Duration::from_millis(500)); 639 | assert_eq!(v, 1.0); 640 | 641 | let v = animation.animate(Duration::from_millis(1000)); 642 | assert_eq!(v, 1.0); 643 | } 644 | 645 | #[test] 646 | fn test_seek_by() { 647 | let animation = Options::new(0.0, 1.0) 648 | .easing(easing::linear()) 649 | .duration(Duration::from_millis(1000)) 650 | .auto_reverse(false) 651 | .build() 652 | .seek(SeekFrom::Percent(0.5)); 653 | 654 | let v = animation.animate(DURATION_ZERO); 655 | assert_eq!(v, 0.5); 656 | 657 | let v = animation.animate(Duration::from_millis(250)); 658 | assert_eq!(v, 0.75); 659 | 660 | let v = animation.animate(Duration::from_millis(500)); 661 | assert_eq!(v, 1.0); 662 | 663 | let v = animation.animate(Duration::from_millis(1000)); 664 | assert_eq!(v, 1.0); 665 | } 666 | 667 | #[test] 668 | fn test_seek_by_negative() { 669 | let animation = Options::new(0.0, 1.0) 670 | .easing(easing::linear()) 671 | .duration(Duration::from_millis(1000)) 672 | .auto_reverse(false) 673 | .build() 674 | .seek(SeekFrom::Percent(-0.5)); 675 | 676 | let v = animation.animate(DURATION_ZERO); 677 | assert_eq!(v, 0.5); 678 | 679 | let v = animation.animate(Duration::from_millis(250)); 680 | assert_eq!(v, 0.75); 681 | 682 | let v = animation.animate(Duration::from_millis(500)); 683 | assert_eq!(v, 1.0); 684 | 685 | let v = animation.animate(Duration::from_millis(1000)); 686 | assert_eq!(v, 1.0); 687 | } 688 | 689 | #[test] 690 | fn test_delay() { 691 | let animation = Options::new(0.0, 1.0) 692 | .easing(easing::linear()) 693 | .duration(Duration::from_millis(1000)) 694 | .auto_reverse(false) 695 | .build() 696 | .delay(Duration::from_millis(500)); 697 | 698 | let v = animation.animate(DURATION_ZERO); 699 | assert_eq!(v, 0.0); 700 | 701 | let v = animation.animate(Duration::from_millis(250)); 702 | assert_eq!(v, 0.0); 703 | 704 | let v = animation.animate(Duration::from_millis(500)); 705 | assert_eq!(v, 0.0); 706 | 707 | let v = animation.animate(Duration::from_millis(1000)); 708 | assert_eq!(v, 0.5); 709 | 710 | let v = animation.animate(Duration::from_millis(1500)); 711 | assert_eq!(v, 1.0); 712 | 713 | let v = animation.animate(Duration::from_millis(1600)); 714 | assert_eq!(v, 1.0); 715 | } 716 | 717 | #[test] 718 | fn test_chain() { 719 | let animation = Options::new(0.0, 1.0) 720 | .easing(easing::linear()) 721 | .duration(Duration::from_millis(1000)) 722 | .auto_reverse(false) 723 | .build() 724 | .chain( 725 | Options::new(0.0, 1.0) 726 | .easing(easing::custom(|t| t)) 727 | .duration(Duration::from_millis(1000)) 728 | .auto_reverse(false) 729 | .build(), 730 | ); 731 | 732 | let v = animation.animate(DURATION_ZERO); 733 | assert_eq!(v, 0.0); 734 | 735 | let v = animation.animate(Duration::from_millis(250)); 736 | assert_eq!(v, 0.25); 737 | 738 | let v = animation.animate(Duration::from_millis(500)); 739 | assert_eq!(v, 0.5); 740 | 741 | //note: it's not continuous. 742 | // previous animation ended with value 1.0 743 | // next animation started with value 0.0 744 | let v = animation.animate(Duration::from_millis(1000)); 745 | assert_eq!(v, 0.0); 746 | 747 | let v = animation.animate(Duration::from_millis(1500)); 748 | assert_eq!(v, 0.5); 749 | 750 | let v = animation.animate(Duration::from_millis(2000)); 751 | assert_eq!(v, 1.0); 752 | 753 | let v = animation.animate(Duration::from_millis(2100)); 754 | assert_eq!(v, 1.0); 755 | } 756 | 757 | #[test] 758 | fn test_parallel() { 759 | let animation = Options::new(0.0, 1.0) 760 | .easing(easing::linear()) 761 | .duration(Duration::from_millis(1000)) 762 | .auto_reverse(false) 763 | .build() 764 | .parallel( 765 | Options::new(0.0, 1.0) 766 | .easing(easing::linear()) 767 | .duration(Duration::from_millis(2000)) 768 | .auto_reverse(false) 769 | .build(), 770 | ); 771 | 772 | let v = animation.animate(Duration::from_millis(0)); 773 | assert_eq!(v, (0.0, 0.0)); 774 | 775 | let v = animation.animate(Duration::from_millis(500)); 776 | assert_eq!(v, (0.5, 0.25)); 777 | 778 | let v = animation.animate(Duration::from_millis(1000)); 779 | assert_eq!(v, (1.0, 0.5)); 780 | 781 | let v = animation.animate(Duration::from_millis(1500)); 782 | assert_eq!(v, (1.0, 0.75)); 783 | 784 | let v = animation.animate(Duration::from_millis(2000)); 785 | assert_eq!(v, (1.0, 1.0)); 786 | 787 | let v = animation.animate(Duration::from_millis(2300)); 788 | assert_eq!(v, (1.0, 1.0)); 789 | } 790 | 791 | #[test] 792 | fn test_repeat() { 793 | let animation = Options::new(0.0, 1.0) 794 | .easing(easing::linear()) 795 | .duration(Duration::from_millis(1000)) 796 | .auto_reverse(false) 797 | .build() 798 | .times(1.5); 799 | 800 | let v = animation.animate(DURATION_ZERO); 801 | assert_eq!(v, 0.0); 802 | 803 | let v = animation.animate(Duration::from_millis(500)); 804 | assert_eq!(v, 0.5); 805 | 806 | let v = animation.animate(Duration::from_millis(1000)); 807 | assert_eq!(v, 1.0); 808 | 809 | let v = animation.animate(Duration::from_millis(1500)); 810 | assert_eq!(v, 0.5); 811 | 812 | let v = animation.animate(Duration::from_millis(2000)); 813 | assert_eq!(v, 0.5); 814 | 815 | let v = animation.animate(Duration::from_millis(2100)); 816 | assert_eq!(v, 0.5); 817 | } 818 | 819 | #[test] 820 | fn test_scale_up() { 821 | let animation = Options::new(0.0, 1.0) 822 | .easing(easing::linear()) 823 | .duration(Duration::from_millis(1000)) 824 | .auto_reverse(false) 825 | .build() 826 | .scale(2.0); 827 | 828 | let v = animation.animate(DURATION_ZERO); 829 | assert_eq!(v, 0.0); 830 | 831 | let v = animation.animate(Duration::from_millis(500)); 832 | assert_eq!(v, 0.25); 833 | 834 | let v = animation.animate(Duration::from_millis(1000)); 835 | assert_eq!(v, 0.5); 836 | 837 | let v = animation.animate(Duration::from_millis(2000)); 838 | assert_eq!(v, 1.0); 839 | 840 | let v = animation.animate(Duration::from_millis(2100)); 841 | assert_eq!(v, 1.0); 842 | } 843 | 844 | #[test] 845 | fn test_scale_down() { 846 | let animation = Options::new(0.0, 1.0) 847 | .easing(easing::linear()) 848 | .duration(Duration::from_millis(2000)) 849 | .auto_reverse(false) 850 | .build() 851 | .scale(0.5); 852 | 853 | let v = animation.animate(DURATION_ZERO); 854 | assert_eq!(v, 0.0); 855 | 856 | let v = animation.animate(Duration::from_millis(500)); 857 | assert_eq!(v, 0.5); 858 | 859 | let v = animation.animate(Duration::from_millis(1000)); 860 | assert_eq!(v, 1.0); 861 | 862 | let v = animation.animate(Duration::from_millis(1200)); 863 | assert_eq!(v, 1.0); 864 | 865 | let v = animation.animate(Duration::from_millis(2100)); 866 | assert_eq!(v, 1.0); 867 | } 868 | 869 | #[test] 870 | fn test_speed_up() { 871 | let animation = Options::new(0.0, 1.0) 872 | .easing(easing::linear()) 873 | .duration(Duration::from_millis(2000)) 874 | .auto_reverse(false) 875 | .build() 876 | .speed_up(2.0); 877 | 878 | let v = animation.animate(DURATION_ZERO); 879 | assert_eq!(v, 0.0); 880 | 881 | let v = animation.animate(Duration::from_millis(500)); 882 | assert_eq!(v, 0.5); 883 | 884 | let v = animation.animate(Duration::from_millis(1000)); 885 | assert_eq!(v, 1.0); 886 | 887 | let v = animation.animate(Duration::from_millis(1200)); 888 | assert_eq!(v, 1.0); 889 | 890 | let v = animation.animate(Duration::from_millis(2100)); 891 | assert_eq!(v, 1.0); 892 | } 893 | 894 | #[test] 895 | fn test_key_frames() { 896 | let key_frames = key_frames(vec![ 897 | KeyFrame::new(0.5).by_percent(0.5), 898 | KeyFrame::new(1.0).by_duration(Duration::from_millis(2000)), 899 | ]); 900 | 901 | let v = key_frames.animate(Duration::from_millis(0)); 902 | assert_eq!(v, 0.5); 903 | 904 | let v = key_frames.animate(Duration::from_millis(500)); 905 | assert_eq!(v, 0.5); 906 | 907 | let v = key_frames.animate(Duration::from_millis(1000)); 908 | assert_eq!(v, 0.5); 909 | 910 | let v = key_frames.animate(Duration::from_millis(1500)); 911 | assert_eq!(v, 0.75); 912 | 913 | let v = key_frames.animate(Duration::from_millis(2000)); 914 | assert_eq!(v, 1.0); 915 | 916 | let v = key_frames.animate(Duration::from_millis(2100)); 917 | assert_eq!(v, 1.0); 918 | } 919 | 920 | #[test] 921 | fn test_steps_infinite() { 922 | let steps = steps_infinite( 923 | |i| { 924 | if i == 0 { 925 | return Action::Stand; 926 | } 927 | match (i - 1) % 3 { 928 | 0 => Action::Step1, 929 | 1 => Action::Step2, 930 | _ => Action::Run, 931 | } 932 | }, 933 | Duration::from_millis(100), 934 | ); 935 | let v = steps.animate(DURATION_ZERO); 936 | assert_eq!(v, Action::Stand); 937 | 938 | let v = steps.animate(Duration::from_millis(100)); 939 | assert_eq!(v, Action::Step1); 940 | 941 | let v = steps.animate(Duration::from_millis(199)); 942 | assert_eq!(v, Action::Step1); 943 | 944 | let v = steps.animate(Duration::from_millis(900)); 945 | assert_eq!(v, Action::Run); 946 | 947 | let v = steps.animate(Duration::from_millis(999)); 948 | assert_eq!(v, Action::Run); 949 | } 950 | 951 | #[test] 952 | fn test_take_in_range() { 953 | let animation = Options::new(0.0, 1.0) 954 | .easing(easing::linear()) 955 | .duration(Duration::from_millis(2000)) 956 | .auto_reverse(false) 957 | .build() 958 | .take(Duration::from_millis(1000)); 959 | 960 | let v = animation.animate(DURATION_ZERO); 961 | assert_eq!(v, 0.0); 962 | 963 | let v = animation.animate(Duration::from_millis(500)); 964 | assert_eq!(v, 0.25); 965 | 966 | let v = animation.animate(Duration::from_millis(1000)); 967 | assert_eq!(v, 0.5); 968 | 969 | let v = animation.animate(Duration::from_millis(1500)); 970 | assert_eq!(v, 0.5); 971 | } 972 | 973 | #[test] 974 | fn test_take_out_range() { 975 | let animation = Options::new(0.0, 1.0) 976 | .easing(easing::linear()) 977 | .duration(Duration::from_millis(2000)) 978 | .auto_reverse(false) 979 | .build() 980 | .skip(Duration::from_millis(1000)) 981 | .take(Duration::from_millis(2000)); 982 | 983 | let v = animation.animate(DURATION_ZERO); 984 | assert_eq!(v, 0.5); 985 | 986 | let v = animation.animate(Duration::from_millis(500)); 987 | assert_eq!(v, 0.75); 988 | 989 | let v = animation.animate(Duration::from_millis(1000)); 990 | assert_eq!(v, 1.0); 991 | 992 | let v = animation.animate(Duration::from_millis(1500)); 993 | assert_eq!(v, 1.0); 994 | 995 | let v = animation.animate(Duration::from_millis(2111)); 996 | assert_eq!(v, 1.0); 997 | } 998 | 999 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 1000 | enum Action { 1001 | Stand, 1002 | Step1, 1003 | Step2, 1004 | Run, 1005 | } 1006 | } 1007 | -------------------------------------------------------------------------------- /src/core/animation/parallel.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use std::time::Duration; 9 | 10 | /// parallel animations 11 | #[derive(Debug, Clone)] 12 | pub struct Parallel { 13 | first: A, 14 | second: B, 15 | } 16 | 17 | impl Parallel { 18 | #[inline] 19 | pub(super) fn new(first: A, second: B) -> Self { 20 | Self { first, second } 21 | } 22 | } 23 | 24 | impl BaseAnimation for Parallel 25 | where 26 | A: Animation, 27 | B: Animation, 28 | { 29 | type Item = (A::Item, B::Item); 30 | #[inline] 31 | fn duration(&self) -> Option { 32 | if let Some(first) = self.first.duration() { 33 | if let Some(second) = self.second.duration() { 34 | return Some(first.max(second)); 35 | } 36 | } 37 | None 38 | } 39 | #[inline] 40 | fn animate(&self, elapsed: Duration) -> Self::Item { 41 | let first = self.first.animate(elapsed); 42 | let second = self.second.animate(elapsed); 43 | (first, second) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/core/animation/primitive.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::BaseAnimation; 8 | use crate::{ 9 | core::{Animatable, Options, RepeatBehavior}, 10 | DURATION_ZERO, 11 | }; 12 | use std::time::Duration; 13 | 14 | /// primitive animation which is built from [`Options`] 15 | #[derive(Debug, Clone)] 16 | pub struct Primitive { 17 | opt: Options, 18 | duration: Option, 19 | } 20 | 21 | impl Primitive { 22 | #[inline] 23 | pub(crate) fn new(opt: Options) -> Self { 24 | let duration = { 25 | if opt.duration == DURATION_ZERO { 26 | Some(DURATION_ZERO) 27 | } else { 28 | match opt.repeat { 29 | RepeatBehavior::Count(count) => Some(if count > 0.0 { 30 | opt.duration.mul_f32(count) 31 | } else { 32 | DURATION_ZERO 33 | }), 34 | RepeatBehavior::Forever => None, 35 | } 36 | } 37 | }; 38 | Self { opt, duration } 39 | } 40 | } 41 | 42 | impl BaseAnimation for Primitive { 43 | type Item = T; 44 | 45 | #[inline] 46 | fn duration(&self) -> Option { 47 | if let Some(mut duration) = self.duration { 48 | //apply delay 49 | if let Some(delay) = self.opt.delay { 50 | duration += delay; 51 | } 52 | //apply skip 53 | if let Some(skip) = self.opt.skip { 54 | if duration > skip { 55 | duration -= skip 56 | } else { 57 | duration = DURATION_ZERO; 58 | } 59 | } 60 | Some(duration) 61 | } else { 62 | None 63 | } 64 | } 65 | 66 | #[inline] 67 | fn animate(&self, mut elapsed: Duration) -> Self::Item { 68 | //apply skip 69 | if let Some(skip) = self.opt.skip { 70 | elapsed += skip; 71 | } 72 | //apply delay 73 | if let Some(delay) = self.opt.delay { 74 | if elapsed > delay { 75 | elapsed -= delay; 76 | } else { 77 | elapsed = DURATION_ZERO; 78 | } 79 | } 80 | 81 | // TODO: optimize for T:Eq 82 | if let Some(duration) = self.duration { 83 | // opt.duration<=0 || repeat count <=0 84 | if duration == DURATION_ZERO { 85 | return self.opt.from.clone(); 86 | } 87 | //apply repeat limit 88 | if elapsed > duration { 89 | elapsed = duration; 90 | } 91 | } 92 | 93 | // calc normalized time 94 | let time = elapsed.as_secs_f64() / self.opt.duration.as_secs_f64(); 95 | let count = time.floor(); 96 | let mut time = time - count; 97 | if count > 0.0 && time == 0.0 { 98 | time = 1.0; 99 | } 100 | time = self.opt.easing.ease(time); 101 | if self.opt.auto_reverse { 102 | if time > 0.5 { 103 | //reverse 104 | self.opt.to.animate(&self.opt.from, time * 2.0 - 1.0) 105 | } else { 106 | self.opt.from.animate(&self.opt.to, time * 2.0) 107 | } 108 | } else { 109 | self.opt.from.animate(&self.opt.to, time) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/core/animation/repeat.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use crate::{core::RepeatBehavior, core::DURATION_ZERO}; 9 | use std::time::Duration; 10 | /// repeat animations 11 | #[derive(Debug, Clone)] 12 | pub struct Repeat { 13 | src: T, 14 | duration: Option, 15 | } 16 | 17 | impl Repeat { 18 | #[inline] 19 | pub(super) fn new(src: T, repeat: RepeatBehavior) -> Self { 20 | let duration = src.duration().and_then(|duration| { 21 | if duration == DURATION_ZERO { 22 | return Some(DURATION_ZERO); 23 | } 24 | match repeat { 25 | RepeatBehavior::Count(count) => { 26 | assert!(count >= 0.0); 27 | Some(duration.mul_f32(count)) 28 | } 29 | RepeatBehavior::Forever => None, 30 | } 31 | }); 32 | Self { src, duration } 33 | } 34 | } 35 | 36 | impl BaseAnimation for Repeat { 37 | type Item = T::Item; 38 | #[inline] 39 | fn duration(&self) -> Option { 40 | self.duration 41 | } 42 | 43 | #[inline] 44 | fn animate(&self, mut elapsed: Duration) -> Self::Item { 45 | let simple_duration = match self.src.duration() { 46 | Some(duration) => duration, 47 | None => { 48 | return self.src.animate(elapsed); 49 | } 50 | }; 51 | 52 | if let Some(duration) = self.duration { 53 | if elapsed > duration { 54 | elapsed = duration; 55 | } 56 | } 57 | 58 | let time = elapsed.as_secs_f64() / simple_duration.as_secs_f64(); 59 | let count = time.floor(); 60 | let mut time = time - count; 61 | if count > 0.0 && time == 0.0 { 62 | time = 1.0 63 | }; 64 | self.src.animate(simple_duration.mul_f64(time)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/core/animation/scale.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use crate::core::DURATION_ZERO; 9 | use std::time::Duration; 10 | /// repeat animations 11 | #[derive(Debug, Clone)] 12 | pub struct Scale { 13 | src: T, 14 | scale: f32, 15 | } 16 | 17 | impl Scale { 18 | #[inline] 19 | pub(super) fn new(src: T, scale: f32) -> Self { 20 | assert!(scale >= 0.0); 21 | Self { src, scale } 22 | } 23 | } 24 | 25 | impl BaseAnimation for Scale { 26 | type Item = T::Item; 27 | #[inline] 28 | fn duration(&self) -> Option { 29 | self.src.duration().map(|duration| { 30 | if duration == DURATION_ZERO || self.scale == 0.0 { 31 | return DURATION_ZERO; 32 | } 33 | duration.div_f32(self.scale) 34 | }) 35 | } 36 | 37 | #[inline] 38 | fn animate(&self, elapsed: Duration) -> Self::Item { 39 | if self.scale == 0.0 { 40 | return self.src.animate(DURATION_ZERO); 41 | } 42 | let elapsed = elapsed.div_f32(self.scale); 43 | self.src.animate(elapsed) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/core/animation/seek.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use crate::core::DURATION_ZERO; 9 | use std::time::Duration; 10 | 11 | /// seek progress of current animation, only keep the remaining part 12 | #[derive(Clone, Copy)] 13 | pub enum SeekFrom { 14 | /// from the beginning 15 | Begin(Duration), 16 | /// from the end 17 | End(Duration), 18 | /// by percentage, 0.0-1.0; negative value means from the end 19 | Percent(f32), 20 | } 21 | 22 | /// always bypass specified time 23 | #[derive(Debug, Clone)] 24 | pub struct Seek { 25 | src: T, 26 | progress: Duration, 27 | } 28 | 29 | impl Seek { 30 | pub(super) fn new(src: T, seek: SeekFrom) -> Self { 31 | let progress = match seek { 32 | SeekFrom::Begin(progress) => progress, 33 | SeekFrom::End(progress) => { 34 | if let Some(duration) = src.duration() { 35 | if duration > progress { 36 | duration - progress 37 | } else { 38 | DURATION_ZERO 39 | } 40 | } else { 41 | panic!("cannot seek from end for infinite animation"); 42 | } 43 | } 44 | SeekFrom::Percent(percent) => { 45 | assert!((-1.0..=1.0).contains(&percent)); 46 | if let Some(duration) = src.duration() { 47 | if percent < 0.0 { 48 | duration.mul_f32(1.0 + percent) 49 | } else { 50 | duration.mul_f32(percent) 51 | } 52 | } else { 53 | panic!("cannot seek by percent for infinite animation"); 54 | } 55 | } 56 | }; 57 | Self { src, progress } 58 | } 59 | } 60 | 61 | impl BaseAnimation for Seek { 62 | type Item = T::Item; 63 | #[inline] 64 | fn duration(&self) -> Option { 65 | self.src.duration().map(|d| { 66 | if d > self.progress { 67 | d - self.progress 68 | } else { 69 | DURATION_ZERO 70 | } 71 | }) 72 | } 73 | 74 | #[inline] 75 | fn animate(&self, elapsed: Duration) -> Self::Item { 76 | let elapsed = self.progress + elapsed; 77 | self.src.animate(elapsed) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/core/animation/step.rs: -------------------------------------------------------------------------------- 1 | use super::BaseAnimation; 2 | use crate::DURATION_ZERO; 3 | use std::time::Duration; 4 | 5 | /// like `Iterator`, but does not consume any element 6 | /// 7 | /// built-in types that derives [`Cursor`] 8 | /// - `Vec` 9 | /// - `[T]` 10 | /// - `&[T]` 11 | /// - `Box` where `T:Cursor` 12 | /// - `&T` where `T:Cursor` 13 | pub trait Cursor { 14 | /// item of the cursor 15 | type Item; 16 | /// none means that it's infinite 17 | fn size(&self) -> Option; 18 | 19 | /// seek to specified element 20 | fn index(&self, n: usize) -> Self::Item; 21 | } 22 | 23 | impl Cursor for [T] { 24 | type Item = T; 25 | #[inline] 26 | fn size(&self) -> Option { 27 | Some(self.len()) 28 | } 29 | #[inline] 30 | fn index(&self, n: usize) -> T { 31 | self[n].to_owned() 32 | } 33 | } 34 | 35 | impl Cursor for &[T] { 36 | type Item = T; 37 | #[inline] 38 | fn size(&self) -> Option { 39 | Some(self.len()) 40 | } 41 | #[inline] 42 | fn index(&self, n: usize) -> T { 43 | self[n].to_owned() 44 | } 45 | } 46 | 47 | impl Cursor for Vec { 48 | type Item = T; 49 | #[inline] 50 | fn size(&self) -> Option { 51 | Some(self.len()) 52 | } 53 | #[inline] 54 | fn index(&self, n: usize) -> T { 55 | self[n].to_owned() 56 | } 57 | } 58 | 59 | impl Cursor for &T { 60 | type Item = T::Item; 61 | #[inline] 62 | fn size(&self) -> Option { 63 | (**self).size() 64 | } 65 | #[inline] 66 | fn index(&self, n: usize) -> Self::Item { 67 | (**self).index(n) 68 | } 69 | } 70 | 71 | impl Cursor for Box { 72 | type Item = T::Item; 73 | #[inline] 74 | fn size(&self) -> Option { 75 | (**self).size() 76 | } 77 | #[inline] 78 | fn index(&self, n: usize) -> Self::Item { 79 | (**self).index(n) 80 | } 81 | } 82 | 83 | struct Finite { 84 | src: T, 85 | } 86 | 87 | impl Cursor for Finite 88 | where 89 | T: ExactSizeIterator + Clone, 90 | { 91 | type Item = ::Item; 92 | 93 | #[inline] 94 | fn size(&self) -> Option { 95 | self.src.len().into() 96 | } 97 | 98 | #[inline] 99 | fn index(&self, n: usize) -> Self::Item { 100 | let mut src = self.src.clone(); 101 | src.nth(n).unwrap() 102 | } 103 | } 104 | 105 | impl From for Finite 106 | where 107 | T: ExactSizeIterator + Clone, 108 | { 109 | #[inline] 110 | fn from(src: T) -> Self { 111 | Finite { src } 112 | } 113 | } 114 | 115 | #[derive(Clone)] 116 | pub struct Infinite T, T> { 117 | f: F, 118 | } 119 | 120 | impl Infinite 121 | where 122 | F: Fn(usize) -> T, 123 | { 124 | #[inline] 125 | pub(super) fn new(f: F) -> Self { 126 | Self { f } 127 | } 128 | } 129 | 130 | impl Cursor for Infinite 131 | where 132 | F: Fn(usize) -> T, 133 | { 134 | type Item = T; 135 | #[inline] 136 | fn size(&self) -> Option { 137 | None 138 | } 139 | #[inline] 140 | fn index(&self, n: usize) -> Self::Item { 141 | (self.f)(n) 142 | } 143 | } 144 | 145 | #[derive(Debug, Clone)] 146 | pub struct StepAnimation { 147 | src: T, 148 | interval: Duration, 149 | } 150 | 151 | impl StepAnimation 152 | where 153 | T: Cursor, 154 | { 155 | /// create animation 156 | #[inline] 157 | pub(super) fn new(src: T) -> Self { 158 | Self { 159 | src, 160 | interval: DURATION_ZERO, 161 | } 162 | } 163 | 164 | /// set duration of the animation 165 | #[inline] 166 | pub fn interval(mut self, interval: Duration) -> Self { 167 | self.interval = interval; 168 | self 169 | } 170 | } 171 | 172 | impl BaseAnimation for StepAnimation 173 | where 174 | T: Cursor, 175 | { 176 | type Item = ::Item; 177 | 178 | #[inline] 179 | fn duration(&self) -> Option { 180 | if self.interval == DURATION_ZERO { 181 | return Some(DURATION_ZERO); 182 | } 183 | self.src 184 | .size() 185 | .map(|size| self.interval.mul_f64(size as f64)) 186 | } 187 | 188 | #[inline] 189 | fn animate(&self, elapsed: Duration) -> Self::Item { 190 | let n = match self.duration() { 191 | Some(duration) if duration == DURATION_ZERO => 0, 192 | Some(duration) if elapsed >= duration => self.src.size().unwrap(), 193 | _ => { 194 | let n = elapsed.as_secs_f64() / self.interval.as_secs_f64(); 195 | n as usize 196 | } 197 | }; 198 | self.src.index(n) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/core/animation/take.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{Animation, BaseAnimation}; 8 | use crate::core::DURATION_ZERO; 9 | use std::time::Duration; 10 | /// repeat animations 11 | #[derive(Debug, Clone)] 12 | pub struct Take { 13 | src: T, 14 | duration: Duration, 15 | } 16 | 17 | impl Take { 18 | #[inline] 19 | pub(super) fn new(src: T, duration: Duration) -> Self { 20 | Take { src, duration } 21 | } 22 | } 23 | 24 | impl BaseAnimation for Take { 25 | type Item = T::Item; 26 | #[inline] 27 | fn duration(&self) -> Option { 28 | if self.duration > DURATION_ZERO { 29 | if let Some(duration) = self.src.duration() { 30 | if self.duration >= duration { 31 | return Some(duration); 32 | } 33 | } 34 | }; 35 | 36 | Some(self.duration) 37 | } 38 | 39 | #[inline] 40 | fn animate(&self, elapsed: Duration) -> Self::Item { 41 | let duration = self.duration().unwrap(); 42 | if elapsed > duration { 43 | self.src.animate(duration) 44 | } else { 45 | self.src.animate(elapsed) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/core/clock.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::Sub, 3 | time::{Duration, Instant}, 4 | }; 5 | 6 | /// [`Clock`] allow you to control the time 7 | pub trait Clock: Default { 8 | /// represents the time 9 | type Time: Sub + Clone; 10 | 11 | /// current time 12 | fn now(&self) -> Self::Time; 13 | } 14 | 15 | /// a default implementation of [`Clock`] 16 | #[derive(Debug, Default)] 17 | pub struct DefaultClock; 18 | 19 | impl Clock for DefaultClock { 20 | type Time = Instant; 21 | #[inline] 22 | fn now(&self) -> Instant { 23 | Instant::now() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/core/easing.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use dyn_clone::DynClone; 8 | pub use functions::*; 9 | 10 | /// easing function 11 | pub trait Function: DynClone { 12 | /// output time based on normalized time, which is between 0-1 13 | fn ease(&self, normalized_time: f64) -> f64; 14 | } 15 | 16 | impl Function for Box { 17 | #[inline] 18 | fn ease(&self, normalized_time: f64) -> f64 { 19 | (**self).ease(normalized_time) 20 | } 21 | } 22 | 23 | #[doc(hidden)] 24 | #[allow(missing_docs)] 25 | pub trait FunctionClone: Function + Clone {} 26 | 27 | impl FunctionClone for F {} 28 | 29 | /// easing mode, default [`EasingMode::In`] 30 | #[derive(Debug, Clone, Copy)] 31 | pub enum EasingMode { 32 | /// ease in 33 | In, 34 | /// ease out 35 | Out, 36 | /// ease in & out 37 | InOut, 38 | } 39 | 40 | impl Default for EasingMode { 41 | fn default() -> Self { 42 | EasingMode::In 43 | } 44 | } 45 | 46 | impl EasingMode { 47 | #[inline] 48 | fn apply f64>(&self, time: f64, f: &F) -> f64 { 49 | let time = crate::utils::check_time(time); 50 | match self { 51 | EasingMode::In => f(time), 52 | EasingMode::Out => 1.0 - f(1.0 - time), 53 | EasingMode::InOut => { 54 | if time < 0.5 { 55 | f(time * 2.0) / 2.0 56 | } else { 57 | //let t = time * 2.0 - 1.0; 58 | //let v = 1.0 - f(1.0 - t); 59 | //0.5 + v / 2.0 60 | 1.0 - f(2.0 - time * 2.0) / 2.0 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | /// [`Function`] builder 68 | #[derive(Debug, Clone)] 69 | pub struct Easing f64> { 70 | mode: EasingMode, 71 | f: F, 72 | } 73 | 74 | impl f64> Easing { 75 | /// set ease mod, see [`EasingMode`] 76 | #[inline] 77 | pub fn mode(mut self, mode: EasingMode) -> Self { 78 | self.mode = mode; 79 | self 80 | } 81 | } 82 | 83 | impl f64 + Clone> Function for Easing { 84 | #[inline] 85 | fn ease(&self, normalized_time: f64) -> f64 { 86 | self.mode.apply(normalized_time, &self.f) 87 | } 88 | } 89 | 90 | impl f64 + Clone + 'static> From for Easing { 91 | #[inline] 92 | fn from(f: F) -> Self { 93 | functions::custom(f) 94 | } 95 | } 96 | 97 | /// please refer to: 98 | /// - https://easings.net 99 | /// - http://robertpenner.com/easing/ 100 | /// - https://docs.microsoft.com/en-us/dotnet/desktop/wpf/graphics-multimedia/easing-functions?redirectedfrom=MSDN&view=netframeworkdesktop-4.8 101 | mod functions { 102 | use super::Easing; 103 | use std::f64::consts::PI; 104 | 105 | /// linear x=t 106 | #[inline] 107 | pub fn linear() -> Easing f64 + Clone> { 108 | custom(|t| t) 109 | } 110 | 111 | /// sine ease 112 | #[inline] 113 | pub fn sine_ease() -> Easing f64 + Clone> { 114 | custom(move |t| 1.0 - ((t * PI) / 2.0).cos()) 115 | } 116 | 117 | /// pow ease 118 | #[inline] 119 | pub fn pow_ease(power: f32) -> Easing f64 + Clone> { 120 | let power = power as f64; 121 | custom(move |t| t.powf(power)) 122 | } 123 | 124 | /// quadratic ease 125 | #[inline] 126 | pub fn quad_ease() -> Easing f64 + Clone> { 127 | pow_ease(2.0) 128 | } 129 | 130 | /// cubic ease 131 | #[inline] 132 | pub fn cubic_ease() -> Easing f64 + Clone> { 133 | pow_ease(3.0) 134 | } 135 | 136 | /// quart ease 137 | #[inline] 138 | pub fn quart_ease() -> Easing f64 + Clone> { 139 | pow_ease(4.0) 140 | } 141 | 142 | /// qunit ease 143 | #[inline] 144 | pub fn qunit_ease() -> Easing f64 + Clone> { 145 | pow_ease(5.0) 146 | } 147 | 148 | /// expo ease 149 | #[inline] 150 | pub fn expo_ease() -> Easing f64 + Clone> { 151 | custom(|t| { 152 | if t == 0.0 { 153 | 0.0 154 | } else { 155 | (2.0_f64).powf(10.0 * t - 10.0) 156 | } 157 | }) 158 | } 159 | 160 | /// circle ease 161 | #[inline] 162 | pub fn circle_ease() -> Easing f64 + Clone> { 163 | custom(|t| 1.0 - (1.0 - t.powi(2)).sqrt()) 164 | } 165 | 166 | /// back ease 167 | #[inline] 168 | pub fn back_ease(amplitude: f64) -> Easing f64 + Clone> { 169 | custom(move |t| t.powi(3) - t * amplitude * (t * PI).sin()) 170 | } 171 | 172 | /// elastic ease 173 | #[inline] 174 | pub fn elastic_ease() -> Easing f64 + Clone> { 175 | const C4: f64 = (2.0 * PI) / 3.0; 176 | custom(|t| { 177 | if t == 0.0 { 178 | 0.0 179 | } else if (1.0 - t).abs() < f64::EPSILON { 180 | 1.0 181 | } else { 182 | -(2.0_f64.powf(10.0 * t - 10.0) * ((t * 10.0 - 10.75) * C4).sin()) 183 | } 184 | }) 185 | } 186 | 187 | /// bounce ease 188 | #[inline] 189 | pub fn bounce_ease() -> Easing f64 + Clone> { 190 | const N1: f64 = 7.5625; 191 | const D1: f64 = 2.75; 192 | custom(|t| { 193 | let v = if t < 1.0 / D1 { 194 | N1 * t * t 195 | } else if t < 2.0 / D1 { 196 | let t = t - 1.5 / D1; 197 | N1 * t * t + 0.75 198 | } else if t < 2.5 / D1 { 199 | let t = t - 2.25 / D1; 200 | N1 * t * t + 0.9375 201 | } else { 202 | let t = t - 2.625 / D1; 203 | N1 * t * t + 0.984375 204 | }; 205 | 1.0 - v 206 | }) 207 | } 208 | 209 | /// custom ease function 210 | #[inline] 211 | pub fn custom f64 + Clone + 'static>(f: F) -> Easing { 212 | Easing { 213 | mode: Default::default(), 214 | f, 215 | } 216 | } 217 | } 218 | 219 | #[cfg(test)] 220 | mod test { 221 | use super::*; 222 | #[test] 223 | fn test_linear() { 224 | let modes = [EasingMode::In, EasingMode::Out, EasingMode::InOut]; 225 | for mode in modes.iter() { 226 | let f = linear().mode(*mode); 227 | let v = f.ease(0.0); 228 | assert_eq!(v, 0.0); 229 | 230 | let v = f.ease(0.5); 231 | assert_eq!(v, 0.5); 232 | 233 | let v = f.ease(0.75); 234 | assert_eq!(v, 0.75); 235 | 236 | let v = f.ease(1.0); 237 | assert_eq!(v, 1.0); 238 | } 239 | } 240 | 241 | #[test] 242 | fn test_quad_in() { 243 | let f = quad_ease().mode(EasingMode::In); 244 | let v = f.ease(0.0); 245 | assert_eq!(v, 0.0); 246 | 247 | let v = f.ease(0.5); 248 | assert_eq!(v, 0.25); 249 | 250 | let v = f.ease(0.75); 251 | assert_eq!(v, 0.5625); 252 | 253 | let v = f.ease(1.0); 254 | assert_eq!(v, 1.0); 255 | } 256 | 257 | #[test] 258 | fn test_quad_out() { 259 | let f = quad_ease().mode(EasingMode::Out); 260 | let v = f.ease(0.0); 261 | assert_eq!(v, 0.0); 262 | 263 | let v = f.ease(0.5); 264 | assert_eq!(v, 0.75); 265 | 266 | let v = f.ease(0.75); 267 | assert_eq!(v, 0.9375); 268 | 269 | let v = f.ease(1.0); 270 | assert_eq!(v, 1.0); 271 | } 272 | 273 | #[test] 274 | fn test_quad_in_out() { 275 | let f = quad_ease().mode(EasingMode::InOut); 276 | let v = f.ease(0.0); 277 | assert_eq!(v, 0.0); 278 | 279 | let v = f.ease(0.5); 280 | assert_eq!(v, 0.5); 281 | 282 | let v = f.ease(0.75); 283 | assert_eq!(v, 0.875); 284 | 285 | let v = f.ease(1.0); 286 | assert_eq!(v, 1.0); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | /// make a type animatable 8 | pub mod animatable; 9 | pub(crate) mod animation; 10 | /// ease functions 11 | pub mod easing; 12 | mod options; 13 | /// timeline definitions 14 | pub mod timeline; 15 | /// utilities 16 | pub mod utils; 17 | 18 | mod clock; 19 | 20 | use std::time::Duration; 21 | 22 | #[doc(inline)] 23 | pub use animatable::Animatable; 24 | #[doc(inline)] 25 | pub use animation::{Animation, Cursor, KeyFrame, KeyTime, SeekFrom}; 26 | #[doc(inline)] 27 | pub use easing::Function; 28 | #[doc(inline)] 29 | pub use options::*; 30 | #[doc(inline)] 31 | pub use timeline::Timeline; 32 | 33 | /// deprecated, please use [`builder::linear`] instead 34 | #[deprecated] 35 | #[doc(hidden)] 36 | pub use animation::linear; 37 | 38 | /// [`Duration`]::ZERO 39 | pub const DURATION_ZERO: Duration = Duration::from_secs(0); 40 | 41 | /// default animation time, 1 second 42 | pub const DEFAULT_ANIMATION_DURATION: Duration = Duration::from_secs(1); 43 | 44 | /// animation builders 45 | pub mod builder { 46 | #[doc(inline)] 47 | pub use super::animation::{constant, key_frames, linear, steps, steps_infinite}; 48 | } 49 | -------------------------------------------------------------------------------- /src/core/options.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use crate::{ 8 | core::{animation::Primitive, easing, Animatable}, 9 | Animation, Timeline, DEFAULT_ANIMATION_DURATION, 10 | }; 11 | use std::{fmt, time::Duration}; 12 | 13 | /// how an [`Animation`] repeats its simple duration 14 | #[derive(Debug, Clone, Copy)] 15 | pub enum RepeatBehavior { 16 | /// specifies the number of times the simple duration of a an [`Animation`] plays. default 1.0 17 | Count(f32), 18 | /// The [`Animation`] repeats indefinitely 19 | Forever, 20 | } 21 | 22 | impl Default for RepeatBehavior { 23 | #[inline] 24 | fn default() -> Self { 25 | RepeatBehavior::Count(1.0) 26 | } 27 | } 28 | 29 | /// options to build an [`Animation`] 30 | pub struct Options { 31 | pub(crate) from: T, 32 | pub(crate) to: T, 33 | pub(crate) auto_reverse: bool, 34 | pub(crate) skip: Option, 35 | pub(crate) delay: Option, 36 | pub(crate) duration: Duration, 37 | pub(crate) repeat: RepeatBehavior, 38 | pub(crate) easing: Box, 39 | } 40 | 41 | impl Default for Options { 42 | fn default() -> Self { 43 | Self { 44 | from: Default::default(), 45 | to: Default::default(), 46 | auto_reverse: false, 47 | skip: None, 48 | delay: None, 49 | duration: DEFAULT_ANIMATION_DURATION, 50 | repeat: Default::default(), 51 | easing: Box::new(easing::linear()), 52 | } 53 | } 54 | } 55 | 56 | impl Options { 57 | /// create new [`Options`] from range 58 | #[inline] 59 | pub fn new(from: T, to: T) -> Self { 60 | Options { 61 | from, 62 | to, 63 | auto_reverse: false, 64 | skip: None, 65 | delay: None, 66 | duration: DEFAULT_ANIMATION_DURATION, 67 | repeat: Default::default(), 68 | easing: Box::new(easing::cubic_ease()), 69 | } 70 | } 71 | 72 | /// animation from value 73 | #[inline] 74 | pub fn from(mut self, value: T) -> Self { 75 | self.from = value; 76 | self 77 | } 78 | 79 | /// animation to value 80 | #[inline] 81 | pub fn to(mut self, value: T) -> Self { 82 | self.to = value; 83 | self 84 | } 85 | 86 | /// auto reverse animation when it reaches the end; default false. 87 | /// Note: it will not increase the duration or repeat times. 88 | /// 89 | /// auto_reverse | effect 90 | /// ------------- | ------------------- 91 | /// false | from -> to 92 | /// true | from -> to -> from 93 | /// 94 | #[inline] 95 | pub fn auto_reverse(mut self, auto_reverse: bool) -> Self { 96 | self.auto_reverse = auto_reverse; 97 | self 98 | } 99 | 100 | /// deprecated, use [`Options::skip()`] instead 101 | #[deprecated()] 102 | #[inline] 103 | pub fn begin_time(self, begin_time: Duration) -> Self { 104 | self.skip(begin_time) 105 | } 106 | 107 | /// play animation from the specified progress, same effect as [`Animation::skip()`] 108 | /// 109 | /// see [`Animation::skip()`] 110 | #[inline] 111 | pub fn skip(mut self, skip: Duration) -> Self { 112 | self.skip = Some(skip); 113 | self 114 | } 115 | 116 | /// play animation with delay, same effect as [`Animation::delay()`]; 117 | /// take effect only once when the animation loops more than once. 118 | /// 119 | /// see [`Animation::delay()`] 120 | #[inline] 121 | pub fn delay(mut self, delay: Duration) -> Self { 122 | self.delay = Some(delay); 123 | self 124 | } 125 | 126 | /// animation simple duration, this animation will last for how long if it plays once. default 1 second. 127 | /// 128 | /// If [`Options::repeat()`] is specified, the animation might play more than once. 129 | #[inline] 130 | pub fn duration(mut self, duration: Duration) -> Self { 131 | self.duration = duration; 132 | self 133 | } 134 | 135 | /// repeat behavior 136 | #[inline] 137 | pub fn repeat(mut self, behavior: RepeatBehavior) -> Self { 138 | if let RepeatBehavior::Count(count) = behavior { 139 | assert!(count >= 0.0); 140 | } 141 | self.repeat = behavior; 142 | self 143 | } 144 | 145 | /// your [`Animation`] repeats indefinitely 146 | /// 147 | /// alias of [`Options::cycle()`], see [`Options::repeat()`] 148 | #[inline] 149 | pub fn forever(self) -> Self { 150 | self.cycle() 151 | } 152 | 153 | /// your [`Animation`] repeats indefinitely 154 | /// 155 | pub fn cycle(mut self) -> Self { 156 | self.repeat = RepeatBehavior::Forever; 157 | self 158 | } 159 | 160 | /// your [`Animation`] repeats for specified times 161 | /// 162 | /// see [`Options::repeat()`] 163 | /// 164 | /// panics if count<=0 165 | #[inline] 166 | pub fn times(mut self, count: f32) -> Self { 167 | assert!(count >= 0.0); 168 | self.repeat = RepeatBehavior::Count(count); 169 | self 170 | } 171 | 172 | /// set ease function, default [`easing::linear`] 173 | #[inline] 174 | pub fn easing(mut self, func: impl easing::Function + Clone + 'static) -> Self { 175 | self.easing = Box::new(func); 176 | self 177 | } 178 | 179 | /// build [`Animation`] 180 | #[inline] 181 | pub fn build(self) -> impl Animation + Clone { 182 | Primitive::new(self) 183 | } 184 | } 185 | 186 | impl Options { 187 | /// build [`Timeline`] and start animation 188 | #[inline] 189 | pub fn begin_animation(self) -> Timeline { 190 | let mut timeline: Timeline<_> = self.into(); 191 | timeline.begin(); 192 | timeline 193 | } 194 | } 195 | 196 | impl fmt::Debug for Options { 197 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 198 | f.debug_struct("Options") 199 | .field("from", &self.from) 200 | .field("to", &self.to) 201 | .field("auto_reverse", &self.auto_reverse) 202 | .field("begin_time", &self.skip) 203 | .field("duration", &self.duration) 204 | .field("repeat", &self.repeat) 205 | .field("easing", &"???") 206 | .finish() 207 | } 208 | } 209 | 210 | impl Clone for Options { 211 | #[inline] 212 | fn clone(&self) -> Self { 213 | Self { 214 | from: self.from.clone(), 215 | to: self.to.clone(), 216 | auto_reverse: self.auto_reverse, 217 | skip: self.skip, 218 | delay: self.delay, 219 | duration: self.duration, 220 | repeat: self.repeat, 221 | easing: dyn_clone::clone_box(&*self.easing), 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/core/timeline.rs: -------------------------------------------------------------------------------- 1 | // anim 2 | // 3 | // A framework independent animation library for rust, works nicely with Iced and the others 4 | // Copyright: 2021, Joylei 5 | // License: MIT 6 | 7 | use super::{ 8 | animation::{Animation, BaseAnimation, Boxed, IsFinished}, 9 | clock::*, 10 | Animatable, Options, DURATION_ZERO, 11 | }; 12 | use std::{ 13 | fmt::Debug, 14 | sync::atomic::AtomicUsize, 15 | time::{Duration, Instant}, 16 | }; 17 | /// unique id 18 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 19 | pub struct TimelineId(usize); 20 | 21 | /// animation status 22 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 23 | pub enum Status { 24 | /// animation not yet run 25 | Idle, 26 | /// animation is in progress 27 | Animating, 28 | /// animation was paused 29 | Paused, 30 | /// animation was completed 31 | Completed, 32 | } 33 | 34 | impl Status { 35 | /// is animation idle? 36 | #[inline] 37 | pub fn is_idle(&self) -> bool { 38 | self == &Status::Idle 39 | } 40 | /// is animation in progress? 41 | #[inline] 42 | pub fn is_animating(&self) -> bool { 43 | self == &Status::Animating 44 | } 45 | /// is animation paused? 46 | #[inline] 47 | pub fn is_paused(&self) -> bool { 48 | self == &Status::Paused 49 | } 50 | /// is animation completed? 51 | #[inline] 52 | pub fn is_completed(&self) -> bool { 53 | self == &Status::Completed 54 | } 55 | } 56 | 57 | /// animation state 58 | #[derive(Debug)] 59 | enum State