Skip to main content

inset_shell/
lib.rs

1//! The one [`EmbedderClient`] the framework provides: owns the [`AppCell`] and
2//! translates host pushes into binding methods.
3//!
4//! Dart has no type for this — the engine owns the isolate. [`App`] stays in
5//! foundation. This crate sits above scheduler and gestures so the isolate
6//! can name both.
7#![feature(arbitrary_self_types)]
8
9use std::cell::RefMut;
10use std::rc::Rc;
11use std::time::Duration;
12
13use inset_embedder::{
14    EmbedderClient, Frame, Instant, KeyData, PlatformRef, PointerDataPacket, TextEditingValue,
15    TextInputAction, ViewFocusEvent, ViewId,
16};
17use inset_foundation::{App, AppCell};
18use inset_gestures::GestureBinding;
19use inset_painting::PaintingBinding;
20use inset_rendering::RendererBinding;
21use inset_scheduler::SchedulerBinding;
22use inset_services::{KeyEventManager, TextInput};
23
24/// Host-facing isolate: the [`AppCell`] plus the methods the embedder pushes.
25///
26/// Every push is one turn: the `App` is borrowed for the binding call and released before the
27/// cell's checkpoint, where the microtasks and futures the event queued run.
28pub struct Shell {
29    app: Rc<AppCell>,
30    platform: PlatformRef,
31    /// The platform time the app clock was last advanced to.
32    clock: Duration,
33    /// The platform's `now` at its `elapsed` zero, known once a frame has reported its `elapsed`; lets every push, not only a frame or wake, move the app clock to the platform's.
34    origin: Option<Instant>,
35}
36
37impl Shell {
38    /// Builds the heap from `platform`, then runs application setup.
39    ///
40    /// Setup (later widgets `run_app`) requests the first frame when it
41    /// installs work that needs one.
42    pub fn new(platform: PlatformRef, setup: impl FnOnce(&mut App)) -> Shell {
43        let shell = Shell {
44            app: AppCell::with_platform(platform.clone()),
45            platform,
46            clock: Duration::ZERO,
47            origin: None,
48        };
49        shell.turn(|app| {
50            // Flutter's engine collects the platform's fonts before the framework runs.
51            PaintingBinding::instance(app).install_platform_fonts(app);
52            setup(app);
53        });
54        shell
55    }
56
57    /// The `App`, borrowed until the guard drops. A host that needs a full turn — a borrow, then
58    /// the checkpoint — takes [`cell`](Self::cell).
59    pub fn app(&self) -> RefMut<'_, App> {
60        self.app.borrow_mut()
61    }
62
63    pub fn cell(&self) -> &Rc<AppCell> {
64        &self.app
65    }
66
67    /// One platform event: `f` on the borrowed `App`, then the checkpoint.
68    fn turn<R>(&self, f: impl FnOnce(&mut App) -> R) -> R {
69        let result = f(&mut self.app.borrow_mut());
70        self.app.checkpoint();
71        result
72    }
73
74    /// An input or platform push: the app clock catches up with the platform's first, as Dart's
75    /// timers measure real time from the moment they are created, then the turn runs.
76    fn push<R>(&mut self, f: impl FnOnce(&mut App) -> R) -> R {
77        if let Some(origin) = self.origin {
78            let elapsed = self.platform.now().saturating_duration_since(origin);
79            self.advance_clock(elapsed);
80        }
81        self.turn(f)
82    }
83
84    /// Moves the app clock up to the platform's `elapsed`, firing the timers that came due.
85    fn advance_clock(&mut self, elapsed: Duration) {
86        if elapsed <= self.clock {
87            return;
88        }
89        self.app.elapse(elapsed - self.clock);
90        self.clock = elapsed;
91    }
92}
93
94impl EmbedderClient for Shell {
95    fn frame(&mut self, frame: Frame) {
96        self.origin = Some(self.platform.now() - frame.elapsed);
97        self.advance_clock(frame.elapsed);
98        // The engine runs `_beginFrame` and `_drawFrame` as two native tasks: two turns.
99        self.turn(|app| SchedulerBinding::handle_begin_frame(app, Some(frame.elapsed)));
100        self.turn(SchedulerBinding::handle_draw_frame);
101    }
102
103    fn view_added(&mut self, _id: ViewId) {}
104
105    fn view_metrics_changed(&mut self, _id: ViewId) {
106        self.push(|app| RendererBinding::instance(app).handle_metrics_changed(app));
107    }
108
109    fn view_removed(&mut self, _id: ViewId) {}
110
111    fn view_focus_changed(&mut self, event: ViewFocusEvent) {
112        self.push(|app| {
113            let callback = app.platform_callbacks().on_view_focus_change.clone();
114            if let Some(callback) = callback {
115                callback(app, event);
116            }
117        });
118    }
119
120    fn pointer_data_packet(&mut self, packet: PointerDataPacket) {
121        self.push(|app| GestureBinding::instance(app).handle_pointer_data_packet(app, packet));
122    }
123
124    fn key_data(&mut self, data: KeyData) -> bool {
125        self.push(|app| KeyEventManager::instance(app).handle_key_data(app, data))
126    }
127
128    fn platform_brightness_changed(&mut self) {
129        self.push(|app| {
130            let callback = app
131                .platform_callbacks()
132                .on_platform_brightness_changed
133                .clone();
134            if let Some(callback) = callback {
135                callback.call(app);
136            }
137        });
138    }
139
140    fn locales_changed(&mut self) {
141        self.push(|app| {
142            let callback = app.platform_callbacks().on_locale_changed.clone();
143            if let Some(callback) = callback {
144                callback.call(app);
145            }
146        });
147    }
148
149    fn system_fonts_changed(&mut self) {
150        self.push(|app| {
151            PaintingBinding::instance(app).handle_system_fonts_did_change(app);
152        });
153    }
154
155    fn wake(&mut self, elapsed: Duration) {
156        self.advance_clock(elapsed);
157    }
158
159    fn text_input_editing_value(&mut self, _view: ViewId, value: TextEditingValue) {
160        self.push(|app| TextInput::instance(app).update_editing_value(app, value));
161    }
162
163    fn text_input_action(&mut self, _view: ViewId, action: TextInputAction) {
164        self.push(|app| TextInput::instance(app).perform_action(app, action));
165    }
166
167    fn text_input_closed(&mut self, _view: ViewId) {
168        self.push(|app| TextInput::instance(app).connection_closed(app));
169    }
170
171    fn system_context_menu_hidden(&mut self) {
172        self.push(inset_services::SystemContextMenuController::dispatch_system_hide);
173    }
174
175    fn custom_context_menu_action(&mut self, callback_id: &str) {
176        let callback_id = callback_id.to_string();
177        self.push(move |app| {
178            inset_services::SystemContextMenuController::dispatch_custom_action(app, &callback_id);
179        });
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use std::cell::Cell;
186    use std::rc::Rc;
187    use std::sync::Arc;
188    use std::sync::atomic::{AtomicUsize, Ordering};
189    use std::time::Duration;
190
191    use inset_embedder::{
192        EmbedderClient, Frame, KeyData, KeyEventType, Picture, Platform, PointerChange,
193        PointerData, PointerDataPacket, PointerDeviceKind, View, ViewId, ViewMetrics, ViewRef,
194    };
195    use inset_foundation::App;
196    use inset_gestures::{GestureBinding, PointerRoute};
197    use inset_scheduler::SchedulerBinding;
198    use inset_services::{HardwareKeyboard, KeyEvent, LogicalKeyboardKey, PhysicalKeyboardKey};
199
200    use super::Shell;
201
202    struct RecordingPlatform {
203        frames: Arc<AtomicUsize>,
204        view: Option<ViewRef>,
205    }
206
207    impl Platform for RecordingPlatform {
208        fn target_platform(&self) -> inset_embedder::TargetPlatform {
209            inset_embedder::TargetPlatform::Android
210        }
211
212        fn request_frame(&self) {
213            self.frames.fetch_add(1, Ordering::SeqCst);
214        }
215
216        fn now(&self) -> std::time::Instant {
217            std::time::Instant::now()
218        }
219
220        fn wake_at(&self, _deadline: std::time::Instant) {}
221
222        fn views(&self) -> Vec<ViewRef> {
223            self.view.iter().cloned().collect()
224        }
225
226        fn view(&self, id: ViewId) -> Option<ViewRef> {
227            self.view.as_ref().filter(|view| view.id() == id).cloned()
228        }
229
230        fn implicit_view(&self) -> Option<ViewRef> {
231            self.view.clone()
232        }
233    }
234
235    struct TestView;
236
237    impl View for TestView {
238        fn id(&self) -> ViewId {
239            ViewId(0)
240        }
241
242        fn metrics(&self) -> ViewMetrics {
243            ViewMetrics::default()
244        }
245
246        fn present(&self, _picture: &Picture) {}
247    }
248
249    #[test]
250    fn new_runs_setup_and_frame_runs_both_scheduler_phases() {
251        let frames = Arc::new(AtomicUsize::new(0));
252        let platform = std::rc::Rc::new(RecordingPlatform {
253            frames: Arc::clone(&frames),
254            view: None,
255        });
256        let mut setup_ran = false;
257        let mut shell = Shell::new(platform, |_app: &mut App| {
258            setup_ran = true;
259        });
260        assert!(setup_ran);
261        assert_eq!(frames.load(Ordering::SeqCst), 0);
262
263        SchedulerBinding::schedule_frame(&mut shell.app());
264        assert_eq!(frames.load(Ordering::SeqCst), 1);
265        shell.frame(Frame {
266            elapsed: Duration::from_millis(16),
267        });
268    }
269
270    #[test]
271    fn pointer_data_packet_reaches_gesture_binding() {
272        let ran = Rc::new(Cell::new(false));
273        let ran_flag = Rc::clone(&ran);
274        let platform = std::rc::Rc::new(RecordingPlatform {
275            frames: Arc::new(AtomicUsize::new(0)),
276            view: Some(Rc::new(TestView)),
277        });
278        let mut shell = Shell::new(platform, |app| {
279            let binding = GestureBinding::instance(app);
280            binding.pointer_router(app).add_route(
281                app,
282                1,
283                PointerRoute::new(move |_app, _event| {
284                    ran_flag.set(true);
285                }),
286                None,
287            );
288        });
289
290        shell.pointer_data_packet(PointerDataPacket::new(vec![PointerData {
291            change: PointerChange::Down,
292            kind: PointerDeviceKind::Mouse,
293            buttons: 1,
294            pointer_identifier: 1,
295            ..PointerData::default()
296        }]));
297        assert!(ran.get());
298    }
299
300    #[test]
301    fn key_data_reaches_the_hardware_keyboard() {
302        let seen = Rc::new(Cell::new(0));
303        let seen_flag = Rc::clone(&seen);
304        let platform = std::rc::Rc::new(RecordingPlatform {
305            frames: Arc::new(AtomicUsize::new(0)),
306            view: None,
307        });
308        let mut shell = Shell::new(platform, |app| {
309            HardwareKeyboard::instance(app).add_handler(
310                app,
311                Rc::new(move |_app: &mut App, _event: &KeyEvent| {
312                    seen_flag.set(seen_flag.get() + 1);
313                    true
314                }),
315            );
316        });
317
318        let key_a = KeyData {
319            event_type: KeyEventType::Down,
320            physical: PhysicalKeyboardKey::KEY_A.usb_hid_usage,
321            logical: LogicalKeyboardKey::KEY_A.key_id,
322            character: Some("a".to_owned()),
323            ..KeyData::default()
324        };
325        assert!(shell.key_data(key_a), "the handler claimed the event");
326        assert_eq!(seen.get(), 1);
327
328        let keyboard = HardwareKeyboard::instance(&mut shell.app());
329        assert!(keyboard.is_logical_key_pressed(&shell.app(), LogicalKeyboardKey::KEY_A));
330    }
331
332    #[test]
333    fn wake_and_frame_advance_the_app_clock_and_fire_due_timers() {
334        let platform: inset_embedder::PlatformRef = Rc::new(RecordingPlatform {
335            frames: Arc::new(AtomicUsize::new(0)),
336            view: None,
337        });
338        let fired = Rc::new(Cell::new(Vec::new()));
339        let mut shell = Shell::new(platform, |app| {
340            for (name, delay) in [("early", 10), ("late", 30)] {
341                let fired = Rc::clone(&fired);
342                inset_foundation::Timer::new(
343                    app,
344                    Duration::from_millis(delay),
345                    inset_foundation::Listener::new(move |_app| {
346                        let mut log = fired.take();
347                        log.push(name);
348                        fired.set(log);
349                    }),
350                );
351            }
352        });
353        assert!(fired.take().is_empty());
354
355        shell.wake(Duration::from_millis(15));
356        assert_eq!(fired.take(), vec!["early"]);
357
358        shell.frame(Frame {
359            elapsed: Duration::from_millis(32),
360        });
361        assert_eq!(fired.take(), vec!["late"]);
362    }
363
364    #[test]
365    fn view_focus_is_delivered_through_the_dispatcher_callback() {
366        use inset_embedder::{ViewFocusDirection, ViewFocusEvent, ViewFocusState};
367        let received = Rc::new(Cell::new(None));
368        let sink = received.clone();
369        let platform = Rc::new(RecordingPlatform {
370            frames: Arc::default(),
371            view: Some(Rc::new(TestView)),
372        });
373        let mut shell = Shell::new(platform, move |app| {
374            app.platform_callbacks_mut().on_view_focus_change = Some(Rc::new(move |app, event| {
375                let sink = sink.clone();
376                app.schedule_microtask(inset_foundation::Listener::new(move |_| {
377                    sink.set(Some(event))
378                }));
379            }));
380        });
381        shell.view_focus_changed(ViewFocusEvent {
382            view_id: ViewId(0),
383            state: ViewFocusState::Focused,
384            direction: ViewFocusDirection::Backward,
385        });
386        let event = received
387            .get()
388            .expect("the shell checkpoints the callback's microtask");
389        assert_eq!(event.view_id, ViewId(0));
390        assert_eq!(event.state, ViewFocusState::Focused);
391        assert_eq!(event.direction, ViewFocusDirection::Backward);
392    }
393}