Skip to main content

zest_core/
runtime.rs

1//! The async event loop that drives an [`Application`].
2
3use crate::application::{Application, Subscription, Task};
4use crate::dirty::DirtyRegion;
5use crate::event::{ButtonState, InputEvent, Key, TouchPhase, UiAction};
6use crate::focus::{FocusDirection, FocusState, WidgetId};
7use crate::platform::Platform;
8use crate::screen::ScreenView;
9use crate::widget::Widget;
10use alloc::vec::Vec;
11use core::marker::PhantomData;
12use embassy_futures::select::{Either3, select3};
13use embedded_graphics::{prelude::*, primitives::Rectangle};
14
15/// Drives an [`Application`]'s event loop on a [`Platform`].
16pub struct Runtime<A: Application> {
17    _ph: PhantomData<A>,
18}
19
20impl<A: Application> Runtime<A> {
21    /// Create a runtime for application type `A`.
22    #[must_use]
23    pub fn new() -> Self {
24        Self { _ph: PhantomData }
25    }
26
27    /// Run the application event loop on `platform`: poll input, drive
28    /// `update` and subscriptions, and redraw on demand. Does not return.
29    pub async fn run<P>(self, mut platform: P)
30    where
31        P: Platform<Color = A::Color>,
32    {
33        let (mut app, initial_task) = A::init();
34
35        let mut viewport_size = platform.viewport();
36        let mut pending: Task<A::Message> = initial_task;
37        let mut subscription: Subscription<A::Message> = app.subscription();
38        let mut focus = FocusState::new();
39        let mut pressed_at: Option<Point> = None;
40        let mut dirty = DirtyRegion::full();
41        let capabilities = platform.capabilities();
42
43        loop {
44            let new_viewport = platform.viewport();
45            if new_viewport != viewport_size {
46                viewport_size = new_viewport;
47                dirty = DirtyRegion::full();
48            }
49            let viewport_rect = Rectangle::new(Point::zero(), viewport_size);
50
51            let msg_to_update = {
52                let screen = app.view();
53                let theme = screen.theme();
54                let mut elem = screen.view();
55                elem.arrange(viewport_rect);
56                let mut focus_order = Vec::new();
57                elem.collect_focusable(&mut focus_order);
58                focus.reconcile(&focus_order);
59                elem.sync_focus(focus.focused());
60                if let Some(p) = pressed_at {
61                    elem.mark_pressed(p);
62                }
63
64                let bg = theme.background.base;
65                if !dirty.is_none() {
66                    let _ = platform
67                        .render_with_dirty(&dirty, |renderer| {
68                            if capabilities.supports_clip
69                                && let Some(rects) = dirty.rects()
70                            {
71                                for rect in rects {
72                                    renderer.push_clip(*rect);
73                                    let _ = renderer.fill_rect(viewport_rect, bg);
74                                    let _ = elem.draw(renderer, theme);
75                                    renderer.pop_clip();
76                                }
77                            } else {
78                                let _ = renderer.fill_rect(viewport_rect, bg);
79                                let _ = elem.draw(renderer, theme);
80                            }
81                            Ok(())
82                        })
83                        .await;
84                }
85                dirty = DirtyRegion::none();
86
87                let outcome =
88                    select3(platform.next_event(), pending.next(), subscription.next()).await;
89                let previous_focus = focus.focused();
90
91                match outcome {
92                    Either3::First(None) => return,
93                    Either3::First(Some(event)) => match event {
94                        InputEvent::Touch(t) => {
95                            if t.phase == TouchPhase::Down
96                                && let Some(target) = elem.focus_at(t.point)
97                            {
98                                focus.set(Some(target));
99                            }
100                            let m = elem.handle_touch(t.point, t.phase);
101                            pressed_at = match t.phase {
102                                TouchPhase::Down | TouchPhase::Moved => Some(t.point),
103                                TouchPhase::Up => None,
104                            };
105                            dirty = DirtyRegion::full();
106                            m
107                        }
108                        InputEvent::Key(key) => match (key.key, key.state) {
109                            (Key::Tab, ButtonState::Pressed | ButtonState::Repeated) => {
110                                focus.advance(&focus_order, FocusDirection::Forward);
111                                dirty = Self::focus_dirty(&elem, previous_focus, focus.focused());
112                                None
113                            }
114                            (Key::Escape, ButtonState::Pressed) => {
115                                let msg =
116                                    Self::dispatch_action(&mut elem, &mut focus, UiAction::Cancel);
117                                dirty = Self::dirty_after_action(
118                                    &elem,
119                                    previous_focus,
120                                    focus.focused(),
121                                    msg.is_some(),
122                                );
123                                msg
124                            }
125                            (Key::Enter, ButtonState::Pressed) => {
126                                let msg = Self::dispatch_action(
127                                    &mut elem,
128                                    &mut focus,
129                                    UiAction::Activate,
130                                );
131                                dirty = Self::dirty_after_action(
132                                    &elem,
133                                    previous_focus,
134                                    focus.focused(),
135                                    msg.is_some(),
136                                );
137                                msg
138                            }
139                            (Key::Left, ButtonState::Pressed | ButtonState::Repeated) => {
140                                let msg = Self::dispatch_action(
141                                    &mut elem,
142                                    &mut focus,
143                                    UiAction::NavigateLeft,
144                                );
145                                dirty = Self::dirty_after_action(
146                                    &elem,
147                                    previous_focus,
148                                    focus.focused(),
149                                    msg.is_some(),
150                                );
151                                msg
152                            }
153                            (Key::Right, ButtonState::Pressed | ButtonState::Repeated) => {
154                                let msg = Self::dispatch_action(
155                                    &mut elem,
156                                    &mut focus,
157                                    UiAction::NavigateRight,
158                                );
159                                dirty = Self::dirty_after_action(
160                                    &elem,
161                                    previous_focus,
162                                    focus.focused(),
163                                    msg.is_some(),
164                                );
165                                msg
166                            }
167                            (Key::Up, ButtonState::Pressed | ButtonState::Repeated) => {
168                                let msg = Self::dispatch_action(
169                                    &mut elem,
170                                    &mut focus,
171                                    UiAction::NavigateUp,
172                                );
173                                dirty = Self::dirty_after_action(
174                                    &elem,
175                                    previous_focus,
176                                    focus.focused(),
177                                    msg.is_some(),
178                                );
179                                msg
180                            }
181                            (Key::Down, ButtonState::Pressed | ButtonState::Repeated) => {
182                                let msg = Self::dispatch_action(
183                                    &mut elem,
184                                    &mut focus,
185                                    UiAction::NavigateDown,
186                                );
187                                dirty = Self::dirty_after_action(
188                                    &elem,
189                                    previous_focus,
190                                    focus.focused(),
191                                    msg.is_some(),
192                                );
193                                msg
194                            }
195                            _ => None,
196                        },
197                        InputEvent::Encoder(encoder) => {
198                            if encoder.delta > 0 {
199                                focus.advance(&focus_order, FocusDirection::Forward);
200                            } else if encoder.delta < 0 {
201                                focus.advance(&focus_order, FocusDirection::Backward);
202                            }
203                            dirty = Self::focus_dirty(&elem, previous_focus, focus.focused());
204                            None
205                        }
206                        InputEvent::Action(action) => match action {
207                            UiAction::FocusNext => {
208                                focus.advance(&focus_order, FocusDirection::Forward);
209                                dirty = Self::focus_dirty(&elem, previous_focus, focus.focused());
210                                None
211                            }
212                            UiAction::FocusPrevious => {
213                                focus.advance(&focus_order, FocusDirection::Backward);
214                                dirty = Self::focus_dirty(&elem, previous_focus, focus.focused());
215                                None
216                            }
217                            UiAction::Cancel => {
218                                let msg =
219                                    Self::dispatch_action(&mut elem, &mut focus, UiAction::Cancel);
220                                dirty = Self::dirty_after_action(
221                                    &elem,
222                                    previous_focus,
223                                    focus.focused(),
224                                    msg.is_some(),
225                                );
226                                msg
227                            }
228                            _ => {
229                                let msg = Self::dispatch_action(&mut elem, &mut focus, action);
230                                dirty = Self::dirty_after_action(
231                                    &elem,
232                                    previous_focus,
233                                    focus.focused(),
234                                    msg.is_some(),
235                                );
236                                msg
237                            }
238                        },
239                    },
240                    Either3::Second(m) => {
241                        if m.is_some() {
242                            dirty = DirtyRegion::full();
243                        }
244                        m
245                    }
246                    Either3::Third(m) => {
247                        dirty = DirtyRegion::full();
248                        Some(m)
249                    }
250                }
251            };
252
253            if let Some(m) = msg_to_update {
254                let task = app.update(m);
255                pending.extend(task);
256                subscription.refresh(app.subscription());
257                dirty = DirtyRegion::full();
258            }
259        }
260    }
261
262    fn dispatch_action(
263        elem: &mut impl Widget<A::Color, A::Message>,
264        focus: &mut FocusState,
265        action: UiAction,
266    ) -> Option<A::Message> {
267        let target = focus.focused()?;
268        if let Some(next) = elem.navigate_focus(target, action) {
269            focus.set(Some(next));
270            None
271        } else {
272            elem.route_action(target, action)
273        }
274    }
275
276    fn focus_dirty(
277        elem: &impl Widget<A::Color, A::Message>,
278        previous: Option<WidgetId>,
279        current: Option<WidgetId>,
280    ) -> DirtyRegion {
281        if previous == current {
282            return DirtyRegion::none();
283        }
284
285        let mut dirty = DirtyRegion::none();
286        if let Some(target) = previous
287            && let Some(rect) = elem.focus_rect(target)
288        {
289            dirty.add_rect(rect);
290        }
291        if let Some(target) = current
292            && let Some(rect) = elem.focus_rect(target)
293        {
294            dirty.add_rect(rect);
295        }
296
297        if dirty.is_none() {
298            DirtyRegion::full()
299        } else {
300            dirty
301        }
302    }
303
304    fn dirty_after_action(
305        elem: &impl Widget<A::Color, A::Message>,
306        previous: Option<WidgetId>,
307        current: Option<WidgetId>,
308        emitted_message: bool,
309    ) -> DirtyRegion {
310        if emitted_message {
311            DirtyRegion::full()
312        } else {
313            Self::focus_dirty(elem, previous, current)
314        }
315    }
316}
317
318impl<A: Application> Default for Runtime<A> {
319    fn default() -> Self {
320        Self::new()
321    }
322}