Skip to main content

iced_widget/
transition.rs

1//! A widget to make animated views.
2use std::hash::{DefaultHasher, Hash, Hasher};
3
4use crate::core::animation::{Animation, Float};
5use crate::core::layout::{self, Layout};
6use crate::core::mouse;
7use crate::core::overlay;
8use crate::core::renderer;
9use crate::core::shell;
10use crate::core::time::Instant;
11use crate::core::widget::{self, Operation, Tree, tree};
12use crate::core::{self, Event, Length, Rectangle, Shell, Size, Vector, Widget};
13
14/// The logic of a [`Transition`].
15pub trait Program: 'static {
16    /// The type of value that the [`Program`] animates.
17    type Value: Copy + 'static;
18
19    /// Transitions the [`Program`] from its current state towards the given value at
20    /// the given time.
21    fn go(&mut self, value: Self::Value, now: Instant);
22
23    /// Returns true if the [`Program`] is currently in progress.
24    fn is_animating(&self, now: Instant) -> bool;
25}
26
27impl<I> Program for Animation<I>
28where
29    I: Float + Clone + Copy + PartialEq + 'static,
30{
31    type Value = I;
32
33    fn go(&mut self, value: Self::Value, now: Instant) {
34        self.go_mut(value, now);
35    }
36
37    fn is_animating(&self, now: Instant) -> bool {
38        self.is_animating(now)
39    }
40}
41
42/// A widget that can be used to animate its contents.
43pub struct Transition<'a, Message, W, P>
44where
45    P: Program,
46{
47    init: Box<dyn Fn() -> P + 'a>,
48    view: Box<dyn Fn(&P, Instant) -> W + 'a>,
49    on_finish: Option<Box<dyn Fn() -> Message + 'a>>,
50    element: Option<W>,
51    next_element: Option<W>,
52    last_limits: layout::Limits,
53    key: Key,
54    id: Option<widget::Id>,
55    value: P::Value,
56}
57
58impl<'a, Message, W, P> Transition<'a, Message, W, P>
59where
60    P: Program,
61{
62    /// Creates a new [`Transition`].
63    ///
64    /// The `init` closure will be used to initialize an animation.
65    ///
66    /// The `view` closure will receive the animation and an [`Instant`], which can be used for interpolating values.
67    /// This will be called every frame until the given `value` is reached.
68    pub fn new(
69        init: impl Fn() -> P + 'a,
70        value: P::Value,
71        view: impl Fn(&P, Instant) -> W + 'a,
72    ) -> Self {
73        Self {
74            init: Box::new(init),
75            view: Box::new(view),
76            on_finish: None,
77            element: None,
78            next_element: None,
79            last_limits: layout::Limits::new(Size::ZERO, Size::ZERO),
80            key: Key::default(),
81            id: None,
82            value,
83        }
84    }
85
86    /// Sets the [`widget::Id`] of the [`Transition`].
87    ///
88    /// The [`widget::Id`] can subsequently be used to reset the [`Animation`] via [`reset`].
89    pub fn id(mut self, id: impl Into<widget::Id>) -> Self {
90        self.id = Some(id.into());
91        self
92    }
93
94    /// Sets the key of the [`Transition`] widget for continuity.
95    ///
96    /// Changing the key will reset the [`Animation`].
97    pub fn key(mut self, key: impl Hash) -> Self {
98        self.key = Key::new(key);
99        self
100    }
101
102    /// Sets the message that will be produced when the [`Transition`] has finished animating.
103    ///
104    /// Note that if an [`Animation`] is set to loop forever, the message will never be produced!
105    pub fn on_finish(self, on_finish: Message) -> Self
106    where
107        Message: Clone + 'a,
108    {
109        self.on_finish_maybe(on_finish)
110    }
111
112    /// Sets the message that will be produced when the [`Transition`] has finished animating, if [`Some`].
113    ///
114    /// Note that if an [`Animation`] is set to loop forever, the message will never be produced!
115    pub fn on_finish_maybe(mut self, on_finish: impl Into<Option<Message>>) -> Self
116    where
117        Message: Clone + 'a,
118    {
119        self.on_finish = on_finish
120            .into()
121            .map(|on_finish| Box::new(move || on_finish.clone()) as _);
122
123        self
124    }
125
126    /// Sets the message that will be produced when the [`Transition`] has finished animating.
127    ///
128    /// This is analogous to [`Transition::on_finish`], but using a closure to produce
129    /// the message.
130    ///
131    /// This closure will only be called when the [`Transition`] has actually finished animating and,
132    /// therefore, this method is useful to reduce overhead if creating the resulting
133    /// message is slow.
134    ///
135    /// Note that if an [`Animation`] is set to loop forever, the message will never be produced!
136    pub fn on_finish_with(mut self, on_finish: impl Fn() -> Message + 'a) -> Self {
137        self.on_finish = Some(Box::new(on_finish));
138        self
139    }
140}
141
142impl<Message, W, P> widget::Meta for Transition<'_, Message, W, P> where P: Program {}
143
144impl<Message, W, Theme, Renderer, P> Widget<Message, Theme, Renderer>
145    for Transition<'_, Message, W, P>
146where
147    P: Program,
148    W: Widget<Message, Theme, Renderer>,
149{
150    fn size(&self) -> Size<Length> {
151        self.element.size()
152    }
153
154    fn tag(&self) -> tree::Tag {
155        tree::Tag::of::<State<P>>()
156    }
157
158    fn state(&self) -> tree::State {
159        tree::State::new(State {
160            animation: (self.init)(),
161            instant: Instant::now(),
162            key: self.key,
163            should_reset: false,
164            size: None,
165        })
166    }
167
168    fn diff(&mut self, tree: &mut Tree) {
169        let State::<P> {
170            animation,
171            key: old_key,
172            should_reset,
173            instant,
174            ..
175        } = tree.state.downcast_mut();
176
177        if *old_key != self.key {
178            *old_key = self.key;
179            *animation = (self.init)();
180            *should_reset = false;
181        }
182
183        if let Some(next_element) = self.next_element.take() {
184            self.element = Some(next_element);
185        } else {
186            self.element = Some((self.view)(animation, *instant));
187        }
188
189        tree.diff_children(std::slice::from_mut(&mut self.element));
190    }
191
192    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) {
193        self.last_limits = *limits;
194
195        self.element
196            .layout(&mut tree.children[0], renderer, &limits.loose());
197
198        tree.size = tree.children[0].size;
199    }
200
201    fn update(
202        &mut self,
203        tree: &mut Tree,
204        event: &Event,
205        layout: Layout,
206        cursor: mouse::Cursor,
207        renderer: &Renderer,
208        shell: &mut Shell<'_, Message>,
209        viewport: &Rectangle,
210    ) {
211        if let core::Event::Window(core::window::Event::RedrawRequested(redraw)) = event {
212            let State::<P> {
213                animation,
214                instant,
215                should_reset,
216                size,
217                ..
218            } = tree.state.downcast_mut();
219
220            if instant == redraw {
221                shell.request_redraw();
222            } else {
223                let was_animating = animation.is_animating(*instant);
224
225                if *should_reset {
226                    *animation = (self.init)();
227                    *should_reset = false;
228                }
229
230                *instant = *redraw;
231                animation.go(self.value, *instant);
232
233                let is_animating = animation.is_animating(*instant);
234                let just_finished = was_animating && !is_animating;
235
236                if is_animating || just_finished {
237                    let size = *size;
238
239                    let mut new = (self.view)(animation, *instant);
240                    tree.diff_children(&mut [&mut new]);
241
242                    let new_size = new.size();
243
244                    if size != Some(new_size) {
245                        self.next_element = Some(new);
246                        shell.invalidate_layout_with(shell::Diff::Perform);
247
248                        let state = tree.state.downcast_mut::<State<P>>();
249                        state.size = Some(new_size);
250                    } else {
251                        self.element = Some(new);
252                        self.element
253                            .layout(&mut tree.children[0], renderer, &self.last_limits);
254                    }
255
256                    shell.request_redraw();
257                }
258
259                if just_finished && let Some(on_finish) = &self.on_finish {
260                    shell.publish(on_finish());
261                }
262            }
263        }
264
265        self.element.update(
266            &mut tree.children[0],
267            event,
268            layout,
269            cursor,
270            renderer,
271            shell,
272            viewport,
273        );
274    }
275
276    fn draw(
277        &self,
278        tree: &Tree,
279        renderer: &mut Renderer,
280        theme: &Theme,
281        style: &renderer::Style,
282        layout: Layout,
283        cursor: mouse::Cursor,
284        viewport: &Rectangle,
285    ) {
286        self.element.draw(
287            &tree.children[0],
288            renderer,
289            theme,
290            style,
291            layout,
292            cursor,
293            viewport,
294        );
295    }
296
297    fn mouse_interaction(
298        &self,
299        tree: &Tree,
300        layout: Layout,
301        cursor: mouse::Cursor,
302        viewport: &Rectangle,
303        renderer: &Renderer,
304    ) -> mouse::Interaction {
305        self.element
306            .mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
307    }
308
309    fn operate(
310        &mut self,
311        tree: &mut Tree,
312        layout: Layout,
313        viewport: &Rectangle,
314        renderer: &Renderer,
315        operation: &mut dyn widget::Operation,
316    ) {
317        let mut should_reset = ShouldReset(false);
318        operation.custom(self.id.as_ref(), layout.bounds(), &mut should_reset);
319
320        if should_reset.0 {
321            tree.state.downcast_mut::<State<P>>().should_reset = true;
322        }
323
324        self.element
325            .operate(&mut tree.children[0], layout, viewport, renderer, operation);
326    }
327
328    fn overlay<'a>(
329        &'a mut self,
330        tree: &'a mut Tree,
331        layout: Layout,
332        renderer: &Renderer,
333        viewport: &Rectangle,
334        translation: Vector,
335        window: Size,
336    ) -> Vec<overlay::Element<'a, Message, Theme, Renderer>> {
337        self.element.overlay(
338            &mut tree.children[0],
339            layout,
340            renderer,
341            viewport,
342            translation,
343            window,
344        )
345    }
346}
347
348struct State<P: Program> {
349    animation: P,
350    instant: Instant,
351    key: Key,
352    should_reset: bool,
353    size: Option<Size<Length>>,
354}
355
356#[derive(Default, Clone, Copy, Hash, PartialEq, Eq)]
357struct Key(u64);
358
359impl Key {
360    fn new(data: impl Hash) -> Self {
361        let mut hasher = DefaultHasher::new();
362        data.hash(&mut hasher);
363        Self(hasher.finish())
364    }
365}
366
367struct ShouldReset(bool);
368
369/// Reset the [`Animation`] of a [`Transition`].
370pub fn reset<Message>(id: impl Into<widget::Id>) -> iced_runtime::Task<Message>
371where
372    Message: iced_runtime::futures::MaybeSend + 'static,
373{
374    let id = id.into();
375    iced_runtime::task::widget(reset_raw(id)).discard()
376}
377
378/// An [`Operation`] to reset the [`Animation`] of a [`Transition`].
379pub fn reset_raw(id: impl Into<widget::Id>) -> impl Operation {
380    struct Reset(widget::Id);
381
382    impl Operation for Reset {
383        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<()>)) {
384            operate(self);
385        }
386
387        fn custom(
388            &mut self,
389            id: Option<&widget::Id>,
390            _bounds: Rectangle,
391            state: &mut dyn std::any::Any,
392        ) {
393            if id == Some(&self.0)
394                && let Some(ShouldReset(should_reset)) = state.downcast_mut()
395            {
396                *should_reset = true;
397            }
398        }
399    }
400
401    Reset(id.into())
402}