Skip to main content

fluent_app/
app_builder.rs

1use std::sync::Arc;
2
3use fluent_core::Theme;
4use fluent_layout::{modal::ModalStack, ToastStack};
5use gpui::{
6    prelude::*, px, size, App, Application, AssetSource, Bounds, Context, Entity, IntoElement,
7    Render, SharedString, TitlebarOptions, Window, WindowBounds, WindowDecorations, WindowOptions,
8};
9
10use crate::{assets::FluentAssets, chrome::wrap_with_chrome, title_bar::TitleBar};
11
12const DEFAULT_W: f32 = 1280.0;
13const DEFAULT_H: f32 = 800.0;
14
15struct ThemeRoot<V: Render + 'static> {
16    content: Entity<V>,
17}
18
19impl<V: Render + 'static> ThemeRoot<V> {
20    fn new(cx: &mut Context<Self>, content: Entity<V>) -> Self {
21        let themed_content = content.clone();
22        cx.observe_global::<Theme>(move |_, cx| {
23            themed_content.update(cx, |_, cx| cx.notify());
24            cx.notify();
25        })
26        .detach();
27
28        Self { content }
29    }
30}
31
32impl<V: Render + 'static> Render for ThemeRoot<V> {
33    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
34        self.content.clone()
35    }
36}
37
38/// Builder for a FluentGUI application.
39///
40/// ```ignore
41/// FluentApp::new("MyApp")
42///     .window_size(1440.0, 900.0)
43///     .run(|cx| {
44///         cx.new(|_| Workspace::new()...)
45///     });
46/// ```
47pub struct FluentApp {
48    title: SharedString,
49    window_w: f32,
50    window_h: f32,
51    dark: bool,
52    assets: Option<Arc<dyn AssetSource>>,
53}
54
55impl FluentApp {
56    pub fn new(title: impl Into<SharedString>) -> Self {
57        Self {
58            title: title.into(),
59            window_w: DEFAULT_W,
60            window_h: DEFAULT_H,
61            dark: true,
62            assets: None,
63        }
64    }
65
66    pub fn window_size(mut self, w: f32, h: f32) -> Self {
67        self.window_w = w;
68        self.window_h = h;
69        self
70    }
71
72    pub fn dark_theme(mut self) -> Self {
73        self.dark = true;
74        self
75    }
76
77    pub fn light_theme(mut self) -> Self {
78        self.dark = false;
79        self
80    }
81
82    pub fn assets(mut self, assets: impl AssetSource) -> Self {
83        self.assets = Some(Arc::new(assets));
84        self
85    }
86
87    /// Launch the application.
88    ///
89    /// The `build` closure runs on the main thread with `&mut App` and must
90    /// return the root `Entity<V>` to display as the window's content.
91    pub fn run<V: Render + 'static>(self, build: impl FnOnce(&mut App) -> Entity<V> + 'static) {
92        let title = self.title.clone();
93        let w = self.window_w;
94        let h = self.window_h;
95        let dark = self.dark;
96        let assets = self.assets;
97
98        Application::new()
99            .with_assets(FluentAssets::new(assets))
100            .run(move |cx: &mut App| {
101                if dark {
102                    Theme::init(cx);
103                } else {
104                    cx.set_global(Theme::light());
105                }
106                ModalStack::init(cx);
107                ToastStack::init(cx);
108
109                let bounds = Bounds::centered(None, size(px(w), px(h)), cx);
110
111                cx.open_window(
112                    WindowOptions {
113                        window_bounds: Some(WindowBounds::Windowed(bounds)),
114                        titlebar: Some(TitlebarOptions {
115                            title: Some(title.clone()),
116                            appears_transparent: true,
117                            traffic_light_position: None,
118                        }),
119                        window_decorations: Some(WindowDecorations::Client),
120                        ..Default::default()
121                    },
122                    move |_window, cx: &mut App| {
123                        let root = build(cx);
124                        let chromed = wrap_with_chrome(root, cx);
125                        cx.new(|cx| ThemeRoot::new(cx, chromed))
126                    },
127                )
128                .unwrap();
129
130                cx.activate(true);
131            });
132    }
133}
134
135/// Create a `TitleBar` entity — include as the first child of your `Workspace`.
136pub fn title_bar(title: impl Into<SharedString>, cx: &mut App) -> Entity<TitleBar> {
137    let t = title.into();
138    cx.new(|cx| TitleBar::new(cx, t))
139}