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