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, ClipboardEvent, DropData, EditCommand, EmbedderClient, Frame,
15 Instant, KeyData, MenuId, NotificationActivation, OpenRequest, PlatformRef, PointerDataPacket,
16 RequestId, TextEditingValue, TextInputAction, TrayIconEvent, TrayIconId, ViewError,
17 ViewFocusEvent, ViewId,
18};
19use inset_foundation::{App, AppCell};
20use inset_gestures::GestureBinding;
21use inset_painting::PaintingBinding;
22use inset_rendering::RendererBinding;
23use inset_scheduler::{FrameTarget, SchedulerBinding};
24use inset_services::{KeyEventManager, ServicesBinding, TextInput};
25
26pub struct Shell {
32 app: Rc<AppCell>,
33 platform: PlatformRef,
34 clock: Duration,
36 origin: Option<Instant>,
38}
39
40impl Shell {
41 pub fn new(platform: PlatformRef, setup: impl FnOnce(&mut App)) -> Shell {
46 let shell = Shell {
47 app: AppCell::with_platform(platform.clone()),
48 platform,
49 clock: Duration::ZERO,
50 origin: None,
51 };
52 shell.handle_event(|app| {
53 PaintingBinding::instance(app).install_platform_fonts(app);
55 setup(app);
56 });
57 shell
58 }
59
60 pub fn app(&self) -> RefMut<'_, App> {
63 self.app.borrow_mut()
64 }
65
66 pub fn cell(&self) -> &Rc<AppCell> {
67 &self.app
68 }
69
70 fn handle_event<R>(&self, f: impl FnOnce(&mut App) -> R) -> R {
72 let result = f(&mut self.app.borrow_mut());
73 self.app.checkpoint();
74 result
75 }
76
77 fn push<R>(&mut self, f: impl FnOnce(&mut App) -> R) -> R {
80 if let Some(origin) = self.origin {
81 let elapsed = self.platform.now().saturating_duration_since(origin);
82 self.advance_clock(elapsed);
83 }
84 self.handle_event(f)
85 }
86
87 fn advance_clock(&mut self, elapsed: Duration) {
89 if elapsed <= self.clock {
90 return;
91 }
92 self.app.elapse(elapsed - self.clock);
93 self.clock = elapsed;
94 }
95
96 fn metrics_changed(&mut self) {
101 self.push(|app| RendererBinding::instance(app).handle_metrics_changed(app));
102 }
103}
104
105impl EmbedderClient for Shell {
106 fn frame(&mut self, views: &[ViewId], frame: Frame) {
107 self.origin = Some(self.platform.now() - frame.elapsed);
108 self.advance_clock(frame.elapsed);
109 self.handle_event(|app| SchedulerBinding::on_begin_frame(app, views, Some(frame.elapsed)));
111 self.handle_event(|app| SchedulerBinding::on_draw_frame(app, views));
112 }
113
114 fn view_added(&mut self, id: ViewId) {
116 self.push(|app| {
117 let callback = app.platform_callbacks().on_view_added.clone();
118 if let Some(callback) = callback {
119 callback(app, id);
120 }
121 });
122 self.metrics_changed();
123 }
124
125 fn view_metrics_changed(&mut self, _id: ViewId) {
126 self.metrics_changed();
127 }
128
129 fn view_removed(&mut self, id: ViewId) {
132 self.push(|app| {
133 let callback = app.platform_callbacks().on_view_removed.clone();
134 if let Some(callback) = callback {
135 callback(app, id);
136 }
137 });
138 self.metrics_changed();
139 }
140
141 fn screens_changed(&mut self) {
143 self.metrics_changed();
144 }
145
146 fn view_focus_changed(&mut self, event: ViewFocusEvent) {
147 self.push(|app| {
148 let callback = app.platform_callbacks().on_view_focus_change.clone();
149 if let Some(callback) = callback {
150 callback(app, event);
151 }
152 });
153 }
154
155 fn drop_data(&mut self, data: DropData) {
156 self.push(|app| {
157 let callback = app.platform_callbacks().on_drop.clone();
158 if let Some(callback) = callback {
159 callback(app, data);
160 }
161 });
162 }
163
164 fn clipboard_event(&mut self, view: ViewId, event: ClipboardEvent) {
165 self.push(|app| {
166 let callback = app.platform_callbacks().on_clipboard_event.clone();
167 if let Some(callback) = callback {
168 callback(app, view, event);
169 }
170 });
171 }
172
173 fn edit_command(&mut self, view: ViewId, command: EditCommand) {
174 self.push(|app| {
175 let callback = app.platform_callbacks().on_edit_command.clone();
176 if let Some(callback) = callback {
177 callback(app, view, command);
178 }
179 });
180 }
181
182 fn menu_selected(&mut self, id: MenuId) {
183 self.push(|app| {
184 let callback = app.platform_callbacks().on_menu_selected.clone();
185 if let Some(callback) = callback {
186 callback(app, id);
187 }
188 });
189 }
190
191 fn dock_menu_selected(&mut self, id: MenuId) {
192 self.push(|app| {
193 let callback = app.platform_callbacks().on_dock_menu_selected.clone();
194 if let Some(callback) = callback {
195 callback(app, id);
196 }
197 });
198 }
199
200 fn tray_icon_event(&mut self, icon: TrayIconId, event: TrayIconEvent) {
201 self.push(|app| {
202 let callback = app.platform_callbacks().on_tray_icon_event.clone();
203 if let Some(callback) = callback {
204 callback(app, icon, event);
205 }
206 });
207 }
208
209 fn open_requested(&mut self, request: OpenRequest) {
210 self.push(|app| app.open_requested(request));
211 }
212
213 fn notification_activated(&mut self, activation: NotificationActivation) {
214 self.push(|app| app.notification_activated(activation));
215 }
216
217 fn pointer_data_packet(&mut self, packet: PointerDataPacket) {
218 self.push(|app| GestureBinding::instance(app).handle_pointer_data_packet(app, packet));
219 }
220
221 fn key_data(&mut self, data: KeyData) -> bool {
222 self.push(|app| KeyEventManager::instance(app).handle_key_data(app, data))
223 }
224
225 fn pop_route(&mut self) -> bool {
226 self.push(|app| {
227 let callback = app.platform_callbacks().on_pop_route.clone();
228 callback.is_some_and(|callback| callback(app))
229 })
230 }
231
232 fn view_close_requested(&mut self, view: ViewId) {
233 self.push(|app| {
234 let callback = app.platform_callbacks().on_view_close_requested.clone();
235 if let Some(callback) = callback {
236 callback(app, view);
237 }
238 });
239 }
240
241 fn window_changed(&mut self, view: ViewId) {
242 self.push(|app| {
243 let callback = app.platform_callbacks().on_window_changed.clone();
244 if let Some(callback) = callback {
245 callback(app, view);
246 }
247 });
248 }
249
250 fn view_request_failed(&mut self, request: RequestId, error: ViewError) {
251 self.push(|app| {
252 let callback = app.platform_callbacks().on_view_request_failed.clone();
253 if let Some(callback) = callback {
254 callback(app, request, error);
255 }
256 });
257 }
258
259 fn request_app_exit(&mut self, reply: AppExitReply) {
262 self.push(|app| {
263 let answer = ServicesBinding::instance(app).handle_request_app_exit(app);
264 app.spawn(async move |_cx| reply.answer(answer.await));
265 });
266 }
267
268 fn app_lifecycle_state_changed(&mut self, state: AppLifecycleState) {
272 self.push(|app| {
273 if matches!(
274 state,
275 AppLifecycleState::Resumed | AppLifecycleState::Inactive
276 ) {
277 RendererBinding::instance(app).handle_mark_all_views_need_render(app);
278 SchedulerBinding::schedule_forced_frame(app, FrameTarget::EveryView);
279 }
280 ServicesBinding::instance(app).handle_lifecycle_message(app, state);
281 });
282 }
283
284 fn platform_brightness_changed(&mut self) {
285 self.push(|app| {
286 let callback = app
287 .platform_callbacks()
288 .on_platform_brightness_changed
289 .clone();
290 if let Some(callback) = callback {
291 callback.call(app);
292 }
293 });
294 }
295
296 fn locales_changed(&mut self) {
297 self.push(|app| {
298 let callback = app.platform_callbacks().on_locale_changed.clone();
299 if let Some(callback) = callback {
300 callback.call(app);
301 }
302 });
303 }
304
305 fn system_fonts_changed(&mut self) {
306 self.push(|app| {
307 PaintingBinding::instance(app).handle_system_fonts_did_change(app);
308 });
309 }
310
311 fn clipboard_changed(&mut self) {
312 self.push(|app| ServicesBinding::instance(app).handle_clipboard_changed(app));
313 }
314
315 fn wake(&mut self, elapsed: Duration) {
316 self.advance_clock(elapsed);
317 self.app.wake();
319 }
320
321 fn text_input_editing_value(&mut self, _view: ViewId, value: TextEditingValue) {
322 self.push(|app| TextInput::instance(app).update_editing_value(app, value));
323 }
324
325 fn text_input_action(&mut self, _view: ViewId, action: TextInputAction) {
326 self.push(|app| TextInput::instance(app).perform_action(app, action));
327 }
328
329 fn text_input_closed(&mut self, _view: ViewId) {
330 self.push(|app| TextInput::instance(app).connection_closed(app));
331 }
332
333 fn system_context_menu_hidden(&mut self) {
334 self.push(inset_services::SystemContextMenuController::dispatch_system_hide);
335 }
336
337 fn custom_context_menu_action(&mut self, callback_id: &str) {
338 let callback_id = callback_id.to_string();
339 self.push(move |app| {
340 inset_services::SystemContextMenuController::dispatch_custom_action(app, &callback_id);
341 });
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use std::cell::Cell;
348 use std::rc::Rc;
349 use std::time::Duration;
350
351 use inset_embedder::{
352 AppLifecycleState, EmbedderClient, Frame, KeyData, KeyEventType, PointerChange,
353 PointerData, PointerDataPacket, PointerDeviceKind, ViewId,
354 };
355 use inset_foundation::App;
356 use inset_gestures::{GestureBinding, PointerRoute};
357 use inset_scheduler::{FrameTarget, SchedulerBinding};
358 use inset_services::{HardwareKeyboard, KeyEvent, LogicalKeyboardKey, PhysicalKeyboardKey};
359 use inset_test::{IMPLICIT_VIEW_ID, TestPlatform};
360
361 use super::Shell;
362
363 fn platform_with_a_view() -> TestPlatform {
365 TestPlatform::new().with_views(&[IMPLICIT_VIEW_ID])
366 }
367
368 #[test]
369 fn drop_data_reaches_the_platform_callback() {
370 use std::cell::RefCell;
371 use std::path::PathBuf;
372
373 use inset_embedder::{DropChange, DropData};
374
375 let seen = Rc::new(RefCell::new(None));
376 let seen_slot = Rc::clone(&seen);
377 let platform = Rc::new(platform_with_a_view());
378 let mut shell = Shell::new(platform, move |app: &mut App| {
379 app.platform_callbacks_mut().on_drop = Some(Rc::new(move |_app, data| {
380 *seen_slot.borrow_mut() = Some(data);
381 }));
382 });
383 shell.drop_data(DropData {
384 view_id: IMPLICIT_VIEW_ID,
385 change: DropChange::Dropped,
386 physical_position: None,
387 paths: vec![PathBuf::from("/a.wasm")],
388 });
389 let seen = seen.borrow();
390 let seen = seen.as_ref().expect("the callback ran");
391 assert_eq!(seen.change, DropChange::Dropped);
392 assert_eq!(seen.paths, [PathBuf::from("/a.wasm")]);
393 }
394
395 #[test]
396 fn a_clipboard_event_reaches_the_platform_callback_with_its_view() {
397 use inset_embedder::{ClipboardEvent, CopyRequest};
398
399 let seen = Rc::new(Cell::new(None));
400 let seen_slot = Rc::clone(&seen);
401 let mut shell = Shell::new(Rc::new(platform_with_a_view()), move |app: &mut App| {
402 app.platform_callbacks_mut().on_clipboard_event =
403 Some(Rc::new(move |_app, view, event| {
404 seen_slot.set(Some((view, matches!(event, ClipboardEvent::Cut(_)))));
405 }));
406 });
407 let request = CopyRequest::new(|_items, _options| Box::pin(std::future::ready(Ok(()))));
408 shell.clipboard_event(IMPLICIT_VIEW_ID, ClipboardEvent::Cut(request));
409 assert_eq!(seen.get(), Some((IMPLICIT_VIEW_ID, true)));
410 }
411
412 #[test]
413 fn an_open_request_waits_for_the_apps_handler() {
414 use std::cell::RefCell;
415
416 use inset_embedder::OpenRequest;
417
418 let seen = Rc::new(RefCell::new(Vec::new()));
419 let mut shell = Shell::new(Rc::new(platform_with_a_view()), |_app: &mut App| {});
420 shell.open_requested(OpenRequest {
421 files: Vec::new(),
422 links: vec!["https://example.org/launch".to_owned()],
423 });
424 let slot = Rc::clone(&seen);
425 shell
426 .app
427 .borrow_mut()
428 .set_open_requested_handler(Some(Rc::new(move |_app, request: OpenRequest| {
429 slot.borrow_mut().extend(request.links)
430 })));
431 shell.open_requested(OpenRequest {
432 files: Vec::new(),
433 links: vec!["https://example.org/later".to_owned()],
434 });
435 assert_eq!(
436 *seen.borrow(),
437 ["https://example.org/launch", "https://example.org/later"],
438 "the launch's request waited for the handler"
439 );
440 }
441
442 #[test]
443 fn a_notification_click_waits_for_the_apps_handler() {
444 use std::cell::RefCell;
445
446 use inset_embedder::NotificationActivation;
447
448 let seen = Rc::new(RefCell::new(Vec::new()));
449 let mut shell = Shell::new(Rc::new(platform_with_a_view()), |_app: &mut App| {});
450 let click = |action: Option<&str>| NotificationActivation {
451 id: "message-7".to_owned(),
452 action: action.map(str::to_owned),
453 reply: None,
454 };
455 shell.notification_activated(click(None));
456 let slot = Rc::clone(&seen);
457 shell
458 .app
459 .borrow_mut()
460 .set_notification_activated_handler(Some(Rc::new(move |_app, activation| {
461 slot.borrow_mut().push(activation)
462 })));
463 shell.notification_activated(click(Some("later")));
464 assert_eq!(
465 *seen.borrow(),
466 [click(None), click(Some("later"))],
467 "the click that started the app waited for the handler"
468 );
469 }
470
471 #[test]
472 fn menu_choices_edit_commands_and_tray_icon_events_reach_their_callbacks() {
473 use std::cell::RefCell;
474
475 use inset_embedder::{EditCommand, Offset, TrayIconEvent};
476
477 let seen = Rc::new(RefCell::new(Vec::new()));
478 let slot = Rc::clone(&seen);
479 let mut shell = Shell::new(Rc::new(platform_with_a_view()), move |app: &mut App| {
480 let callbacks = app.platform_callbacks_mut();
481 let menu = Rc::clone(&slot);
482 callbacks.on_menu_selected = Some(Rc::new(move |_app, id| {
483 menu.borrow_mut().push(format!("menu {id}"));
484 }));
485 let dock = Rc::clone(&slot);
486 callbacks.on_dock_menu_selected = Some(Rc::new(move |_app, id| {
487 dock.borrow_mut().push(format!("dock {id}"));
488 }));
489 let edit = Rc::clone(&slot);
490 callbacks.on_edit_command = Some(Rc::new(move |_app, view, command| {
491 edit.borrow_mut().push(format!("{view:?} {command:?}"));
492 }));
493 let tray = Rc::clone(&slot);
494 callbacks.on_tray_icon_event = Some(Rc::new(move |_app, icon, event| {
495 tray.borrow_mut().push(format!("tray {icon} {event:?}"));
496 }));
497 });
498 shell.menu_selected(3);
499 shell.dock_menu_selected(4);
500 shell.edit_command(IMPLICIT_VIEW_ID, EditCommand::Undo);
501 shell.tray_icon_event(
502 7,
503 TrayIconEvent::Activated {
504 position: Offset::new(1.0, 2.0),
505 },
506 );
507 assert_eq!(
508 *seen.borrow(),
509 [
510 "menu 3".to_owned(),
511 "dock 4".to_owned(),
512 format!("{IMPLICIT_VIEW_ID:?} Undo"),
513 format!(
514 "tray 7 {:?}",
515 TrayIconEvent::Activated {
516 position: Offset::new(1.0, 2.0)
517 }
518 ),
519 ]
520 );
521 }
522
523 #[test]
524 fn a_change_of_the_clipboard_reaches_the_services_bindings_listenable() {
525 use inset_foundation::{Listenable, Listener};
526 use inset_services::ServicesBinding;
527
528 let heard = Rc::new(Cell::new(0));
529 let counter = Rc::clone(&heard);
530 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
531 let clipboard = ServicesBinding::instance(app).clipboard(app);
532 clipboard.add_listener(
533 app,
534 Listener::new(move |_app| counter.set(counter.get() + 1)),
535 );
536 });
537 shell.clipboard_changed();
538 assert_eq!(heard.get(), 1);
539 }
540
541 fn recorded_reply() -> (
543 inset_embedder::AppExitReply,
544 Rc<Cell<Option<inset_embedder::AppExitResponse>>>,
545 ) {
546 let answer = Rc::new(Cell::new(None));
547 let given = Rc::clone(&answer);
548 let reply = inset_embedder::AppExitReply::new(move |response| given.set(Some(response)));
549 (reply, answer)
550 }
551
552 #[test]
553 fn a_view_added_or_removed_reaches_the_framework_with_its_id() {
554 let heard = Rc::new(std::cell::RefCell::new(Vec::new()));
555 let (added, removed) = (Rc::clone(&heard), Rc::clone(&heard));
556 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
557 let callbacks = app.platform_callbacks_mut();
558 callbacks.on_view_added = Some(Rc::new(move |_app, view| {
559 added.borrow_mut().push(("added", view))
560 }));
561 callbacks.on_view_removed = Some(Rc::new(move |_app, view| {
562 removed.borrow_mut().push(("removed", view))
563 }));
564 });
565 shell.view_added(ViewId(4));
566 shell.view_removed(ViewId(4));
567 assert_eq!(
568 *heard.borrow(),
569 [("added", ViewId(4)), ("removed", ViewId(4))]
570 );
571 }
572
573 #[test]
574 fn a_view_close_request_reaches_the_framework() {
575 let heard = Rc::new(Cell::new(None));
576 let heard_slot = Rc::clone(&heard);
577 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
578 app.platform_callbacks_mut().on_view_close_requested =
579 Some(Rc::new(move |_app, view| heard_slot.set(Some(view))));
580 });
581 shell.view_close_requested(ViewId(3));
582 assert_eq!(heard.get(), Some(ViewId(3)));
583 }
584
585 #[test]
586 fn a_window_change_reaches_the_framework() {
587 let heard = Rc::new(Cell::new(None));
588 let heard_slot = Rc::clone(&heard);
589 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
590 app.platform_callbacks_mut().on_window_changed =
591 Some(Rc::new(move |_app, view| heard_slot.set(Some(view))));
592 });
593 shell.window_changed(ViewId(5));
594 assert_eq!(heard.get(), Some(ViewId(5)));
595 }
596
597 #[test]
598 fn a_lifecycle_state_the_host_reports_reaches_the_scheduler_with_the_states_between() {
599 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
600 shell.app_lifecycle_state_changed(AppLifecycleState::Resumed);
601 shell.app_lifecycle_state_changed(AppLifecycleState::Hidden);
602 let mut app = shell.app();
603 assert_eq!(
604 SchedulerBinding::lifecycle_state(&mut app),
605 Some(AppLifecycleState::Hidden)
606 );
607 assert!(!SchedulerBinding::frames_enabled(&mut app));
608 }
609
610 #[test]
611 fn resuming_marks_every_view_to_be_rendered_and_asks_each_for_a_frame() {
612 use inset_rendering::RendererBinding;
613
614 let platform = Rc::new(TestPlatform::new().with_views(&[IMPLICIT_VIEW_ID, ViewId(1)]));
615 let first = platform.test_view(IMPLICIT_VIEW_ID);
616 let mut shell = Shell::new(platform.clone(), |_app| {});
617 let render_view = {
618 let mut app = shell.app();
619 let binding = RendererBinding::instance(&mut app);
620 binding.init_render_view(&mut app, first.clone())
621 };
622 let at = |ms| Frame {
623 elapsed: Duration::from_millis(ms),
624 };
625 shell.frame(&[IMPLICIT_VIEW_ID], at(16));
626 shell.frame(&[IMPLICIT_VIEW_ID], at(32));
627 assert_eq!(
628 first.presented(),
629 1,
630 "nothing changed after the first frame"
631 );
632
633 shell.app_lifecycle_state_changed(AppLifecycleState::Hidden);
634 platform.take_frame_requests();
635 shell.app_lifecycle_state_changed(AppLifecycleState::Resumed);
636 assert_eq!(
637 platform.take_frame_requests(),
638 [IMPLICIT_VIEW_ID, ViewId(1)]
639 );
640 assert!(render_view.needs_composite_frame(&shell.app()));
641 shell.frame(&[IMPLICIT_VIEW_ID], at(48));
642 assert_eq!(first.presented(), 2, "drawn again though nothing changed");
643 }
644
645 #[test]
646 fn request_app_exit_is_answered_by_the_services_binding() {
647 use inset_embedder::AppExitResponse;
648 use inset_foundation::{Handle, Task};
649 use inset_services::{ServicesBinding, ServicesBindingOverridesObject};
650
651 #[derive(Default)]
652 struct Refusing;
653
654 impl ServicesBindingOverridesObject for Refusing {
655 fn handle_request_app_exit(
656 self: Handle<Self>,
657 _app: &mut App,
658 ) -> Task<AppExitResponse> {
659 Task::ready(AppExitResponse::Cancel)
660 }
661 }
662
663 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
664 let (reply, answer) = recorded_reply();
665 shell.request_app_exit(reply);
666 assert_eq!(answer.get(), Some(AppExitResponse::Exit));
667
668 let refusing = shell.app().create(Refusing);
669 let binding = ServicesBinding::instance(&mut shell.app());
670 binding.set_overrides(&mut shell.app(), Rc::new(refusing));
671 let (reply, answer) = recorded_reply();
672 shell.request_app_exit(reply);
673 assert_eq!(answer.get(), Some(AppExitResponse::Cancel));
674 }
675
676 #[test]
678 fn request_app_exit_answers_during_the_call_in_which_the_framework_decides() {
679 use inset_embedder::AppExitResponse;
680 use inset_foundation::{Completer, Handle, Task};
681 use inset_services::{ServicesBinding, ServicesBindingOverridesObject};
682
683 struct Deciding {
684 decision: Completer<AppExitResponse>,
685 }
686
687 impl ServicesBindingOverridesObject for Deciding {
688 fn handle_request_app_exit(self: Handle<Self>, app: &mut App) -> Task<AppExitResponse> {
689 let decision = app.get(self).decision.future();
690 app.spawn(async move |_cx| decision.await)
691 }
692 }
693
694 let decision = Completer::new();
695 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
696 let deciding = shell.app().create(Deciding {
697 decision: decision.clone(),
698 });
699 let binding = ServicesBinding::instance(&mut shell.app());
700 binding.set_overrides(&mut shell.app(), Rc::new(deciding));
701
702 let (reply, answer) = recorded_reply();
703 shell.request_app_exit(reply);
704 assert_eq!(answer.get(), None);
705
706 decision.complete(&mut shell.app(), AppExitResponse::Exit);
707 assert_eq!(answer.get(), None, "the task resumes at a checkpoint");
708 shell.wake(Duration::ZERO);
709 assert_eq!(answer.get(), Some(AppExitResponse::Exit));
710 }
711
712 #[test]
713 fn new_runs_setup_and_frame_runs_both_scheduler_phases() {
714 let platform = Rc::new(platform_with_a_view());
715 let mut setup_ran = false;
716 let mut shell = Shell::new(platform.clone(), |_app: &mut App| {
717 setup_ran = true;
718 });
719 assert!(setup_ran);
720 assert!(platform.frame_requests().is_empty());
721
722 SchedulerBinding::schedule_frame(&mut shell.app(), FrameTarget::View(IMPLICIT_VIEW_ID));
723 assert_eq!(platform.frame_requests(), [IMPLICIT_VIEW_ID]);
724 shell.frame(
725 &[IMPLICIT_VIEW_ID],
726 Frame {
727 elapsed: Duration::from_millis(16),
728 },
729 );
730 }
731
732 #[test]
733 fn pointer_data_packet_reaches_gesture_binding() {
734 let ran = Rc::new(Cell::new(false));
735 let ran_flag = Rc::clone(&ran);
736 let platform = Rc::new(platform_with_a_view());
737 let mut shell = Shell::new(platform, |app| {
738 let binding = GestureBinding::instance(app);
739 binding.pointer_router(app).add_route(
740 app,
741 1,
742 PointerRoute::new(move |_app, _event| {
743 ran_flag.set(true);
744 }),
745 None,
746 );
747 });
748
749 shell.pointer_data_packet(PointerDataPacket::new(vec![PointerData {
750 change: PointerChange::Down,
751 kind: PointerDeviceKind::Mouse,
752 buttons: 1,
753 pointer_identifier: 1,
754 ..PointerData::default()
755 }]));
756 assert!(ran.get());
757 }
758
759 #[test]
760 fn key_data_reaches_the_hardware_keyboard() {
761 let seen = Rc::new(Cell::new(0));
762 let seen_flag = Rc::clone(&seen);
763 let platform = Rc::new(TestPlatform::new());
764 let mut shell = Shell::new(platform, |app| {
765 HardwareKeyboard::instance(app).add_handler(
766 app,
767 Rc::new(move |_app: &mut App, _event: &KeyEvent| {
768 seen_flag.set(seen_flag.get() + 1);
769 true
770 }),
771 );
772 });
773
774 let key_a = KeyData {
775 event_type: KeyEventType::Down,
776 physical: PhysicalKeyboardKey::KEY_A.usb_hid_usage,
777 logical: LogicalKeyboardKey::KEY_A.key_id,
778 character: Some("a".to_owned()),
779 ..KeyData::default()
780 };
781 assert!(shell.key_data(key_a), "the handler claimed the event");
782 assert_eq!(seen.get(), 1);
783
784 let keyboard = HardwareKeyboard::instance(&mut shell.app());
785 assert!(keyboard.is_logical_key_pressed(&shell.app(), LogicalKeyboardKey::KEY_A));
786 }
787
788 #[test]
789 fn wake_and_frame_advance_the_app_clock_and_fire_due_timers() {
790 let platform = Rc::new(TestPlatform::new());
791 let fired = Rc::new(Cell::new(Vec::new()));
792 let mut shell = Shell::new(platform, |app| {
793 for (name, delay) in [("early", 10), ("late", 30)] {
794 let fired = Rc::clone(&fired);
795 inset_foundation::Timer::new(
796 app,
797 Duration::from_millis(delay),
798 inset_foundation::Listener::new(move |_app| {
799 let mut log = fired.take();
800 log.push(name);
801 fired.set(log);
802 }),
803 );
804 }
805 });
806 assert!(fired.take().is_empty());
807
808 shell.wake(Duration::from_millis(15));
809 assert_eq!(fired.take(), vec!["early"]);
810
811 shell.frame(
812 &[IMPLICIT_VIEW_ID],
813 Frame {
814 elapsed: Duration::from_millis(32),
815 },
816 );
817 assert_eq!(fired.take(), vec!["late"]);
818 }
819
820 #[test]
821 fn a_view_coming_or_going_and_a_change_to_the_screens_are_metrics_changes() {
822 use inset_foundation::Handle;
823 use inset_rendering::{RendererBinding, RendererBindingOverridesObject};
824
825 struct CountingMetricsChanges {
826 count: Rc<Cell<usize>>,
827 }
828
829 impl RendererBindingOverridesObject for CountingMetricsChanges {
830 fn will_draw_frame(self: Handle<Self>, _app: &mut App) {}
831
832 fn did_draw_frame(self: Handle<Self>, _app: &mut App) {}
833
834 fn did_handle_metrics_changed(self: Handle<Self>, app: &mut App) {
835 let count = &app.get(self).count;
836 count.set(count.get() + 1);
837 }
838 }
839
840 let count = Rc::new(Cell::new(0));
841 let mut shell = Shell::new(Rc::new(platform_with_a_view()), |_app| {});
842 let counting = shell.app().create(CountingMetricsChanges {
843 count: Rc::clone(&count),
844 });
845 let binding = RendererBinding::instance(&mut shell.app());
846 binding.set_overrides(&mut shell.app(), Rc::new(counting));
847
848 shell.view_added(ViewId(1));
849 assert_eq!(count.get(), 1, "a view added");
850 shell.view_removed(ViewId(1));
851 assert_eq!(count.get(), 2, "a view removed");
852 shell.screens_changed();
853 assert_eq!(count.get(), 3, "the screens changed");
854 }
855
856 #[test]
857 fn view_focus_is_delivered_through_the_dispatcher_callback() {
858 use inset_embedder::{ViewFocusDirection, ViewFocusEvent, ViewFocusState};
859 let received = Rc::new(Cell::new(None));
860 let sink = received.clone();
861 let platform = Rc::new(platform_with_a_view());
862 let mut shell = Shell::new(platform, move |app| {
863 app.platform_callbacks_mut().on_view_focus_change = Some(Rc::new(move |app, event| {
864 let sink = sink.clone();
865 app.schedule_microtask(inset_foundation::Listener::new(move |_| {
866 sink.set(Some(event))
867 }));
868 }));
869 });
870 shell.view_focus_changed(ViewFocusEvent {
871 view_id: IMPLICIT_VIEW_ID,
872 state: ViewFocusState::Focused,
873 direction: ViewFocusDirection::Backward,
874 });
875 let event = received
876 .get()
877 .expect("the shell checkpoints the callback's microtask");
878 assert_eq!(event.view_id, IMPLICIT_VIEW_ID);
879 assert_eq!(event.state, ViewFocusState::Focused);
880 assert_eq!(event.direction, ViewFocusDirection::Backward);
881 }
882}