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