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