From ad017a5df52ebb465fabcde85031cbdac92950fc Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 13 Nov 2023 21:42:27 -0700 Subject: [PATCH] Allow clicking on commands in the command palette --- crates/gpui2/src/interactive.rs | 40 +++++++++++++++--- crates/gpui2/src/window.rs | 6 +++ crates/picker2/src/picker2.rs | 56 ++++++++++++++++++++----- crates/ui2/src/components/keybinding.rs | 2 + crates/workspace2/src/modal_layer.rs | 25 ++++++----- 5 files changed, 104 insertions(+), 25 deletions(-) diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index aacaeac01ff0dd60dfc096d04a82fb2029704874..312121c95410fb2c6c99d0070316a753fa5c2442 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -71,6 +71,40 @@ pub trait StatelessInteractive: Element { self } + fn on_any_mouse_down( + mut self, + handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + 'static, + ) -> Self + where + Self: Sized, + { + self.stateless_interactivity() + .mouse_down_listeners + .push(Box::new(move |view, event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + handler(view, event, cx) + } + })); + self + } + + fn on_any_mouse_up( + mut self, + handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + 'static, + ) -> Self + where + Self: Sized, + { + self.stateless_interactivity() + .mouse_up_listeners + .push(Box::new(move |view, event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + handler(view, event, cx) + } + })); + self + } + fn on_mouse_up( mut self, button: MouseButton, @@ -111,7 +145,6 @@ pub trait StatelessInteractive: Element { fn on_mouse_up_out( mut self, - button: MouseButton, handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext) + 'static, ) -> Self where @@ -120,10 +153,7 @@ pub trait StatelessInteractive: Element { self.stateless_interactivity() .mouse_up_listeners .push(Box::new(move |view, event, bounds, phase, cx| { - if phase == DispatchPhase::Capture - && event.button == button - && !bounds.contains_point(&event.position) - { + if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) { handler(view, event, cx); } })); diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 450b36ea81b15848e08725a0038933f2756da252..eb69b451b3af924a909004ec1addebb539e48e19 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -101,6 +101,12 @@ pub struct FocusHandle { handles: Arc>>, } +impl std::fmt::Debug for FocusHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("FocusHandle({:?})", self.id)) + } +} + impl FocusHandle { pub(crate) fn new(handles: &Arc>>) -> Self { let id = handles.write().insert(AtomicUsize::new(1)); diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 836a6124913668f4011a98bea747f77ffc4a5e13..0cfe5c8992a49a6c56650f02c73845dfd5e1c337 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -1,7 +1,7 @@ use editor::Editor; use gpui::{ - div, uniform_list, Component, Div, ParentElement, Render, StatelessInteractive, Styled, Task, - UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext, + div, uniform_list, Component, Div, MouseButton, ParentElement, Render, StatelessInteractive, + Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext, }; use std::{cmp, sync::Arc}; use ui::{prelude::*, v_stack, Divider, Label, LabelColor}; @@ -11,6 +11,7 @@ pub struct Picker { scroll_handle: UniformListScrollHandle, editor: View, pending_update_matches: Option>, + confirm_on_update: Option, } pub trait PickerDelegate: Sized + 'static { @@ -44,9 +45,10 @@ impl Picker { cx.subscribe(&editor, Self::on_input_editor_event).detach(); let mut this = Self { delegate, + editor, scroll_handle: UniformListScrollHandle::new(), pending_update_matches: None, - editor, + confirm_on_update: None, }; this.update_matches("".to_string(), cx); this @@ -101,11 +103,26 @@ impl Picker { } fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { - self.delegate.confirm(false, cx); + if self.pending_update_matches.is_some() { + self.confirm_on_update = Some(false) + } else { + self.delegate.confirm(false, cx); + } } fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext) { - self.delegate.confirm(true, cx); + if self.pending_update_matches.is_some() { + self.confirm_on_update = Some(true) + } else { + self.delegate.confirm(true, cx); + } + } + + fn handle_click(&mut self, ix: usize, secondary: bool, cx: &mut ViewContext) { + cx.stop_propagation(); + cx.prevent_default(); + self.delegate.set_selected_index(ix, cx); + self.delegate.confirm(secondary, cx); } fn on_input_editor_event( @@ -136,6 +153,9 @@ impl Picker { let index = self.delegate.selected_index(); self.scroll_handle.scroll_to_item(index); self.pending_update_matches = None; + if let Some(secondary) = self.confirm_on_update.take() { + self.delegate.confirm(secondary, cx); + } cx.notify(); } } @@ -173,7 +193,22 @@ impl Render for Picker { let selected_ix = this.delegate.selected_index(); visible_range .map(|ix| { - this.delegate.render_match(ix, ix == selected_ix, cx) + div() + .on_mouse_down( + MouseButton::Left, + move |this: &mut Self, event, cx| { + this.handle_click( + ix, + event.modifiers.command, + cx, + ) + }, + ) + .child(this.delegate.render_match( + ix, + ix == selected_ix, + cx, + )) }) .collect() } @@ -186,10 +221,11 @@ impl Render for Picker { }) .when(self.delegate.match_count() == 0, |el| { el.child( - v_stack() - .p_1() - .grow() - .child(Label::new("No matches").color(LabelColor::Muted)), + v_stack().p_1().grow().child( + div() + .px_1() + .child(Label::new("No matches").color(LabelColor::Muted)), + ), ) }) } diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index b6c435c607da91f7f1d96b382a3a37bd6f452489..a3e5a870a6b61cad638a1a8b174c364273d559f2 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -32,6 +32,7 @@ impl KeyBinding { div() .flex() .gap_1() + .when(keystroke.modifiers.function, |el| el.child(Key::new("fn"))) .when(keystroke.modifiers.control, |el| el.child(Key::new("^"))) .when(keystroke.modifiers.alt, |el| el.child(Key::new("⌥"))) .when(keystroke.modifiers.command, |el| el.child(Key::new("⌘"))) @@ -136,6 +137,7 @@ mod stories { .child(KeyBinding::new(binding("a z"))) .child(Story::label(cx, "Chord with Modifier")) .child(KeyBinding::new(binding("ctrl-a shift-z"))) + .child(KeyBinding::new(binding("fn-s"))) } } } diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index 09ffa6c13f92b892ad1717f200a8c1ced84c0361..b3a5de8fb26d9caf25d652347b9a24ce47818630 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -2,7 +2,7 @@ use gpui::{ div, px, AnyView, Div, EventEmitter, FocusHandle, ParentElement, Render, StatelessInteractive, Styled, Subscription, View, ViewContext, VisualContext, WindowContext, }; -use ui::v_stack; +use ui::{h_stack, v_stack}; pub struct ActiveModal { modal: AnyView, @@ -33,8 +33,6 @@ impl ModalLayer { V: Modal, B: FnOnce(&mut ViewContext) -> V, { - let previous_focus = cx.focused(); - if let Some(active_modal) = &self.active_modal { let is_close = active_modal.modal.clone().downcast::().is_ok(); self.hide_modal(cx); @@ -85,9 +83,6 @@ impl Render for ModalLayer { div() .absolute() - .flex() - .flex_col() - .items_center() .size_full() .top_0() .left_0() @@ -96,11 +91,21 @@ impl Render for ModalLayer { v_stack() .h(px(0.0)) .top_20() + .flex() + .flex_col() + .items_center() .track_focus(&active_modal.focus_handle) - .on_mouse_down_out(|this: &mut Self, event, cx| { - this.hide_modal(cx); - }) - .child(active_modal.modal.clone()), + .child( + h_stack() + // needed to prevent mouse events leaking to the + // UI below. // todo! for gpui3. + .on_any_mouse_down(|_, _, cx| cx.stop_propagation()) + .on_any_mouse_up(|_, _, cx| cx.stop_propagation()) + .on_mouse_down_out(|this: &mut Self, event, cx| { + this.hide_modal(cx); + }) + .child(active_modal.modal.clone()), + ), ) } }