iced/application/
timed.rs

1//! An [`Application`] that receives an [`Instant`] in update logic.
2use crate::application::{Application, BootFn, ViewFn};
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 BootFn<State, Message>,
24    update: impl UpdateFn<State, Message>,
25    subscription: impl Fn(&State) -> Subscription<Message>,
26    view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
27) -> Application<
28    impl Program<State = State, Message = (Message, Instant), Theme = Theme>,
29>
30where
31    State: 'static,
32    Message: Send + 'static,
33    Theme: 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: Send + 'static,
72        Theme: theme::Base + 'static,
73        Renderer: program::Renderer + 'static,
74        Boot: self::BootFn<State, Message>,
75        Update: self::UpdateFn<State, Message>,
76        Subscription: Fn(&State) -> self::Subscription<Message>,
77        View: for<'a> self::ViewFn<'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 settings(&self) -> Settings {
92            Settings::default()
93        }
94
95        fn window(&self) -> Option<iced_core::window::Settings> {
96            Some(window::Settings::default())
97        }
98
99        fn boot(&self) -> (State, Task<Self::Message>) {
100            let (state, task) = self.boot.boot();
101
102            (state, task.map(|message| (message, Instant::now())))
103        }
104
105        fn update(
106            &self,
107            state: &mut Self::State,
108            (message, now): Self::Message,
109        ) -> Task<Self::Message> {
110            debug::hot(move || {
111                self.update
112                    .update(state, message, now)
113                    .into()
114                    .map(|message| (message, Instant::now()))
115            })
116        }
117
118        fn view<'a>(
119            &self,
120            state: &'a Self::State,
121            _window: window::Id,
122        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
123            debug::hot(|| {
124                self.view
125                    .view(state)
126                    .map(|message| (message, Instant::now()))
127            })
128        }
129
130        fn subscription(
131            &self,
132            state: &Self::State,
133        ) -> self::Subscription<Self::Message> {
134            debug::hot(|| {
135                (self.subscription)(state)
136                    .map(|message| (message, Instant::now()))
137            })
138        }
139    }
140
141    Application {
142        raw: Instance {
143            boot,
144            update,
145            subscription,
146            view,
147            _state: PhantomData,
148            _message: PhantomData,
149            _theme: PhantomData,
150            _renderer: PhantomData,
151        },
152        settings: Settings::default(),
153        window: window::Settings::default(),
154        presets: Vec::new(),
155    }
156}
157
158/// The update logic of some timed [`Application`].
159///
160/// This is like [`application::UpdateFn`](super::UpdateFn),
161/// but it also takes an [`Instant`].
162pub trait UpdateFn<State, Message> {
163    /// Processes the message and updates the state of the [`Application`].
164    fn update(
165        &self,
166        state: &mut State,
167        message: Message,
168        now: Instant,
169    ) -> impl Into<Task<Message>>;
170}
171
172impl<State, Message> UpdateFn<State, Message> for () {
173    fn update(
174        &self,
175        _state: &mut State,
176        _message: Message,
177        _now: Instant,
178    ) -> impl Into<Task<Message>> {
179    }
180}
181
182impl<T, State, Message, C> UpdateFn<State, Message> for T
183where
184    T: Fn(&mut State, Message, Instant) -> C,
185    C: Into<Task<Message>>,
186{
187    fn update(
188        &self,
189        state: &mut State,
190        message: Message,
191        now: Instant,
192    ) -> impl Into<Task<Message>> {
193        self(state, message, now)
194    }
195}