1#![feature(arbitrary_self_types)]
8
9use std::cell::RefMut;
10use std::rc::Rc;
11use std::time::Duration;
12
13use inset_embedder::{
14 AppExitReply, AppLifecycleState, DropData, EmbedderClient, Frame, Instant, KeyData,
15 PlatformRef, PointerDataPacket, RequestId, TextEditingValue, TextInputAction, ViewError,
16 ViewFocusEvent, ViewId,
17};
18use inset_foundation::{App, AppCell};
19use inset_gestures::GestureBinding;
20use inset_painting::PaintingBinding;
21use inset_rendering::RendererBinding;
22use inset_scheduler::SchedulerBinding;
23use inset_services::{KeyEventManager, ServicesBinding, TextInput};
24
25pub struct Shell {
31 app: Rc<AppCell>,
32 platform: PlatformRef,
33 clock: Duration,
35 origin: Option<Instant>,
37}
38
39impl Shell {
40 pub fn new(platform: PlatformRef, setup: impl FnOnce(&mut App)) -> Shell {
45 let shell = Shell {
46 app: AppCell::with_platform(platform.clone()),
47 platform,
48 clock: Duration::ZERO,
49 origin: None,
50 };
51 shell.handle_event(|app| {
52 PaintingBinding::instance(app).install_platform_fonts(app);
54 setup(app);
55 });
56 shell
57 }
58
59 pub fn app(&self) -> RefMut<'_, App> {
62 self.app.borrow_mut()
63 }
64
65 pub fn cell(&self) -> &Rc<AppCell> {
66 &self.app
67 }
68
69 fn handle_event<R>(&self, f: impl FnOnce(&mut App) -> R) -> R {
71 let result = f(&mut self.app.borrow_mut());
72 self.app.checkpoint();
73 result
74 }
75
76 fn push<R>(&mut self, f: impl FnOnce(&mut App) -> R) -> R {
79 if let Some(origin) = self.origin {
80 let elapsed = self.platform.now().saturating_duration_since(origin);
81 self.advance_clock(elapsed);
82 }
83 self.handle_event(f)
84 }
85
86 fn advance_clock(&mut self, elapsed: Duration) {
88 if elapsed <= self.clock {
89 return;
90 }
91 self.app.elapse(elapsed - self.clock);
92 self.clock = elapsed;
93 }
94
95 fn metrics_changed(&mut self) {
99 self.push(|app| RendererBinding::instance(app).handle_metrics_changed(app));
100 }
101}
102
103impl EmbedderClient for Shell {
104 fn frame(&mut self, frame: Frame) {
105 self.origin = Some(self.platform.now() - frame.elapsed);
106 self.advance_clock(frame.elapsed);
107 self.handle_event(|app| SchedulerBinding::on_begin_frame(app, Some(frame.elapsed)));
109 self.handle_event(SchedulerBinding::on_draw_frame);
110 }
111
112 fn view_added(&mut self, id: ViewId) {
114 self.push(|app| {
115 let callback = app.platform_callbacks().on_view_added.clone();
116 if let Some(callback) = callback {
117 callback(app, id);
118 }
119 });
120 self.metrics_changed();
121 }
122
123 fn view_metrics_changed(&mut self, _id: ViewId) {
124 self.metrics_changed();
125 }
126
127 fn view_removed(&mut self, id: ViewId) {
130 self.push(|app| {
131 let callback = app.platform_callbacks().on_view_removed.clone();
132 if let Some(callback) = callback {
133 callback(app, id);
134 }
135 });
136 self.metrics_changed();
137 }
138
139 fn view_focus_changed(&mut self, event: ViewFocusEvent) {
140 self.push(|app| {
141 let callback = app.platform_callbacks().on_view_focus_change.clone();
142 if let Some(callback) = callback {
143 callback(app, event);
144 }
145 });
146 }
147
148 fn drop_data(&mut self, data: DropData) {
149 self.push(|app| {
150 let callback = app.platform_callbacks().on_drop.clone();
151 if let Some(callback) = callback {
152 callback(app, data);
153 }
154 });
155 }
156
157 fn pointer_data_packet(&mut self, packet: PointerDataPacket) {
158 self.push(|app| GestureBinding::instance(app).handle_pointer_data_packet(app, packet));
159 }
160
161 fn key_data(&mut self, data: KeyData) -> bool {
162 self.push(|app| KeyEventManager::instance(app).handle_key_data(app, data))
163 }
164
165 fn pop_route(&mut self) -> bool {
166 self.push(|app| {
167 let callback = app.platform_callbacks().on_pop_route.clone();
168 callback.is_some_and(|callback| callback(app))
169 })
170 }
171
172 fn view_close_requested(&mut self, view: ViewId) {
173 self.push(|app| {
174 let callback = app.platform_callbacks().on_view_close_requested.clone();
175 if let Some(callback) = callback {
176 callback(app, view);
177 }
178 });
179 }
180
181 fn view_request_failed(&mut self, request: RequestId, error: ViewError) {
182 self.push(|app| {
183 let callback = app.platform_callbacks().on_view_request_failed.clone();
184 if let Some(callback) = callback {
185 callback(app, request, error);
186 }
187 });
188 }
189
190 fn request_app_exit(&mut self, reply: AppExitReply) {
193 self.push(|app| {
194 let answer = ServicesBinding::instance(app).handle_request_app_exit(app);
195 app.spawn(async move |_cx| reply.answer(answer.await));
196 });
197 }
198
199 fn app_lifecycle_state_changed(&mut self, state: AppLifecycleState) {
200 self.push(|app| ServicesBinding::instance(app).handle_lifecycle_message(app, state));
201 }
202
203 fn platform_brightness_changed(&mut self) {
204 self.push(|app| {
205 let callback = app
206 .platform_callbacks()
207 .on_platform_brightness_changed
208 .clone();
209 if let Some(callback) = callback {
210 callback.call(app);
211 }
212 });
213 }
214
215 fn locales_changed(&mut self) {
216 self.push(|app| {
217 let callback = app.platform_callbacks().on_locale_changed.clone();
218 if let Some(callback) = callback {
219 callback.call(app);
220 }
221 });
222 }
223
224 fn system_fonts_changed(&mut self) {
225 self.push(|app| {
226 PaintingBinding::instance(app).handle_system_fonts_did_change(app);
227 });
228 }
229
230 fn wake(&mut self, elapsed: Duration) {
231 self.advance_clock(elapsed);
232 self.app.wake();
234 }
235
236 fn text_input_editing_value(&mut self, _view: ViewId, value: TextEditingValue) {
237 self.push(|app| TextInput::instance(app).update_editing_value(app, value));
238 }
239
240 fn text_input_action(&mut self, _view: ViewId, action: TextInputAction) {
241 self.push(|app| TextInput::instance(app).perform_action(app, action));
242 }
243
244 fn text_input_closed(&mut self, _view: ViewId) {
245 self.push(|app| TextInput::instance(app).connection_closed(app));
246 }
247
248 fn system_context_menu_hidden(&mut self) {
249 self.push(inset_services::SystemContextMenuController::dispatch_system_hide);
250 }
251
252 fn custom_context_menu_action(&mut self, callback_id: &str) {
253 let callback_id = callback_id.to_string();
254 self.push(move |app| {
255 inset_services::SystemContextMenuController::dispatch_custom_action(app, &callback_id);
256 });
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use std::cell::Cell;
263 use std::rc::Rc;
264 use std::time::Duration;
265
266 use inset_embedder::{
267 AppLifecycleState, EmbedderClient, Frame, KeyData, KeyEventType, PointerChange,
268 PointerData, PointerDataPacket, PointerDeviceKind, ViewId,
269 };
270 use inset_foundation::App;
271 use inset_gestures::{GestureBinding, PointerRoute};
272 use inset_scheduler::SchedulerBinding;
273 use inset_services::{HardwareKeyboard, KeyEvent, LogicalKeyboardKey, PhysicalKeyboardKey};
274 use inset_test::{TestPlatform, TestView};
275
276 use super::Shell;
277
278 fn platform_with_a_view() -> TestPlatform {
280 TestPlatform::new().with_view(Rc::new(TestView::new(0.0, 0.0)))
281 }
282
283 #[test]
284 fn drop_data_reaches_the_platform_callback() {
285 use std::cell::RefCell;
286 use std::path::PathBuf;
287
288 use inset_embedder::{DropChange, DropData, ViewId};
289
290 let seen = Rc::new(RefCell::new(None));
291 let seen_slot = Rc::clone(&seen);
292 let platform = Rc::new(platform_with_a_view());
293 let mut shell = Shell::new(platform, move |app: &mut App| {
294 app.platform_callbacks_mut().on_drop = Some(Rc::new(move |_app, data| {
295 *seen_slot.borrow_mut() = Some(data);
296 }));
297 });
298 shell.drop_data(DropData {
299 view_id: ViewId(0),
300 change: DropChange::Dropped,
301 physical_position: None,
302 paths: vec![PathBuf::from("/a.wasm")],
303 });
304 let seen = seen.borrow();
305 let seen = seen.as_ref().expect("the callback ran");
306 assert_eq!(seen.change, DropChange::Dropped);
307 assert_eq!(seen.paths, [PathBuf::from("/a.wasm")]);
308 }
309
310 fn recorded_reply() -> (
312 inset_embedder::AppExitReply,
313 Rc<Cell<Option<inset_embedder::AppExitResponse>>>,
314 ) {
315 let answer = Rc::new(Cell::new(None));
316 let given = Rc::clone(&answer);
317 let reply = inset_embedder::AppExitReply::new(move |response| given.set(Some(response)));
318 (reply, answer)
319 }
320
321 #[test]
322 fn a_view_added_or_removed_reaches_the_framework_with_its_id() {
323 let heard = Rc::new(std::cell::RefCell::new(Vec::new()));
324 let (added, removed) = (Rc::clone(&heard), Rc::clone(&heard));
325 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
326 let callbacks = app.platform_callbacks_mut();
327 callbacks.on_view_added = Some(Rc::new(move |_app, view| {
328 added.borrow_mut().push(("added", view))
329 }));
330 callbacks.on_view_removed = Some(Rc::new(move |_app, view| {
331 removed.borrow_mut().push(("removed", view))
332 }));
333 });
334 shell.view_added(ViewId(4));
335 shell.view_removed(ViewId(4));
336 assert_eq!(
337 *heard.borrow(),
338 [("added", ViewId(4)), ("removed", ViewId(4))]
339 );
340 }
341
342 #[test]
343 fn a_view_close_request_reaches_the_framework() {
344 let heard = Rc::new(Cell::new(None));
345 let heard_slot = Rc::clone(&heard);
346 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
347 app.platform_callbacks_mut().on_view_close_requested =
348 Some(Rc::new(move |_app, view| heard_slot.set(Some(view))));
349 });
350 shell.view_close_requested(ViewId(3));
351 assert_eq!(heard.get(), Some(ViewId(3)));
352 }
353
354 #[test]
355 fn a_lifecycle_state_the_host_reports_reaches_the_scheduler_with_the_states_between() {
356 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
357 shell.app_lifecycle_state_changed(AppLifecycleState::Resumed);
358 shell.app_lifecycle_state_changed(AppLifecycleState::Hidden);
359 let mut app = shell.app();
360 assert_eq!(
361 SchedulerBinding::lifecycle_state(&mut app),
362 Some(AppLifecycleState::Hidden)
363 );
364 assert!(!SchedulerBinding::frames_enabled(&mut app));
365 }
366
367 #[test]
368 fn request_app_exit_is_answered_by_the_services_binding() {
369 use inset_embedder::AppExitResponse;
370 use inset_foundation::{Handle, Task};
371 use inset_services::{ServicesBinding, ServicesBindingOverridesObject};
372
373 #[derive(Default)]
374 struct Refusing;
375
376 impl ServicesBindingOverridesObject for Refusing {
377 fn handle_request_app_exit(
378 self: Handle<Self>,
379 _app: &mut App,
380 ) -> Task<AppExitResponse> {
381 Task::ready(AppExitResponse::Cancel)
382 }
383 }
384
385 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
386 let (reply, answer) = recorded_reply();
387 shell.request_app_exit(reply);
388 assert_eq!(answer.get(), Some(AppExitResponse::Exit));
389
390 let refusing = shell.app().create(Refusing);
391 let binding = ServicesBinding::instance(&mut shell.app());
392 binding.set_overrides(&mut shell.app(), Rc::new(refusing));
393 let (reply, answer) = recorded_reply();
394 shell.request_app_exit(reply);
395 assert_eq!(answer.get(), Some(AppExitResponse::Cancel));
396 }
397
398 #[test]
400 fn request_app_exit_answers_during_the_call_in_which_the_framework_decides() {
401 use inset_embedder::AppExitResponse;
402 use inset_foundation::{Completer, Handle, Task};
403 use inset_services::{ServicesBinding, ServicesBindingOverridesObject};
404
405 struct Deciding {
406 decision: Completer<AppExitResponse>,
407 }
408
409 impl ServicesBindingOverridesObject for Deciding {
410 fn handle_request_app_exit(self: Handle<Self>, app: &mut App) -> Task<AppExitResponse> {
411 let decision = app.get(self).decision.future();
412 app.spawn(async move |_cx| decision.await)
413 }
414 }
415
416 let decision = Completer::new();
417 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
418 let deciding = shell.app().create(Deciding {
419 decision: decision.clone(),
420 });
421 let binding = ServicesBinding::instance(&mut shell.app());
422 binding.set_overrides(&mut shell.app(), Rc::new(deciding));
423
424 let (reply, answer) = recorded_reply();
425 shell.request_app_exit(reply);
426 assert_eq!(answer.get(), None);
427
428 decision.complete(&mut shell.app(), AppExitResponse::Exit);
429 assert_eq!(answer.get(), None, "the task resumes at a checkpoint");
430 shell.wake(Duration::ZERO);
431 assert_eq!(answer.get(), Some(AppExitResponse::Exit));
432 }
433
434 #[test]
435 fn new_runs_setup_and_frame_runs_both_scheduler_phases() {
436 let platform = Rc::new(TestPlatform::new());
437 let mut setup_ran = false;
438 let mut shell = Shell::new(platform.clone(), |_app: &mut App| {
439 setup_ran = true;
440 });
441 assert!(setup_ran);
442 assert_eq!(platform.frames_requested(), 0);
443
444 SchedulerBinding::schedule_frame(&mut shell.app());
445 assert_eq!(platform.frames_requested(), 1);
446 shell.frame(Frame {
447 elapsed: Duration::from_millis(16),
448 });
449 }
450
451 #[test]
452 fn pointer_data_packet_reaches_gesture_binding() {
453 let ran = Rc::new(Cell::new(false));
454 let ran_flag = Rc::clone(&ran);
455 let platform = Rc::new(platform_with_a_view());
456 let mut shell = Shell::new(platform, |app| {
457 let binding = GestureBinding::instance(app);
458 binding.pointer_router(app).add_route(
459 app,
460 1,
461 PointerRoute::new(move |_app, _event| {
462 ran_flag.set(true);
463 }),
464 None,
465 );
466 });
467
468 shell.pointer_data_packet(PointerDataPacket::new(vec![PointerData {
469 change: PointerChange::Down,
470 kind: PointerDeviceKind::Mouse,
471 buttons: 1,
472 pointer_identifier: 1,
473 ..PointerData::default()
474 }]));
475 assert!(ran.get());
476 }
477
478 #[test]
479 fn key_data_reaches_the_hardware_keyboard() {
480 let seen = Rc::new(Cell::new(0));
481 let seen_flag = Rc::clone(&seen);
482 let platform = Rc::new(TestPlatform::new());
483 let mut shell = Shell::new(platform, |app| {
484 HardwareKeyboard::instance(app).add_handler(
485 app,
486 Rc::new(move |_app: &mut App, _event: &KeyEvent| {
487 seen_flag.set(seen_flag.get() + 1);
488 true
489 }),
490 );
491 });
492
493 let key_a = KeyData {
494 event_type: KeyEventType::Down,
495 physical: PhysicalKeyboardKey::KEY_A.usb_hid_usage,
496 logical: LogicalKeyboardKey::KEY_A.key_id,
497 character: Some("a".to_owned()),
498 ..KeyData::default()
499 };
500 assert!(shell.key_data(key_a), "the handler claimed the event");
501 assert_eq!(seen.get(), 1);
502
503 let keyboard = HardwareKeyboard::instance(&mut shell.app());
504 assert!(keyboard.is_logical_key_pressed(&shell.app(), LogicalKeyboardKey::KEY_A));
505 }
506
507 #[test]
508 fn wake_and_frame_advance_the_app_clock_and_fire_due_timers() {
509 let platform = Rc::new(TestPlatform::new());
510 let fired = Rc::new(Cell::new(Vec::new()));
511 let mut shell = Shell::new(platform, |app| {
512 for (name, delay) in [("early", 10), ("late", 30)] {
513 let fired = Rc::clone(&fired);
514 inset_foundation::Timer::new(
515 app,
516 Duration::from_millis(delay),
517 inset_foundation::Listener::new(move |_app| {
518 let mut log = fired.take();
519 log.push(name);
520 fired.set(log);
521 }),
522 );
523 }
524 });
525 assert!(fired.take().is_empty());
526
527 shell.wake(Duration::from_millis(15));
528 assert_eq!(fired.take(), vec!["early"]);
529
530 shell.frame(Frame {
531 elapsed: Duration::from_millis(32),
532 });
533 assert_eq!(fired.take(), vec!["late"]);
534 }
535
536 #[test]
537 fn a_view_coming_or_going_is_a_metrics_change() {
538 use inset_foundation::Handle;
539 use inset_rendering::{RendererBinding, RendererBindingOverridesObject};
540
541 struct CountingMetricsChanges {
542 count: Rc<Cell<usize>>,
543 }
544
545 impl RendererBindingOverridesObject for CountingMetricsChanges {
546 fn will_draw_frame(self: Handle<Self>, _app: &mut App) {}
547
548 fn did_draw_frame(self: Handle<Self>, _app: &mut App) {}
549
550 fn did_handle_metrics_changed(self: Handle<Self>, app: &mut App) {
551 let count = &app.get(self).count;
552 count.set(count.get() + 1);
553 }
554 }
555
556 let count = Rc::new(Cell::new(0));
557 let mut shell = Shell::new(Rc::new(platform_with_a_view()), |_app| {});
558 let counting = shell.app().create(CountingMetricsChanges {
559 count: Rc::clone(&count),
560 });
561 let binding = RendererBinding::instance(&mut shell.app());
562 binding.set_overrides(&mut shell.app(), Rc::new(counting));
563
564 shell.view_added(ViewId(1));
565 assert_eq!(count.get(), 1, "a view added");
566 shell.view_removed(ViewId(1));
567 assert_eq!(count.get(), 2, "a view removed");
568 }
569
570 #[test]
571 fn view_focus_is_delivered_through_the_dispatcher_callback() {
572 use inset_embedder::{ViewFocusDirection, ViewFocusEvent, ViewFocusState};
573 let received = Rc::new(Cell::new(None));
574 let sink = received.clone();
575 let platform = Rc::new(platform_with_a_view());
576 let mut shell = Shell::new(platform, move |app| {
577 app.platform_callbacks_mut().on_view_focus_change = Some(Rc::new(move |app, event| {
578 let sink = sink.clone();
579 app.schedule_microtask(inset_foundation::Listener::new(move |_| {
580 sink.set(Some(event))
581 }));
582 }));
583 });
584 shell.view_focus_changed(ViewFocusEvent {
585 view_id: ViewId(0),
586 state: ViewFocusState::Focused,
587 direction: ViewFocusDirection::Backward,
588 });
589 let event = received
590 .get()
591 .expect("the shell checkpoints the callback's microtask");
592 assert_eq!(event.view_id, ViewId(0));
593 assert_eq!(event.state, ViewFocusState::Focused);
594 assert_eq!(event.direction, ViewFocusDirection::Backward);
595 }
596}