⎇ d3649bfd1a80153652c2a028315369bb4b184024
crates/modules/ui_engine/libs/launcher/src/lib.rs
History
Who touched what
raw
feat(yahweh-launcher): F3 — extracción del shell standard de explorers
1//! Yahweh shell — reduce el boot de un app GPUI temed a una línea.
2//!
3//! Las 4 (próximamente más) apps explorer del repo declaran el mismo
4//! patrón: `Application::new + Theme::install_default + cx.open_window
5//! + cx.activate(true)`. Sólo varían el título, el tamaño inicial y la
6//! fábrica del root entity.
7//!
8//! Antes (~20 líneas):
9//!
10//! ```ignore
11//! Application::new().run(|cx: &mut App| {
12//! Theme::install_default(cx);
13//! let bounds = Bounds::centered(None, gpui::size(px(900.), px(640.)), cx);
14//! cx.open_window(
15//! WindowOptions {
16//! window_bounds: Some(WindowBounds::Windowed(bounds)),
17//! titlebar: Some(gpui::TitlebarOptions {
18//! title: Some(SharedString::from("Nakui — Event Log")),
19//! ..Default::default()
20//! }),
21//! ..Default::default()
22//! },
23//! |_w, cx| cx.new(Explorer::new),
24//! ).expect("open window");
25//! cx.activate(true);
26//! });
27//! ```
28//!
29//! Ahora (1 línea):
30//!
31//! ```ignore
32//! launch_app("Nakui — Event Log", (900., 640.), Explorer::new);
33//! ```
34
35use gpui::{
36 App, AppContext, Application, Bounds, Context, Render, SharedString, TitlebarOptions,
37 WindowBounds, WindowOptions, px,
38};
39use yahweh_theme::Theme;
40
41/// Configuración del primer (y normalmente único) ventana del app.
42///
43/// `size` es `(ancho, alto)` en píxeles lógicos. La ventana queda
44/// centrada en el monitor primario.
45pub struct AppLaunchConfig {
46 pub title: SharedString,
47 pub size: (f32, f32),
48}
49
50impl AppLaunchConfig {
51 pub fn new(title: impl Into<SharedString>, size: (f32, f32)) -> Self {
52 Self {
53 title: title.into(),
54 size,
55 }
56 }
57}
58
59/// Levanta un app GPUI con tema instalado y root entity construido.
60///
61/// El root debe implementar `Render`. La fábrica `root_factory` recibe
62/// el `Context<T>` del nuevo entity para que pueda usar `cx.spawn`,
63/// suscribirse a eventos, etc — lo mismo que en el patrón directo.
64///
65/// Bloquea el thread main hasta que se cierre la ventana
66/// (`Application::run` no retorna).
67pub fn launch_app<T, F>(title: impl Into<SharedString>, size: (f32, f32), root_factory: F)
68where
69 T: Render + 'static,
70 F: FnOnce(&mut Context<T>) -> T + Send + 'static,
71{
72 launch_app_with(AppLaunchConfig::new(title, size), root_factory);
73}
74
75/// Variante que acepta un `AppLaunchConfig` armado afuera. Útil cuando
76/// el config se calcula condicionalmente (env var para tamaño, etc).
77pub fn launch_app_with<T, F>(config: AppLaunchConfig, root_factory: F)
78where
79 T: Render + 'static,
80 F: FnOnce(&mut Context<T>) -> T + Send + 'static,
81{
82 Application::new().run(move |cx: &mut App| {
83 Theme::install_default(cx);
84 let bounds = Bounds::centered(None, gpui::size(px(config.size.0), px(config.size.1)), cx);
85 cx.open_window(
86 WindowOptions {
87 window_bounds: Some(WindowBounds::Windowed(bounds)),
88 titlebar: Some(TitlebarOptions {
89 title: Some(config.title.clone()),
90 ..Default::default()
91 }),
92 ..Default::default()
93 },
94 |_w, cx| cx.new(root_factory),
95 )
96 .expect("open window");
97 cx.activate(true);
98 });
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn config_new_normalizes_inputs() {
107 let c = AppLaunchConfig::new("My App", (800.0, 600.0));
108 assert_eq!(c.title.as_ref(), "My App");
109 assert_eq!(c.size, (800.0, 600.0));
110 }
111
112 #[test]
113 fn config_accepts_owned_string_title() {
114 let owned = String::from("Owned Title");
115 let c = AppLaunchConfig::new(owned, (400.0, 300.0));
116 assert_eq!(c.title.as_ref(), "Owned Title");
117 }
118
119 // No hay test de `launch_app` aquí: bloquea el thread main hasta
120 // que la ventana se cierre, y en sandbox no hay DISPLAY. La
121 // cobertura real es que cada explorer app lo invoque y arranque
122 // (smoke test manual o con DISPLAY).
123}