editor: Add delay for selection drag to prevent accidental drag over attempt for new selection (#32586)

- Add `300ms` delay for it to consider it as selection drag instead of
an attempt to make a new selection.
- Add cursor icon while dragging the selection.

This is same as what chromium does:
https://chromium.googlesource.com/chromium/blink/+/master/Source/core/input/EventHandler.cpp#142

Release Notes:

- Fixed issue where you accidentally end up dragging the selection where
intent was to make a new one instead. To drag selection now, you need to
hold just a little longer before dragging.
This commit is contained in:
Smit Barmase 2025-06-12 06:07:20 +05:30 committed by GitHub
parent 04223f304b
commit 13ee78c0b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 16 deletions

View file

@ -912,6 +912,7 @@ enum SelectionDragState {
ReadyToDrag { ReadyToDrag {
selection: Selection<Anchor>, selection: Selection<Anchor>,
click_position: gpui::Point<Pixels>, click_position: gpui::Point<Pixels>,
mouse_down_time: Instant,
}, },
/// State when the mouse is dragging the selection in the editor. /// State when the mouse is dragging the selection in the editor.
Dragging { Dragging {

View file

@ -75,7 +75,7 @@ use std::{
ops::{Deref, Range}, ops::{Deref, Range},
rc::Rc, rc::Rc,
sync::Arc, sync::Arc,
time::Duration, time::{Duration, Instant},
}; };
use sum_tree::Bias; use sum_tree::Bias;
use text::{BufferId, SelectionGoal}; use text::{BufferId, SelectionGoal};
@ -87,6 +87,7 @@ use util::{RangeExt, ResultExt, debug_panic};
use workspace::{CollaboratorId, Workspace, item::Item, notifications::NotifyTaskExt}; use workspace::{CollaboratorId, Workspace, item::Item, notifications::NotifyTaskExt};
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.; const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
const SELECTION_DRAG_DELAY: Duration = Duration::from_millis(300);
/// Determines what kinds of highlights should be applied to a lines background. /// Determines what kinds of highlights should be applied to a lines background.
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
@ -642,6 +643,7 @@ impl EditorElement {
editor.selection_drag_state = SelectionDragState::ReadyToDrag { editor.selection_drag_state = SelectionDragState::ReadyToDrag {
selection: newest_anchor.clone(), selection: newest_anchor.clone(),
click_position: event.position, click_position: event.position,
mouse_down_time: Instant::now(),
}; };
cx.stop_propagation(); cx.stop_propagation();
return; return;
@ -837,6 +839,7 @@ impl EditorElement {
SelectionDragState::ReadyToDrag { SelectionDragState::ReadyToDrag {
selection: _, selection: _,
ref click_position, ref click_position,
mouse_down_time: _,
} => { } => {
if event.position == *click_position { if event.position == *click_position {
editor.select( editor.select(
@ -851,6 +854,8 @@ impl EditorElement {
editor.selection_drag_state = SelectionDragState::None; editor.selection_drag_state = SelectionDragState::None;
cx.stop_propagation(); cx.stop_propagation();
return; return;
} else {
debug_panic!("drag state can never be in ready state after drag")
} }
} }
SelectionDragState::Dragging { ref selection, .. } => { SelectionDragState::Dragging { ref selection, .. } => {
@ -993,25 +998,54 @@ impl EditorElement {
drop_cursor.start = drop_anchor; drop_cursor.start = drop_anchor;
drop_cursor.end = drop_anchor; drop_cursor.end = drop_anchor;
*hide_drop_cursor = !text_hitbox.is_hovered(window); *hide_drop_cursor = !text_hitbox.is_hovered(window);
editor.apply_scroll_delta(scroll_delta, window, cx);
cx.notify();
} }
SelectionDragState::ReadyToDrag { ref selection, .. } => { SelectionDragState::ReadyToDrag {
let drop_cursor = Selection { ref selection,
id: post_inc(&mut editor.selections.next_selection_id), ref click_position,
start: drop_anchor, ref mouse_down_time,
end: drop_anchor, } => {
reversed: false, if mouse_down_time.elapsed() >= SELECTION_DRAG_DELAY {
goal: SelectionGoal::None, let drop_cursor = Selection {
}; id: post_inc(&mut editor.selections.next_selection_id),
editor.selection_drag_state = SelectionDragState::Dragging { start: drop_anchor,
selection: selection.clone(), end: drop_anchor,
drop_cursor, reversed: false,
hide_drop_cursor: false, goal: SelectionGoal::None,
}; };
editor.selection_drag_state = SelectionDragState::Dragging {
selection: selection.clone(),
drop_cursor,
hide_drop_cursor: false,
};
editor.apply_scroll_delta(scroll_delta, window, cx);
cx.notify();
} else {
let click_point = position_map.point_for_position(*click_position);
editor.selection_drag_state = SelectionDragState::None;
editor.select(
SelectPhase::Begin {
position: click_point.previous_valid,
add: false,
click_count: 1,
},
window,
cx,
);
editor.select(
SelectPhase::Update {
position: point_for_position.previous_valid,
goal_column: point_for_position.exact_unclipped.column(),
scroll_delta,
},
window,
cx,
);
}
} }
_ => {} _ => {}
} }
editor.apply_scroll_delta(scroll_delta, window, cx);
cx.notify();
} else { } else {
editor.select( editor.select(
SelectPhase::Update { SelectPhase::Update {
@ -5577,6 +5611,12 @@ impl EditorElement {
let editor = self.editor.read(cx); let editor = self.editor.read(cx);
if editor.mouse_cursor_hidden { if editor.mouse_cursor_hidden {
window.set_window_cursor_style(CursorStyle::None); window.set_window_cursor_style(CursorStyle::None);
} else if matches!(
editor.selection_drag_state,
SelectionDragState::Dragging { .. }
) {
window
.set_cursor_style(CursorStyle::DragCopy, &layout.position_map.text_hitbox);
} else if editor } else if editor
.hovered_link_state .hovered_link_state
.as_ref() .as_ref()