1use crate::{
2 drawer::Drawer,
3 input::InputState,
4 modal::Modal,
5 notification::{Notification, NotificationList},
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 CONTENT: &str = "Root";
18
19pub(crate) fn init(cx: &mut App) {
20 cx.bind_keys([
21 KeyBinding::new("tab", Tab, Some(CONTENT)),
22 KeyBinding::new("shift-tab", TabPrev, Some(CONTENT)),
23 ]);
24}
25
26pub trait ContextModal: Sized {
28 fn open_drawer<F>(&mut self, cx: &mut App, build: F)
30 where
31 F: Fn(Drawer, &mut Window, &mut App) -> Drawer + 'static;
32
33 fn open_drawer_at<F>(&mut self, placement: Placement, cx: &mut App, build: F)
35 where
36 F: Fn(Drawer, &mut Window, &mut App) -> Drawer + 'static;
37
38 fn has_active_drawer(&mut self, cx: &mut App) -> bool;
40
41 fn close_drawer(&mut self, cx: &mut App);
43
44 fn open_modal<F>(&mut self, cx: &mut App, build: F)
46 where
47 F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static;
48
49 fn has_active_modal(&mut self, cx: &mut App) -> bool;
51
52 fn close_modal(&mut self, cx: &mut App);
54
55 fn close_all_modals(&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 ContextModal for Window {
77 fn open_drawer<F>(&mut self, cx: &mut App, build: F)
78 where
79 F: Fn(Drawer, &mut Window, &mut App) -> Drawer + 'static,
80 {
81 self.open_drawer_at(Placement::Right, cx, build)
82 }
83
84 fn open_drawer_at<F>(&mut self, placement: Placement, cx: &mut App, build: F)
85 where
86 F: Fn(Drawer, &mut Window, &mut App) -> Drawer + 'static,
87 {
88 Root::update(self, cx, move |root, window, cx| {
89 if root.active_drawer.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_drawer = Some(ActiveDrawer {
97 focus_handle,
98 placement,
99 builder: Rc::new(build),
100 });
101 cx.notify();
102 })
103 }
104
105 fn has_active_drawer(&mut self, cx: &mut App) -> bool {
106 Root::read(self, cx).active_drawer.is_some()
107 }
108
109 fn close_drawer(&mut self, cx: &mut App) {
110 Root::update(self, cx, |root, window, cx| {
111 root.focused_input = None;
112 root.active_drawer = None;
113 root.focus_back(window, cx);
114 cx.notify();
115 })
116 }
117
118 fn open_modal<F>(&mut self, cx: &mut App, build: F)
119 where
120 F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static,
121 {
122 Root::update(self, cx, move |root, window, cx| {
123 if root.active_modals.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_modals.push(ActiveModal {
133 focus_handle,
134 builder: Rc::new(build),
135 });
136 cx.notify();
137 })
138 }
139
140 fn has_active_modal(&mut self, cx: &mut App) -> bool {
141 Root::read(self, cx).active_modals.len() > 0
142 }
143
144 fn close_modal(&mut self, cx: &mut App) {
145 Root::update(self, cx, move |root, window, cx| {
146 root.focused_input = None;
147 root.active_modals.pop();
148
149 if let Some(top_modal) = root.active_modals.last() {
150 top_modal.focus_handle.focus(window);
152 } else {
153 root.focus_back(window, cx);
155 }
156 cx.notify();
157 })
158 }
159
160 fn close_all_modals(&mut self, cx: &mut App) {
161 Root::update(self, cx, |root, window, cx| {
162 root.focused_input = None;
163 root.active_modals.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_drawer: Option<ActiveDrawer>,
218 pub(crate) active_modals: Vec<ActiveModal>,
219 pub(super) focused_input: Option<Entity<InputState>>,
220 pub notification: Entity<NotificationList>,
221 drawer_size: Option<DefiniteLength>,
222 view: AnyView,
223}
224
225#[derive(Clone)]
226struct ActiveDrawer {
227 focus_handle: FocusHandle,
228 placement: Placement,
229 builder: Rc<dyn Fn(Drawer, &mut Window, &mut App) -> Drawer + 'static>,
230}
231
232#[derive(Clone)]
233pub(crate) struct ActiveModal {
234 focus_handle: FocusHandle,
235 builder: Rc<dyn Fn(Modal, &mut Window, &mut App) -> Modal + 'static>,
236}
237
238impl Root {
239 pub fn new(view: AnyView, window: &mut Window, cx: &mut Context<Self>) -> Self {
240 Self {
241 previous_focus_handle: None,
242 active_drawer: None,
243 active_modals: Vec::new(),
244 focused_input: None,
245 notification: cx.new(|cx| NotificationList::new(window, cx)),
246 drawer_size: None,
247 view,
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_drawer_placement = root.read(cx).active_drawer.clone().map(|d| d.placement);
285
286 let (mt, mr) = match active_drawer_placement {
287 Some(Placement::Right) => (None, root.read(cx).drawer_size),
288 Some(Placement::Top) => (root.read(cx).drawer_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_drawer_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
305 let root = window.root::<Root>()??;
306
307 if let Some(active_drawer) = root.read(cx).active_drawer.clone() {
308 let mut drawer = Drawer::new(window, cx);
309 drawer = (active_drawer.builder)(drawer, window, cx);
310 drawer.focus_handle = active_drawer.focus_handle.clone();
311 drawer.placement = active_drawer.placement;
312
313 let drawer_size = drawer.size;
314
315 return Some(
316 div().relative().child(drawer).child(
317 canvas(
318 move |_, _, cx| root.update(cx, |r, _| r.drawer_size = Some(drawer_size)),
319 |_, _, _, _| {},
320 )
321 .absolute()
322 .size_full(),
323 ),
324 );
325 }
326
327 None
328 }
329
330 pub fn render_modal_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
332 let root = window.root::<Root>()??;
333
334 let active_modals = root.read(cx).active_modals.clone();
335
336 if active_modals.is_empty() {
337 return None;
338 }
339
340 let mut show_overlay_ix = None;
341
342 let mut modals = active_modals
343 .iter()
344 .enumerate()
345 .map(|(i, active_modal)| {
346 let mut modal = Modal::new(window, cx);
347
348 modal = (active_modal.builder)(modal, window, cx);
349
350 modal.focus_handle = active_modal.focus_handle.clone();
355
356 modal.layer_ix = i;
357 if modal.has_overlay() {
359 show_overlay_ix = Some(i);
360 }
361
362 modal
363 })
364 .collect::<Vec<_>>();
365
366 if let Some(ix) = show_overlay_ix {
367 if let Some(modal) = modals.get_mut(ix) {
368 modal.overlay_visible = true;
369 }
370 }
371
372 Some(div().children(modals))
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 let base_font_size = cx.theme().font_size;
392 window.set_rem_size(base_font_size);
393
394 window_border().child(
395 div()
396 .id("root")
397 .key_context(CONTENT)
398 .on_action(cx.listener(Self::on_action_tab))
399 .on_action(cx.listener(Self::on_action_tab_prev))
400 .relative()
401 .size_full()
402 .font_family(".SystemUIFont")
403 .bg(cx.theme().background)
404 .text_color(cx.theme().foreground)
405 .child(self.view.clone()),
406 )
407 }
408}