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: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>) -> Self {
240 Self {
241 previous_focus_handle: None,
242 active_sheet: None,
243 active_dialogs: Vec::new(),
244 focused_input: None,
245 notification: cx.new(|cx| NotificationList::new(window, cx)),
246 sheet_size: None,
247 view: view.into(),
248 }
249 }
250
251 pub fn update<F, R>(window: &mut Window, cx: &mut App, f: F) -> R
252 where
253 F: FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
254 {
255 let root = window
256 .root::<Root>()
257 .flatten()
258 .expect("BUG: window first layer should be a gpui_component::Root.");
259
260 root.update(cx, |root, cx| f(root, window, cx))
261 }
262
263 pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self {
264 &window
265 .root::<Root>()
266 .expect("The window root view should be of type `ui::Root`.")
267 .unwrap()
268 .read(cx)
269 }
270
271 fn focus_back(&mut self, window: &mut Window, _: &mut App) {
272 if let Some(handle) = self.previous_focus_handle.clone() {
273 window.focus(&handle);
274 }
275 }
276
277 pub fn render_notification_layer(
279 window: &mut Window,
280 cx: &mut App,
281 ) -> Option<impl IntoElement> {
282 let root = window.root::<Root>()??;
283
284 let active_sheet_placement = root.read(cx).active_sheet.clone().map(|d| d.placement);
285
286 let (mt, mr) = match active_sheet_placement {
287 Some(Placement::Right) => (None, root.read(cx).sheet_size),
288 Some(Placement::Top) => (root.read(cx).sheet_size, None),
289 _ => (None, None),
290 };
291
292 Some(
293 div()
294 .absolute()
295 .top_0()
296 .right_0()
297 .when_some(mt, |this, offset| this.mt(offset))
298 .when_some(mr, |this, offset| this.mr(offset))
299 .child(root.read(cx).notification.clone()),
300 )
301 }
302
303 pub fn render_sheet_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
305 let root = window.root::<Root>()??;
306
307 if let Some(active_sheet) = root.read(cx).active_sheet.clone() {
308 let mut sheet = Sheet::new(window, cx);
309 sheet = (active_sheet.builder)(sheet, window, cx);
310 sheet.focus_handle = active_sheet.focus_handle.clone();
311 sheet.placement = active_sheet.placement;
312
313 let size = sheet.size;
314
315 return Some(
316 div().relative().child(sheet).child(
317 canvas(
318 move |_, _, cx| root.update(cx, |r, _| r.sheet_size = Some(size)),
319 |_, _, _, _| {},
320 )
321 .absolute()
322 .size_full(),
323 ),
324 );
325 }
326
327 None
328 }
329
330 pub fn render_dialog_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
332 let root = window.root::<Root>()??;
333
334 let active_dialogs = root.read(cx).active_dialogs.clone();
335
336 if active_dialogs.is_empty() {
337 return None;
338 }
339
340 let mut show_overlay_ix = None;
341
342 let mut dialogs = active_dialogs
343 .iter()
344 .enumerate()
345 .map(|(i, active_dialog)| {
346 let mut dialog = Dialog::new(window, cx);
347
348 dialog = (active_dialog.builder)(dialog, window, cx);
349
350 dialog.focus_handle = active_dialog.focus_handle.clone();
355
356 dialog.layer_ix = i;
357 if dialog.has_overlay() {
359 show_overlay_ix = Some(i);
360 }
361
362 dialog
363 })
364 .collect::<Vec<_>>();
365
366 if let Some(ix) = show_overlay_ix {
367 if let Some(dialog) = dialogs.get_mut(ix) {
368 dialog.overlay_visible = true;
369 }
370 }
371
372 Some(div().children(dialogs))
373 }
374
375 pub fn view(&self) -> &AnyView {
377 &self.view
378 }
379
380 fn on_action_tab(&mut self, _: &Tab, window: &mut Window, _: &mut Context<Self>) {
381 window.focus_next();
382 }
383
384 fn on_action_tab_prev(&mut self, _: &TabPrev, window: &mut Window, _: &mut Context<Self>) {
385 window.focus_prev();
386 }
387}
388
389impl Render for Root {
390 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
391 window.set_rem_size(cx.theme().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(cx.theme().font_family.clone())
402 .bg(cx.theme().background)
403 .text_color(cx.theme().foreground)
404 .child(self.view.clone()),
405 )
406 }
407}