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::{FrameTarget, 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) {
100 self.push(|app| RendererBinding::instance(app).handle_metrics_changed(app));
101 }
102}
103
104impl EmbedderClient for Shell {
105 fn frame(&mut self, views: &[ViewId], frame: Frame) {
106 self.origin = Some(self.platform.now() - frame.elapsed);
107 self.advance_clock(frame.elapsed);
108 self.handle_event(|app| SchedulerBinding::on_begin_frame(app, views, Some(frame.elapsed)));
110 self.handle_event(|app| SchedulerBinding::on_draw_frame(app, views));
111 }
112
113 fn view_added(&mut self, id: ViewId) {
115 self.push(|app| {
116 let callback = app.platform_callbacks().on_view_added.clone();
117 if let Some(callback) = callback {
118 callback(app, id);
119 }
120 });
121 self.metrics_changed();
122 }
123
124 fn view_metrics_changed(&mut self, _id: ViewId) {
125 self.metrics_changed();
126 }
127
128 fn view_removed(&mut self, id: ViewId) {
131 self.push(|app| {
132 let callback = app.platform_callbacks().on_view_removed.clone();
133 if let Some(callback) = callback {
134 callback(app, id);
135 }
136 });
137 self.metrics_changed();
138 }
139
140 fn screens_changed(&mut self) {
142 self.metrics_changed();
143 }
144
145 fn view_focus_changed(&mut self, event: ViewFocusEvent) {
146 self.push(|app| {
147 let callback = app.platform_callbacks().on_view_focus_change.clone();
148 if let Some(callback) = callback {
149 callback(app, event);
150 }
151 });
152 }
153
154 fn drop_data(&mut self, data: DropData) {
155 self.push(|app| {
156 let callback = app.platform_callbacks().on_drop.clone();
157 if let Some(callback) = callback {
158 callback(app, data);
159 }
160 });
161 }
162
163 fn pointer_data_packet(&mut self, packet: PointerDataPacket) {
164 self.push(|app| GestureBinding::instance(app).handle_pointer_data_packet(app, packet));
165 }
166
167 fn key_data(&mut self, data: KeyData) -> bool {
168 self.push(|app| KeyEventManager::instance(app).handle_key_data(app, data))
169 }
170
171 fn pop_route(&mut self) -> bool {
172 self.push(|app| {
173 let callback = app.platform_callbacks().on_pop_route.clone();
174 callback.is_some_and(|callback| callback(app))
175 })
176 }
177
178 fn view_close_requested(&mut self, view: ViewId) {
179 self.push(|app| {
180 let callback = app.platform_callbacks().on_view_close_requested.clone();
181 if let Some(callback) = callback {
182 callback(app, view);
183 }
184 });
185 }
186
187 fn window_changed(&mut self, view: ViewId) {
188 self.push(|app| {
189 let callback = app.platform_callbacks().on_window_changed.clone();
190 if let Some(callback) = callback {
191 callback(app, view);
192 }
193 });
194 }
195
196 fn view_request_failed(&mut self, request: RequestId, error: ViewError) {
197 self.push(|app| {
198 let callback = app.platform_callbacks().on_view_request_failed.clone();
199 if let Some(callback) = callback {
200 callback(app, request, error);
201 }
202 });
203 }
204
205 fn request_app_exit(&mut self, reply: AppExitReply) {
208 self.push(|app| {
209 let answer = ServicesBinding::instance(app).handle_request_app_exit(app);
210 app.spawn(async move |_cx| reply.answer(answer.await));
211 });
212 }
213
214 fn app_lifecycle_state_changed(&mut self, state: AppLifecycleState) {
218 self.push(|app| {
219 if matches!(
220 state,
221 AppLifecycleState::Resumed | AppLifecycleState::Inactive
222 ) {
223 RendererBinding::instance(app).handle_mark_all_views_need_render(app);
224 SchedulerBinding::schedule_forced_frame(app, FrameTarget::EveryView);
225 }
226 ServicesBinding::instance(app).handle_lifecycle_message(app, state);
227 });
228 }
229
230 fn platform_brightness_changed(&mut self) {
231 self.push(|app| {
232 let callback = app
233 .platform_callbacks()
234 .on_platform_brightness_changed
235 .clone();
236 if let Some(callback) = callback {
237 callback.call(app);
238 }
239 });
240 }
241
242 fn locales_changed(&mut self) {
243 self.push(|app| {
244 let callback = app.platform_callbacks().on_locale_changed.clone();
245 if let Some(callback) = callback {
246 callback.call(app);
247 }
248 });
249 }
250
251 fn system_fonts_changed(&mut self) {
252 self.push(|app| {
253 PaintingBinding::instance(app).handle_system_fonts_did_change(app);
254 });
255 }
256
257 fn wake(&mut self, elapsed: Duration) {
258 self.advance_clock(elapsed);
259 self.app.wake();
261 }
262
263 fn text_input_editing_value(&mut self, _view: ViewId, value: TextEditingValue) {
264 self.push(|app| TextInput::instance(app).update_editing_value(app, value));
265 }
266
267 fn text_input_action(&mut self, _view: ViewId, action: TextInputAction) {
268 self.push(|app| TextInput::instance(app).perform_action(app, action));
269 }
270
271 fn text_input_closed(&mut self, _view: ViewId) {
272 self.push(|app| TextInput::instance(app).connection_closed(app));
273 }
274
275 fn system_context_menu_hidden(&mut self) {
276 self.push(inset_services::SystemContextMenuController::dispatch_system_hide);
277 }
278
279 fn custom_context_menu_action(&mut self, callback_id: &str) {
280 let callback_id = callback_id.to_string();
281 self.push(move |app| {
282 inset_services::SystemContextMenuController::dispatch_custom_action(app, &callback_id);
283 });
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use std::cell::Cell;
290 use std::rc::Rc;
291 use std::time::Duration;
292
293 use inset_embedder::{
294 AppLifecycleState, EmbedderClient, Frame, KeyData, KeyEventType, PointerChange,
295 PointerData, PointerDataPacket, PointerDeviceKind, ViewId,
296 };
297 use inset_foundation::App;
298 use inset_gestures::{GestureBinding, PointerRoute};
299 use inset_scheduler::{FrameTarget, SchedulerBinding};
300 use inset_services::{HardwareKeyboard, KeyEvent, LogicalKeyboardKey, PhysicalKeyboardKey};
301 use inset_test::{IMPLICIT_VIEW_ID, TestPlatform};
302
303 use super::Shell;
304
305 fn platform_with_a_view() -> TestPlatform {
307 TestPlatform::new().with_views(&[IMPLICIT_VIEW_ID])
308 }
309
310 #[test]
311 fn drop_data_reaches_the_platform_callback() {
312 use std::cell::RefCell;
313 use std::path::PathBuf;
314
315 use inset_embedder::{DropChange, DropData};
316
317 let seen = Rc::new(RefCell::new(None));
318 let seen_slot = Rc::clone(&seen);
319 let platform = Rc::new(platform_with_a_view());
320 let mut shell = Shell::new(platform, move |app: &mut App| {
321 app.platform_callbacks_mut().on_drop = Some(Rc::new(move |_app, data| {
322 *seen_slot.borrow_mut() = Some(data);
323 }));
324 });
325 shell.drop_data(DropData {
326 view_id: IMPLICIT_VIEW_ID,
327 change: DropChange::Dropped,
328 physical_position: None,
329 paths: vec![PathBuf::from("/a.wasm")],
330 });
331 let seen = seen.borrow();
332 let seen = seen.as_ref().expect("the callback ran");
333 assert_eq!(seen.change, DropChange::Dropped);
334 assert_eq!(seen.paths, [PathBuf::from("/a.wasm")]);
335 }
336
337 fn recorded_reply() -> (
339 inset_embedder::AppExitReply,
340 Rc<Cell<Option<inset_embedder::AppExitResponse>>>,
341 ) {
342 let answer = Rc::new(Cell::new(None));
343 let given = Rc::clone(&answer);
344 let reply = inset_embedder::AppExitReply::new(move |response| given.set(Some(response)));
345 (reply, answer)
346 }
347
348 #[test]
349 fn a_view_added_or_removed_reaches_the_framework_with_its_id() {
350 let heard = Rc::new(std::cell::RefCell::new(Vec::new()));
351 let (added, removed) = (Rc::clone(&heard), Rc::clone(&heard));
352 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
353 let callbacks = app.platform_callbacks_mut();
354 callbacks.on_view_added = Some(Rc::new(move |_app, view| {
355 added.borrow_mut().push(("added", view))
356 }));
357 callbacks.on_view_removed = Some(Rc::new(move |_app, view| {
358 removed.borrow_mut().push(("removed", view))
359 }));
360 });
361 shell.view_added(ViewId(4));
362 shell.view_removed(ViewId(4));
363 assert_eq!(
364 *heard.borrow(),
365 [("added", ViewId(4)), ("removed", ViewId(4))]
366 );
367 }
368
369 #[test]
370 fn a_view_close_request_reaches_the_framework() {
371 let heard = Rc::new(Cell::new(None));
372 let heard_slot = Rc::clone(&heard);
373 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
374 app.platform_callbacks_mut().on_view_close_requested =
375 Some(Rc::new(move |_app, view| heard_slot.set(Some(view))));
376 });
377 shell.view_close_requested(ViewId(3));
378 assert_eq!(heard.get(), Some(ViewId(3)));
379 }
380
381 #[test]
382 fn a_window_change_reaches_the_framework() {
383 let heard = Rc::new(Cell::new(None));
384 let heard_slot = Rc::clone(&heard);
385 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
386 app.platform_callbacks_mut().on_window_changed =
387 Some(Rc::new(move |_app, view| heard_slot.set(Some(view))));
388 });
389 shell.window_changed(ViewId(5));
390 assert_eq!(heard.get(), Some(ViewId(5)));
391 }
392
393 #[test]
394 fn a_lifecycle_state_the_host_reports_reaches_the_scheduler_with_the_states_between() {
395 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
396 shell.app_lifecycle_state_changed(AppLifecycleState::Resumed);
397 shell.app_lifecycle_state_changed(AppLifecycleState::Hidden);
398 let mut app = shell.app();
399 assert_eq!(
400 SchedulerBinding::lifecycle_state(&mut app),
401 Some(AppLifecycleState::Hidden)
402 );
403 assert!(!SchedulerBinding::frames_enabled(&mut app));
404 }
405
406 #[test]
407 fn resuming_marks_every_view_to_be_rendered_and_asks_each_for_a_frame() {
408 use inset_rendering::RendererBinding;
409
410 let platform = Rc::new(TestPlatform::new().with_views(&[IMPLICIT_VIEW_ID, ViewId(1)]));
411 let first = platform.test_view(IMPLICIT_VIEW_ID);
412 let mut shell = Shell::new(platform.clone(), |_app| {});
413 let render_view = {
414 let mut app = shell.app();
415 let binding = RendererBinding::instance(&mut app);
416 binding.init_render_view(&mut app, first.clone())
417 };
418 let at = |ms| Frame {
419 elapsed: Duration::from_millis(ms),
420 };
421 shell.frame(&[IMPLICIT_VIEW_ID], at(16));
422 shell.frame(&[IMPLICIT_VIEW_ID], at(32));
423 assert_eq!(
424 first.presented(),
425 1,
426 "nothing changed after the first frame"
427 );
428
429 shell.app_lifecycle_state_changed(AppLifecycleState::Hidden);
430 platform.take_frame_requests();
431 shell.app_lifecycle_state_changed(AppLifecycleState::Resumed);
432 assert_eq!(
433 platform.take_frame_requests(),
434 [IMPLICIT_VIEW_ID, ViewId(1)]
435 );
436 assert!(render_view.needs_composite_frame(&shell.app()));
437 shell.frame(&[IMPLICIT_VIEW_ID], at(48));
438 assert_eq!(first.presented(), 2, "drawn again though nothing changed");
439 }
440
441 #[test]
442 fn request_app_exit_is_answered_by_the_services_binding() {
443 use inset_embedder::AppExitResponse;
444 use inset_foundation::{Handle, Task};
445 use inset_services::{ServicesBinding, ServicesBindingOverridesObject};
446
447 #[derive(Default)]
448 struct Refusing;
449
450 impl ServicesBindingOverridesObject for Refusing {
451 fn handle_request_app_exit(
452 self: Handle<Self>,
453 _app: &mut App,
454 ) -> Task<AppExitResponse> {
455 Task::ready(AppExitResponse::Cancel)
456 }
457 }
458
459 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
460 let (reply, answer) = recorded_reply();
461 shell.request_app_exit(reply);
462 assert_eq!(answer.get(), Some(AppExitResponse::Exit));
463
464 let refusing = shell.app().create(Refusing);
465 let binding = ServicesBinding::instance(&mut shell.app());
466 binding.set_overrides(&mut shell.app(), Rc::new(refusing));
467 let (reply, answer) = recorded_reply();
468 shell.request_app_exit(reply);
469 assert_eq!(answer.get(), Some(AppExitResponse::Cancel));
470 }
471
472 #[test]
474 fn request_app_exit_answers_during_the_call_in_which_the_framework_decides() {
475 use inset_embedder::AppExitResponse;
476 use inset_foundation::{Completer, Handle, Task};
477 use inset_services::{ServicesBinding, ServicesBindingOverridesObject};
478
479 struct Deciding {
480 decision: Completer<AppExitResponse>,
481 }
482
483 impl ServicesBindingOverridesObject for Deciding {
484 fn handle_request_app_exit(self: Handle<Self>, app: &mut App) -> Task<AppExitResponse> {
485 let decision = app.get(self).decision.future();
486 app.spawn(async move |_cx| decision.await)
487 }
488 }
489
490 let decision = Completer::new();
491 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
492 let deciding = shell.app().create(Deciding {
493 decision: decision.clone(),
494 });
495 let binding = ServicesBinding::instance(&mut shell.app());
496 binding.set_overrides(&mut shell.app(), Rc::new(deciding));
497
498 let (reply, answer) = recorded_reply();
499 shell.request_app_exit(reply);
500 assert_eq!(answer.get(), None);
501
502 decision.complete(&mut shell.app(), AppExitResponse::Exit);
503 assert_eq!(answer.get(), None, "the task resumes at a checkpoint");
504 shell.wake(Duration::ZERO);
505 assert_eq!(answer.get(), Some(AppExitResponse::Exit));
506 }
507
508 #[test]
509 fn new_runs_setup_and_frame_runs_both_scheduler_phases() {
510 let platform = Rc::new(platform_with_a_view());
511 let mut setup_ran = false;
512 let mut shell = Shell::new(platform.clone(), |_app: &mut App| {
513 setup_ran = true;
514 });
515 assert!(setup_ran);
516 assert!(platform.frame_requests().is_empty());
517
518 SchedulerBinding::schedule_frame(&mut shell.app(), FrameTarget::View(IMPLICIT_VIEW_ID));
519 assert_eq!(platform.frame_requests(), [IMPLICIT_VIEW_ID]);
520 shell.frame(
521 &[IMPLICIT_VIEW_ID],
522 Frame {
523 elapsed: Duration::from_millis(16),
524 },
525 );
526 }
527
528 #[test]
529 fn pointer_data_packet_reaches_gesture_binding() {
530 let ran = Rc::new(Cell::new(false));
531 let ran_flag = Rc::clone(&ran);
532 let platform = Rc::new(platform_with_a_view());
533 let mut shell = Shell::new(platform, |app| {
534 let binding = GestureBinding::instance(app);
535 binding.pointer_router(app).add_route(
536 app,
537 1,
538 PointerRoute::new(move |_app, _event| {
539 ran_flag.set(true);
540 }),
541 None,
542 );
543 });
544
545 shell.pointer_data_packet(PointerDataPacket::new(vec![PointerData {
546 change: PointerChange::Down,
547 kind: PointerDeviceKind::Mouse,
548 buttons: 1,
549 pointer_identifier: 1,
550 ..PointerData::default()
551 }]));
552 assert!(ran.get());
553 }
554
555 #[test]
556 fn key_data_reaches_the_hardware_keyboard() {
557 let seen = Rc::new(Cell::new(0));
558 let seen_flag = Rc::clone(&seen);
559 let platform = Rc::new(TestPlatform::new());
560 let mut shell = Shell::new(platform, |app| {
561 HardwareKeyboard::instance(app).add_handler(
562 app,
563 Rc::new(move |_app: &mut App, _event: &KeyEvent| {
564 seen_flag.set(seen_flag.get() + 1);
565 true
566 }),
567 );
568 });
569
570 let key_a = KeyData {
571 event_type: KeyEventType::Down,
572 physical: PhysicalKeyboardKey::KEY_A.usb_hid_usage,
573 logical: LogicalKeyboardKey::KEY_A.key_id,
574 character: Some("a".to_owned()),
575 ..KeyData::default()
576 };
577 assert!(shell.key_data(key_a), "the handler claimed the event");
578 assert_eq!(seen.get(), 1);
579
580 let keyboard = HardwareKeyboard::instance(&mut shell.app());
581 assert!(keyboard.is_logical_key_pressed(&shell.app(), LogicalKeyboardKey::KEY_A));
582 }
583
584 #[test]
585 fn wake_and_frame_advance_the_app_clock_and_fire_due_timers() {
586 let platform = Rc::new(TestPlatform::new());
587 let fired = Rc::new(Cell::new(Vec::new()));
588 let mut shell = Shell::new(platform, |app| {
589 for (name, delay) in [("early", 10), ("late", 30)] {
590 let fired = Rc::clone(&fired);
591 inset_foundation::Timer::new(
592 app,
593 Duration::from_millis(delay),
594 inset_foundation::Listener::new(move |_app| {
595 let mut log = fired.take();
596 log.push(name);
597 fired.set(log);
598 }),
599 );
600 }
601 });
602 assert!(fired.take().is_empty());
603
604 shell.wake(Duration::from_millis(15));
605 assert_eq!(fired.take(), vec!["early"]);
606
607 shell.frame(
608 &[IMPLICIT_VIEW_ID],
609 Frame {
610 elapsed: Duration::from_millis(32),
611 },
612 );
613 assert_eq!(fired.take(), vec!["late"]);
614 }
615
616 #[test]
617 fn a_view_coming_or_going_and_a_change_to_the_screens_are_metrics_changes() {
618 use inset_foundation::Handle;
619 use inset_rendering::{RendererBinding, RendererBindingOverridesObject};
620
621 struct CountingMetricsChanges {
622 count: Rc<Cell<usize>>,
623 }
624
625 impl RendererBindingOverridesObject for CountingMetricsChanges {
626 fn will_draw_frame(self: Handle<Self>, _app: &mut App) {}
627
628 fn did_draw_frame(self: Handle<Self>, _app: &mut App) {}
629
630 fn did_handle_metrics_changed(self: Handle<Self>, app: &mut App) {
631 let count = &app.get(self).count;
632 count.set(count.get() + 1);
633 }
634 }
635
636 let count = Rc::new(Cell::new(0));
637 let mut shell = Shell::new(Rc::new(platform_with_a_view()), |_app| {});
638 let counting = shell.app().create(CountingMetricsChanges {
639 count: Rc::clone(&count),
640 });
641 let binding = RendererBinding::instance(&mut shell.app());
642 binding.set_overrides(&mut shell.app(), Rc::new(counting));
643
644 shell.view_added(ViewId(1));
645 assert_eq!(count.get(), 1, "a view added");
646 shell.view_removed(ViewId(1));
647 assert_eq!(count.get(), 2, "a view removed");
648 shell.screens_changed();
649 assert_eq!(count.get(), 3, "the screens changed");
650 }
651
652 #[test]
653 fn view_focus_is_delivered_through_the_dispatcher_callback() {
654 use inset_embedder::{ViewFocusDirection, ViewFocusEvent, ViewFocusState};
655 let received = Rc::new(Cell::new(None));
656 let sink = received.clone();
657 let platform = Rc::new(platform_with_a_view());
658 let mut shell = Shell::new(platform, move |app| {
659 app.platform_callbacks_mut().on_view_focus_change = Some(Rc::new(move |app, event| {
660 let sink = sink.clone();
661 app.schedule_microtask(inset_foundation::Listener::new(move |_| {
662 sink.set(Some(event))
663 }));
664 }));
665 });
666 shell.view_focus_changed(ViewFocusEvent {
667 view_id: IMPLICIT_VIEW_ID,
668 state: ViewFocusState::Focused,
669 direction: ViewFocusDirection::Backward,
670 });
671 let event = received
672 .get()
673 .expect("the shell checkpoints the callback's microtask");
674 assert_eq!(event.view_id, IMPLICIT_VIEW_ID);
675 assert_eq!(event.state, ViewFocusState::Focused);
676 assert_eq!(event.direction, ViewFocusDirection::Backward);
677 }
678}