1use gpui::prelude::*;
21use gpui::{div, px, AnyElement, App, IntoElement, Window};
22
23use crate::devtools::Probed;
24use crate::theme::theme;
25
26type Content = Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>;
29
30#[derive(IntoElement)]
36pub struct AppShell {
37 header: Option<(f32, Content)>,
38 navbar: Option<(f32, Content)>,
39 aside: Option<(f32, Content)>,
40 footer: Option<(f32, Content)>,
41 children: Vec<AnyElement>,
42}
43
44impl AppShell {
45 pub fn new() -> Self {
46 AppShell {
47 header: None,
48 navbar: None,
49 aside: None,
50 footer: None,
51 children: Vec::new(),
52 }
53 }
54
55 pub fn header<E>(
57 mut self,
58 height: f32,
59 content: impl Fn(&mut Window, &mut App) -> E + 'static,
60 ) -> Self
61 where
62 E: IntoElement,
63 {
64 self.header = Some((
65 height,
66 Box::new(move |window, cx| content(window, cx).into_any_element()),
67 ));
68 self
69 }
70
71 pub fn navbar<E>(
73 mut self,
74 width: f32,
75 content: impl Fn(&mut Window, &mut App) -> E + 'static,
76 ) -> Self
77 where
78 E: IntoElement,
79 {
80 self.navbar = Some((
81 width,
82 Box::new(move |window, cx| content(window, cx).into_any_element()),
83 ));
84 self
85 }
86
87 pub fn aside<E>(
89 mut self,
90 width: f32,
91 content: impl Fn(&mut Window, &mut App) -> E + 'static,
92 ) -> Self
93 where
94 E: IntoElement,
95 {
96 self.aside = Some((
97 width,
98 Box::new(move |window, cx| content(window, cx).into_any_element()),
99 ));
100 self
101 }
102
103 pub fn footer<E>(
105 mut self,
106 height: f32,
107 content: impl Fn(&mut Window, &mut App) -> E + 'static,
108 ) -> Self
109 where
110 E: IntoElement,
111 {
112 self.footer = Some((
113 height,
114 Box::new(move |window, cx| content(window, cx).into_any_element()),
115 ));
116 self
117 }
118}
119
120impl Default for AppShell {
121 fn default() -> Self {
122 AppShell::new()
123 }
124}
125
126impl ParentElement for AppShell {
127 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
128 self.children.extend(elements);
129 }
130}
131
132impl RenderOnce for AppShell {
133 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
134 let t = theme(cx);
135 let body = t.body().hsla();
136 let surface = t.surface().hsla();
137 let border = t.border().hsla();
138
139 let mut root = div().size_full().flex().flex_col().bg(body);
140
141 if let Some((height, content)) = self.header {
142 root = root.child(
143 div()
144 .flex_none()
145 .w_full()
146 .h(px(height))
147 .flex()
148 .flex_col()
149 .overflow_hidden()
150 .bg(surface)
151 .border_b_1()
152 .border_color(border)
153 .child(content(window, cx)),
154 );
155 }
156
157 let mut middle = div().flex_1().min_h(px(0.0)).w_full().flex();
158
159 if let Some((width, content)) = self.navbar {
160 middle = middle.child(
161 div()
162 .flex_none()
163 .w(px(width))
164 .flex()
165 .flex_col()
166 .overflow_hidden()
167 .bg(surface)
168 .border_r_1()
169 .border_color(border)
170 .child(content(window, cx)),
171 );
172 }
173
174 middle = middle.child(
175 div()
176 .id("guise-appshell-main")
177 .flex_1()
178 .min_w(px(0.0))
179 .flex()
180 .flex_col()
181 .overflow_y_scroll()
182 .children(self.children),
183 );
184
185 if let Some((width, content)) = self.aside {
186 middle = middle.child(
187 div()
188 .flex_none()
189 .w(px(width))
190 .flex()
191 .flex_col()
192 .overflow_hidden()
193 .bg(surface)
194 .border_l_1()
195 .border_color(border)
196 .child(content(window, cx)),
197 );
198 }
199
200 root = root.child(middle);
201
202 if let Some((height, content)) = self.footer {
203 root = root.child(
204 div()
205 .flex_none()
206 .w_full()
207 .h(px(height))
208 .flex()
209 .flex_col()
210 .overflow_hidden()
211 .bg(surface)
212 .border_t_1()
213 .border_color(border)
214 .child(content(window, cx)),
215 );
216 }
217
218 root.probe("AppShell")
219 }
220}