gpui: Improve window.prompt
to support ESC with non-English cancel text on macOS (#29538)
Release Notes: - N/A ---- The before version GPUI used `Cancel` for cancel text, if we use non-English text (e.g.: "取消" in Chinese), then the press `Esc` to cancel will not work. So this PR to change it by use `PromptButton` to instead the `&str`, then we can use `PromptButton::cancel("取消")` for the `Cancel` button. Run `cargo run -p gpui --example window` to test. --- Platform Test: - [x] macOS - [x] Windows - [x] Linux (x11 and Wayland) --------- Co-authored-by: Mikayla Maki <mikayla@zed.dev> Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
parent
1d5d3de85c
commit
047e7eacec
14 changed files with 174 additions and 66 deletions
|
@ -1,6 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Application, Bounds, Context, KeyBinding, SharedString, Timer, Window, WindowBounds,
|
App, Application, Bounds, Context, KeyBinding, PromptButton, PromptLevel, SharedString, Timer,
|
||||||
WindowKind, WindowOptions, actions, div, prelude::*, px, rgb, size,
|
Window, WindowBounds, WindowKind, WindowOptions, actions, div, prelude::*, px, rgb, size,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SubWindow {
|
struct SubWindow {
|
||||||
|
@ -169,6 +169,42 @@ impl Render for WindowDemo {
|
||||||
let content_size = window.bounds().size;
|
let content_size = window.bounds().size;
|
||||||
window.resize(size(content_size.height, content_size.width));
|
window.resize(size(content_size.height, content_size.width));
|
||||||
}))
|
}))
|
||||||
|
.child(button("Prompt", |window, cx| {
|
||||||
|
let answer = window.prompt(
|
||||||
|
PromptLevel::Info,
|
||||||
|
"Are you sure?",
|
||||||
|
None,
|
||||||
|
&["Ok", "Cancel"],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.spawn(async move |_| {
|
||||||
|
if answer.await.unwrap() == 0 {
|
||||||
|
println!("You have clicked Ok");
|
||||||
|
} else {
|
||||||
|
println!("You have clicked Cancel");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}))
|
||||||
|
.child(button("Prompt (non-English)", |window, cx| {
|
||||||
|
let answer = window.prompt(
|
||||||
|
PromptLevel::Info,
|
||||||
|
"Are you sure?",
|
||||||
|
None,
|
||||||
|
&[PromptButton::ok("确定"), PromptButton::cancel("取消")],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.spawn(async move |_| {
|
||||||
|
if answer.await.unwrap() == 0 {
|
||||||
|
println!("You have clicked Ok");
|
||||||
|
} else {
|
||||||
|
println!("You have clicked Cancel");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +231,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
cx.activate(true);
|
cx.activate(true);
|
||||||
cx.on_action(|_: &Quit, cx| cx.quit());
|
cx.on_action(|_: &Quit, cx| cx.quit());
|
||||||
cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
|
cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
|
||||||
|
|
|
@ -37,10 +37,10 @@ use crate::{
|
||||||
AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId,
|
AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId,
|
||||||
EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext,
|
EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext,
|
||||||
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
||||||
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptHandle, PromptLevel,
|
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
|
||||||
Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString,
|
PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
|
||||||
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
|
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window,
|
||||||
WindowHandle, WindowId, WindowInvalidator,
|
WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
|
||||||
colors::{Colors, GlobalColors},
|
colors::{Colors, GlobalColors},
|
||||||
current_platform, hash, init_app_menus,
|
current_platform, hash, init_app_menus,
|
||||||
};
|
};
|
||||||
|
@ -1578,14 +1578,14 @@ impl App {
|
||||||
PromptLevel,
|
PromptLevel,
|
||||||
&str,
|
&str,
|
||||||
Option<&str>,
|
Option<&str>,
|
||||||
&[&str],
|
&[PromptButton],
|
||||||
PromptHandle,
|
PromptHandle,
|
||||||
&mut Window,
|
&mut Window,
|
||||||
&mut App,
|
&mut App,
|
||||||
) -> RenderablePromptHandle
|
) -> RenderablePromptHandle
|
||||||
+ 'static,
|
+ 'static,
|
||||||
) {
|
) {
|
||||||
self.prompt_builder = Some(PromptBuilder::Custom(Box::new(renderer)))
|
self.prompt_builder = Some(PromptBuilder::Custom(Box::new(renderer)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset the prompt builder to the default implementation.
|
/// Reset the prompt builder to the default implementation.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyView, AnyWindowHandle, App, AppCell, AppContext, BackgroundExecutor, BorrowAppContext,
|
AnyView, AnyWindowHandle, App, AppCell, AppContext, BackgroundExecutor, BorrowAppContext,
|
||||||
Entity, EventEmitter, Focusable, ForegroundExecutor, Global, PromptLevel, Render, Reservation,
|
Entity, EventEmitter, Focusable, ForegroundExecutor, Global, PromptButton, PromptLevel, Render,
|
||||||
Result, Subscription, Task, VisualContext, Window, WindowHandle,
|
Reservation, Result, Subscription, Task, VisualContext, Window, WindowHandle,
|
||||||
};
|
};
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
|
@ -314,13 +314,16 @@ impl AsyncWindowContext {
|
||||||
/// Present a platform dialog.
|
/// Present a platform dialog.
|
||||||
/// The provided message will be presented, along with buttons for each answer.
|
/// The provided message will be presented, along with buttons for each answer.
|
||||||
/// When a button is clicked, the returned Receiver will receive the index of the clicked button.
|
/// When a button is clicked, the returned Receiver will receive the index of the clicked button.
|
||||||
pub fn prompt(
|
pub fn prompt<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
message: &str,
|
message: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[T],
|
||||||
) -> oneshot::Receiver<usize> {
|
) -> oneshot::Receiver<usize>
|
||||||
|
where
|
||||||
|
T: Clone + Into<PromptButton>,
|
||||||
|
{
|
||||||
self.window
|
self.window
|
||||||
.update(self, |_, window, cx| {
|
.update(self, |_, window, cx| {
|
||||||
window.prompt(level, message, detail, answers, cx)
|
window.prompt(level, message, detail, answers, cx)
|
||||||
|
|
|
@ -418,7 +418,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[PromptButton],
|
||||||
) -> Option<oneshot::Receiver<usize>>;
|
) -> Option<oneshot::Receiver<usize>>;
|
||||||
fn activate(&self);
|
fn activate(&self);
|
||||||
fn is_active(&self) -> bool;
|
fn is_active(&self) -> bool;
|
||||||
|
@ -1244,6 +1244,58 @@ pub enum PromptLevel {
|
||||||
Critical,
|
Critical,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prompt Button
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum PromptButton {
|
||||||
|
/// Ok button
|
||||||
|
Ok(SharedString),
|
||||||
|
/// Cancel button
|
||||||
|
Cancel(SharedString),
|
||||||
|
/// Other button
|
||||||
|
Other(SharedString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromptButton {
|
||||||
|
/// Create a button with label
|
||||||
|
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||||
|
PromptButton::Other(label.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an Ok button
|
||||||
|
pub fn ok(label: impl Into<SharedString>) -> Self {
|
||||||
|
PromptButton::Ok(label.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a Cancel button
|
||||||
|
pub fn cancel(label: impl Into<SharedString>) -> Self {
|
||||||
|
PromptButton::Cancel(label.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn is_cancel(&self) -> bool {
|
||||||
|
matches!(self, PromptButton::Cancel(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the label of the button
|
||||||
|
pub fn label(&self) -> &SharedString {
|
||||||
|
match self {
|
||||||
|
PromptButton::Ok(label) => label,
|
||||||
|
PromptButton::Cancel(label) => label,
|
||||||
|
PromptButton::Other(label) => label,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for PromptButton {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
match value.to_lowercase().as_str() {
|
||||||
|
"ok" => PromptButton::Ok("Ok".into()),
|
||||||
|
"cancel" => PromptButton::Cancel("Cancel".into()),
|
||||||
|
_ => PromptButton::Other(SharedString::from(value.to_owned())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The style of the cursor (pointer)
|
/// The style of the cursor (pointer)
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
||||||
pub enum CursorStyle {
|
pub enum CursorStyle {
|
||||||
|
|
|
@ -29,8 +29,8 @@ use crate::platform::{
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
|
AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
|
||||||
PlatformDisplay, PlatformInput, Point, PromptLevel, RequestFrameOptions, ResizeEdge,
|
PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
|
||||||
ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance,
|
ResizeEdge, ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance,
|
||||||
WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowParams, px,
|
WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowParams, px,
|
||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
|
@ -862,7 +862,7 @@ impl PlatformWindow for WaylandWindow {
|
||||||
_level: PromptLevel,
|
_level: PromptLevel,
|
||||||
_msg: &str,
|
_msg: &str,
|
||||||
_detail: Option<&str>,
|
_detail: Option<&str>,
|
||||||
_answers: &[&str],
|
_answers: &[PromptButton],
|
||||||
) -> Option<Receiver<usize>> {
|
) -> Option<Receiver<usize>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
|
AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
|
||||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
||||||
Point, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size, Tiling,
|
Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
|
||||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
|
Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
|
||||||
WindowParams, X11ClientStatePtr, px, size,
|
WindowKind, WindowParams, X11ClientStatePtr, px, size,
|
||||||
};
|
};
|
||||||
|
|
||||||
use blade_graphics as gpu;
|
use blade_graphics as gpu;
|
||||||
|
@ -1227,7 +1227,7 @@ impl PlatformWindow for X11Window {
|
||||||
_level: PromptLevel,
|
_level: PromptLevel,
|
||||||
_msg: &str,
|
_msg: &str,
|
||||||
_detail: Option<&str>,
|
_detail: Option<&str>,
|
||||||
_answers: &[&str],
|
_answers: &[PromptButton],
|
||||||
) -> Option<futures::channel::oneshot::Receiver<usize>> {
|
) -> Option<futures::channel::oneshot::Receiver<usize>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ const BACKSPACE_KEY: u16 = 0x7f;
|
||||||
const SPACE_KEY: u16 = b' ' as u16;
|
const SPACE_KEY: u16 = b' ' as u16;
|
||||||
const ENTER_KEY: u16 = 0x0d;
|
const ENTER_KEY: u16 = 0x0d;
|
||||||
const NUMPAD_ENTER_KEY: u16 = 0x03;
|
const NUMPAD_ENTER_KEY: u16 = 0x03;
|
||||||
const ESCAPE_KEY: u16 = 0x1b;
|
pub(crate) const ESCAPE_KEY: u16 = 0x1b;
|
||||||
const TAB_KEY: u16 = 0x09;
|
const TAB_KEY: u16 = 0x09;
|
||||||
const SHIFT_TAB_KEY: u16 = 0x19;
|
const SHIFT_TAB_KEY: u16 = 0x19;
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ use crate::{
|
||||||
AnyWindowHandle, Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor,
|
AnyWindowHandle, Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor,
|
||||||
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||||
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||||
PlatformWindow, Point, PromptLevel, RequestFrameOptions, ScaledPixels, Size, Timer,
|
PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions, ScaledPixels, Size,
|
||||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowParams,
|
Timer, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowParams,
|
||||||
platform::PlatformInputHandler, point, px, size,
|
platform::PlatformInputHandler, point, px, size,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
|
@ -902,7 +902,7 @@ impl PlatformWindow for MacWindow {
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[PromptButton],
|
||||||
) -> Option<oneshot::Receiver<usize>> {
|
) -> Option<oneshot::Receiver<usize>> {
|
||||||
// macOs applies overrides to modal window buttons after they are added.
|
// macOs applies overrides to modal window buttons after they are added.
|
||||||
// Two most important for this logic are:
|
// Two most important for this logic are:
|
||||||
|
@ -926,7 +926,7 @@ impl PlatformWindow for MacWindow {
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.rev()
|
.rev()
|
||||||
.find(|(_, label)| **label != "Cancel")
|
.find(|(_, label)| !label.is_cancel())
|
||||||
.filter(|&(label_index, _)| label_index > 0);
|
.filter(|&(label_index, _)| label_index > 0);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -948,11 +948,19 @@ impl PlatformWindow for MacWindow {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
|
.filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
|
||||||
{
|
{
|
||||||
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
|
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer.label())];
|
||||||
let _: () = msg_send![button, setTag: ix as NSInteger];
|
let _: () = msg_send![button, setTag: ix as NSInteger];
|
||||||
|
|
||||||
|
if answer.is_cancel() {
|
||||||
|
// Bind Escape Key to Cancel Button
|
||||||
|
if let Some(key) = std::char::from_u32(super::events::ESCAPE_KEY as u32) {
|
||||||
|
let _: () =
|
||||||
|
msg_send![button, setKeyEquivalent: ns_string(&key.to_string())];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some((ix, answer)) = latest_non_cancel_label {
|
if let Some((ix, answer)) = latest_non_cancel_label {
|
||||||
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
|
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer.label())];
|
||||||
let _: () = msg_send![button, setTag: ix as NSInteger];
|
let _: () = msg_send![button, setTag: ix as NSInteger];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
|
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
|
||||||
ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout,
|
ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout,
|
||||||
PlatformTextSystem, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, Size, Task,
|
PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
|
||||||
TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
|
Size, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
|
@ -165,10 +165,10 @@ impl TestPlatform {
|
||||||
&self,
|
&self,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[PromptButton],
|
||||||
) -> oneshot::Receiver<usize> {
|
) -> oneshot::Receiver<usize> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
let answers: Vec<String> = answers.iter().map(|&s| s.to_string()).collect();
|
let answers: Vec<String> = answers.iter().map(|s| s.label().to_string()).collect();
|
||||||
self.background_executor()
|
self.background_executor()
|
||||||
.set_waiting_hint(Some(format!("PROMPT: {:?} {:?}", msg, detail)));
|
.set_waiting_hint(Some(format!("PROMPT: {:?} {:?}", msg, detail)));
|
||||||
self.prompts
|
self.prompts
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
|
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
|
||||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
||||||
Point, RequestFrameOptions, ScaledPixels, Size, TestPlatform, TileId, WindowAppearance,
|
Point, PromptButton, RequestFrameOptions, ScaledPixels, Size, TestPlatform, TileId,
|
||||||
WindowBackgroundAppearance, WindowBounds, WindowParams,
|
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowParams,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -164,7 +164,7 @@ impl PlatformWindow for TestWindow {
|
||||||
_level: crate::PromptLevel,
|
_level: crate::PromptLevel,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[PromptButton],
|
||||||
) -> Option<futures::channel::oneshot::Receiver<usize>> {
|
) -> Option<futures::channel::oneshot::Receiver<usize>> {
|
||||||
Some(
|
Some(
|
||||||
self.0
|
self.0
|
||||||
|
|
|
@ -608,7 +608,7 @@ impl PlatformWindow for WindowsWindow {
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[PromptButton],
|
||||||
) -> Option<Receiver<usize>> {
|
) -> Option<Receiver<usize>> {
|
||||||
let (done_tx, done_rx) = oneshot::channel();
|
let (done_tx, done_rx) = oneshot::channel();
|
||||||
let msg = msg.to_string();
|
let msg = msg.to_string();
|
||||||
|
@ -616,8 +616,8 @@ impl PlatformWindow for WindowsWindow {
|
||||||
Some(info) => Some(info.to_string()),
|
Some(info) => Some(info.to_string()),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
|
||||||
let handle = self.0.hwnd;
|
let handle = self.0.hwnd;
|
||||||
|
let answers = answers.to_vec();
|
||||||
self.0
|
self.0
|
||||||
.executor
|
.executor
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
|
@ -653,9 +653,9 @@ impl PlatformWindow for WindowsWindow {
|
||||||
let mut button_id_map = Vec::with_capacity(answers.len());
|
let mut button_id_map = Vec::with_capacity(answers.len());
|
||||||
let mut buttons = Vec::new();
|
let mut buttons = Vec::new();
|
||||||
let mut btn_encoded = Vec::new();
|
let mut btn_encoded = Vec::new();
|
||||||
for (index, btn_string) in answers.iter().enumerate() {
|
for (index, btn) in answers.iter().enumerate() {
|
||||||
let encoded = HSTRING::from(btn_string);
|
let encoded = HSTRING::from(btn.label().as_ref());
|
||||||
let button_id = if btn_string == "Cancel" {
|
let button_id = if btn.is_cancel() {
|
||||||
IDCANCEL.0
|
IDCANCEL.0
|
||||||
} else {
|
} else {
|
||||||
index as i32 - 100
|
index as i32 - 100
|
||||||
|
|
|
@ -9,13 +9,13 @@ use crate::{
|
||||||
KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId, LineLayoutIndex, Modifiers,
|
KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId, LineLayoutIndex, Modifiers,
|
||||||
ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent,
|
ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
|
Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
|
||||||
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
|
PlatformWindow, Point, PolychromeSprite, PromptButton, PromptLevel, Quad, Render,
|
||||||
RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge, SMOOTH_SVG_SCALE_FACTOR,
|
RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge,
|
||||||
SUBPIXEL_VARIANTS, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style,
|
SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS, ScaledPixels, Scene, Shadow, SharedString, Size,
|
||||||
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
|
StrikethroughStyle, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle,
|
||||||
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
|
TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, WindowAppearance,
|
||||||
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
|
WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowOptions,
|
||||||
point, prelude::*, px, rems, size, transparent_black,
|
WindowParams, WindowTextSystem, point, prelude::*, px, rems, size, transparent_black,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use collections::{FxHashMap, FxHashSet};
|
use collections::{FxHashMap, FxHashSet};
|
||||||
|
@ -3821,28 +3821,36 @@ impl Window {
|
||||||
/// Present a platform dialog.
|
/// Present a platform dialog.
|
||||||
/// The provided message will be presented, along with buttons for each answer.
|
/// The provided message will be presented, along with buttons for each answer.
|
||||||
/// When a button is clicked, the returned Receiver will receive the index of the clicked button.
|
/// When a button is clicked, the returned Receiver will receive the index of the clicked button.
|
||||||
pub fn prompt(
|
pub fn prompt<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
message: &str,
|
message: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[T],
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> oneshot::Receiver<usize> {
|
) -> oneshot::Receiver<usize>
|
||||||
|
where
|
||||||
|
T: Clone + Into<PromptButton>,
|
||||||
|
{
|
||||||
let prompt_builder = cx.prompt_builder.take();
|
let prompt_builder = cx.prompt_builder.take();
|
||||||
let Some(prompt_builder) = prompt_builder else {
|
let Some(prompt_builder) = prompt_builder else {
|
||||||
unreachable!("Re-entrant window prompting is not supported by GPUI");
|
unreachable!("Re-entrant window prompting is not supported by GPUI");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let answers = answers
|
||||||
|
.iter()
|
||||||
|
.map(|answer| answer.clone().into())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let receiver = match &prompt_builder {
|
let receiver = match &prompt_builder {
|
||||||
PromptBuilder::Default => self
|
PromptBuilder::Default => self
|
||||||
.platform_window
|
.platform_window
|
||||||
.prompt(level, message, detail, answers)
|
.prompt(level, message, detail, &answers)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
self.build_custom_prompt(&prompt_builder, level, message, detail, answers, cx)
|
self.build_custom_prompt(&prompt_builder, level, message, detail, &answers, cx)
|
||||||
}),
|
}),
|
||||||
PromptBuilder::Custom(_) => {
|
PromptBuilder::Custom(_) => {
|
||||||
self.build_custom_prompt(&prompt_builder, level, message, detail, answers, cx)
|
self.build_custom_prompt(&prompt_builder, level, message, detail, &answers, cx)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3857,7 +3865,7 @@ impl Window {
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
message: &str,
|
message: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[PromptButton],
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> oneshot::Receiver<usize> {
|
) -> oneshot::Receiver<usize> {
|
||||||
let (sender, receiver) = oneshot::channel();
|
let (sender, receiver) = oneshot::channel();
|
||||||
|
|
|
@ -4,7 +4,7 @@ use futures::channel::oneshot;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyView, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
AnyView, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
InteractiveElement, IntoElement, ParentElement, PromptLevel, Render,
|
InteractiveElement, IntoElement, ParentElement, PromptButton, PromptLevel, Render,
|
||||||
StatefulInteractiveElement, Styled, div, opaque_grey, white,
|
StatefulInteractiveElement, Styled, div, opaque_grey, white,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ pub fn fallback_prompt_renderer(
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
message: &str,
|
message: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
actions: &[&str],
|
actions: &[PromptButton],
|
||||||
handle: PromptHandle,
|
handle: PromptHandle,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
|
@ -83,7 +83,7 @@ pub fn fallback_prompt_renderer(
|
||||||
_level: level,
|
_level: level,
|
||||||
message: message.to_string(),
|
message: message.to_string(),
|
||||||
detail: detail.map(ToString::to_string),
|
detail: detail.map(ToString::to_string),
|
||||||
actions: actions.iter().map(ToString::to_string).collect(),
|
actions: actions.to_vec(),
|
||||||
focus: cx.focus_handle(),
|
focus: cx.focus_handle(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ pub struct FallbackPromptRenderer {
|
||||||
_level: PromptLevel,
|
_level: PromptLevel,
|
||||||
message: String,
|
message: String,
|
||||||
detail: Option<String>,
|
detail: Option<String>,
|
||||||
actions: Vec<String>,
|
actions: Vec<PromptButton>,
|
||||||
focus: FocusHandle,
|
focus: FocusHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ impl Render for FallbackPromptRenderer {
|
||||||
.rounded_xs()
|
.rounded_xs()
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.child(action.clone())
|
.child(action.label().clone())
|
||||||
.id(ix)
|
.id(ix)
|
||||||
.on_click(cx.listener(move |_, _, _, cx| {
|
.on_click(cx.listener(move |_, _, _, cx| {
|
||||||
cx.emit(PromptResponse(ix));
|
cx.emit(PromptResponse(ix));
|
||||||
|
@ -202,7 +202,7 @@ pub(crate) enum PromptBuilder {
|
||||||
PromptLevel,
|
PromptLevel,
|
||||||
&str,
|
&str,
|
||||||
Option<&str>,
|
Option<&str>,
|
||||||
&[&str],
|
&[PromptButton],
|
||||||
PromptHandle,
|
PromptHandle,
|
||||||
&mut Window,
|
&mut Window,
|
||||||
&mut App,
|
&mut App,
|
||||||
|
@ -216,7 +216,7 @@ impl Deref for PromptBuilder {
|
||||||
PromptLevel,
|
PromptLevel,
|
||||||
&str,
|
&str,
|
||||||
Option<&str>,
|
Option<&str>,
|
||||||
&[&str],
|
&[PromptButton],
|
||||||
PromptHandle,
|
PromptHandle,
|
||||||
&mut Window,
|
&mut Window,
|
||||||
&mut App,
|
&mut App,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, FontWeight,
|
App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, FontWeight,
|
||||||
InteractiveElement, IntoElement, ParentElement, PromptHandle, PromptLevel, PromptResponse,
|
InteractiveElement, IntoElement, ParentElement, PromptButton, PromptHandle, PromptLevel,
|
||||||
Refineable, Render, RenderablePromptHandle, SharedString, Styled, TextStyleRefinement, Window,
|
PromptResponse, Refineable, Render, RenderablePromptHandle, SharedString, Styled,
|
||||||
div,
|
TextStyleRefinement, Window, div,
|
||||||
};
|
};
|
||||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
|
@ -35,7 +35,7 @@ fn zed_prompt_renderer(
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
message: &str,
|
message: &str,
|
||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
actions: &[&str],
|
actions: &[PromptButton],
|
||||||
handle: PromptHandle,
|
handle: PromptHandle,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
|
@ -44,7 +44,7 @@ fn zed_prompt_renderer(
|
||||||
|cx| ZedPromptRenderer {
|
|cx| ZedPromptRenderer {
|
||||||
_level: level,
|
_level: level,
|
||||||
message: message.to_string(),
|
message: message.to_string(),
|
||||||
actions: actions.iter().map(ToString::to_string).collect(),
|
actions: actions.iter().map(|a| a.label().to_string()).collect(),
|
||||||
focus: cx.focus_handle(),
|
focus: cx.focus_handle(),
|
||||||
active_action_id: 0,
|
active_action_id: 0,
|
||||||
detail: detail
|
detail: detail
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue