ZIm/crates/gpui/src/window/prompts.rs
Marshall Bowers 99216acdec
gpui: Rename rounded_sm to rounded_xs (#26221)
This PR renames the `rounded_sm` style method to `rounded_xs`.

This will allow us to add an additional step in the scale.

Release Notes:

- N/A
2025-03-06 16:08:19 +00:00

231 lines
6.5 KiB
Rust

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<PromptResponse> + Focusable {}
impl<V: EventEmitter<PromptResponse> + Focusable> Prompt for V {}
/// A handle to a prompt that can be used to interact with it.
pub struct PromptHandle {
sender: oneshot::Sender<usize>,
}
impl PromptHandle {
pub(crate) fn new(sender: oneshot::Sender<usize>) -> Self {
Self { sender }
}
/// Construct a new prompt handle from a view of the appropriate types
pub fn with_view<V: Prompt + Render>(
self,
view: Entity<V>,
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<V>, 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<dyn PromptViewHandle>,
}
/// 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<String>,
actions: Vec<String>,
focus: FocusHandle,
}
impl Render for FallbackPromptRenderer {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> 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<PromptResponse> 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<V: Prompt + Render> PromptViewHandle for Entity<V> {
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(),
}
}
}