From a60acd7e4d5290cd1377967cc891ba1c7aad137b Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 2 Apr 2025 16:45:27 +0800 Subject: [PATCH] list: Add secondary confirm support to list. (#769) --- crates/story/src/input_story.rs | 10 ++++-- crates/story/src/list_story.rs | 4 +-- crates/story/src/main.rs | 1 - crates/story/src/modal_story.rs | 10 +++--- crates/story/src/table_story.rs | 2 +- crates/story/src/webview_story.rs | 2 +- crates/ui/src/actions.rs | 11 +++++++ crates/ui/src/color_picker.rs | 31 +++++++++-------- crates/ui/src/drawer.rs | 11 ++++--- crates/ui/src/dropdown.rs | 55 +++++++++++++++---------------- crates/ui/src/input/input.rs | 33 ++++++++++++------- crates/ui/src/lib.rs | 2 ++ crates/ui/src/list/list.rs | 54 ++++++++++++++++++++++-------- crates/ui/src/modal.rs | 19 +++++------ crates/ui/src/popover.rs | 21 ++++++------ crates/ui/src/popup_menu.rs | 31 ++++++++--------- crates/ui/src/table.rs | 12 ++----- crates/ui/src/time/date_picker.rs | 12 ++++--- 18 files changed, 187 insertions(+), 134 deletions(-) create mode 100644 crates/ui/src/actions.rs diff --git a/crates/story/src/input_story.rs b/crates/story/src/input_story.rs index ddeabe97..97d8b55f 100644 --- a/crates/story/src/input_story.rs +++ b/crates/story/src/input_story.rs @@ -249,7 +249,7 @@ impl InputStory { ) { match event { InputEvent::Change(text) => println!("Change: {}", text), - InputEvent::PressEnter => println!("PressEnter"), + InputEvent::PressEnter { secondary } => println!("PressEnter secondary: {}", secondary), InputEvent::Focus => println!("Focus"), InputEvent::Blur => println!("Blur"), }; @@ -270,7 +270,9 @@ impl InputStory { } println!("Change: {}", text); } - InputEvent::PressEnter => println!("PressEnter"), + InputEvent::PressEnter { secondary } => { + println!("PressEnter secondary: {}", secondary) + } InputEvent::Focus => println!("Focus"), InputEvent::Blur => println!("Blur"), }, @@ -306,7 +308,9 @@ impl InputStory { } println!("Change: {}", text); } - InputEvent::PressEnter => println!("PressEnter"), + InputEvent::PressEnter { secondary } => { + println!("PressEnter secondary: {}", secondary); + } InputEvent::Focus => println!("Focus"), InputEvent::Blur => println!("Blur"), }, diff --git a/crates/story/src/list_story.rs b/crates/story/src/list_story.rs index b27f6790..752a80d3 100644 --- a/crates/story/src/list_story.rs +++ b/crates/story/src/list_story.rs @@ -174,8 +174,8 @@ impl ListDelegate for CompanyListDelegate { Task::ready(()) } - fn confirm(&mut self, ix: usize, window: &mut Window, cx: &mut Context>) { - self.confirmed_index = Some(ix); + fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context>) { + println!("Confirmed with secondary: {}", secondary); window.dispatch_action(Box::new(SelectedCompany), cx); } diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index 2cc0d6a9..03ae2f5f 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -368,7 +368,6 @@ impl StoryWorkspace { Arc::new(StoryContainer::panel::(window, cx)), Arc::new(StoryContainer::panel::(window, cx)), Arc::new(StoryContainer::panel::(window, cx)), - Arc::new(StoryContainer::panel::(window, cx)), ], None, &dock_area, diff --git a/crates/story/src/modal_story.rs b/crates/story/src/modal_story.rs index fa95c45e..1a4f9ace 100644 --- a/crates/story/src/modal_story.rs +++ b/crates/story/src/modal_story.rs @@ -128,11 +128,13 @@ impl ListDelegate for ListItemDeletegate { }); } - fn confirm(&mut self, ix: usize, window: &mut Window, cx: &mut Context>) { + fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context>) { _ = self.story.update(cx, |this, cx| { - self.confirmed_index = Some(ix); - if let Some(item) = self.matches.get(ix) { - this.selected_value = Some(SharedString::from(item.to_string())); + self.confirmed_index = self.selected_index; + if let Some(ix) = self.confirmed_index { + if let Some(item) = self.matches.get(ix) { + this.selected_value = Some(SharedString::from(item.to_string())); + } } window.close_drawer(cx); diff --git a/crates/story/src/table_story.rs b/crates/story/src/table_story.rs index dc13b31d..1cf5a947 100644 --- a/crates/story/src/table_story.rs +++ b/crates/story/src/table_story.rs @@ -660,7 +660,7 @@ impl TableStory { ) { match event { // Update when the user presses Enter or the input loses focus - InputEvent::PressEnter | InputEvent::Blur => { + InputEvent::PressEnter { .. } | InputEvent::Blur => { let text = self.num_stocks_input.read(cx).text().to_string(); if let Ok(num) = text.parse::() { self.table.update(cx, |table, _| { diff --git a/crates/story/src/webview_story.rs b/crates/story/src/webview_story.rs index a2a7cf92..026cbeb0 100644 --- a/crates/story/src/webview_story.rs +++ b/crates/story/src/webview_story.rs @@ -97,7 +97,7 @@ impl WebViewStory { cx.subscribe( &address_input, |this: &mut Self, input, event: &InputEvent, cx| match event { - InputEvent::PressEnter => { + InputEvent::PressEnter { .. } => { let url = input.read(cx).text(); this.webview.update(cx, |view, _| { view.load_url(&url); diff --git a/crates/ui/src/actions.rs b/crates/ui/src/actions.rs new file mode 100644 index 00000000..17acbb62 --- /dev/null +++ b/crates/ui/src/actions.rs @@ -0,0 +1,11 @@ +use gpui::{actions, impl_internal_actions}; +use serde::Deserialize; + +#[derive(Clone, PartialEq, Eq, Deserialize)] +pub struct Confirm { + /// Is confirm with secondary. + pub secondary: bool, +} + +actions!(list, [Cancel, SelectPrev, SelectNext]); +impl_internal_actions!(list, [Confirm]); diff --git a/crates/ui/src/color_picker.rs b/crates/ui/src/color_picker.rs index 9ac68371..5c3ca725 100644 --- a/crates/ui/src/color_picker.rs +++ b/crates/ui/src/color_picker.rs @@ -6,19 +6,19 @@ use gpui::{ }; use crate::{ + actions::Cancel, button::{Button, ButtonVariants}, divider::Divider, h_flex, input::{InputEvent, TextInput}, - popover::Escape, tooltip::Tooltip, v_flex, ActiveTheme as _, Colorize as _, Icon, Selectable as _, Sizable, Size, StyleSized, }; -const KEY_CONTEXT: &'static str = "ColorPicker"; +const CONTEXT: &'static str = "ColorPicker"; pub fn init(cx: &mut App) { - cx.bind_keys([KeyBinding::new("escape", Escape, Some(KEY_CONTEXT))]) + cx.bind_keys([KeyBinding::new("escape", Cancel, Some(CONTEXT))]) } #[derive(Clone)] @@ -73,7 +73,7 @@ pub struct ColorPicker { impl ColorPicker { pub fn new(id: impl Into, window: &mut Window, cx: &mut Context) -> Self { - let color_input = cx.new(|cx| TextInput::new(window, cx).xsmall()); + let color_input = cx.new(|cx| TextInput::new(window, cx).small()); let _subscriptions = vec![cx.subscribe_in( &color_input, @@ -85,7 +85,7 @@ impl ColorPicker { this.hovered_color = Some(color); } } - InputEvent::PressEnter => { + InputEvent::PressEnter { .. } => { let val = this.color_input.read(cx).text(); if let Ok(color) = Hsla::parse_hex(&val) { this.open = false; @@ -170,8 +170,11 @@ impl ColorPicker { self } - fn on_escape(&mut self, _: &Escape, _: &mut Window, cx: &mut Context) { - cx.propagate(); + fn on_escape(&mut self, _: &Cancel, _: &mut Window, cx: &mut Context) { + if !self.open { + cx.propagate(); + } + self.open = false; cx.notify(); } @@ -314,7 +317,7 @@ impl Render for ColorPicker { div() .id(self.id.clone()) - .key_context(KEY_CONTEXT) + .key_context(CONTEXT) .track_focus(&self.focus_handle) .on_action(cx.listener(Self::on_escape)) .child( @@ -358,10 +361,6 @@ impl Render for ColorPicker { }) .when_some(self.label.clone(), |this, label| this.child(label)) .on_click(cx.listener(Self::toggle_picker)) - .on_mouse_up_out( - MouseButton::Left, - cx.listener(|view, _, window, cx| view.on_escape(&Escape, window, cx)), - ) .child( canvas( move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds), @@ -394,7 +393,13 @@ impl Render for ColorPicker { .shadow_lg() .rounded(cx.theme().radius) .bg(cx.theme().background) - .child(self.render_colors(window, cx)), + .child(self.render_colors(window, cx)) + .on_mouse_up_out( + MouseButton::Left, + cx.listener(|view, _, window, cx| { + view.on_escape(&Cancel, window, cx) + }), + ), ), ) .with_priority(1), diff --git a/crates/ui/src/drawer.rs b/crates/ui/src/drawer.rs index 4773565a..4d809781 100644 --- a/crates/ui/src/drawer.rs +++ b/crates/ui/src/drawer.rs @@ -1,13 +1,14 @@ use std::{rc::Rc, time::Duration}; use gpui::{ - actions, anchored, div, point, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, + anchored, div, point, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, App, ClickEvent, DefiniteLength, DismissEvent, Div, EventEmitter, FocusHandle, InteractiveElement as _, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, RenderOnce, Styled, Window, }; use crate::{ + actions::Cancel, button::{Button, ButtonVariants as _}, h_flex, modal::overlay_color, @@ -17,11 +18,9 @@ use crate::{ v_flex, ActiveTheme, IconName, Placement, Sizable, StyledExt as _, }; -actions!(drawer, [Escape]); - const CONTEXT: &str = "Drawer"; pub fn init(cx: &mut App) { - cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))]) + cx.bind_keys([KeyBinding::new("escape", Cancel, Some(CONTEXT))]) } #[derive(IntoElement)] @@ -154,7 +153,9 @@ impl RenderOnce for Drawer { .track_focus(&self.focus_handle) .on_action({ let on_close = self.on_close.clone(); - move |_: &Escape, window, cx| { + move |_: &Cancel, window, cx| { + cx.propagate(); + on_close(&ClickEvent::default(), window, cx); window.close_drawer(cx); } diff --git a/crates/ui/src/dropdown.rs b/crates/ui/src/dropdown.rs index 229ea1fa..e925fee8 100644 --- a/crates/ui/src/dropdown.rs +++ b/crates/ui/src/dropdown.rs @@ -1,21 +1,19 @@ use gpui::{ - actions, anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, App, - AppContext, Bounds, ClickEvent, Context, DismissEvent, ElementId, Entity, EventEmitter, - FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement, - Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, - WeakEntity, Window, + anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, App, AppContext, + Bounds, ClickEvent, Context, DismissEvent, ElementId, Entity, EventEmitter, FocusHandle, + Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement, Pixels, Render, + SharedString, StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, Window, }; use rust_i18n::t; use crate::{ + actions::{Cancel, Confirm, SelectNext, SelectPrev}, h_flex, input::clear_button, - list::{self, List, ListDelegate, ListItem}, + list::{List, ListDelegate, ListItem}, v_flex, ActiveTheme, Disableable as _, Icon, IconName, Sizable, Size, StyleSized, StyledExt, }; -actions!(dropdown, [Up, Down, Enter, Escape]); - #[derive(Clone)] pub enum ListEvent { /// Single click or move to selected row. @@ -29,10 +27,15 @@ pub enum ListEvent { const CONTEXT: &str = "Dropdown"; pub fn init(cx: &mut App) { cx.bind_keys([ - KeyBinding::new("up", Up, Some(CONTEXT)), - KeyBinding::new("down", Down, Some(CONTEXT)), - KeyBinding::new("enter", Enter, Some(CONTEXT)), - KeyBinding::new("escape", Escape, Some(CONTEXT)), + KeyBinding::new("up", SelectPrev, Some(CONTEXT)), + KeyBinding::new("down", SelectNext, Some(CONTEXT)), + KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)), + KeyBinding::new( + "secondary-enter", + Confirm { secondary: true }, + Some(CONTEXT), + ), + KeyBinding::new("escape", Cancel, Some(CONTEXT)), ]) } @@ -173,9 +176,7 @@ where }); } - fn confirm(&mut self, ix: usize, window: &mut Window, cx: &mut Context>) { - self.selected_index = Some(ix); - + fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context>) { let selected_value = self .selected_index .and_then(|ix| self.delegate.get(ix)) @@ -495,24 +496,25 @@ where cx.notify(); } - fn up(&mut self, _: &Up, window: &mut Window, cx: &mut Context) { + fn up(&mut self, _: &SelectPrev, window: &mut Window, cx: &mut Context) { if !self.open { return; } + self.list.focus_handle(cx).focus(window); - cx.dispatch_action(&list::SelectPrev); + cx.propagate(); } - fn down(&mut self, _: &Down, window: &mut Window, cx: &mut Context) { + fn down(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context) { if !self.open { self.open = true; } self.list.focus_handle(cx).focus(window); - cx.dispatch_action(&list::SelectNext); + cx.propagate(); } - fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context) { + fn enter(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context) { // Propagate the event to the parent view, for example to the Modal to support ENTER to confirm. cx.propagate(); @@ -521,7 +523,6 @@ where cx.notify(); } else { self.list.focus_handle(cx).focus(window); - cx.dispatch_action(&list::Confirm); } } @@ -535,9 +536,10 @@ where cx.notify(); } - fn escape(&mut self, _: &Escape, _: &mut Window, cx: &mut Context) { - // Propagate the event to the parent view, for example to the Modal to support ESC to close. - cx.propagate(); + fn escape(&mut self, _: &Cancel, _: &mut Window, cx: &mut Context) { + if !self.open { + cx.propagate(); + } self.open = false; cx.notify(); @@ -734,13 +736,10 @@ where .border_color(cx.theme().border) .rounded(popup_radius) .shadow_md() - .on_mouse_down_out(|_, _, cx| { - cx.dispatch_action(&Escape); - }) .child(self.list.clone()), ) .on_mouse_down_out(cx.listener(|this, _, window, cx| { - this.escape(&Escape, window, cx); + this.escape(&Cancel, window, cx); })), ), ) diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index 12b38f5a..43717c2b 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -3,6 +3,7 @@ //! Based on the `Input` example from the `gpui` crate. //! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs +use serde::Deserialize; use smallvec::SmallVec; use std::cell::Cell; use std::ops::Range; @@ -11,12 +12,12 @@ use unicode_segmentation::*; use gpui::prelude::FluentBuilder as _; use gpui::{ - actions, div, point, px, relative, AnyElement, App, AppContext, Bounds, ClickEvent, - ClipboardItem, Context, DefiniteLength, Entity, EntityInputHandler, EventEmitter, FocusHandle, - Focusable, InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render, - ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, - Window, WrappedLine, + actions, div, impl_internal_actions, point, px, relative, AnyElement, App, AppContext, Bounds, + ClickEvent, ClipboardItem, Context, DefiniteLength, Entity, EntityInputHandler, EventEmitter, + FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, + Rems, Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription, + UTF16Selection, Window, WrappedLine, }; // TODO: @@ -37,6 +38,14 @@ use crate::{ActiveTheme, Root}; use crate::{IconName, Size}; use crate::{Sizable, StyleSized}; +#[derive(Clone, PartialEq, Eq, Deserialize)] +pub struct Enter { + /// Is confirm with secondary. + pub secondary: bool, +} + +impl_internal_actions!(input, [Enter]); + actions!( input, [ @@ -46,7 +55,6 @@ actions!( DeleteToEndOfLine, DeleteToPreviousWordStart, DeleteToNextWordEnd, - Enter, Up, Down, Left, @@ -83,7 +91,7 @@ actions!( #[derive(Clone)] pub enum InputEvent { Change(SharedString), - PressEnter, + PressEnter { secondary: bool }, Focus, Blur, } @@ -106,7 +114,8 @@ pub fn init(cx: &mut App) { KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)), #[cfg(not(target_os = "macos"))] KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)), - KeyBinding::new("enter", Enter, Some(CONTEXT)), + KeyBinding::new("enter", Enter { secondary: false }, Some(CONTEXT)), + KeyBinding::new("secondary-enter", Enter { secondary: true }, Some(CONTEXT)), KeyBinding::new("up", Up, Some(CONTEXT)), KeyBinding::new("down", Down, Some(CONTEXT)), KeyBinding::new("left", Left, Some(CONTEXT)), @@ -1004,7 +1013,7 @@ impl TextInput { self.pause_blink_cursor(cx); } - fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context) { + fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context) { if self.is_multi_line() { let is_eof = self.selected_range.end == self.text.len(); self.replace_text_in_range(None, "\n", window, cx); @@ -1017,7 +1026,9 @@ impl TextInput { self.move_to(new_offset, window, cx); } - cx.emit(InputEvent::PressEnter); + cx.emit(InputEvent::PressEnter { + secondary: action.secondary, + }); } fn check_to_auto_grow(&mut self, _: &mut Window, cx: &mut Context) { diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 8100b921..02194ca7 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -11,6 +11,8 @@ mod title_bar; mod virtual_list; mod window_border; +pub(crate) mod actions; + pub mod accordion; pub mod alert; pub mod animation; diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index 744ef3bd..9489b661 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -1,6 +1,7 @@ use std::time::Duration; use std::{cell::Cell, rc::Rc}; +use crate::actions::{Cancel, Confirm, SelectNext, SelectPrev}; use crate::Icon; use crate::{ input::{InputEvent, TextInput}, @@ -8,23 +9,22 @@ use crate::{ v_flex, ActiveTheme, IconName, Size, }; use gpui::{ - actions, div, prelude::FluentBuilder, uniform_list, AnyElement, AppContext, Entity, - FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length, - ListSizingBehavior, MouseButton, ParentElement, Render, SharedString, Styled, Task, - UniformListScrollHandle, Window, + div, prelude::FluentBuilder, uniform_list, AnyElement, AppContext, Entity, FocusHandle, + Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior, + MouseButton, ParentElement, Render, SharedString, Styled, Task, UniformListScrollHandle, + Window, }; -use gpui::{px, App, Context, EventEmitter, ScrollStrategy, Subscription}; +use gpui::{px, App, Context, EventEmitter, MouseDownEvent, ScrollStrategy, Subscription}; use smol::Timer; use super::loading::Loading; -actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]); - pub fn init(cx: &mut App) { let context: Option<&str> = Some("List"); cx.bind_keys([ KeyBinding::new("escape", Cancel, context), - KeyBinding::new("enter", Confirm, context), + KeyBinding::new("enter", Confirm { secondary: false }, context), + KeyBinding::new("secondary-enter", Confirm { secondary: true }, context), KeyBinding::new("up", SelectPrev, context), KeyBinding::new("down", SelectNext, context), ]); @@ -112,7 +112,9 @@ pub trait ListDelegate: Sized + 'static { ); /// Set the confirm and give the selected index, this is means user have clicked the item or pressed Enter. - fn confirm(&mut self, ix: usize, window: &mut Window, cx: &mut Context>) {} + /// + /// This will always to `set_selected_index` before confirm. + fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context>) {} /// Cancel the selection, e.g.: Pressed ESC. fn cancel(&mut self, window: &mut Window, cx: &mut Context>) {} @@ -347,7 +349,13 @@ where }); }); } - InputEvent::PressEnter => self.on_action_confirm(&Confirm, window, cx), + InputEvent::PressEnter { secondary } => self.on_action_confirm( + &Confirm { + secondary: *secondary, + }, + window, + cx, + ), _ => {} } } @@ -389,15 +397,25 @@ where } fn on_action_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context) { + if self.selected_index.is_none() { + cx.propagate(); + } + if self.reset_on_cancel { self.set_selected_index(None, window, cx); } + self.delegate.cancel(window, cx); cx.emit(ListEvent::Cancel); cx.notify(); } - fn on_action_confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context) { + fn on_action_confirm( + &mut self, + confirm: &Confirm, + window: &mut Window, + cx: &mut Context, + ) { if self.delegate.items_count(cx) == 0 { return; } @@ -406,7 +424,9 @@ where return; }; - self.delegate.confirm(ix, window, cx); + self.delegate + .set_selected_index(self.selected_index, window, cx); + self.delegate.confirm(confirm.secondary, window, cx); cx.emit(ListEvent::Confirm(ix)); cx.notify(); } @@ -496,10 +516,16 @@ where }) .on_mouse_down( MouseButton::Left, - cx.listener(move |this, _, window, cx| { + cx.listener(move |this, ev: &MouseDownEvent, window, cx| { this.right_clicked_index = None; this.selected_index = Some(ix); - this.on_action_confirm(&Confirm, window, cx); + this.on_action_confirm( + &Confirm { + secondary: ev.modifiers.secondary(), + }, + window, + cx, + ); }), ) .on_mouse_down( diff --git a/crates/ui/src/modal.rs b/crates/ui/src/modal.rs index 61382a39..c0ac9d7d 100644 --- a/crates/ui/src/modal.rs +++ b/crates/ui/src/modal.rs @@ -1,26 +1,25 @@ use std::{rc::Rc, time::Duration}; use gpui::{ - actions, anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation, - AnimationExt as _, AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, Hsla, - InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, - RenderOnce, SharedString, Styled, Window, + anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation, AnimationExt as _, + AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, Hsla, InteractiveElement, IntoElement, + KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, Styled, + Window, }; use rust_i18n::t; use crate::{ + actions::{Cancel, Confirm}, animation::cubic_bezier, button::{Button, ButtonVariant, ButtonVariants as _}, h_flex, v_flex, ActiveTheme as _, ContextModal, IconName, Sizable as _, StyledExt, }; -actions!(modal, [Escape, Enter]); - const CONTEXT: &str = "Modal"; pub fn init(cx: &mut App) { cx.bind_keys([ - KeyBinding::new("escape", Escape, Some(CONTEXT)), - KeyBinding::new("enter", Enter, Some(CONTEXT)), + KeyBinding::new("escape", Cancel, Some(CONTEXT)), + KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)), ]); } @@ -382,7 +381,7 @@ impl RenderOnce for Modal { this.on_action({ let on_cancel = on_cancel.clone(); let on_close = on_close.clone(); - move |_: &Escape, window, cx| { + move |_: &Cancel, window, cx| { // FIXME: // // Here some Modal have no focus_handle, so it will not work will Escape key. @@ -395,7 +394,7 @@ impl RenderOnce for Modal { .on_action({ let on_ok = on_ok.clone(); let on_close = on_close.clone(); - move |_: &Enter, window, cx| { + move |_: &Confirm, window, cx| { if on_ok(&ClickEvent::default(), window, cx) { on_close(&ClickEvent::default(), window, cx); window.close_modal(cx); diff --git a/crates/ui/src/popover.rs b/crates/ui/src/popover.rs index 166d842d..49a4e5f4 100644 --- a/crates/ui/src/popover.rs +++ b/crates/ui/src/popover.rs @@ -1,20 +1,18 @@ use gpui::{ - actions, anchored, deferred, div, prelude::FluentBuilder as _, px, AnyElement, App, Bounds, - Context, Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity, EventEmitter, - FocusHandle, Focusable, GlobalElementId, Hitbox, InteractiveElement as _, IntoElement, - KeyBinding, LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, - Render, Style, StyleRefinement, Styled, Window, + anchored, deferred, div, prelude::FluentBuilder as _, px, AnyElement, App, Bounds, Context, + Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity, EventEmitter, FocusHandle, + Focusable, GlobalElementId, Hitbox, InteractiveElement as _, IntoElement, KeyBinding, LayoutId, + ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render, Style, + StyleRefinement, Styled, Window, }; use std::{cell::RefCell, rc::Rc}; -use crate::{Selectable, StyledExt as _}; +use crate::{actions::Cancel, Selectable, StyledExt as _}; const CONTEXT: &str = "Popover"; -actions!(popover, [Escape]); - pub fn init(cx: &mut App) { - cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))]) + cx.bind_keys([KeyBinding::new("escape", Cancel, Some(CONTEXT))]) } pub struct PopoverContent { @@ -55,7 +53,10 @@ impl Render for PopoverContent { div() .track_focus(&self.focus_handle) .key_context(CONTEXT) - .on_action(cx.listener(|_, _: &Escape, _, cx| cx.emit(DismissEvent))) + .on_action(cx.listener(|_, _: &Cancel, _, cx| { + cx.propagate(); + cx.emit(DismissEvent); + })) .p_2() .when_some(self.max_width, |this, v| this.max_w(v)) .child(self.content.clone()(window, cx)) diff --git a/crates/ui/src/popup_menu.rs b/crates/ui/src/popup_menu.rs index 718f7f49..b6e0568d 100644 --- a/crates/ui/src/popup_menu.rs +++ b/crates/ui/src/popup_menu.rs @@ -1,3 +1,4 @@ +use crate::actions::{Cancel, Confirm, SelectNext, SelectPrev}; use crate::scroll::{Scrollbar, ScrollbarState}; use crate::{ button::Button, h_flex, list::ListItem, popover::Popover, v_flex, ActiveTheme, Icon, IconName, @@ -6,24 +7,22 @@ use crate::{ use crate::{Kbd, StyledExt}; use gpui::Subscription; use gpui::{ - actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App, - AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, - Focusable, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render, - ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, + anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App, AppContext, + Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable, + InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render, ScrollHandle, + SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, }; use std::cell::Cell; use std::ops::Deref; use std::rc::Rc; -actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]); - const ITEM_HEIGHT: Pixels = px(26.); pub fn init(cx: &mut App) { let context = Some("PopupMenu"); cx.bind_keys([ - KeyBinding::new("enter", Confirm, context), - KeyBinding::new("escape", Dismiss, context), + KeyBinding::new("enter", Confirm { secondary: false }, context), + KeyBinding::new("escape", Cancel, context), KeyBinding::new("up", SelectPrev, context), KeyBinding::new("down", SelectNext, context), ]); @@ -127,7 +126,7 @@ impl PopupMenu { let _subscriptions = vec![ cx.on_blur(&focus_handle, window, |this: &mut PopupMenu, window, cx| { - this.dismiss(&Dismiss, window, cx) + this.dismiss(&Cancel, window, cx) }), ]; @@ -544,7 +543,7 @@ impl PopupMenu { cx.stop_propagation(); window.prevent_default(); self.selected_index = Some(ix); - self.confirm(&Confirm, window, cx); + self.confirm(&Confirm { secondary: false }, window, cx); } fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context) { @@ -554,11 +553,11 @@ impl PopupMenu { match item { Some(PopupMenuItem::Item { handler, .. }) => { handler(window, cx); - self.dismiss(&Dismiss, window, cx) + self.dismiss(&Cancel, window, cx) } Some(PopupMenuItem::ElementItem { handler, .. }) => { handler(window, cx); - self.dismiss(&Dismiss, window, cx) + self.dismiss(&Cancel, window, cx) } _ => {} } @@ -601,7 +600,7 @@ impl PopupMenu { } } - fn dismiss(&mut self, _: &Dismiss, window: &mut Window, cx: &mut Context) { + fn dismiss(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context) { if self.active_submenu().is_some() { return; } @@ -620,7 +619,7 @@ impl PopupMenu { // Dismiss parent menu, when this menu is dismissed _ = parent_menu.update(cx, |view, cx| { view.hovered_menu_ix = None; - view.dismiss(&Dismiss, window, cx); + view.dismiss(&Cancel, window, cx); }); } @@ -883,9 +882,7 @@ impl Render for PopupMenu { .on_action(cx.listener(Self::select_prev)) .on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::dismiss)) - .on_mouse_down_out( - cx.listener(|this, _, window, cx| this.dismiss(&Dismiss, window, cx)), - ) + .on_mouse_down_out(cx.listener(|this, _, window, cx| this.dismiss(&Cancel, window, cx))) .popover_style(cx) .text_color(cx.theme().popover_foreground) .relative() diff --git a/crates/ui/src/table.rs b/crates/ui/src/table.rs index eb8f3d95..e6f0b993 100644 --- a/crates/ui/src/table.rs +++ b/crates/ui/src/table.rs @@ -1,6 +1,7 @@ use std::{cell::Cell, ops::Range, rc::Rc, time::Duration}; use crate::{ + actions::{Cancel, SelectNext, SelectPrev}, context_menu::ContextMenuExt, h_flex, popup_menu::PopupMenu, @@ -18,16 +19,7 @@ use gpui::{ mod loading; -actions!( - table, - [ - Cancel, - SelectPrev, - SelectNext, - SelectPrevColumn, - SelectNextColumn - ] -); +actions!(table, [SelectPrevColumn, SelectNextColumn]); pub fn init(cx: &mut App) { let context = Some("Table"); diff --git a/crates/ui/src/time/date_picker.rs b/crates/ui/src/time/date_picker.rs index 95b25645..4da6b96b 100644 --- a/crates/ui/src/time/date_picker.rs +++ b/crates/ui/src/time/date_picker.rs @@ -8,8 +8,8 @@ use gpui::{ use rust_i18n::t; use crate::{ + actions::Cancel, button::{Button, ButtonVariants as _}, - dropdown::Escape, h_flex, input::clear_button, v_flex, ActiveTheme, Icon, IconName, Sizable, Size, StyleSized as _, StyledExt as _, @@ -19,7 +19,7 @@ use super::calendar::{Calendar, CalendarEvent, Date, Matcher}; pub fn init(cx: &mut App) { let context = Some("DatePicker"); - cx.bind_keys([KeyBinding::new("escape", Escape, context)]) + cx.bind_keys([KeyBinding::new("escape", Cancel, context)]) } #[derive(Clone)] @@ -208,7 +208,11 @@ impl DatePicker { cx.notify(); } - fn escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Context) { + fn escape(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context) { + if !self.open { + cx.propagate(); + } + self.focus_back_if_need(window, cx); self.open = false; @@ -364,7 +368,7 @@ impl Render for DatePicker { .on_mouse_up_out( MouseButton::Left, cx.listener(|view, _, window, cx| { - view.escape(&Escape, window, cx); + view.escape(&Cancel, window, cx); }), ) .child(