1use crate::{
2 dialog::Dialog,
3 input::InputState,
4 notification::{Notification, NotificationList},
5 sheet::Sheet,
6 window_border, ActiveTheme, Placement,
7};
8use gpui::{
9 actions, canvas, div, prelude::FluentBuilder as _, AnyView, App, AppContext, Context,
10 DefiniteLength, Entity, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
11 ParentElement as _, Render, Styled, Window,
12};
13use std::{any::TypeId, rc::Rc};
14
15actions!(root, [Tab, TabPrev]);
16
17const CONTEXT: &str = "Root";
18pub(crate) fn init(cx: &mut App) {
19 cx.bind_keys([
20 KeyBinding::new("tab", Tab, Some(CONTEXT)),
21 KeyBinding::new("shift-tab", TabPrev, Some(CONTEXT)),
22 ]);
23}
24
25pub trait WindowExt: Sized {
27 fn open_sheet<F>(&mut self, cx: &mut App, build: F)
29 where
30 F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static;
31
32 fn open_sheet_at<F>(&mut self, placement: Placement, cx: &mut App, build: F)
34 where
35 F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static;
36
37 fn has_active_sheet(&mut self, cx: &mut App) -> bool;
39
40 fn close_sheet(&mut self, cx: &mut App);
42
43 fn open_dialog<F>(&mut self, cx: &mut App, build: F)
45 where
46 F: Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static;
47
48 fn has_active_dialog(&mut self, cx: &mut App) -> bool;
50
51 fn close_dialog(&mut self, cx: &mut App);
53
54 fn close_all_dialogs(&mut self, cx: &mut App);
56
57 fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App);
59
60 fn remove_notification<T: Sized + 'static>(&mut self, cx: &mut App);
62
63 fn clear_notifications(&mut self, cx: &mut App);
65
66 fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>>;
68
69 fn focused_input(&mut self, cx: &mut App) -> Option<Entity<InputState>>;
71 fn has_focused_input(&mut self, cx: &mut App) -> bool;
73}
74
75impl WindowExt for Window {
76 fn open_sheet<F>(&mut self, cx: &mut App, build: F)
77 where
78 F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static,
79 {
80 self.open_sheet_at(Placement::Right, cx, build)
81 }
82
83 fn open_sheet_at<F>(&mut self, placement: Placement, cx: &mut App, build: F)
84 where
85 F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static,
86 {
87 Root::update(self, cx, move |root, window, cx| {
88 if root.active_sheet.is_none() {
89 root.previous_focus_handle = window.focused(cx);
90 }
91
92 let focus_handle = cx.focus_handle();
93 focus_handle.focus(window);
94
95 root.active_sheet = Some(ActiveSheet {
96 focus_handle,
97 placement,
98 builder: Rc::new(build),
99 });
100 cx.notify();
101 })
102 }
103
104 fn has_active_sheet(&mut self, cx: &mut App) -> bool {
105 Root::read(self, cx).active_sheet.is_some()
106 }
107
108 fn close_sheet(&mut self, cx: &mut App) {
109 Root::update(self, cx, |root, window, cx| {
110 root.focused_input = None;
111 root.active_sheet = None;
112 root.focus_back(window, cx);
113 cx.notify();
114 })
115 }
116
117 fn open_dialog<F>(&mut self, cx: &mut App, build: F)
118 where
119 F: Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static,
120 {
121 Root::update(self, cx, move |root, window, cx| {
122 if root.active_dialogs.len() == 0 {
125 root.previous_focus_handle = window.focused(cx);
126 }
127
128 let focus_handle = cx.focus_handle();
129 focus_handle.focus(window);
130
131 root.active_dialogs.push(ActiveDialog {
132 focus_handle,
133 builder: Rc::new(build),
134 });
135 cx.notify();
136 })
137 }
138
139 fn has_active_dialog(&mut self, cx: &mut App) -> bool {
140 Root::read(self, cx).active_dialogs.len() > 0
141 }
142
143 fn close_dialog(&mut self, cx: &mut App) {
144 Root::update(self, cx, move |root, window, cx| {
145 root.focused_input = None;
146 root.active_dialogs.pop();
147
148 if let Some(top_dialog) = root.active_dialogs.last() {
149 top_dialog.focus_handle.focus(window);
151 } else {
152 root.focus_back(window, cx);
154 }
155 cx.notify();
156 })
157 }
158
159 fn close_all_dialogs(&mut self, cx: &mut App) {
160 Root::update(self, cx, |root, window, cx| {
161 root.focused_input = None;
162 root.active_dialogs.clear();
163 root.focus_back(window, cx);
164 cx.notify();
165 })
166 }
167
168 fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App) {
169 let note = note.into();
170 Root::update(self, cx, move |root, window, cx| {
171 root.notification
172 .update(cx, |view, cx| view.push(note, window, cx));
173 cx.notify();
174 })
175 }
176
177 fn remove_notification<T: Sized + 'static>(&mut self, cx: &mut App) {
178 Root::update(self, cx, move |root, window, cx| {
179 root.notification.update(cx, |view, cx| {
180 let id = TypeId::of::<T>();
181 view.close(id, window, cx);
182 });
183 cx.notify();
184 })
185 }
186
187 fn clear_notifications(&mut self, cx: &mut App) {
188 Root::update(self, cx, move |root, window, cx| {
189 root.notification
190 .update(cx, |view, cx| view.clear(window, cx));
191 cx.notify();
192 })
193 }
194
195 fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>> {
196 let entity = Root::read(self, cx).notification.clone();
197 Rc::new(entity.read(cx).notifications())
198 }
199
200 fn has_focused_input(&mut self, cx: &mut App) -> bool {
201 Root::read(self, cx).focused_input.is_some()
202 }
203
204 fn focused_input(&mut self, cx: &mut App) -> Option<Entity<InputState>> {
205 Root::read(self, cx).focused_input.clone()
206 }
207}
208
209pub struct Root {
213 previous_focus_handle: Option<FocusHandle>,
216 active_sheet: Option<ActiveSheet>,
217 pub(crate) active_dialogs: Vec<ActiveDialog>,
218 pub(super) focused_input: Option<Entity<InputState>>,
219 pub notification: Entity<NotificationList>,
220 sheet_size: Option<DefiniteLength>,
221 view: AnyView,
222}
223
224#[derive(Clone)]
225struct ActiveSheet {
226 focus_handle: FocusHandle,
227 placement: Placement,
228 builder: Rc<dyn Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static>,
229}
230
231#[derive(Clone)]
232pub(crate) struct ActiveDialog {
233 focus_handle: FocusHandle,
234 builder: Rc<dyn Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static>,
235}
236
237impl Root {
238 pub fn new(view: AnyView, window: &mut Window, cx: &mut Context<Self>) -> Self {
239 Self {
240 previous_focus_handle: None,
241 active_sheet: None,
242 active_dialogs: Vec::new(),
243 focused_input: None,
244 notification: cx.new(|cx| NotificationList::new(window, cx)),
245 sheet_size: None,
246 view,
247 }
248 }
249
250 pub fn update<F, R>(window: &mut Window, cx: &mut App, f: F) -> R
251 where
252 F: FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
253 {
254 let root = window
255 .root::<Root>()
256 .flatten()
257 .expect("BUG: window first layer should be a gpui_component::Root.");
258
259 root.update(cx, |root, cx| f(root, window, cx))
260 }
261
262 pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self {
263 &window
264 .root::<Root>()
265 .expect("The window root view should be of type `ui::Root`.")
266 .unwrap()
267 .read(cx)
268 }
269
270 fn focus_back(&mut self, window: &mut Window, _: &mut App) {
271 if let Some(handle) = self.previous_focus_handle.clone() {
272 window.focus(&handle);
273 }
274 }
275
276 pub fn render_notification_layer(
278 window: &mut Window,
279 cx: &mut App,
280 ) -> Option<impl IntoElement> {
281 let root = window.root::<Root>()??;
282
283 let active_sheet_placement = root.read(cx).active_sheet.clone().map(|d| d.placement);
284
285 let (mt, mr) = match active_sheet_placement {
286 Some(Placement::Right) => (None, root.read(cx).sheet_size),
287 Some(Placement::Top) => (root.read(cx).sheet_size, None),
288 _ => (None, None),
289 };
290
291 Some(
292 div()
293 .absolute()
294 .top_0()
295 .right_0()
296 .when_some(mt, |this, offset| this.mt(offset))
297 .when_some(mr, |this, offset| this.mr(offset))
298 .child(root.read(cx).notification.clone()),
299 )
300 }
301
302 pub fn render_sheet_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
304 let root = window.root::<Root>()??;
305
306 if let Some(active_sheet) = root.read(cx).active_sheet.clone() {
307 let mut sheet = Sheet::new(window, cx);
308 sheet = (active_sheet.builder)(sheet, window, cx);
309 sheet.focus_handle = active_sheet.focus_handle.clone();
310 sheet.placement = active_sheet.placement;
311
312 let size = sheet.size;
313
314 return Some(
315 div().relative().child(sheet).child(
316 canvas(
317 move |_, _, cx| root.update(cx, |r, _| r.sheet_size = Some(size)),
318 |_, _, _, _| {},
319 )
320 .absolute()
321 .size_full(),
322 ),
323 );
324 }
325
326 None
327 }
328
329 pub fn render_dialog_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
331 let root = window.root::<Root>()??;
332
333 let active_dialogs = root.read(cx).active_dialogs.clone();
334
335 if active_dialogs.is_empty() {
336 return None;
337 }
338
339 let mut show_overlay_ix = None;
340
341 let mut dialogs = active_dialogs
342 .iter()
343 .enumerate()
344 .map(|(i, active_dialog)| {
345 let mut dialog = Dialog::new(window, cx);
346
347 dialog = (active_dialog.builder)(dialog, window, cx);
348
349 dialog.focus_handle = active_dialog.focus_handle.clone();
354
355 dialog.layer_ix = i;
356 if dialog.has_overlay() {
358 show_overlay_ix = Some(i);
359 }
360
361 dialog
362 })
363 .collect::<Vec<_>>();
364
365 if let Some(ix) = show_overlay_ix {
366 if let Some(dialog) = dialogs.get_mut(ix) {
367 dialog.overlay_visible = true;
368 }
369 }
370
371 Some(div().children(dialogs))
372 }
373
374 pub fn view(&self) -> &AnyView {
376 &self.view
377 }
378
379 fn on_action_tab(&mut self, _: &Tab, window: &mut Window, _: &mut Context<Self>) {
380 window.focus_next();
381 }
382
383 fn on_action_tab_prev(&mut self, _: &TabPrev, window: &mut Window, _: &mut Context<Self>) {
384 window.focus_prev();
385 }
386}
387
388impl Render for Root {
389 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
390 let base_font_size = cx.theme().font_size;
391 window.set_rem_size(base_font_size);
392
393 window_border().child(
394 div()
395 .id("root")
396 .key_context(CONTEXT)
397 .on_action(cx.listener(Self::on_action_tab))
398 .on_action(cx.listener(Self::on_action_tab_prev))
399 .relative()
400 .size_full()
401 .font_family(".SystemUIFont")
402 .bg(cx.theme().background)
403 .text_color(cx.theme().foreground)
404 .child(self.view.clone()),
405 )
406 }
407}