use std::ops::Deref; use futures::channel::oneshot; use crate::{ div, opaque_grey, white, AnyView, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, PromptLevel, Render, StatefulInteractiveElement, Styled, }; use super::Window; /// The event emitted when a prompt's option is selected. /// The usize is the index of the selected option, from the actions /// passed to the prompt. pub struct PromptResponse(pub usize); /// A prompt that can be rendered in the window. pub trait Prompt: EventEmitter + Focusable {} impl + Focusable> Prompt for V {} /// A handle to a prompt that can be used to interact with it. pub struct PromptHandle { sender: oneshot::Sender, } impl PromptHandle { pub(crate) fn new(sender: oneshot::Sender) -> Self { Self { sender } } /// Construct a new prompt handle from a view of the appropriate types pub fn with_view( self, view: Entity, window: &mut Window, cx: &mut App, ) -> RenderablePromptHandle { let mut sender = Some(self.sender); let previous_focus = window.focused(cx); let window_handle = window.window_handle(); cx.subscribe(&view, move |_: Entity, e: &PromptResponse, cx| { if let Some(sender) = sender.take() { sender.send(e.0).ok(); window_handle .update(cx, |_, window, _cx| { window.prompt.take(); if let Some(previous_focus) = &previous_focus { window.focus(previous_focus); } }) .ok(); } }) .detach(); window.focus(&view.focus_handle(cx)); RenderablePromptHandle { view: Box::new(view), } } } /// A prompt handle capable of being rendered in a window. pub struct RenderablePromptHandle { pub(crate) view: Box, } /// Use this function in conjunction with [App::set_prompt_builder] to force /// GPUI to always use the fallback prompt renderer. pub fn fallback_prompt_renderer( level: PromptLevel, message: &str, detail: Option<&str>, actions: &[&str], handle: PromptHandle, window: &mut Window, cx: &mut App, ) -> RenderablePromptHandle { let renderer = cx.new(|cx| FallbackPromptRenderer { _level: level, message: message.to_string(), detail: detail.map(ToString::to_string), actions: actions.iter().map(ToString::to_string).collect(), focus: cx.focus_handle(), }); handle.with_view(renderer, window, cx) } /// The default GPUI fallback for rendering prompts, when the platform doesn't support it. pub struct FallbackPromptRenderer { _level: PromptLevel, message: String, detail: Option, actions: Vec, focus: FocusHandle, } impl Render for FallbackPromptRenderer { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let prompt = div() .cursor_default() .track_focus(&self.focus) .w_72() .bg(white()) .rounded_lg() .overflow_hidden() .p_3() .child( div() .w_full() .flex() .flex_row() .justify_around() .child(div().overflow_hidden().child(self.message.clone())), ) .children(self.detail.clone().map(|detail| { div() .w_full() .flex() .flex_row() .justify_around() .text_sm() .mb_2() .child(div().child(detail)) })) .children(self.actions.iter().enumerate().map(|(ix, action)| { div() .flex() .flex_row() .justify_around() .border_1() .border_color(opaque_grey(0.2, 0.5)) .mt_1() .rounded_xs() .cursor_pointer() .text_sm() .child(action.clone()) .id(ix) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(PromptResponse(ix)); })) })); div() .size_full() .child( div() .size_full() .bg(opaque_grey(0.5, 0.6)) .absolute() .top_0() .left_0(), ) .child( div() .size_full() .absolute() .top_0() .left_0() .flex() .flex_col() .justify_around() .child( div() .w_full() .flex() .flex_row() .justify_around() .child(prompt), ), ) } } impl EventEmitter for FallbackPromptRenderer {} impl Focusable for FallbackPromptRenderer { fn focus_handle(&self, _: &crate::App) -> FocusHandle { self.focus.clone() } } pub(crate) trait PromptViewHandle { fn any_view(&self) -> AnyView; } impl PromptViewHandle for Entity { fn any_view(&self) -> AnyView { self.clone().into() } } pub(crate) enum PromptBuilder { Default, Custom( Box< dyn Fn( PromptLevel, &str, Option<&str>, &[&str], PromptHandle, &mut Window, &mut App, ) -> RenderablePromptHandle, >, ), } impl Deref for PromptBuilder { type Target = dyn Fn( PromptLevel, &str, Option<&str>, &[&str], PromptHandle, &mut Window, &mut App, ) -> RenderablePromptHandle; fn deref(&self) -> &Self::Target { match self { Self::Default => &fallback_prompt_renderer, Self::Custom(f) => f.as_ref(), } } }