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<impl Program<State = State, Message = (Message, Instant), Theme = Theme>>
28where
29    State: 'static,
30    Message: Send + 'static,
31    Theme: theme::Base + 'static,
32    Renderer: program::Renderer + 'static,
33{
34    use std::marker::PhantomData;
35
36    struct Instance<State, Message, Theme, Renderer, Boot, Update, Subscription, View> {
37        boot: Boot,
38        update: Update,
39        subscription: Subscription,
40        view: View,
41        _state: PhantomData<State>,
42        _message: PhantomData<Message>,
43        _theme: PhantomData<Theme>,
44        _renderer: PhantomData<Renderer>,
45    }
46
47    impl<State, Message, Theme, Renderer, Boot, Update, Subscription, View> Program
48        for Instance<State, Message, Theme, Renderer, Boot, Update, Subscription, View>
49    where
50        Message: Send + 'static,
51        Theme: theme::Base + 'static,
52        Renderer: program::Renderer + 'static,
53        Boot: self::BootFn<State, Message>,
54        Update: self::UpdateFn<State, Message>,
55        Subscription: Fn(&State) -> self::Subscription<Message>,
56        View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
57    {
58        type State = State;
59        type Message = (Message, Instant);
60        type Theme = Theme;
61        type Renderer = Renderer;
62        type Executor = iced_futures::backend::default::Executor;
63
64        fn name() -> &'static str {
65            let name = std::any::type_name::<State>();
66
67            name.split("::").next().unwrap_or("a_cool_application")
68        }
69
70        fn settings(&self) -> Settings {
71            Settings::default()
72        }
73
74        fn window(&self) -> Option<iced_core::window::Settings> {
75            Some(window::Settings::default())
76        }
77
78        fn boot(&self) -> (State, Task<Self::Message>) {
79            let (state, task) = self.boot.boot();
80
81            (state, task.map(|message| (message, Instant::now())))
82        }
83
84        fn update(
85            &self,
86            state: &mut Self::State,
87            (message, now): Self::Message,
88        ) -> Task<Self::Message> {
89            debug::hot(move || {
90                self.update
91                    .update(state, message, now)
92                    .into()
93                    .map(|message| (message, Instant::now()))
94            })
95        }
96
97        fn view<'a>(
98            &self,
99            state: &'a Self::State,
100            _window: window::Id,
101        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
102            debug::hot(|| {
103                self.view
104                    .view(state)
105                    .map(|message| (message, Instant::now()))
106            })
107        }
108
109        fn subscription(&self, state: &Self::State) -> self::Subscription<Self::Message> {
110            debug::hot(|| (self.subscription)(state).map(|message| (message, Instant::now())))
111        }
112    }
113
114    Application {
115        raw: Instance {
116            boot,
117            update,
118            subscription,
119            view,
120            _state: PhantomData,
121            _message: PhantomData,
122            _theme: PhantomData,
123            _renderer: PhantomData,
124        },
125        settings: Settings::default(),
126        window: window::Settings::default(),
127        presets: Vec::new(),
128    }
129}
130
131/// The update logic of some timed [`Application`].
132///
133/// This is like [`application::UpdateFn`](super::UpdateFn),
134/// but it also takes an [`Instant`].
135pub trait UpdateFn<State, Message> {
136    /// Processes the message and updates the state of the [`Application`].
137    fn update(&self, state: &mut State, message: Message, now: Instant)
138    -> impl Into<Task<Message>>;
139}
140
141impl<State, Message> UpdateFn<State, Message> for () {
142    fn update(
143        &self,
144        _state: &mut State,
145        _message: Message,
146        _now: Instant,
147    ) -> impl Into<Task<Message>> {
148    }
149}
150
151impl<T, State, Message, C> UpdateFn<State, Message> for T
152where
153    T: Fn(&mut State, Message, Instant) -> C,
154    C: Into<Task<Message>>,
155{
156    fn update(
157        &self,
158        state: &mut State,
159        message: Message,
160        now: Instant,
161    ) -> impl Into<Task<Message>> {
162        self(state, message, now)
163    }
164}