1#![feature(arbitrary_self_types)]
8
9use std::cell::RefMut;
10use std::rc::Rc;
11use std::time::Duration;
12
13use inset_embedder::{
14 DropData, EmbedderClient, Frame, Instant, KeyData, PlatformRef, PointerDataPacket,
15 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, 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
95impl EmbedderClient for Shell {
96 fn frame(&mut self, frame: Frame) {
97 self.origin = Some(self.platform.now() - frame.elapsed);
98 self.advance_clock(frame.elapsed);
99 self.handle_event(|app| SchedulerBinding::on_begin_frame(app, Some(frame.elapsed)));
101 self.handle_event(SchedulerBinding::on_draw_frame);
102 }
103
104 fn view_added(&mut self, _id: ViewId) {}
105
106 fn view_metrics_changed(&mut self, _id: ViewId) {
107 self.push(|app| RendererBinding::instance(app).handle_metrics_changed(app));
108 }
109
110 fn view_removed(&mut self, _id: ViewId) {}
111
112 fn view_focus_changed(&mut self, event: ViewFocusEvent) {
113 self.push(|app| {
114 let callback = app.platform_callbacks().on_view_focus_change.clone();
115 if let Some(callback) = callback {
116 callback(app, event);
117 }
118 });
119 }
120
121 fn drop_data(&mut self, data: DropData) {
122 self.push(|app| {
123 let callback = app.platform_callbacks().on_drop.clone();
124 if let Some(callback) = callback {
125 callback(app, data);
126 }
127 });
128 }
129
130 fn pointer_data_packet(&mut self, packet: PointerDataPacket) {
131 self.push(|app| GestureBinding::instance(app).handle_pointer_data_packet(app, packet));
132 }
133
134 fn key_data(&mut self, data: KeyData) -> bool {
135 self.push(|app| KeyEventManager::instance(app).handle_key_data(app, data))
136 }
137
138 fn pop_route(&mut self) -> bool {
139 self.push(|app| {
140 let callback = app.platform_callbacks().on_pop_route.clone();
141 callback.is_some_and(|callback| callback(app))
142 })
143 }
144
145 fn platform_brightness_changed(&mut self) {
146 self.push(|app| {
147 let callback = app
148 .platform_callbacks()
149 .on_platform_brightness_changed
150 .clone();
151 if let Some(callback) = callback {
152 callback.call(app);
153 }
154 });
155 }
156
157 fn locales_changed(&mut self) {
158 self.push(|app| {
159 let callback = app.platform_callbacks().on_locale_changed.clone();
160 if let Some(callback) = callback {
161 callback.call(app);
162 }
163 });
164 }
165
166 fn system_fonts_changed(&mut self) {
167 self.push(|app| {
168 PaintingBinding::instance(app).handle_system_fonts_did_change(app);
169 });
170 }
171
172 fn wake(&mut self, elapsed: Duration) {
173 self.advance_clock(elapsed);
174 self.app.wake();
176 }
177
178 fn text_input_editing_value(&mut self, _view: ViewId, value: TextEditingValue) {
179 self.push(|app| TextInput::instance(app).update_editing_value(app, value));
180 }
181
182 fn text_input_action(&mut self, _view: ViewId, action: TextInputAction) {
183 self.push(|app| TextInput::instance(app).perform_action(app, action));
184 }
185
186 fn text_input_closed(&mut self, _view: ViewId) {
187 self.push(|app| TextInput::instance(app).connection_closed(app));
188 }
189
190 fn system_context_menu_hidden(&mut self) {
191 self.push(inset_services::SystemContextMenuController::dispatch_system_hide);
192 }
193
194 fn custom_context_menu_action(&mut self, callback_id: &str) {
195 let callback_id = callback_id.to_string();
196 self.push(move |app| {
197 inset_services::SystemContextMenuController::dispatch_custom_action(app, &callback_id);
198 });
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use std::cell::Cell;
205 use std::rc::Rc;
206 use std::time::Duration;
207
208 use inset_embedder::{
209 EmbedderClient, Frame, KeyData, KeyEventType, PointerChange, PointerData,
210 PointerDataPacket, PointerDeviceKind, ViewId,
211 };
212 use inset_foundation::App;
213 use inset_gestures::{GestureBinding, PointerRoute};
214 use inset_scheduler::SchedulerBinding;
215 use inset_services::{HardwareKeyboard, KeyEvent, LogicalKeyboardKey, PhysicalKeyboardKey};
216 use inset_test::{TestPlatform, TestView};
217
218 use super::Shell;
219
220 fn platform_with_a_view() -> TestPlatform {
222 TestPlatform::new().with_view(Rc::new(TestView::new(0.0, 0.0)))
223 }
224
225 #[test]
226 fn drop_data_reaches_the_platform_callback() {
227 use std::cell::RefCell;
228 use std::path::PathBuf;
229
230 use inset_embedder::{DropChange, DropData, ViewId};
231
232 let seen = Rc::new(RefCell::new(None));
233 let seen_slot = Rc::clone(&seen);
234 let platform = Rc::new(platform_with_a_view());
235 let mut shell = Shell::new(platform, move |app: &mut App| {
236 app.platform_callbacks_mut().on_drop = Some(Rc::new(move |_app, data| {
237 *seen_slot.borrow_mut() = Some(data);
238 }));
239 });
240 shell.drop_data(DropData {
241 view_id: ViewId(0),
242 change: DropChange::Dropped,
243 physical_position: None,
244 paths: vec![PathBuf::from("/a.wasm")],
245 });
246 let seen = seen.borrow();
247 let seen = seen.as_ref().expect("the callback ran");
248 assert_eq!(seen.change, DropChange::Dropped);
249 assert_eq!(seen.paths, [PathBuf::from("/a.wasm")]);
250 }
251
252 #[test]
253 fn new_runs_setup_and_frame_runs_both_scheduler_phases() {
254 let platform = Rc::new(TestPlatform::new());
255 let mut setup_ran = false;
256 let mut shell = Shell::new(platform.clone(), |_app: &mut App| {
257 setup_ran = true;
258 });
259 assert!(setup_ran);
260 assert_eq!(platform.frames_requested(), 0);
261
262 SchedulerBinding::schedule_frame(&mut shell.app());
263 assert_eq!(platform.frames_requested(), 1);
264 shell.frame(Frame {
265 elapsed: Duration::from_millis(16),
266 });
267 }
268
269 #[test]
270 fn pointer_data_packet_reaches_gesture_binding() {
271 let ran = Rc::new(Cell::new(false));
272 let ran_flag = Rc::clone(&ran);
273 let platform = Rc::new(platform_with_a_view());
274 let mut shell = Shell::new(platform, |app| {
275 let binding = GestureBinding::instance(app);
276 binding.pointer_router(app).add_route(
277 app,
278 1,
279 PointerRoute::new(move |_app, _event| {
280 ran_flag.set(true);
281 }),
282 None,
283 );
284 });
285
286 shell.pointer_data_packet(PointerDataPacket::new(vec![PointerData {
287 change: PointerChange::Down,
288 kind: PointerDeviceKind::Mouse,
289 buttons: 1,
290 pointer_identifier: 1,
291 ..PointerData::default()
292 }]));
293 assert!(ran.get());
294 }
295
296 #[test]
297 fn key_data_reaches_the_hardware_keyboard() {
298 let seen = Rc::new(Cell::new(0));
299 let seen_flag = Rc::clone(&seen);
300 let platform = Rc::new(TestPlatform::new());
301 let mut shell = Shell::new(platform, |app| {
302 HardwareKeyboard::instance(app).add_handler(
303 app,
304 Rc::new(move |_app: &mut App, _event: &KeyEvent| {
305 seen_flag.set(seen_flag.get() + 1);
306 true
307 }),
308 );
309 });
310
311 let key_a = KeyData {
312 event_type: KeyEventType::Down,
313 physical: PhysicalKeyboardKey::KEY_A.usb_hid_usage,
314 logical: LogicalKeyboardKey::KEY_A.key_id,
315 character: Some("a".to_owned()),
316 ..KeyData::default()
317 };
318 assert!(shell.key_data(key_a), "the handler claimed the event");
319 assert_eq!(seen.get(), 1);
320
321 let keyboard = HardwareKeyboard::instance(&mut shell.app());
322 assert!(keyboard.is_logical_key_pressed(&shell.app(), LogicalKeyboardKey::KEY_A));
323 }
324
325 #[test]
326 fn wake_and_frame_advance_the_app_clock_and_fire_due_timers() {
327 let platform = Rc::new(TestPlatform::new());
328 let fired = Rc::new(Cell::new(Vec::new()));
329 let mut shell = Shell::new(platform, |app| {
330 for (name, delay) in [("early", 10), ("late", 30)] {
331 let fired = Rc::clone(&fired);
332 inset_foundation::Timer::new(
333 app,
334 Duration::from_millis(delay),
335 inset_foundation::Listener::new(move |_app| {
336 let mut log = fired.take();
337 log.push(name);
338 fired.set(log);
339 }),
340 );
341 }
342 });
343 assert!(fired.take().is_empty());
344
345 shell.wake(Duration::from_millis(15));
346 assert_eq!(fired.take(), vec!["early"]);
347
348 shell.frame(Frame {
349 elapsed: Duration::from_millis(32),
350 });
351 assert_eq!(fired.take(), vec!["late"]);
352 }
353
354 #[test]
355 fn view_focus_is_delivered_through_the_dispatcher_callback() {
356 use inset_embedder::{ViewFocusDirection, ViewFocusEvent, ViewFocusState};
357 let received = Rc::new(Cell::new(None));
358 let sink = received.clone();
359 let platform = Rc::new(platform_with_a_view());
360 let mut shell = Shell::new(platform, move |app| {
361 app.platform_callbacks_mut().on_view_focus_change = Some(Rc::new(move |app, event| {
362 let sink = sink.clone();
363 app.schedule_microtask(inset_foundation::Listener::new(move |_| {
364 sink.set(Some(event))
365 }));
366 }));
367 });
368 shell.view_focus_changed(ViewFocusEvent {
369 view_id: ViewId(0),
370 state: ViewFocusState::Focused,
371 direction: ViewFocusDirection::Backward,
372 });
373 let event = received
374 .get()
375 .expect("the shell checkpoints the callback's microtask");
376 assert_eq!(event.view_id, ViewId(0));
377 assert_eq!(event.state, ViewFocusState::Focused);
378 assert_eq!(event.direction, ViewFocusDirection::Backward);
379 }
380}