Skip to main content

iced/application/
timed.rs

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