corundum_framework_gui/window/
mod.rs1use corundum_framework_platform::launcher::{self, LauncherSpec};
42
43use crate::gpu;
44
45#[cfg(all(target_os = "android", feature = "android"))]
46mod android;
47#[cfg(target_os = "ios")]
48mod ios;
49#[cfg_attr(not(target_os = "ios"), allow(dead_code))]
51mod marked_text;
52#[cfg_attr(not(all(target_os = "android", feature = "android")), allow(dead_code))]
54mod text_input;
55
56#[cfg(all(target_os = "android", feature = "android"))]
57pub use android::{AndroidApp, run_android};
58
59pub const IOS_SCENE_DELEGATE: &str = "CorundumSceneDelegate";
62
63pub type InstallFonts = Box<dyn FnOnce(&egui::Context)>;
65
66pub struct WindowOptions {
68 title: String,
69 inner_size: [f32; 2],
70 min_inner_size: [f32; 2],
71 fonts: Option<InstallFonts>,
72 launcher: Option<LauncherSpec>,
73 #[cfg(feature = "serde")]
74 store: Option<crate::store::Store>,
75}
76
77impl WindowOptions {
78 pub fn new(title: impl Into<String>) -> WindowOptions {
80 WindowOptions {
81 title: title.into(),
82 inner_size: [900.0, 600.0],
83 min_inner_size: [600.0, 400.0],
84 fonts: None,
85 launcher: None,
86 #[cfg(feature = "serde")]
87 store: None,
88 }
89 }
90
91 pub fn inner_size(mut self, size: [f32; 2]) -> WindowOptions {
93 self.inner_size = size;
94 self
95 }
96
97 pub fn min_inner_size(mut self, size: [f32; 2]) -> WindowOptions {
99 self.min_inner_size = size;
100 self
101 }
102
103 pub fn fonts(mut self, install: impl FnOnce(&egui::Context) + 'static) -> WindowOptions {
106 self.fonts = Some(Box::new(install));
107 self
108 }
109
110 pub fn launcher(mut self, spec: LauncherSpec) -> WindowOptions {
115 self.launcher = Some(spec);
116 self
117 }
118
119 #[cfg_attr(not(test), allow(dead_code))]
121 pub(crate) fn launcher_spec(&self) -> Option<&LauncherSpec> {
122 self.launcher.as_ref()
123 }
124
125 fn wants_launcher(&self) -> bool {
128 #[cfg(feature = "serde")]
129 if let Some(store) = &self.store {
130 return crate::store::wants_launcher(&store.load().unwrap_or_default());
131 }
132 true
133 }
134
135 #[cfg(feature = "serde")]
139 pub fn remember(mut self, store: crate::store::Store) -> WindowOptions {
140 self.store = Some(store);
141 self
142 }
143
144 #[cfg_attr(not(feature = "serde"), allow(unused_mut))]
146 pub(crate) fn wrap_create<'f>(
147 &mut self,
148 create: impl FnOnce(&eframe::CreationContext<'_>) -> Box<dyn eframe::App> + 'f,
149 ) -> impl FnOnce(&eframe::CreationContext<'_>) -> Box<dyn eframe::App> + 'f {
150 #[cfg(feature = "serde")]
151 let store = self.store.take();
152 move |cc| {
153 #[cfg(feature = "serde")]
154 if let Some(store) = store {
155 crate::store::attach(&cc.egui_ctx, store);
156 }
157 create(cc)
158 }
159 }
160}
161
162pub fn run(
167 options: WindowOptions,
168 create: impl FnOnce(&eframe::CreationContext<'_>) -> Box<dyn eframe::App>,
169) -> eframe::Result<()> {
170 let mut options = options;
171 let wants_launcher = options.wants_launcher();
173 let create = options.wrap_create(create);
174 let WindowOptions {
175 title,
176 inner_size,
177 min_inner_size,
178 fonts,
179 launcher,
180 ..
181 } = options;
182 gpu::install_panic_hook();
183 let mut viewport = egui::ViewportBuilder::default()
184 .with_inner_size(inner_size)
185 .with_min_inner_size(min_inner_size)
186 .with_title(&title);
187 if let Some(spec) = launcher {
188 if wants_launcher && let Err(err) = launcher::register(&spec) {
189 tracing::warn!("could not put {} in the app menu: {err}", spec.id);
190 }
191 viewport = with_launcher_look(viewport, &spec);
192 }
193 let native = eframe::NativeOptions {
194 viewport,
195 wgpu_options: gpu::wgpu_options(),
196 ..Default::default()
197 };
198 open(&title, native, fonts, create)
199}
200
201fn with_launcher_look(
204 viewport: egui::ViewportBuilder,
205 spec: &LauncherSpec,
206) -> egui::ViewportBuilder {
207 if let Err(err) = launcher::set_process_app_id(spec) {
209 tracing::warn!("could not set the app id of {}: {err}", spec.id);
210 }
211 let viewport = viewport.with_app_id(spec.id);
212 #[cfg(not(any(target_os = "ios", target_os = "android")))]
213 {
214 const SIZE: u32 = 256;
215 match corundum_framework_platform::icon::render_rgba(spec.icon_svg, SIZE) {
216 Ok(rgba) => {
217 return viewport.with_icon(std::sync::Arc::new(egui::IconData {
218 rgba,
219 width: SIZE,
220 height: SIZE,
221 }));
222 }
223 Err(err) => tracing::warn!("could not render the icon of {}: {err}", spec.id),
224 }
225 }
226 viewport
227}
228
229pub fn open(
233 title: &str,
234 native: eframe::NativeOptions,
235 fonts: Option<InstallFonts>,
236 create: impl FnOnce(&eframe::CreationContext<'_>) -> Box<dyn eframe::App>,
237) -> eframe::Result<()> {
238 #[cfg(target_os = "ios")]
240 ios::scene::adopt();
241 eframe::run_native(
242 title,
243 native,
244 Box::new(move |cc| {
245 gpu::on_render_state(cc.wgpu_render_state.as_ref());
246 match fonts {
247 Some(install) => install(&cc.egui_ctx),
248 None => {
249 crate::fonts::install_system_cjk_fallback(&cc.egui_ctx);
250 }
251 }
252 let app = create(cc);
253 #[cfg(target_os = "ios")]
255 let app = ios::full_screen(cc, app);
256 Ok(app)
257 }),
258 )
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn options_default_and_override_the_window_size() {
267 let options = WindowOptions::new("Example");
268 assert_eq!(options.title, "Example");
269 assert_eq!(options.inner_size, [900.0, 600.0]);
270 assert_eq!(options.min_inner_size, [600.0, 400.0]);
271 assert!(options.fonts.is_none());
272 let options = options
273 .inner_size([1000.0, 640.0])
274 .min_inner_size([720.0, 440.0])
275 .fonts(|_| {});
276 assert_eq!(options.inner_size, [1000.0, 640.0]);
277 assert_eq!(options.min_inner_size, [720.0, 440.0]);
278 assert!(options.fonts.is_some());
279 }
280
281 #[test]
282 fn options_keep_the_launcher_spec() {
283 const SPEC: LauncherSpec = LauncherSpec {
284 id: "net.example.Test",
285 name: "Test",
286 description: "A test app",
287 category: "Utility",
288 icon_svg: b"<svg/>",
289 bundle_prefix: "net.example",
290 };
291 assert!(WindowOptions::new("x").launcher_spec().is_none());
292 let options = WindowOptions::new("x").launcher(SPEC);
293 assert_eq!(
294 options.launcher_spec().map(|s| s.id),
295 Some("net.example.Test")
296 );
297 }
298}