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-renderer/src/platform/app.rs
1use strato_ui_core::{
2 integration::TestDriver,
3 keymap::{CustomTag, Keystroke},
4 r#async::LocalBoxFuture,
5 AppContext, AssetProvider,
6};
7 
8pub use strato_ui_core::platform::app::*;
9 
10use super::AsInnerMut;
11 
12/// Platform-specific app implementation. On any given platform, there are at least two possible
13/// implementations:
14/// * The platform-native backend (e.g. Cocoa on macOS, or Winit+X11/Wayland on Linux)
15/// * A headless backend
16pub enum AppBackend {
17 CurrentPlatform(Box<super::current::App>),
18 Headless(Box<super::headless::App>),
19}
20 
21impl AppBackend {
22 fn run(
23 self,
24 init_fn: impl FnOnce(&mut AppContext, LocalBoxFuture<'static, crate::App>) + 'static,
25 ) -> TerminationResult {
26 match self {
27 AppBackend::CurrentPlatform(inner) => {
28 inner.run(init_fn);
29 // We don't report errors for the GUI app on termination.
30 Ok(())
31 }
32 AppBackend::Headless(inner) => inner.run(init_fn),
33 }
34 }
35}
36 
37/// A structure to help us construct and start the application.
38pub struct AppBuilder {
39 /// The actual platform-specific implementation of the app. This
40 /// stores a strong reference to the application state and the
41 /// callback functions to invoke when things occur at the platform
42 /// level.
43 inner: AppBackend,
44 test_driver: Option<TestDriver>,
45 custom_tag_to_keystroke_fn: Option<Box<dyn Fn(CustomTag) -> Option<Keystroke> + 'static>>,
46 default_keystroke_trigger_for_custom_actions:
47 Option<Box<dyn Fn(CustomTag) -> Option<Keystroke> + 'static>>,
48}
49 
50impl AppBuilder {
51 /// Constructs a new application using the current platform backend.
52 pub fn new(
53 callbacks: AppCallbacks,
54 assets: Box<dyn AssetProvider>,
55 test_driver: Option<TestDriver>,
56 ) -> Self {
57 let inner = super::current::App::new(callbacks, assets, test_driver.as_ref());
58 
59 Self {
60 inner: AppBackend::CurrentPlatform(Box::new(inner)),
61 test_driver,
62 custom_tag_to_keystroke_fn: None,
63 default_keystroke_trigger_for_custom_actions: None,
64 }
65 }
66 
67 /// Constructs a new application using the headless backend.
68 pub fn new_headless(
69 callbacks: AppCallbacks,
70 assets: Box<dyn AssetProvider>,
71 test_driver: Option<TestDriver>,
72 ) -> Self {
73 let inner = super::headless::App::new(callbacks, assets, test_driver.as_ref());
74 Self {
75 inner: AppBackend::Headless(Box::new(inner)),
76 test_driver,
77 custom_tag_to_keystroke_fn: None,
78 default_keystroke_trigger_for_custom_actions: None,
79 }
80 }
81 
82 /// Converts any [`crate::keymap::Trigger::Custom`]-based binding to a traditional
83 /// [`Keystroke`]-based binding using the provided `custom_tag_to_keystroke` function.
84 ///
85 /// This can be useful in the cases where an application registers a binding with a
86 /// [`crate::keymap::Trigger::Custom`] for use in a Mac menu, but still wants to register the
87 /// binding with its corresponding `Keystroke` on other platforms that don't support menus.
88 pub fn convert_custom_triggers_to_keystroke_triggers(
89 &mut self,
90 custom_tag_to_keystroke: impl Fn(CustomTag) -> Option<Keystroke> + 'static,
91 ) {
92 self.custom_tag_to_keystroke_fn = Some(Box::new(custom_tag_to_keystroke));
93 }
94 
95 /// Registers a lookup function that returns the default keystroke for a given custom action.
96 /// Used when converting custom actions to key events during keybinding editing.
97 pub fn register_default_keystroke_triggers_for_custom_actions(
98 &mut self,
99 custom_tag_to_keystroke: impl Fn(CustomTag) -> Option<Keystroke> + 'static,
100 ) {
101 self.default_keystroke_trigger_for_custom_actions = Some(Box::new(custom_tag_to_keystroke));
102 }
103 
104 /// Runs the application, invoking the provided function to
105 /// initialize application state and be ready to process
106 /// events from the main event loop.
107 pub fn run(mut self, init_fn: impl FnOnce(&mut AppContext) + 'static) -> TerminationResult {
108 let custom_tag_to_keystroke = self.custom_tag_to_keystroke_fn.take();
109 // Wrap the initialization fn with one that first tries to convert custom triggers to
110 // keystroke triggers.
111 let init_fn = |ctx: &mut AppContext| {
112 if let Some(custom_tag_to_keystroke) = custom_tag_to_keystroke {
113 ctx.convert_custom_triggers_to_keystroke_triggers(custom_tag_to_keystroke);
114 }
115 if let Some(default_keystroke_trigger_for_custom_actions) =
116 self.default_keystroke_trigger_for_custom_actions
117 {
118 ctx.register_default_keystroke_triggers_for_custom_actions(
119 default_keystroke_trigger_for_custom_actions,
120 );
121 }
122 init_fn(ctx);
123 };
124 
125 if let Some(test_driver) = self.test_driver {
126 self.inner.run(move |ctx, ui_app_future| {
127 init_fn(ctx);
128 
129 ctx.foreground_executor()
130 .spawn(async move {
131 let ui_app = ui_app_future.await;
132 test_driver.run_test_and_cleanup(ui_app).await;
133 })
134 .detach();
135 })
136 } else {
137 self.inner.run(|ctx, _| init_fn(ctx))
138 }
139 }
140}
141 
142impl AsInnerMut<AppBackend> for AppBuilder {
143 fn as_inner_mut(&mut self) -> &mut AppBackend {
144 &mut self.inner
145 }
146}
147