Make middle click not mousedown close tabs (#44916)
Closes #44856 This PR also adds an `on_aux_click` interface to the div element that follows MDM standard https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event Release Notes: - fix bug where mouse down middle click would close tab instead of full middle click --------- Co-authored-by: Anthony Eid <anthony@zed.dev>
This commit is contained in:
1 parent
bb4327e1e6
commit
0357a4a95d
3 files changed
+72
-10
No files matched your search
@@ -522,6 +522,20 @@ impl Interactivity {
|
||||
}));
|
||||
}
|
||||
|
||||
/// Bind the given callback to non-primary click events of this element.
|
||||
/// The imperative API equivalent to [`StatefulInteractiveElement::on_aux_click`].
|
||||
///
|
||||
/// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
|
||||
pub fn on_aux_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.aux_click_listeners
|
||||
.push(Rc::new(move |event, window, cx| {
|
||||
listener(event, window, cx)
|
||||
}));
|
||||
}
|
||||
|
||||
/// On drag initiation, this callback will be used to create a new view to render the dragged value for a
|
||||
/// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
|
||||
/// the [`Self::on_drag_move`] API.
|
||||
@@ -1190,6 +1204,21 @@ pub trait StatefulInteractiveElement: InteractiveElement {
|
||||
self
|
||||
}
|
||||
|
||||
/// Bind the given callback to non-primary click events of this element.
|
||||
/// The fluent API equivalent to [`Interactivity::on_aux_click`].
|
||||
///
|
||||
/// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
|
||||
fn on_aux_click(
|
||||
mut self,
|
||||
listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.interactivity().on_aux_click(listener);
|
||||
self
|
||||
}
|
||||
|
||||
/// On drag initiation, this callback will be used to create a new view to render the dragged value for a
|
||||
/// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
|
||||
/// the [`InteractiveElement::on_drag_move`] API.
|
||||
@@ -1622,6 +1651,7 @@ pub struct Interactivity {
|
||||
pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
|
||||
pub(crate) can_drop_predicate: Option<CanDropPredicate>,
|
||||
pub(crate) click_listeners: Vec<ClickListener>,
|
||||
pub(crate) aux_click_listeners: Vec<ClickListener>,
|
||||
pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
|
||||
pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
|
||||
pub(crate) tooltip_builder: Option<TooltipBuilder>,
|
||||
@@ -1815,6 +1845,7 @@ impl Interactivity {
|
||||
|| !self.mouse_down_listeners.is_empty()
|
||||
|| !self.mouse_move_listeners.is_empty()
|
||||
|| !self.click_listeners.is_empty()
|
||||
|| !self.aux_click_listeners.is_empty()
|
||||
|| !self.scroll_wheel_listeners.is_empty()
|
||||
|| self.drag_listener.is_some()
|
||||
|| !self.drop_listeners.is_empty()
|
||||
@@ -2237,6 +2268,7 @@ impl Interactivity {
|
||||
let mut drag_listener = mem::take(&mut self.drag_listener);
|
||||
let drop_listeners = mem::take(&mut self.drop_listeners);
|
||||
let click_listeners = mem::take(&mut self.click_listeners);
|
||||
let aux_click_listeners = mem::take(&mut self.aux_click_listeners);
|
||||
let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
|
||||
|
||||
if !drop_listeners.is_empty() {
|
||||
@@ -2273,7 +2305,10 @@ impl Interactivity {
|
||||
}
|
||||
|
||||
if let Some(element_state) = element_state {
|
||||
if !click_listeners.is_empty() || drag_listener.is_some() {
|
||||
if !click_listeners.is_empty()
|
||||
|| !aux_click_listeners.is_empty()
|
||||
|| drag_listener.is_some()
|
||||
{
|
||||
let pending_mouse_down = element_state
|
||||
.pending_mouse_down
|
||||
.get_or_insert_with(Default::default)
|
||||
@@ -2287,9 +2322,10 @@ impl Interactivity {
|
||||
window.on_mouse_event({
|
||||
let pending_mouse_down = pending_mouse_down.clone();
|
||||
let hitbox = hitbox.clone();
|
||||
let has_aux_click_listeners = !aux_click_listeners.is_empty();
|
||||
move |event: &MouseDownEvent, phase, window, _cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == MouseButton::Left
|
||||
&& (event.button == MouseButton::Left || has_aux_click_listeners)
|
||||
&& hitbox.is_hovered(window)
|
||||
{
|
||||
*pending_mouse_down.borrow_mut() = Some(event.clone());
|
||||
@@ -2311,6 +2347,7 @@ impl Interactivity {
|
||||
&& !cx.has_active_drag()
|
||||
&& (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
|
||||
&& let Some((drag_value, drag_listener)) = drag_listener.take()
|
||||
&& mouse_down.button == MouseButton::Left
|
||||
{
|
||||
*clicked_state.borrow_mut() = ElementClickedState::default();
|
||||
let cursor_offset = event.position - hitbox.origin;
|
||||
@@ -2387,12 +2424,24 @@ impl Interactivity {
|
||||
// Fire click handlers during the bubble phase.
|
||||
DispatchPhase::Bubble => {
|
||||
if let Some(mouse_down) = captured_mouse_down.take() {
|
||||
let btn = mouse_down.button;
|
||||
|
||||
let mouse_click = ClickEvent::Mouse(MouseClickEvent {
|
||||
down: mouse_down,
|
||||
up: event.clone(),
|
||||
});
|
||||
for listener in &click_listeners {
|
||||
listener(&mouse_click, window, cx);
|
||||
|
||||
match btn {
|
||||
MouseButton::Left => {
|
||||
for listener in &click_listeners {
|
||||
listener(&mouse_click, window, cx);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
for listener in &aux_click_listeners {
|
||||
listener(&mouse_click, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,6 +284,19 @@ impl ClickEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if this was a middle click
|
||||
///
|
||||
/// `Keyboard`: false
|
||||
/// `Mouse`: Whether the middle button was pressed and released
|
||||
pub fn is_middle_click(&self) -> bool {
|
||||
match self {
|
||||
ClickEvent::Keyboard(_) => false,
|
||||
ClickEvent::Mouse(event) => {
|
||||
event.down.button == MouseButton::Middle && event.up.button == MouseButton::Middle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the click was a standard click
|
||||
///
|
||||
/// `Keyboard`: Always true
|
||||
|
||||
@@ -2790,8 +2790,6 @@ impl Pane {
|
||||
let item_handle = item.boxed_clone();
|
||||
move |pane: &mut Self, event: &ClickEvent, window, cx| {
|
||||
if event.click_count() > 1 {
|
||||
// On double-click, dispatch the Rename action (when available)
|
||||
// instead of just activating the item.
|
||||
pane.unpreview_item_if_preview(item_id);
|
||||
let extra_actions = item_handle.tab_extra_context_menu_actions(window, cx);
|
||||
if let Some((_, action)) = extra_actions
|
||||
@@ -2809,10 +2807,12 @@ impl Pane {
|
||||
pane.activate_item(ix, true, true, window, cx)
|
||||
}
|
||||
}))
|
||||
// TODO: This should be a click listener with the middle mouse button instead of a mouse down listener.
|
||||
.on_mouse_down(
|
||||
MouseButton::Middle,
|
||||
cx.listener(move |pane, _event, window, cx| {
|
||||
.on_aux_click(
|
||||
cx.listener(move |pane: &mut Self, event: &ClickEvent, window, cx| {
|
||||
if !event.is_middle_click() {
|
||||
return;
|
||||
}
|
||||
|
||||
pane.close_item_by_id(item_id, SaveIntent::Close, window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
|
||||
Reference in new issue
Block a user