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    Renderer: core::Renderer,
148    P: Program,
149    W: Widget<Message, Theme, Renderer>,
150{
151    fn size(&self) -> Size<Length> {
152        self.element.size()
153    }
154
155    fn tag(&self) -> tree::Tag {
156        tree::Tag::of::<State<P>>()
157    }
158
159    fn state(&self) -> tree::State {
160        tree::State::new(State {
161            animation: (self.init)(),
162            instant: Instant::now(),
163            key: self.key,
164            should_reset: false,
165            size: None,
166        })
167    }
168
169    fn diff(&mut self, tree: &mut Tree) {
170        let State::<P> {
171            animation,
172            key: old_key,
173            should_reset,
174            instant,
175            ..
176        } = tree.state.downcast_mut();
177
178        if *old_key != self.key {
179            *old_key = self.key;
180            *animation = (self.init)();
181            *should_reset = false;
182        }
183
184        if let Some(next_element) = self.next_element.take() {
185            self.element = Some(next_element);
186        } else {
187            self.element = Some((self.view)(animation, *instant));
188        }
189
190        tree.diff_children(std::slice::from_mut(&mut self.element));
191    }
192
193    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) {
194        self.last_limits = *limits;
195
196        self.element
197            .layout(&mut tree.children[0], renderer, &limits.loose());
198
199        tree.size = tree.children[0].size;
200    }
201
202    fn update(
203        &mut self,
204        tree: &mut Tree,
205        event: &Event,
206        layout: Layout,
207        cursor: mouse::Cursor,
208        renderer: &Renderer,
209        shell: &mut Shell<'_, Message>,
210        viewport: &Rectangle,
211    ) {
212        if let core::Event::Window(core::window::Event::RedrawRequested(redraw)) = event {
213            let State::<P> {
214                animation,
215                instant,
216                should_reset,
217                size,
218                ..
219            } = tree.state.downcast_mut();
220
221            if instant == redraw {
222                shell.request_redraw();
223            } else {
224                let was_animating = animation.is_animating(*instant);
225
226                if *should_reset {
227                    *animation = (self.init)();
228                    *should_reset = false;
229                }
230
231                *instant = *redraw;
232                animation.go(self.value, *instant);
233
234                let is_animating = animation.is_animating(*instant);
235                let just_finished = was_animating && !is_animating;
236
237                if is_animating || just_finished {
238                    let size = *size;
239
240                    let mut new = (self.view)(animation, *instant);
241                    tree.diff_children(&mut [&mut new]);
242
243                    let new_size = new.size();
244
245                    if size != Some(new_size) {
246                        self.next_element = Some(new);
247                        shell.invalidate_layout_with(shell::Diff::Perform);
248
249                        let state = tree.state.downcast_mut::<State<P>>();
250                        state.size = Some(new_size);
251                    } else {
252                        self.element = Some(new);
253                        self.element
254                            .layout(&mut tree.children[0], renderer, &self.last_limits);
255                    }
256
257                    shell.request_redraw();
258                }
259
260                if just_finished && let Some(on_finish) = &self.on_finish {
261                    shell.publish(on_finish());
262                }
263            }
264        }
265
266        self.element.update(
267            &mut tree.children[0],
268            event,
269            layout,
270            cursor,
271            renderer,
272            shell,
273            viewport,
274        );
275    }
276
277    fn draw(
278        &self,
279        tree: &Tree,
280        renderer: &mut Renderer,
281        theme: &Theme,
282        style: &renderer::Style,
283        layout: Layout,
284        cursor: mouse::Cursor,
285        viewport: &Rectangle,
286    ) {
287        self.element.draw(
288            &tree.children[0],
289            renderer,
290            theme,
291            style,
292            layout,
293            cursor,
294            viewport,
295        );
296    }
297
298    fn mouse_interaction(
299        &self,
300        tree: &Tree,
301        layout: Layout,
302        cursor: mouse::Cursor,
303        viewport: &Rectangle,
304        renderer: &Renderer,
305    ) -> mouse::Interaction {
306        self.element
307            .mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
308    }
309
310    fn operate(
311        &mut self,
312        tree: &mut Tree,
313        layout: Layout,
314        viewport: &Rectangle,
315        renderer: &Renderer,
316        operation: &mut dyn widget::Operation,
317    ) {
318        let mut should_reset = ShouldReset(false);
319        operation.custom(self.id.as_ref(), layout.bounds(), &mut should_reset);
320
321        if should_reset.0 {
322            tree.state.downcast_mut::<State<P>>().should_reset = true;
323        }
324
325        self.element
326            .operate(&mut tree.children[0], layout, viewport, renderer, operation);
327    }
328
329    fn overlay<'a>(
330        &'a mut self,
331        tree: &'a mut Tree,
332        layout: Layout,
333        renderer: &Renderer,
334        viewport: &Rectangle,
335        translation: Vector,
336        window: Size,
337    ) -> Vec<overlay::Element<'a, Message, Theme, Renderer>> {
338        self.element.overlay(
339            &mut tree.children[0],
340            layout,
341            renderer,
342            viewport,
343            translation,
344            window,
345        )
346    }
347}
348
349struct State<P: Program> {
350    animation: P,
351    instant: Instant,
352    key: Key,
353    should_reset: bool,
354    size: Option<Size<Length>>,
355}
356
357#[derive(Default, Clone, Copy, Hash, PartialEq, Eq)]
358struct Key(u64);
359
360impl Key {
361    fn new(data: impl Hash) -> Self {
362        let mut hasher = DefaultHasher::new();
363        data.hash(&mut hasher);
364        Self(hasher.finish())
365    }
366}
367
368struct ShouldReset(bool);
369
370/// Reset the [`Animation`] of a [`Transition`].
371pub fn reset<Message>(id: impl Into<widget::Id>) -> iced_runtime::Task<Message>
372where
373    Message: iced_runtime::futures::MaybeSend + 'static,
374{
375    let id = id.into();
376    iced_runtime::task::widget(reset_raw(id)).discard()
377}
378
379/// An [`Operation`] to reset the [`Animation`] of a [`Transition`].
380pub fn reset_raw(id: impl Into<widget::Id>) -> impl Operation {
381    struct Reset(widget::Id);
382
383    impl Operation for Reset {
384        fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<()>)) {
385            operate(self);
386        }
387
388        fn custom(
389            &mut self,
390            id: Option<&widget::Id>,
391            _bounds: Rectangle,
392            state: &mut dyn std::any::Any,
393        ) {
394            if id == Some(&self.0)
395                && let Some(ShouldReset(should_reset)) = state.downcast_mut()
396            {
397                *should_reset = true;
398            }
399        }
400    }
401
402    Reset(id.into())
403}