Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/platform/test/delegate.rs
1use crate::clipboard::InMemoryClipboard;
2use crate::fonts::FamilyId;
3use crate::geometry;
4use crate::keymap::Keystroke;
5use crate::modals::{AlertDialog, ModalId};
6use crate::platform::{
7 self,
8 file_picker::{FilePickerCallback, FilePickerConfiguration},
9 Cursor, RequestNotificationPermissionsCallback, SendNotificationErrorCallback,
10 WindowFocusBehavior, WindowOptions,
11};
12use crate::platform::{MicrophoneAccessState, TerminationMode, TextLayoutSystem};
13use crate::text_layout::TextAlignment;
14use crate::windowing::WindowCallbacks;
15use crate::{accessibility::AccessibilityContent, notification::UserNotification, Scene, WindowId};
16use crate::{ApplicationBundleInfo, DisplayId, DisplayIdx, OptionalPlatformWindow};
17use anyhow::Result;
18use parking_lot::Mutex;
19use pathfinder_geometry::rect::RectI;
20use pathfinder_geometry::vector::{vec2i, Vector2I};
21use pathfinder_geometry::{
22 rect::RectF,
23 vector::{vec2f, Vector2F},
24};
25use std::any::Any;
26use std::collections::HashMap;
27use std::path::Path;
28use std::rc::Rc;
29use std::sync::Arc;
30 
31pub struct AppDelegate {
32 clipboard: InMemoryClipboard,
33 cursor_shape: Mutex<Cursor>,
34}
35 
36// Dummy IntegrationTestDelegate implementation so the integration test code
37// builds on non-mac platforms (even though running them there is a no-op for now).
38// This is relevant to build on Linux for GitHub Actions.
39pub struct IntegrationTestDelegate {
40 clipboard: InMemoryClipboard,
41 cursor_shape: Mutex<Cursor>,
42}
43 
44pub struct Window {
45 callbacks: WindowCallbacks,
46}
47 
48impl AppDelegate {
49 pub fn new() -> Result<Self> {
50 Ok(Self {
51 clipboard: InMemoryClipboard::default(),
52 cursor_shape: Mutex::new(Cursor::Arrow),
53 })
54 }
55}
56 
57impl IntegrationTestDelegate {
58 pub fn new() -> Result<Self> {
59 Ok(Self {
60 clipboard: InMemoryClipboard::default(),
61 cursor_shape: Mutex::new(Cursor::Arrow),
62 })
63 }
64}
65 
66#[derive(Default)]
67pub(crate) struct WindowManager {
68 windows: HashMap<WindowId, Rc<Window>>,
69}
70 
71impl WindowManager {
72 pub(crate) fn new() -> Self {
73 Default::default()
74 }
75}
76 
77impl platform::WindowManager for WindowManager {
78 fn open_window(
79 &mut self,
80 window_id: WindowId,
81 _window_options: WindowOptions,
82 callbacks: WindowCallbacks,
83 ) -> Result<()> {
84 self.windows
85 .insert(window_id, Rc::new(Window { callbacks }));
86 Ok(())
87 }
88 
89 fn platform_window(&self, window_id: WindowId) -> OptionalPlatformWindow {
90 self.windows
91 .get(&window_id)
92 .map(Rc::clone)
93 .map(|window| window as Rc<dyn platform::Window>)
94 }
95 
96 fn remove_window(&mut self, window_id: WindowId) {
97 self.windows.remove(&window_id);
98 }
99 
100 fn active_window_id(&self) -> Option<WindowId> {
101 None
102 }
103 
104 fn key_window_is_modal_panel(&self) -> bool {
105 false
106 }
107 
108 fn app_is_active(&self) -> bool {
109 true
110 }
111 
112 fn activate_app(&self, _last_active_window: Option<WindowId>) -> Option<WindowId> {
113 // no-op for tests
114 None
115 }
116 
117 fn show_window_and_focus_app(&self, _window_id: WindowId, _behavior: WindowFocusBehavior) {
118 // no-op for tests
119 }
120 
121 fn hide_app(&self) {
122 // no-op for tests
123 }
124 
125 fn hide_window(&self, _window_id: WindowId) {
126 // no-op for tests
127 }
128 
129 fn set_window_bounds(&self, _window_id: WindowId, _bound: RectF) {
130 // no-op for tests
131 }
132 
133 fn set_all_windows_background_blur_radius(&self, _blur_radius_pixels: u8) {
134 // no-op for tests
135 }
136 
137 fn set_all_windows_background_blur_texture(&self, _use_blur_texture: bool) {
138 // no-op for tests
139 }
140 
141 fn set_window_title(&self, _window_id: WindowId, _title: &str) {
142 // no-op for tests
143 }
144 
145 fn close_window_async(&self, _window_id: WindowId, _termination_mode: TerminationMode) {
146 // no-op for tests
147 }
148 
149 fn active_display_bounds(&self) -> geometry::rect::RectF {
150 Default::default()
151 }
152 
153 fn active_display_id(&self) -> DisplayId {
154 DisplayId::from(0)
155 }
156 
157 fn display_count(&self) -> usize {
158 1
159 }
160 
161 fn bounds_for_display_idx(&self, _idx: DisplayIdx) -> Option<RectF> {
162 Default::default()
163 }
164 
165 fn active_cursor_position_updated(&self) {
166 // no-op for tests
167 }
168 
169 fn windowing_system(&self) -> Option<crate::windowing::System> {
170 None
171 }
172 
173 fn os_window_manager_name(&self) -> Option<String> {
174 None
175 }
176 
177 fn is_tiling_window_manager(&self) -> bool {
178 false
179 }
180}
181 
182impl platform::Delegate for AppDelegate {
183 #[cfg(feature = "test-util")]
184 fn get_cursor_shape(&self) -> Cursor {
185 *self.cursor_shape.lock()
186 }
187 
188 fn set_cursor_shape(&self, cursor: Cursor) {
189 *self.cursor_shape.lock() = cursor;
190 }
191 
192 fn open_url(&self, _: &str) {
193 // no-op for tests
194 }
195 
196 fn close_ime_async(&self, _window_id: WindowId) {
197 // no-op for tests
198 }
199 
200 fn open_character_palette(&self) {
201 // no-op for tests
202 }
203 
204 fn open_file_path(&self, _: &Path) {
205 // no-op for tests
206 }
207 
208 fn open_file_path_in_explorer(&self, _: &Path) {
209 // no-op for tests
210 }
211 
212 fn open_file_picker(
213 &self,
214 _callback: FilePickerCallback,
215 _file_picker_config: FilePickerConfiguration,
216 ) {
217 // no-op for tests
218 }
219 
220 fn open_save_file_picker(
221 &self,
222 _callback: platform::SaveFilePickerCallback,
223 _config: platform::SaveFilePickerConfiguration,
224 ) {
225 // no-op for tests
226 }
227 
228 fn application_bundle_info(&self, _: &str) -> Option<ApplicationBundleInfo<'_>> {
229 None
230 }
231 
232 fn is_ime_open(&self) -> bool {
233 false
234 }
235 
236 fn set_accessibility_contents(&self, _: AccessibilityContent) {
237 // no-op for tests
238 }
239 
240 fn request_user_attention(&self, _window_id: WindowId) {
241 // no-op for tests
242 }
243 
244 fn request_desktop_notification_permissions(
245 &self,
246 _on_completion: RequestNotificationPermissionsCallback,
247 ) {
248 // no-op for tests
249 }
250 
251 fn send_desktop_notification(
252 &self,
253 _notification_content: UserNotification,
254 _window_id: WindowId,
255 _on_error: SendNotificationErrorCallback,
256 ) {
257 // no-op for tests
258 }
259 
260 fn clipboard(&mut self) -> &mut dyn crate::Clipboard {
261 &mut self.clipboard
262 }
263 
264 fn system_theme(&self) -> platform::SystemTheme {
265 platform::SystemTheme::Light
266 }
267 
268 fn dispatch_delegate(&self) -> Arc<dyn platform::DispatchDelegate> {
269 Arc::new(DispatchDelegate)
270 }
271 
272 fn register_global_shortcut(&self, _: Keystroke) {
273 // no-op for tests
274 }
275 
276 fn unregister_global_shortcut(&self, _: &Keystroke) {
277 // no-op for tests
278 }
279 
280 fn terminate_app(&self, _termination_mode: TerminationMode) {
281 // no-op for tests
282 }
283 
284 fn is_screen_reader_enabled(&self) -> Option<bool> {
285 None
286 }
287 
288 fn microphone_access_state(&self) -> MicrophoneAccessState {
289 MicrophoneAccessState::NotDetermined
290 }
291 
292 fn show_native_platform_modal(&self, _id: ModalId, _modal: AlertDialog) {
293 // no-op
294 }
295}
296 
297impl platform::Delegate for IntegrationTestDelegate {
298 #[cfg(feature = "test-util")]
299 fn get_cursor_shape(&self) -> Cursor {
300 *self.cursor_shape.lock()
301 }
302 
303 fn set_cursor_shape(&self, cursor: Cursor) {
304 *self.cursor_shape.lock() = cursor;
305 }
306 
307 fn open_url(&self, _: &str) {
308 // no-op for tests
309 }
310 
311 fn close_ime_async(&self, _window_id: WindowId) {
312 // no-op for tests
313 }
314 
315 fn open_character_palette(&self) {
316 // no-op for tests
317 }
318 
319 fn open_file_path(&self, _: &Path) {
320 // no-op for tests
321 }
322 
323 fn open_file_path_in_explorer(&self, _: &Path) {
324 // no-op for tests
325 }
326 
327 fn open_file_picker(
328 &self,
329 _callback: FilePickerCallback,
330 _file_picker_config: FilePickerConfiguration,
331 ) {
332 // no-op for tests
333 }
334 
335 fn open_save_file_picker(
336 &self,
337 _callback: platform::SaveFilePickerCallback,
338 _config: platform::SaveFilePickerConfiguration,
339 ) {
340 // no-op for tests
341 }
342 
343 fn application_bundle_info(&self, _: &str) -> Option<ApplicationBundleInfo<'_>> {
344 None
345 }
346 
347 fn is_ime_open(&self) -> bool {
348 false
349 }
350 
351 fn set_accessibility_contents(&self, _: AccessibilityContent) {
352 // no-op for tests
353 }
354 
355 fn request_user_attention(&self, _window_id: WindowId) {
356 // no-op for tests
357 }
358 
359 fn request_desktop_notification_permissions(
360 &self,
361 _on_completion: RequestNotificationPermissionsCallback,
362 ) {
363 // no-op for tests
364 }
365 
366 fn send_desktop_notification(
367 &self,
368 _notification_content: UserNotification,
369 _window_id: WindowId,
370 _on_error: SendNotificationErrorCallback,
371 ) {
372 // no-op for tests
373 }
374 
375 fn clipboard(&mut self) -> &mut dyn crate::Clipboard {
376 &mut self.clipboard
377 }
378 
379 fn system_theme(&self) -> platform::SystemTheme {
380 platform::SystemTheme::Light
381 }
382 
383 fn dispatch_delegate(&self) -> Arc<dyn platform::DispatchDelegate> {
384 Arc::new(DispatchDelegate)
385 }
386 
387 fn register_global_shortcut(&self, _: Keystroke) {
388 // no-op for tests
389 }
390 
391 fn unregister_global_shortcut(&self, _: &Keystroke) {
392 // no-op for tests
393 }
394 
395 fn terminate_app(&self, _termination_mode: TerminationMode) {
396 // no-op for tests
397 }
398 
399 fn is_screen_reader_enabled(&self) -> Option<bool> {
400 None
401 }
402 
403 fn microphone_access_state(&self) -> MicrophoneAccessState {
404 MicrophoneAccessState::NotDetermined
405 }
406 
407 fn show_native_platform_modal(&self, _id: ModalId, _modal: AlertDialog) {
408 // no-op
409 }
410}
411 
412impl platform::Window for Window {
413 fn callbacks(&self) -> &crate::windowing::WindowCallbacks {
414 &self.callbacks
415 }
416 
417 fn minimize(&self) {}
418 
419 fn toggle_maximized(&self) {}
420 
421 fn toggle_fullscreen(&self) {}
422 
423 fn fullscreen_state(&self) -> platform::FullscreenState {
424 platform::FullscreenState::Normal
425 }
426 
427 fn set_titlebar_height(&self, _height: f64) {}
428 
429 fn as_ctx(&self) -> &dyn platform::WindowContext {
430 self
431 }
432 
433 fn as_any(&self) -> &dyn Any {
434 self
435 }
436 
437 fn supports_transparency(&self) -> bool {
438 true
439 }
440 
441 fn graphics_backend(&self) -> platform::GraphicsBackend {
442 platform::GraphicsBackend::Empty
443 }
444 
445 fn supported_backends(&self) -> Vec<platform::GraphicsBackend> {
446 vec![]
447 }
448 
449 fn uses_native_window_decorations(&self) -> bool {
450 false
451 }
452}
453 
454impl platform::WindowContext for Window {
455 fn size(&self) -> Vector2F {
456 vec2f(1024.0, 768.0)
457 }
458 
459 fn origin(&self) -> Vector2F {
460 vec2f(0., 0.)
461 }
462 
463 fn backing_scale_factor(&self) -> f32 {
464 2.0
465 }
466 
467 fn max_texture_dimension_2d(&self) -> Option<u32> {
468 // For tests, choose a limit so low that it can run on any device.
469 // https://github.com/gfx-rs/wgpu/blob/3b6112d45de8da75e47270fe3b0329e5d5166585/wgpu-types/src/lib.rs#L1278
470 Some(2048)
471 }
472 
473 fn render_scene(&self, _scene: Rc<Scene>) {}
474 
475 fn request_redraw(&self) {}
476 
477 fn request_frame_capture(
478 &self,
479 _callback: Box<dyn FnOnce(platform::CapturedFrame) + Send + 'static>,
480 ) {
481 // no-op for tests
482 }
483}
484 
485struct DispatchDelegate;
486 
487impl platform::DispatchDelegate for DispatchDelegate {
488 fn is_main_thread(&self) -> bool {
489 todo!()
490 }
491 
492 fn run_on_main_thread(&self, _task: async_task::Runnable) {
493 todo!()
494 }
495}
496 
497#[cfg_attr(target_family = "wasm", allow(dead_code))]
498struct LoadedSystemFonts;
499impl platform::LoadedSystemFonts for LoadedSystemFonts {
500 fn as_any(self: Box<Self>) -> Box<dyn Any> {
501 self as Box<dyn Any>
502 }
503}
504 
505/// A no-op font cache for use in tests that don't want to use full platform
506/// functionality.
507#[derive(Default)]
508pub struct FontDB;
509 
510impl FontDB {
511 pub fn new() -> Self {
512 Self
513 }
514}
515 
516impl platform::FontDB for FontDB {
517 fn load_from_bytes(&mut self, _name: &str, _bytes: Vec<Vec<u8>>) -> Result<FamilyId> {
518 Ok(FamilyId(0))
519 }
520 
521 #[cfg(not(target_family = "wasm"))]
522 fn load_from_system(&mut self, _font_family: &str) -> Result<FamilyId> {
523 Ok(FamilyId(0))
524 }
525 
526 #[cfg(not(target_family = "wasm"))]
527 fn load_all_system_fonts(
528 &self,
529 ) -> futures::future::BoxFuture<'static, Box<dyn platform::LoadedSystemFonts>> {
530 use futures::FutureExt as _;
531 
532 futures::future::ready(Box::new(LoadedSystemFonts) as Box<dyn platform::LoadedSystemFonts>)
533 .boxed()
534 }
535 
536 #[cfg(not(target_family = "wasm"))]
537 fn process_loaded_system_fonts(
538 &mut self,
539 loaded_system_fonts: Box<dyn platform::LoadedSystemFonts>,
540 ) -> Vec<(Option<FamilyId>, crate::fonts::FontInfo)> {
541 let _loaded_system_fonts: Box<LoadedSystemFonts> = loaded_system_fonts
542 .as_any()
543 .downcast()
544 .expect("should not fail to downcast to concrete type");
545 vec![]
546 }
547 
548 fn fallback_fonts(
549 &self,
550 _ch: char,
551 _font_id: crate::fonts::FontId,
552 ) -> Vec<crate::fonts::FontId> {
553 vec![]
554 }
555 
556 fn select_font(
557 &self,
558 _family_id: crate::fonts::FamilyId,
559 _properties: crate::fonts::Properties,
560 ) -> crate::fonts::FontId {
561 crate::fonts::FontId(0)
562 }
563 
564 fn font_metrics(&self, _font_id: crate::fonts::FontId) -> crate::fonts::Metrics {
565 crate::fonts::Metrics {
566 units_per_em: 2048,
567 ascent: 1901_i16,
568 descent: (-483_i16),
569 line_gap: 0_i16,
570 }
571 }
572 
573 fn glyph_advance(
574 &self,
575 _font_id: crate::fonts::FontId,
576 _glyph_id: crate::fonts::GlyphId,
577 ) -> Result<Vector2I> {
578 Ok(Vector2I::zero())
579 }
580 
581 fn load_family_name_from_id(&self, _id: crate::fonts::FamilyId) -> Option<String> {
582 None
583 }
584 
585 fn glyph_raster_bounds(
586 &self,
587 _font_id: crate::fonts::FontId,
588 _size: f32,
589 _glyph_id: crate::fonts::GlyphId,
590 _scale: Vector2F,
591 _glyph_config: &crate::rendering::GlyphConfig,
592 ) -> Result<pathfinder_geometry::rect::RectI> {
593 Ok(pathfinder_geometry::rect::RectI::default())
594 }
595 
596 fn glyph_typographic_bounds(
597 &self,
598 _font_id: crate::fonts::FontId,
599 _glyph_id: crate::fonts::GlyphId,
600 ) -> Result<RectI> {
601 Ok(RectI::default())
602 }
603 
604 fn rasterize_glyph(
605 &self,
606 _font_id: crate::fonts::FontId,
607 _size: f32,
608 _glyph_id: crate::fonts::GlyphId,
609 _scale: Vector2F,
610 _subpixel_alignment: crate::fonts::SubpixelAlignment,
611 _glyph_config: &crate::rendering::GlyphConfig,
612 _format: crate::fonts::canvas::RasterFormat,
613 ) -> Result<crate::fonts::RasterizedGlyph> {
614 Ok(crate::fonts::RasterizedGlyph {
615 canvas: crate::fonts::canvas::Canvas {
616 pixels: vec![],
617 size: vec2i(0, 0),
618 row_stride: 0,
619 format: crate::fonts::canvas::RasterFormat::Rgba32,
620 },
621 is_emoji: false,
622 })
623 }
624 
625 fn glyph_for_char(
626 &self,
627 _font_id: crate::fonts::FontId,
628 _char: char,
629 ) -> Option<crate::fonts::GlyphId> {
630 Some(0)
631 }
632 
633 fn family_id_for_name(&self, _name: &str) -> Option<FamilyId> {
634 None
635 }
636 
637 fn text_layout_system(&self) -> &dyn TextLayoutSystem {
638 self
639 }
640}
641 
642impl platform::TextLayoutSystem for FontDB {
643 fn layout_line(
644 &self,
645 _text: &str,
646 line_style: platform::LineStyle,
647 _style_runs: &[(std::ops::Range<usize>, crate::text_layout::StyleAndFont)],
648 _max_width: f32,
649 _clip_config: crate::text_layout::ClipConfig,
650 ) -> crate::text_layout::Line {
651 crate::text_layout::Line::empty(line_style.font_size, line_style.line_height_ratio, 0)
652 }
653 
654 fn layout_text(
655 &self,
656 _text: &str,
657 line_style: platform::LineStyle,
658 _style_runs: &[(std::ops::Range<usize>, crate::text_layout::StyleAndFont)],
659 _max_width: f32,
660 _max_height: f32,
661 _alignment: TextAlignment,
662 _first_line_head_indent: Option<f32>,
663 ) -> crate::text_layout::TextFrame {
664 crate::text_layout::TextFrame::empty(line_style.font_size, line_style.line_height_ratio)
665 }
666}
667