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