1use std::ops::Deref;
2
3use futures::channel::oneshot;
4
5use crate::{
6 div, opaque_grey, white, AnyView, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
7 IntoElement, ParentElement, PromptLevel, Render, StatefulInteractiveElement, Styled, View,
8 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 pub(crate) view: Box<dyn PromptViewHandle>,
61}
62
63/// Use this function in conjunction with [AppContext::set_prompt_renderer] to force
64/// GPUI to always use the fallback prompt renderer.
65pub fn fallback_prompt_renderer(
66 level: PromptLevel,
67 message: &str,
68 detail: Option<&str>,
69 actions: &[&str],
70 handle: PromptHandle,
71 cx: &mut WindowContext,
72) -> RenderablePromptHandle {
73 let renderer = cx.new_view({
74 |cx| FallbackPromptRenderer {
75 _level: level,
76 message: message.to_string(),
77 detail: detail.map(ToString::to_string),
78 actions: actions.iter().map(ToString::to_string).collect(),
79 focus: cx.focus_handle(),
80 }
81 });
82
83 handle.with_view(renderer, cx)
84}
85
86/// The default GPUI fallback for rendering prompts, when the platform doesn't support it.
87pub struct FallbackPromptRenderer {
88 _level: PromptLevel,
89 message: String,
90 detail: Option<String>,
91 actions: Vec<String>,
92 focus: FocusHandle,
93}
94
95impl Render for FallbackPromptRenderer {
96 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
97 let prompt = div()
98 .cursor_default()
99 .track_focus(&self.focus)
100 .w_72()
101 .bg(white())
102 .rounded_lg()
103 .overflow_hidden()
104 .p_3()
105 .child(
106 div()
107 .w_full()
108 .flex()
109 .flex_row()
110 .justify_around()
111 .child(div().overflow_hidden().child(self.message.clone())),
112 )
113 .children(self.detail.clone().map(|detail| {
114 div()
115 .w_full()
116 .flex()
117 .flex_row()
118 .justify_around()
119 .text_sm()
120 .mb_2()
121 .child(div().child(detail))
122 }))
123 .children(self.actions.iter().enumerate().map(|(ix, action)| {
124 div()
125 .flex()
126 .flex_row()
127 .justify_around()
128 .border_1()
129 .border_color(opaque_grey(0.2, 0.5))
130 .mt_1()
131 .rounded_sm()
132 .cursor_pointer()
133 .text_sm()
134 .child(action.clone())
135 .id(ix)
136 .on_click(cx.listener(move |_, _, cx| {
137 cx.emit(PromptResponse(ix));
138 }))
139 }));
140
141 div()
142 .size_full()
143 .child(
144 div()
145 .size_full()
146 .bg(opaque_grey(0.5, 0.6))
147 .absolute()
148 .top_0()
149 .left_0(),
150 )
151 .child(
152 div()
153 .size_full()
154 .absolute()
155 .top_0()
156 .left_0()
157 .flex()
158 .flex_col()
159 .justify_around()
160 .child(
161 div()
162 .w_full()
163 .flex()
164 .flex_row()
165 .justify_around()
166 .child(prompt),
167 ),
168 )
169 }
170}
171
172impl EventEmitter<PromptResponse> for FallbackPromptRenderer {}
173
174impl FocusableView for FallbackPromptRenderer {
175 fn focus_handle(&self, _: &crate::AppContext) -> FocusHandle {
176 self.focus.clone()
177 }
178}
179
180pub(crate) trait PromptViewHandle {
181 fn any_view(&self) -> AnyView;
182}
183
184impl<V: Prompt> PromptViewHandle for View<V> {
185 fn any_view(&self) -> AnyView {
186 self.clone().into()
187 }
188}
189
190pub(crate) enum PromptBuilder {
191 Default,
192 Custom(
193 Box<
194 dyn Fn(
195 PromptLevel,
196 &str,
197 Option<&str>,
198 &[&str],
199 PromptHandle,
200 &mut WindowContext,
201 ) -> RenderablePromptHandle,
202 >,
203 ),
204}
205
206impl Deref for PromptBuilder {
207 type Target = dyn Fn(
208 PromptLevel,
209 &str,
210 Option<&str>,
211 &[&str],
212 PromptHandle,
213 &mut WindowContext,
214 ) -> RenderablePromptHandle;
215
216 fn deref(&self) -> &Self::Target {
217 match self {
218 Self::Default => &fallback_prompt_renderer,
219 Self::Custom(f) => f.as_ref(),
220 }
221 }
222}