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