1use futures::FutureExt;
2use gpui::{
3 div, prelude::*, px, AnyView, Div, FocusHandle, ManagedView, Render, Subscription, Task, View,
4 ViewContext, WindowContext,
5};
6use ui::{h_stack, v_stack};
7
8pub trait ModalView: ManagedView {
9 fn dismiss(&mut self, cx: &mut ViewContext<Self>) -> Task<bool> {
10 Task::ready(true)
11 }
12}
13
14trait ModalViewHandle {
15 fn should_dismiss(&mut self, cx: &mut WindowContext) -> Task<bool>;
16 fn view(&self) -> AnyView;
17}
18
19impl<V: ModalView> ModalViewHandle for View<V> {
20 fn should_dismiss(&mut self, cx: &mut WindowContext) -> Task<bool> {
21 self.update(cx, |this, cx| this.dismiss(cx))
22 }
23
24 fn view(&self) -> AnyView {
25 self.clone().into()
26 }
27}
28
29pub struct ActiveModal {
30 modal: Box<dyn ModalViewHandle>,
31 subscription: Subscription,
32 previous_focus_handle: Option<FocusHandle>,
33 focus_handle: FocusHandle,
34}
35
36pub struct ModalLayer {
37 active_modal: Option<ActiveModal>,
38}
39
40impl ModalLayer {
41 pub fn new() -> Self {
42 Self { active_modal: None }
43 }
44
45 pub fn toggle_modal<V, B>(&mut self, cx: &mut ViewContext<Self>, build_view: B)
46 where
47 V: ModalView,
48 B: FnOnce(&mut ViewContext<V>) -> V,
49 {
50 if let Some(active_modal) = &self.active_modal {
51 let is_close = active_modal.modal.view().downcast::<V>().is_ok();
52 self.hide_modal(cx);
53 if is_close {
54 return;
55 }
56 }
57 let new_modal = cx.build_view(build_view);
58 self.show_modal(new_modal, cx);
59 }
60
61 pub fn show_modal<V>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Self>)
62 where
63 V: ModalView,
64 {
65 self.active_modal = Some(ActiveModal {
66 modal: Box::new(new_modal.clone()),
67 subscription: cx.subscribe(&new_modal, |this, modal, e, cx| this.hide_modal(cx)),
68 previous_focus_handle: cx.focused(),
69 focus_handle: cx.focus_handle(),
70 });
71 cx.focus_view(&new_modal);
72 cx.notify();
73 }
74
75 pub fn hide_modal(&mut self, cx: &mut ViewContext<Self>) {
76 let Some(active_modal) = self.active_modal.as_mut() else {
77 return;
78 };
79
80 let dismiss = active_modal.modal.should_dismiss(cx);
81
82 cx.spawn(|this, mut cx| async move {
83 if dismiss.await {
84 this.update(&mut cx, |this, cx| {
85 if let Some(active_modal) = this.active_modal.take() {
86 if let Some(previous_focus) = active_modal.previous_focus_handle {
87 if active_modal.focus_handle.contains_focused(cx) {
88 previous_focus.focus(cx);
89 }
90 }
91 cx.notify();
92 }
93 })
94 .ok();
95 }
96 })
97 .shared()
98 .detach();
99 }
100
101 pub fn active_modal<V>(&self) -> Option<View<V>>
102 where
103 V: 'static,
104 {
105 let active_modal = self.active_modal.as_ref()?;
106 active_modal.modal.view().downcast::<V>().ok()
107 }
108}
109
110impl Render for ModalLayer {
111 type Element = Div;
112
113 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
114 let Some(active_modal) = &self.active_modal else {
115 return div();
116 };
117
118 div()
119 .absolute()
120 .size_full()
121 .top_0()
122 .left_0()
123 .z_index(400)
124 .child(
125 v_stack()
126 .h(px(0.0))
127 .top_20()
128 .flex()
129 .flex_col()
130 .items_center()
131 .track_focus(&active_modal.focus_handle)
132 .child(
133 h_stack()
134 .on_mouse_down_out(cx.listener(|this, _, cx| {
135 this.hide_modal(cx);
136 }))
137 .child(active_modal.modal.view()),
138 ),
139 )
140 }
141}