iced/application/
timed.rs

1//! An [`Application`] that receives an [`Instant`] in update logic.
2use crate::application::{Application, Boot, View};
3use crate::program;
4use crate::theme;
5use crate::time::Instant;
6use crate::window;
7use crate::{Element, Program, Settings, Subscription, Task};
8
9use iced_debug as debug;
10
11/// Creates an [`Application`] with an `update` function that also
12/// takes the [`Instant`] of each `Message`.
13///
14/// This constructor is useful to create animated applications that
15/// are _pure_ (e.g. without relying on side-effect calls like [`Instant::now`]).
16///
17/// Purity is needed when you want your application to end up in the
18/// same exact state given the same history of messages. This property
19/// enables proper time traveling debugging with [`comet`].
20///
21/// [`comet`]: https://github.com/iced-rs/comet
22pub fn timed<State, Message, Theme, Renderer>(
23    boot: impl Boot<State, Message>,
24    update: impl Update<State, Message>,
25    subscription: impl Fn(&State) -> Subscription<Message>,
26    view: impl for<'a> View<'a, State, Message, Theme, Renderer>,
27) -> Application<
28    impl Program<State = State, Message = (Message, Instant), Theme = Theme>,
29>
30where
31    State: 'static,
32    Message: program::Message + 'static,
33    Theme: Default + theme::Base + 'static,
34    Renderer: program::Renderer + 'static,
35{
36    use std::marker::PhantomData;
37
38    struct Instance<
39        State,
40        Message,
41        Theme,
42        Renderer,
43        Boot,
44        Update,
45        Subscription,
46        View,
47    > {
48        boot: Boot,
49        update: Update,
50        subscription: Subscription,
51        view: View,
52        _state: PhantomData<State>,
53        _message: PhantomData<Message>,
54        _theme: PhantomData<Theme>,
55        _renderer: PhantomData<Renderer>,
56    }
57
58    impl<State, Message, Theme, Renderer, Boot, Update, Subscription, View>
59        Program
60        for Instance<
61            State,
62            Message,
63            Theme,
64            Renderer,
65            Boot,
66            Update,
67            Subscription,
68            View,
69        >
70    where
71        Message: program::Message + 'static,
72        Theme: Default + theme::Base + 'static,
73        Renderer: program::Renderer + 'static,
74        Boot: self::Boot<State, Message>,
75        Update: self::Update<State, Message>,
76        Subscription: Fn(&State) -> self::Subscription<Message>,
77        View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
78    {
79        type State = State;
80        type Message = (Message, Instant);
81        type Theme = Theme;
82        type Renderer = Renderer;
83        type Executor = iced_futures::backend::default::Executor;
84
85        fn name() -> &'static str {
86            let name = std::any::type_name::<State>();
87
88            name.split("::").next().unwrap_or("a_cool_application")
89        }
90
91        fn boot(&self) -> (State, Task<Self::Message>) {
92            let (state, task) = self.boot.boot();
93
94            (state, task.map(|message| (message, Instant::now())))
95        }
96
97        fn update(
98            &self,
99            state: &mut Self::State,
100            (message, now): Self::Message,
101        ) -> Task<Self::Message> {
102            debug::hot(move || {
103                self.update
104                    .update(state, message, now)
105                    .into()
106                    .map(|message| (message, Instant::now()))
107            })
108        }
109
110        fn view<'a>(
111            &self,
112            state: &'a Self::State,
113            _window: window::Id,
114        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
115            debug::hot(|| {
116                self.view
117                    .view(state)
118                    .map(|message| (message, Instant::now()))
119            })
120        }
121
122        fn subscription(
123            &self,
124            state: &Self::State,
125        ) -> self::Subscription<Self::Message> {
126            debug::hot(|| {
127                (self.subscription)(state)
128                    .map(|message| (message, Instant::now()))
129            })
130        }
131    }
132
133    Application {
134        raw: Instance {
135            boot,
136            update,
137            subscription,
138            view,
139            _state: PhantomData,
140            _message: PhantomData,
141            _theme: PhantomData,
142            _renderer: PhantomData,
143        },
144        settings: Settings::default(),
145        window: window::Settings::default(),
146    }
147}
148
149/// The update logic of some timed [`Application`].
150///
151/// This is like [`application::Update`](super::Update),
152/// but it also takes an [`Instant`].
153pub trait Update<State, Message> {
154    /// Processes the message and updates the state of the [`Application`].
155    fn update(
156        &self,
157        state: &mut State,
158        message: Message,
159        now: Instant,
160    ) -> impl Into<Task<Message>>;
161}
162
163impl<State, Message> Update<State, Message> for () {
164    fn update(
165        &self,
166        _state: &mut State,
167        _message: Message,
168        _now: Instant,
169    ) -> impl Into<Task<Message>> {
170    }
171}
172
173impl<T, State, Message, C> Update<State, Message> for T
174where
175    T: Fn(&mut State, Message, Instant) -> C,
176    C: Into<Task<Message>>,
177{
178    fn update(
179        &self,
180        state: &mut State,
181        message: Message,
182        now: Instant,
183    ) -> impl Into<Task<Message>> {
184        self(state, message, now)
185    }
186}