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