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, 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 drop_data(&mut self, data: DropData) {
159 self.push(|app| {
160 let callback = app.platform_callbacks().on_drop.clone();
161 if let Some(callback) = callback {
162 callback(app, data);
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 drop_data_reaches_the_platform_callback() {
378 use std::cell::RefCell;
379 use std::path::PathBuf;
380
381 use inset_embedder::{DropChange, DropData};
382
383 let seen = Rc::new(RefCell::new(None));
384 let seen_slot = Rc::clone(&seen);
385 let platform = Rc::new(platform_with_a_view());
386 let mut shell = Shell::new(platform, move |app: &mut App| {
387 app.platform_callbacks_mut().on_drop = Some(Rc::new(move |_app, data| {
388 *seen_slot.borrow_mut() = Some(data);
389 }));
390 });
391 shell.drop_data(DropData {
392 view_id: IMPLICIT_VIEW_ID,
393 change: DropChange::Dropped,
394 physical_position: None,
395 paths: vec![PathBuf::from("/a.wasm")],
396 });
397 let seen = seen.borrow();
398 let seen = seen.as_ref().expect("the callback ran");
399 assert_eq!(seen.change, DropChange::Dropped);
400 assert_eq!(seen.paths, [PathBuf::from("/a.wasm")]);
401 }
402
403 #[test]
404 fn a_clipboard_event_reaches_the_platform_callback_with_its_view() {
405 use inset_embedder::{ClipboardEvent, CopyRequest};
406
407 let seen = Rc::new(Cell::new(None));
408 let seen_slot = Rc::clone(&seen);
409 let mut shell = Shell::new(Rc::new(platform_with_a_view()), move |app: &mut App| {
410 app.platform_callbacks_mut().on_clipboard_event =
411 Some(Rc::new(move |_app, view, event| {
412 seen_slot.set(Some((view, matches!(event, ClipboardEvent::Cut(_)))));
413 }));
414 });
415 let request = CopyRequest::new(|_items, _options| Box::pin(std::future::ready(Ok(()))));
416 shell.clipboard_event(IMPLICIT_VIEW_ID, ClipboardEvent::Cut(request));
417 assert_eq!(seen.get(), Some((IMPLICIT_VIEW_ID, true)));
418 }
419
420 #[test]
421 fn an_open_request_waits_for_the_apps_handler() {
422 use std::cell::RefCell;
423
424 use inset_embedder::OpenRequest;
425
426 let seen = Rc::new(RefCell::new(Vec::new()));
427 let mut shell = Shell::new(Rc::new(platform_with_a_view()), |_app: &mut App| {});
428 shell.open_requested(OpenRequest {
429 files: Vec::new(),
430 links: vec!["https://example.org/launch".to_owned()],
431 });
432 let slot = Rc::clone(&seen);
433 shell
434 .app
435 .borrow_mut()
436 .set_open_requested_handler(Some(Rc::new(move |_app, request: OpenRequest| {
437 slot.borrow_mut().extend(request.links)
438 })));
439 shell.open_requested(OpenRequest {
440 files: Vec::new(),
441 links: vec!["https://example.org/later".to_owned()],
442 });
443 assert_eq!(
444 *seen.borrow(),
445 ["https://example.org/launch", "https://example.org/later"],
446 "the launch's request waited for the handler"
447 );
448 }
449
450 #[test]
451 fn a_notification_click_waits_for_the_apps_handler() {
452 use std::cell::RefCell;
453
454 use inset_embedder::NotificationActivation;
455
456 let seen = Rc::new(RefCell::new(Vec::new()));
457 let mut shell = Shell::new(Rc::new(platform_with_a_view()), |_app: &mut App| {});
458 let click = |action: Option<&str>| NotificationActivation {
459 id: "message-7".to_owned(),
460 action: action.map(str::to_owned),
461 reply: None,
462 };
463 shell.notification_activated(click(None));
464 let slot = Rc::clone(&seen);
465 shell
466 .app
467 .borrow_mut()
468 .set_notification_activated_handler(Some(Rc::new(move |_app, activation| {
469 slot.borrow_mut().push(activation)
470 })));
471 shell.notification_activated(click(Some("later")));
472 assert_eq!(
473 *seen.borrow(),
474 [click(None), click(Some("later"))],
475 "the click that started the app waited for the handler"
476 );
477 }
478
479 #[test]
480 fn menu_choices_edit_commands_and_tray_icon_events_reach_their_callbacks() {
481 use std::cell::RefCell;
482
483 use inset_embedder::{EditCommand, Offset, TrayIconEvent};
484
485 let seen = Rc::new(RefCell::new(Vec::new()));
486 let slot = Rc::clone(&seen);
487 let mut shell = Shell::new(Rc::new(platform_with_a_view()), move |app: &mut App| {
488 let callbacks = app.platform_callbacks_mut();
489 let menu = Rc::clone(&slot);
490 callbacks.on_menu_selected = Some(Rc::new(move |_app, id| {
491 menu.borrow_mut().push(format!("menu {id}"));
492 }));
493 let dock = Rc::clone(&slot);
494 callbacks.on_dock_menu_selected = Some(Rc::new(move |_app, id| {
495 dock.borrow_mut().push(format!("dock {id}"));
496 }));
497 let edit = Rc::clone(&slot);
498 callbacks.on_edit_command = Some(Rc::new(move |_app, view, command| {
499 edit.borrow_mut().push(format!("{view:?} {command:?}"));
500 }));
501 let tray = Rc::clone(&slot);
502 callbacks.on_tray_icon_event = Some(Rc::new(move |_app, icon, event| {
503 tray.borrow_mut().push(format!("tray {icon} {event:?}"));
504 }));
505 });
506 shell.menu_selected(3);
507 shell.dock_menu_selected(4);
508 shell.edit_command(IMPLICIT_VIEW_ID, EditCommand::Undo);
509 shell.tray_icon_event(
510 7,
511 TrayIconEvent::Activated {
512 position: Offset::new(1.0, 2.0),
513 },
514 );
515 assert_eq!(
516 *seen.borrow(),
517 [
518 "menu 3".to_owned(),
519 "dock 4".to_owned(),
520 format!("{IMPLICIT_VIEW_ID:?} Undo"),
521 format!(
522 "tray 7 {:?}",
523 TrayIconEvent::Activated {
524 position: Offset::new(1.0, 2.0)
525 }
526 ),
527 ]
528 );
529 }
530
531 #[test]
532 fn a_change_of_the_clipboard_reaches_the_services_bindings_listenable() {
533 use inset_foundation::{Listenable, Listener};
534 use inset_services::ServicesBinding;
535
536 let heard = Rc::new(Cell::new(0));
537 let counter = Rc::clone(&heard);
538 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
539 let clipboard = ServicesBinding::instance(app).clipboard(app);
540 clipboard.add_listener(
541 app,
542 Listener::new(move |_app| counter.set(counter.get() + 1)),
543 );
544 });
545 shell.clipboard_changed();
546 assert_eq!(heard.get(), 1);
547 }
548
549 fn recorded_reply() -> (
551 inset_embedder::AppExitReply,
552 Rc<Cell<Option<inset_embedder::AppExitResponse>>>,
553 ) {
554 let answer = Rc::new(Cell::new(None));
555 let given = Rc::clone(&answer);
556 let reply = inset_embedder::AppExitReply::new(move |response| given.set(Some(response)));
557 (reply, answer)
558 }
559
560 #[test]
561 fn a_view_added_or_removed_reaches_the_framework_with_its_id() {
562 let heard = Rc::new(std::cell::RefCell::new(Vec::new()));
563 let (added, removed) = (Rc::clone(&heard), Rc::clone(&heard));
564 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
565 let callbacks = app.platform_callbacks_mut();
566 callbacks.on_view_added = Some(Rc::new(move |_app, view| {
567 added.borrow_mut().push(("added", view))
568 }));
569 callbacks.on_view_removed = Some(Rc::new(move |_app, view| {
570 removed.borrow_mut().push(("removed", view))
571 }));
572 });
573 shell.view_added(ViewId(4));
574 shell.view_removed(ViewId(4));
575 assert_eq!(
576 *heard.borrow(),
577 [("added", ViewId(4)), ("removed", ViewId(4))]
578 );
579 }
580
581 #[test]
582 fn a_view_close_request_reaches_the_framework() {
583 let heard = Rc::new(Cell::new(None));
584 let heard_slot = Rc::clone(&heard);
585 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
586 app.platform_callbacks_mut().on_view_close_requested =
587 Some(Rc::new(move |_app, view| heard_slot.set(Some(view))));
588 });
589 shell.view_close_requested(ViewId(3));
590 assert_eq!(heard.get(), Some(ViewId(3)));
591 }
592
593 #[test]
594 fn a_window_change_reaches_the_framework() {
595 let heard = Rc::new(Cell::new(None));
596 let heard_slot = Rc::clone(&heard);
597 let mut shell = Shell::new(Rc::new(TestPlatform::new()), move |app: &mut App| {
598 app.platform_callbacks_mut().on_window_changed =
599 Some(Rc::new(move |_app, view| heard_slot.set(Some(view))));
600 });
601 shell.window_changed(ViewId(5));
602 assert_eq!(heard.get(), Some(ViewId(5)));
603 }
604
605 #[test]
606 fn a_lifecycle_state_the_host_reports_reaches_the_scheduler_with_the_states_between() {
607 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
608 shell.app_lifecycle_state_changed(AppLifecycleState::Resumed);
609 shell.app_lifecycle_state_changed(AppLifecycleState::Hidden);
610 let mut app = shell.app();
611 assert_eq!(
612 SchedulerBinding::lifecycle_state(&mut app),
613 Some(AppLifecycleState::Hidden)
614 );
615 assert!(!SchedulerBinding::frames_enabled(&mut app));
616 }
617
618 #[test]
619 fn resuming_marks_every_view_to_be_rendered_and_asks_each_for_a_frame() {
620 use inset_rendering::RendererBinding;
621
622 let platform = Rc::new(TestPlatform::new().with_views(&[IMPLICIT_VIEW_ID, ViewId(1)]));
623 let first = platform.test_view(IMPLICIT_VIEW_ID);
624 let mut shell = Shell::new(platform.clone(), |_app| {});
625 let render_view = {
626 let mut app = shell.app();
627 let binding = RendererBinding::instance(&mut app);
628 binding.init_render_view(&mut app, first.clone())
629 };
630 let at = |ms| Frame {
631 elapsed: Duration::from_millis(ms),
632 };
633 shell.frame(&[IMPLICIT_VIEW_ID], at(16));
634 shell.frame(&[IMPLICIT_VIEW_ID], at(32));
635 assert_eq!(
636 first.presented(),
637 1,
638 "nothing changed after the first frame"
639 );
640
641 shell.app_lifecycle_state_changed(AppLifecycleState::Hidden);
642 platform.take_frame_requests();
643 shell.app_lifecycle_state_changed(AppLifecycleState::Resumed);
644 assert_eq!(
645 platform.take_frame_requests(),
646 [IMPLICIT_VIEW_ID, ViewId(1)]
647 );
648 assert!(render_view.needs_composite_frame(&shell.app()));
649 shell.frame(&[IMPLICIT_VIEW_ID], at(48));
650 assert_eq!(first.presented(), 2, "drawn again though nothing changed");
651 }
652
653 #[test]
654 fn request_app_exit_is_answered_by_the_services_binding() {
655 use inset_embedder::AppExitResponse;
656 use inset_foundation::{Handle, Task};
657 use inset_services::{ServicesBinding, ServicesBindingOverridesObject};
658
659 #[derive(Default)]
660 struct Refusing;
661
662 impl ServicesBindingOverridesObject for Refusing {
663 fn handle_request_app_exit(
664 self: Handle<Self>,
665 _app: &mut App,
666 ) -> Task<AppExitResponse> {
667 Task::ready(AppExitResponse::Cancel)
668 }
669 }
670
671 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
672 let (reply, answer) = recorded_reply();
673 shell.request_app_exit(reply);
674 assert_eq!(answer.get(), Some(AppExitResponse::Exit));
675
676 let refusing = shell.app().create(Refusing);
677 let binding = ServicesBinding::instance(&mut shell.app());
678 binding.set_overrides(&mut shell.app(), Rc::new(refusing));
679 let (reply, answer) = recorded_reply();
680 shell.request_app_exit(reply);
681 assert_eq!(answer.get(), Some(AppExitResponse::Cancel));
682 }
683
684 #[test]
686 fn request_app_exit_answers_during_the_call_in_which_the_framework_decides() {
687 use inset_embedder::AppExitResponse;
688 use inset_foundation::{Completer, Handle, Task};
689 use inset_services::{ServicesBinding, ServicesBindingOverridesObject};
690
691 struct Deciding {
692 decision: Completer<AppExitResponse>,
693 }
694
695 impl ServicesBindingOverridesObject for Deciding {
696 fn handle_request_app_exit(self: Handle<Self>, app: &mut App) -> Task<AppExitResponse> {
697 let decision = app.get(self).decision.future();
698 app.spawn(async move |_cx| decision.await)
699 }
700 }
701
702 let decision = Completer::new();
703 let mut shell = Shell::new(Rc::new(TestPlatform::new()), |_app| {});
704 let deciding = shell.app().create(Deciding {
705 decision: decision.clone(),
706 });
707 let binding = ServicesBinding::instance(&mut shell.app());
708 binding.set_overrides(&mut shell.app(), Rc::new(deciding));
709
710 let (reply, answer) = recorded_reply();
711 shell.request_app_exit(reply);
712 assert_eq!(answer.get(), None);
713
714 decision.complete(&mut shell.app(), AppExitResponse::Exit);
715 assert_eq!(answer.get(), None, "the task resumes at a checkpoint");
716 shell.wake(Duration::ZERO);
717 assert_eq!(answer.get(), Some(AppExitResponse::Exit));
718 }
719
720 #[test]
721 fn new_runs_setup_and_frame_runs_both_scheduler_phases() {
722 let platform = Rc::new(platform_with_a_view());
723 let mut setup_ran = false;
724 let mut shell = Shell::new(platform.clone(), |_app: &mut App| {
725 setup_ran = true;
726 });
727 assert!(setup_ran);
728 assert!(platform.frame_requests().is_empty());
729
730 SchedulerBinding::schedule_frame(&mut shell.app(), FrameTarget::View(IMPLICIT_VIEW_ID));
731 assert_eq!(platform.frame_requests(), [IMPLICIT_VIEW_ID]);
732 shell.frame(
733 &[IMPLICIT_VIEW_ID],
734 Frame {
735 elapsed: Duration::from_millis(16),
736 },
737 );
738 }
739
740 #[test]
741 fn a_reported_press_reaches_gesture_binding_numbered_by_the_converter() {
742 let ran = Rc::new(Cell::new(false));
743 let ran_flag = Rc::clone(&ran);
744 let platform = Rc::new(platform_with_a_view());
745 let mut shell = Shell::new(platform, |app| {
746 let binding = GestureBinding::instance(app);
747 binding.pointer_router(app).add_route(
748 app,
749 1,
750 PointerRoute::new(move |_app, _event| {
751 ran_flag.set(true);
752 }),
753 None,
754 );
755 });
756
757 shell.pointer_reports(vec![PointerReport {
758 view_id: IMPLICIT_VIEW_ID,
759 time_stamp: Duration::ZERO,
760 device: 0,
761 kind: PointerDeviceKind::Mouse,
762 position: [0.0, 0.0],
763 change: ReportedChange::Down { buttons: 1 },
764 contact: Contact::default(),
765 }]);
766 assert!(ran.get(), "the first press is pointer 1");
767 }
768
769 #[test]
770 fn key_data_reaches_the_hardware_keyboard() {
771 let seen = Rc::new(Cell::new(0));
772 let seen_flag = Rc::clone(&seen);
773 let platform = Rc::new(TestPlatform::new());
774 let mut shell = Shell::new(platform, |app| {
775 HardwareKeyboard::instance(app).add_handler(
776 app,
777 Rc::new(move |_app: &mut App, _event: &KeyEvent| {
778 seen_flag.set(seen_flag.get() + 1);
779 true
780 }),
781 );
782 });
783
784 let key_a = KeyData {
785 event_type: KeyEventType::Down,
786 physical: PhysicalKeyboardKey::KEY_A.usb_hid_usage,
787 logical: LogicalKeyboardKey::KEY_A.key_id,
788 character: Some("a".to_owned()),
789 ..KeyData::default()
790 };
791 assert!(shell.key_data(key_a), "the handler claimed the event");
792 assert_eq!(seen.get(), 1);
793
794 let keyboard = HardwareKeyboard::instance(&mut shell.app());
795 assert!(keyboard.is_logical_key_pressed(&shell.app(), LogicalKeyboardKey::KEY_A));
796 }
797
798 #[test]
799 fn wake_and_frame_advance_the_app_clock_and_fire_due_timers() {
800 let platform = Rc::new(TestPlatform::new());
801 let fired = Rc::new(Cell::new(Vec::new()));
802 let mut shell = Shell::new(platform, |app| {
803 for (name, delay) in [("early", 10), ("late", 30)] {
804 let fired = Rc::clone(&fired);
805 inset_foundation::Timer::new(
806 app,
807 Duration::from_millis(delay),
808 inset_foundation::Listener::new(move |_app| {
809 let mut log = fired.take();
810 log.push(name);
811 fired.set(log);
812 }),
813 );
814 }
815 });
816 assert!(fired.take().is_empty());
817
818 shell.wake(Duration::from_millis(15));
819 assert_eq!(fired.take(), vec!["early"]);
820
821 shell.frame(
822 &[IMPLICIT_VIEW_ID],
823 Frame {
824 elapsed: Duration::from_millis(32),
825 },
826 );
827 assert_eq!(fired.take(), vec!["late"]);
828 }
829
830 #[test]
831 fn a_view_coming_or_going_and_a_change_to_the_screens_are_metrics_changes() {
832 use inset_foundation::Handle;
833 use inset_rendering::{RendererBinding, RendererBindingOverridesObject};
834
835 struct CountingMetricsChanges {
836 count: Rc<Cell<usize>>,
837 }
838
839 impl RendererBindingOverridesObject for CountingMetricsChanges {
840 fn will_draw_frame(self: Handle<Self>, _app: &mut App) {}
841
842 fn did_draw_frame(self: Handle<Self>, _app: &mut App) {}
843
844 fn did_handle_metrics_changed(self: Handle<Self>, app: &mut App) {
845 let count = &app.get(self).count;
846 count.set(count.get() + 1);
847 }
848 }
849
850 let count = Rc::new(Cell::new(0));
851 let mut shell = Shell::new(Rc::new(platform_with_a_view()), |_app| {});
852 let counting = shell.app().create(CountingMetricsChanges {
853 count: Rc::clone(&count),
854 });
855 let binding = RendererBinding::instance(&mut shell.app());
856 binding.set_overrides(&mut shell.app(), Rc::new(counting));
857
858 shell.view_added(ViewId(1));
859 assert_eq!(count.get(), 1, "a view added");
860 shell.view_removed(ViewId(1));
861 assert_eq!(count.get(), 2, "a view removed");
862 shell.screens_changed();
863 assert_eq!(count.get(), 3, "the screens changed");
864 }
865
866 #[test]
867 fn view_focus_is_delivered_through_the_dispatcher_callback() {
868 use inset_embedder::{ViewFocusDirection, ViewFocusEvent, ViewFocusState};
869 let received = Rc::new(Cell::new(None));
870 let sink = received.clone();
871 let platform = Rc::new(platform_with_a_view());
872 let mut shell = Shell::new(platform, move |app| {
873 app.platform_callbacks_mut().on_view_focus_change = Some(Rc::new(move |app, event| {
874 let sink = sink.clone();
875 app.schedule_microtask(inset_foundation::Listener::new(move |_| {
876 sink.set(Some(event))
877 }));
878 }));
879 });
880 shell.view_focus_changed(ViewFocusEvent {
881 view_id: IMPLICIT_VIEW_ID,
882 state: ViewFocusState::Focused,
883 direction: ViewFocusDirection::Backward,
884 });
885 let event = received
886 .get()
887 .expect("the shell checkpoints the callback's microtask");
888 assert_eq!(event.view_id, IMPLICIT_VIEW_ID);
889 assert_eq!(event.state, ViewFocusState::Focused);
890 assert_eq!(event.direction, ViewFocusDirection::Backward);
891 }
892}