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