list: Add secondary confirm support to list. (#769)

This commit is contained in:
Jason Lee 2025-04-02 16:45:27 +08:00 committed by GitHub
parent c91dce3139
commit a60acd7e4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 187 additions and 134 deletions

View file

@ -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"),
},

View file

@ -174,8 +174,8 @@ impl ListDelegate for CompanyListDelegate {
Task::ready(())
}
fn confirm(&mut self, ix: usize, window: &mut Window, cx: &mut Context<List<Self>>) {
self.confirmed_index = Some(ix);
fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<List<Self>>) {
println!("Confirmed with secondary: {}", secondary);
window.dispatch_action(Box::new(SelectedCompany), cx);
}

View file

@ -368,7 +368,6 @@ impl StoryWorkspace {
Arc::new(StoryContainer::panel::<AccordionStory>(window, cx)),
Arc::new(StoryContainer::panel::<SidebarStory>(window, cx)),
Arc::new(StoryContainer::panel::<FormStory>(window, cx)),
Arc::new(StoryContainer::panel::<WebViewStory>(window, cx)),
],
None,
&dock_area,

View file

@ -128,11 +128,13 @@ impl ListDelegate for ListItemDeletegate {
});
}
fn confirm(&mut self, ix: usize, window: &mut Window, cx: &mut Context<List<Self>>) {
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<List<Self>>) {
_ = 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);

View file

@ -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::<usize>() {
self.table.update(cx, |table, _| {

View file

@ -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);

11
crates/ui/src/actions.rs Normal file
View file

@ -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]);

View file

@ -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<ElementId>, window: &mut Window, cx: &mut Context<Self>) -> 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<Self>) {
cx.propagate();
fn on_escape(&mut self, _: &Cancel, _: &mut Window, cx: &mut Context<Self>) {
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),

View file

@ -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);
}

View file

@ -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<List<Self>>) {
self.selected_index = Some(ix);
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<List<Self>>) {
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<Self>) {
fn up(&mut self, _: &SelectPrev, window: &mut Window, cx: &mut Context<Self>) {
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<Self>) {
fn down(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) {
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<Self>) {
fn enter(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
// 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<Self>) {
// 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<Self>) {
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);
})),
),
)

View file

@ -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<Self>) {
fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
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<Self>) {

View file

@ -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;

View file

@ -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<List<Self>>) {}
///
/// This will always to `set_selected_index` before confirm.
fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<List<Self>>) {}
/// Cancel the selection, e.g.: Pressed ESC.
fn cancel(&mut self, window: &mut Window, cx: &mut Context<List<Self>>) {}
@ -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<Self>) {
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<Self>) {
fn on_action_confirm(
&mut self,
confirm: &Confirm,
window: &mut Window,
cx: &mut Context<Self>,
) {
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(

View file

@ -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);

View file

@ -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))

View file

@ -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<Self>) {
@ -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<Self>) {
fn dismiss(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
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()

View file

@ -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");

View file

@ -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<Self>) {
fn escape(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
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(