1use std::ops::Deref;
2
3use futures::channel::oneshot;
4
5use crate::{
6 AnyView, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable,
7 InteractiveElement, IntoElement, ParentElement, PromptButton, PromptLevel, Render,
8 StatefulInteractiveElement, Styled, div, opaque_grey, white,
9};
10
11use super::Window;
12
13/// The event emitted when a prompt's option is selected.
14/// The usize is the index of the selected option, from the actions
15/// passed to the prompt.
16pub struct PromptResponse(pub usize);
17
18/// A prompt that can be rendered in the window.
19pub trait Prompt: EventEmitter<PromptResponse> + Focusable {}
20
21impl<V: EventEmitter<PromptResponse> + Focusable> Prompt for V {}
22
23/// A handle to a prompt that can be used to interact with it.
24pub struct PromptHandle {
25 sender: oneshot::Sender<usize>,
26}
27
28impl PromptHandle {
29 pub(crate) fn new(sender: oneshot::Sender<usize>) -> Self {
30 Self { sender }
31 }
32
33 /// Construct a new prompt handle from a view of the appropriate types
34 pub fn with_view<V: Prompt + Render>(
35 self,
36 view: Entity<V>,
37 window: &mut Window,
38 cx: &mut App,
39 ) -> RenderablePromptHandle {
40 let mut sender = Some(self.sender);
41 let previous_focus = window.focused(cx);
42 let window_handle = window.window_handle();
43 cx.subscribe(&view, move |_: Entity<V>, e: &PromptResponse, cx| {
44 if let Some(sender) = sender.take() {
45 sender.send(e.0).ok();
46 window_handle
47 .update(cx, |_, window, _cx| {
48 window.prompt.take();
49 if let Some(previous_focus) = &previous_focus {
50 window.focus(previous_focus);
51 }
52 })
53 .ok();
54 }
55 })
56 .detach();
57
58 window.focus(&view.focus_handle(cx));
59
60 RenderablePromptHandle {
61 view: Box::new(view),
62 }
63 }
64}
65
66/// A prompt handle capable of being rendered in a window.
67pub struct RenderablePromptHandle {
68 pub(crate) view: Box<dyn PromptViewHandle>,
69}
70
71/// Use this function in conjunction with [App::set_prompt_builder] to force
72/// GPUI to always use the fallback prompt renderer.
73pub fn fallback_prompt_renderer(
74 level: PromptLevel,
75 message: &str,
76 detail: Option<&str>,
77 actions: &[PromptButton],
78 handle: PromptHandle,
79 window: &mut Window,
80 cx: &mut App,
81) -> RenderablePromptHandle {
82 let renderer = cx.new(|cx| FallbackPromptRenderer {
83 _level: level,
84 message: message.to_string(),
85 detail: detail.map(ToString::to_string),
86 actions: actions.to_vec(),
87 focus: cx.focus_handle(),
88 });
89
90 handle.with_view(renderer, window, cx)
91}
92
93/// The default GPUI fallback for rendering prompts, when the platform doesn't support it.
94pub struct FallbackPromptRenderer {
95 _level: PromptLevel,
96 message: String,
97 detail: Option<String>,
98 actions: Vec<PromptButton>,
99 focus: FocusHandle,
100}
101
102impl Render for FallbackPromptRenderer {
103 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
104 let prompt = div()
105 .cursor_default()
106 .track_focus(&self.focus)
107 .w_72()
108 .bg(white())
109 .rounded_lg()
110 .overflow_hidden()
111 .p_3()
112 .child(
113 div()
114 .w_full()
115 .flex()
116 .flex_row()
117 .justify_around()
118 .child(div().overflow_hidden().child(self.message.clone())),
119 )
120 .children(self.detail.clone().map(|detail| {
121 div()
122 .w_full()
123 .flex()
124 .flex_row()
125 .justify_around()
126 .text_sm()
127 .mb_2()
128 .child(div().child(detail))
129 }))
130 .children(self.actions.iter().enumerate().map(|(ix, action)| {
131 div()
132 .flex()
133 .flex_row()
134 .justify_around()
135 .border_1()
136 .border_color(opaque_grey(0.2, 0.5))
137 .mt_1()
138 .rounded_xs()
139 .cursor_pointer()
140 .text_sm()
141 .child(action.label().clone())
142 .id(ix)
143 .on_click(cx.listener(move |_, _, _, cx| {
144 cx.emit(PromptResponse(ix));
145 cx.stop_propagation();
146 }))
147 }));
148
149 div()
150 .size_full()
151 .child(
152 div()
153 .size_full()
154 .bg(opaque_grey(0.5, 0.6))
155 .absolute()
156 .top_0()
157 .left_0(),
158 )
159 .child(
160 div()
161 .size_full()
162 .absolute()
163 .top_0()
164 .left_0()
165 .flex()
166 .flex_col()
167 .justify_around()
168 .child(
169 div()
170 .w_full()
171 .flex()
172 .flex_row()
173 .justify_around()
174 .child(prompt),
175 ),
176 )
177 }
178}
179
180impl EventEmitter<PromptResponse> for FallbackPromptRenderer {}
181
182impl Focusable for FallbackPromptRenderer {
183 fn focus_handle(&self, _: &crate::App) -> FocusHandle {
184 self.focus.clone()
185 }
186}
187
188pub(crate) trait PromptViewHandle {
189 fn any_view(&self) -> AnyView;
190}
191
192impl<V: Prompt + Render> PromptViewHandle for Entity<V> {
193 fn any_view(&self) -> AnyView {
194 self.clone().into()
195 }
196}
197
198pub(crate) enum PromptBuilder {
199 Default,
200 Custom(
201 Box<
202 dyn Fn(
203 PromptLevel,
204 &str,
205 Option<&str>,
206 &[PromptButton],
207 PromptHandle,
208 &mut Window,
209 &mut App,
210 ) -> RenderablePromptHandle,
211 >,
212 ),
213}
214
215impl Deref for PromptBuilder {
216 type Target = dyn Fn(
217 PromptLevel,
218 &str,
219 Option<&str>,
220 &[PromptButton],
221 PromptHandle,
222 &mut Window,
223 &mut App,
224 ) -> RenderablePromptHandle;
225
226 fn deref(&self) -> &Self::Target {
227 match self {
228 Self::Default => &fallback_prompt_renderer,
229 Self::Custom(f) => f.as_ref(),
230 }
231 }
232}