fluent_app/
app_builder.rs1use 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
38pub 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 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
135pub 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}