1use 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
15pub struct Runtime<A: Application> {
17 _ph: PhantomData<A>,
18}
19
20impl<A: Application> Runtime<A> {
21 #[must_use]
23 pub fn new() -> Self {
24 Self { _ph: PhantomData }
25 }
26
27 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}