Compare commits
1 Commits
quickfix
...
smooth-cur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a9fa435f0 |
@@ -38,6 +38,7 @@ mod proposed_changes_editor;
|
||||
mod rust_analyzer_ext;
|
||||
pub mod scroll;
|
||||
mod selections_collection;
|
||||
mod smooth_cursor_manager;
|
||||
pub mod tasks;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -152,6 +153,7 @@ use selections_collection::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
|
||||
use smallvec::SmallVec;
|
||||
use smooth_cursor_manager::SmoothCursorManager;
|
||||
use snippet::Snippet;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
@@ -760,6 +762,7 @@ pub struct Editor {
|
||||
toggle_fold_multiple_buffers: Task<()>,
|
||||
_scroll_cursor_center_top_bottom_task: Task<()>,
|
||||
serialize_selections: Task<()>,
|
||||
smooth_cursor_manager: SmoothCursorManager,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||
@@ -1467,6 +1470,7 @@ impl Editor {
|
||||
serialize_selections: Task::ready(()),
|
||||
text_style_refinement: None,
|
||||
load_diff_task: load_uncommitted_diff,
|
||||
smooth_cursor_manager: SmoothCursorManager::Inactive,
|
||||
};
|
||||
this.tasks_update_task = Some(this.refresh_runnables(window, cx));
|
||||
this._subscriptions.extend(project_subscriptions);
|
||||
@@ -2030,6 +2034,7 @@ impl Editor {
|
||||
local: bool,
|
||||
old_cursor_position: &Anchor,
|
||||
show_completions: bool,
|
||||
pre_edit_pixel_points: HashMap<usize, Option<gpui::Point<Pixels>>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -2162,6 +2167,23 @@ impl Editor {
|
||||
|
||||
hide_hover(self, cx);
|
||||
|
||||
let mut post_edit_pixel_points = HashMap::default();
|
||||
|
||||
for selection in self.selections.disjoint_anchors().iter() {
|
||||
let head_point =
|
||||
self.to_pixel_point(selection.head(), &self.snapshot(window, cx), window);
|
||||
post_edit_pixel_points.insert(selection.id, head_point);
|
||||
}
|
||||
|
||||
if let Some(pending) = self.selections.pending_anchor() {
|
||||
let head_point =
|
||||
self.to_pixel_point(pending.head(), &self.snapshot(window, cx), window);
|
||||
post_edit_pixel_points.insert(pending.id, head_point);
|
||||
}
|
||||
|
||||
self.smooth_cursor_manager
|
||||
.update(pre_edit_pixel_points, post_edit_pixel_points);
|
||||
|
||||
if old_cursor_position.to_display_point(&display_map).row()
|
||||
!= new_cursor_position.to_display_point(&display_map).row()
|
||||
{
|
||||
@@ -2279,6 +2301,21 @@ impl Editor {
|
||||
change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
|
||||
) -> R {
|
||||
let old_cursor_position = self.selections.newest_anchor().head();
|
||||
|
||||
let mut pre_edit_pixel_points = HashMap::default();
|
||||
|
||||
for selection in self.selections.disjoint_anchors().iter() {
|
||||
let head_point =
|
||||
self.to_pixel_point(selection.head(), &self.snapshot(window, cx), window);
|
||||
pre_edit_pixel_points.insert(selection.id, head_point);
|
||||
}
|
||||
|
||||
if let Some(pending) = self.selections.pending_anchor() {
|
||||
let head_point =
|
||||
self.to_pixel_point(pending.head(), &self.snapshot(window, cx), window);
|
||||
pre_edit_pixel_points.insert(pending.id, head_point);
|
||||
}
|
||||
|
||||
self.push_to_selection_history();
|
||||
|
||||
let (changed, result) = self.selections.change_with(cx, change);
|
||||
@@ -2287,7 +2324,14 @@ impl Editor {
|
||||
if let Some(autoscroll) = autoscroll {
|
||||
self.request_autoscroll(autoscroll, cx);
|
||||
}
|
||||
self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
|
||||
self.selections_did_change(
|
||||
true,
|
||||
&old_cursor_position,
|
||||
request_completions,
|
||||
pre_edit_pixel_points,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
if self.should_open_signature_help_automatically(
|
||||
&old_cursor_position,
|
||||
@@ -3100,6 +3144,20 @@ impl Editor {
|
||||
let initial_buffer_versions =
|
||||
jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
|
||||
|
||||
let mut pre_edit_pixel_points = HashMap::default();
|
||||
|
||||
for selection in this.selections.disjoint_anchors().iter() {
|
||||
let head_point =
|
||||
this.to_pixel_point(selection.head(), &this.snapshot(window, cx), window);
|
||||
pre_edit_pixel_points.insert(selection.id, head_point);
|
||||
}
|
||||
|
||||
if let Some(pending) = this.selections.pending_anchor() {
|
||||
let head_point =
|
||||
this.to_pixel_point(pending.head(), &this.snapshot(window, cx), window);
|
||||
pre_edit_pixel_points.insert(pending.id, head_point);
|
||||
}
|
||||
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, this.autoindent_mode.clone(), cx);
|
||||
});
|
||||
@@ -3201,6 +3259,22 @@ impl Editor {
|
||||
linked_editing_ranges::refresh_linked_ranges(this, window, cx);
|
||||
this.refresh_inline_completion(true, false, window, cx);
|
||||
jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
|
||||
|
||||
let mut post_edit_pixel_points = HashMap::default();
|
||||
|
||||
for selection in this.selections.disjoint_anchors().iter() {
|
||||
let head_point =
|
||||
this.to_pixel_point(selection.head(), &this.snapshot(window, cx), window);
|
||||
post_edit_pixel_points.insert(selection.id, head_point);
|
||||
}
|
||||
|
||||
if let Some(pending) = this.selections.pending_anchor() {
|
||||
let head_point =
|
||||
this.to_pixel_point(pending.head(), &this.snapshot(window, cx), window);
|
||||
post_edit_pixel_points.insert(pending.id, head_point);
|
||||
}
|
||||
this.smooth_cursor_manager
|
||||
.update(pre_edit_pixel_points, post_edit_pixel_points);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3253,6 +3327,20 @@ impl Editor {
|
||||
|
||||
pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
let mut pre_edit_pixel_points = HashMap::default();
|
||||
|
||||
for selection in this.selections.disjoint_anchors().iter() {
|
||||
let head_point =
|
||||
this.to_pixel_point(selection.head(), &this.snapshot(window, cx), window);
|
||||
pre_edit_pixel_points.insert(selection.id, head_point);
|
||||
}
|
||||
|
||||
if let Some(pending) = this.selections.pending_anchor() {
|
||||
let head_point =
|
||||
this.to_pixel_point(pending.head(), &this.snapshot(window, cx), window);
|
||||
pre_edit_pixel_points.insert(pending.id, head_point);
|
||||
}
|
||||
|
||||
let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
let multi_buffer = this.buffer.read(cx);
|
||||
@@ -3363,6 +3451,23 @@ impl Editor {
|
||||
s.select(new_selections)
|
||||
});
|
||||
this.refresh_inline_completion(true, false, window, cx);
|
||||
|
||||
let mut post_edit_pixel_points = HashMap::default();
|
||||
|
||||
for selection in this.selections.disjoint_anchors().iter() {
|
||||
let head_point =
|
||||
this.to_pixel_point(selection.head(), &this.snapshot(window, cx), window);
|
||||
post_edit_pixel_points.insert(selection.id, head_point);
|
||||
}
|
||||
|
||||
if let Some(pending) = this.selections.pending_anchor() {
|
||||
let head_point =
|
||||
this.to_pixel_point(pending.head(), &this.snapshot(window, cx), window);
|
||||
post_edit_pixel_points.insert(pending.id, head_point);
|
||||
}
|
||||
|
||||
this.smooth_cursor_manager
|
||||
.update(pre_edit_pixel_points, post_edit_pixel_points);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13185,7 +13290,14 @@ impl Editor {
|
||||
s.clear_pending();
|
||||
}
|
||||
});
|
||||
self.selections_did_change(false, &old_cursor_position, true, window, cx);
|
||||
self.selections_did_change(
|
||||
false,
|
||||
&old_cursor_position,
|
||||
true,
|
||||
HashMap::default(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn push_to_selection_history(&mut self) {
|
||||
|
||||
@@ -21,9 +21,9 @@ use crate::{
|
||||
EditorSettings, EditorSnapshot, EditorStyle, FocusedBlock, GoToHunk, GoToPreviousHunk,
|
||||
GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, InlayHintRefreshReason,
|
||||
InlineCompletion, JumpData, LineDown, LineHighlight, LineUp, OpenExcerpts, PageDown, PageUp,
|
||||
Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
|
||||
StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
|
||||
FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
|
||||
Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SmoothCursorManager,
|
||||
SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS,
|
||||
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
};
|
||||
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
|
||||
@@ -83,6 +83,7 @@ const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
|
||||
const MIN_SCROLL_THUMB_SIZE: f32 = 25.;
|
||||
|
||||
struct SelectionLayout {
|
||||
id: usize,
|
||||
head: DisplayPoint,
|
||||
cursor_shape: CursorShape,
|
||||
is_newest: bool,
|
||||
@@ -140,6 +141,7 @@ impl SelectionLayout {
|
||||
}
|
||||
|
||||
Self {
|
||||
id: selection.id,
|
||||
head,
|
||||
cursor_shape,
|
||||
is_newest,
|
||||
@@ -1151,12 +1153,29 @@ impl EditorElement {
|
||||
let cursor_layouts = self.editor.update(cx, |editor, cx| {
|
||||
let mut cursors = Vec::new();
|
||||
|
||||
let is_animating =
|
||||
!matches!(editor.smooth_cursor_manager, SmoothCursorManager::Inactive);
|
||||
let animated_selection_ids = if is_animating {
|
||||
match &editor.smooth_cursor_manager {
|
||||
SmoothCursorManager::Active { cursors } => {
|
||||
cursors.keys().copied().collect::<HashSet<_>>()
|
||||
}
|
||||
_ => HashSet::default(),
|
||||
}
|
||||
} else {
|
||||
HashSet::default()
|
||||
};
|
||||
|
||||
let show_local_cursors = editor.show_local_cursors(window, cx);
|
||||
|
||||
for (player_color, selections) in selections {
|
||||
for selection in selections {
|
||||
let cursor_position = selection.head;
|
||||
|
||||
if animated_selection_ids.contains(&selection.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let in_range = visible_display_row_range.contains(&cursor_position.row());
|
||||
if (selection.is_local && !show_local_cursors)
|
||||
|| !in_range
|
||||
@@ -1283,6 +1302,19 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
if is_animating {
|
||||
let animated_cursors = self.layout_animated_cursors(
|
||||
editor,
|
||||
content_origin,
|
||||
line_height,
|
||||
em_advance,
|
||||
selections,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
cursors.extend(animated_cursors);
|
||||
}
|
||||
|
||||
cursors
|
||||
});
|
||||
|
||||
@@ -1293,6 +1325,47 @@ impl EditorElement {
|
||||
cursor_layouts
|
||||
}
|
||||
|
||||
fn layout_animated_cursors(
|
||||
&self,
|
||||
editor: &mut Editor,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
em_advance: Pixels,
|
||||
selections: &[(PlayerColor, Vec<SelectionLayout>)],
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Vec<CursorLayout> {
|
||||
let new_positions = editor.smooth_cursor_manager.animate();
|
||||
if !new_positions.is_empty() {
|
||||
window.request_animation_frame();
|
||||
}
|
||||
new_positions
|
||||
.into_iter()
|
||||
.map(|(id, position)| {
|
||||
// todo smit: worst way to get cursor shape and player color
|
||||
let (cursor_shape, player_color) = selections
|
||||
.iter()
|
||||
.find_map(|(player_color, sels)| {
|
||||
sels.iter()
|
||||
.find(|sel| sel.id == id)
|
||||
.map(|sel| (sel.cursor_shape, *player_color))
|
||||
})
|
||||
.unwrap_or((CursorShape::Bar, editor.current_user_player_color(cx)));
|
||||
let mut cursor = CursorLayout {
|
||||
color: player_color.cursor,
|
||||
block_width: em_advance,
|
||||
origin: position,
|
||||
line_height,
|
||||
shape: cursor_shape,
|
||||
block_text: None,
|
||||
cursor_name: None,
|
||||
};
|
||||
cursor.layout(content_origin, None, window, cx);
|
||||
cursor
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn layout_scrollbars(
|
||||
&self,
|
||||
snapshot: &EditorSnapshot,
|
||||
|
||||
117
crates/editor/src/smooth_cursor_manager.rs
Normal file
117
crates/editor/src/smooth_cursor_manager.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use collections::HashMap;
|
||||
use gpui::Pixels;
|
||||
|
||||
const DELTA_PERCENT_PER_FRAME: f32 = 0.01;
|
||||
|
||||
pub struct Cursor {
|
||||
current_position: gpui::Point<Pixels>,
|
||||
target_position: gpui::Point<Pixels>,
|
||||
}
|
||||
|
||||
pub enum SmoothCursorManager {
|
||||
Inactive,
|
||||
Active { cursors: HashMap<usize, Cursor> },
|
||||
}
|
||||
|
||||
impl SmoothCursorManager {
|
||||
pub fn update(
|
||||
&mut self,
|
||||
source_positions: HashMap<usize, Option<gpui::Point<Pixels>>>,
|
||||
target_positions: HashMap<usize, Option<gpui::Point<Pixels>>>,
|
||||
) {
|
||||
if source_positions.len() == 1 && target_positions.len() == 1 {
|
||||
let old_id = source_positions.keys().next().unwrap();
|
||||
let new_id = target_positions.keys().next().unwrap();
|
||||
if old_id != new_id {
|
||||
if let (Some(Some(old_pos)), Some(Some(new_pos))) = (
|
||||
source_positions.values().next(),
|
||||
target_positions.values().next(),
|
||||
) {
|
||||
*self = Self::Active {
|
||||
cursors: HashMap::from_iter([(
|
||||
*new_id,
|
||||
Cursor {
|
||||
current_position: *old_pos,
|
||||
target_position: *new_pos,
|
||||
},
|
||||
)]),
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
match self {
|
||||
Self::Inactive => {
|
||||
let mut cursors = HashMap::default();
|
||||
for (id, target_position) in target_positions.iter() {
|
||||
let Some(target_position) = target_position else {
|
||||
continue;
|
||||
};
|
||||
let Some(Some(source_position)) = source_positions.get(id) else {
|
||||
continue;
|
||||
};
|
||||
if source_position == target_position {
|
||||
continue;
|
||||
}
|
||||
cursors.insert(
|
||||
*id,
|
||||
Cursor {
|
||||
current_position: *source_position,
|
||||
target_position: *target_position,
|
||||
},
|
||||
);
|
||||
}
|
||||
if !cursors.is_empty() {
|
||||
*self = Self::Active { cursors };
|
||||
}
|
||||
}
|
||||
Self::Active { cursors } => {
|
||||
for (id, target_position) in target_positions.iter() {
|
||||
let Some(target_position) = target_position else {
|
||||
continue;
|
||||
};
|
||||
if let Some(cursor) = cursors.get_mut(id) {
|
||||
cursor.target_position = *target_position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn animate(&mut self) -> HashMap<usize, gpui::Point<Pixels>> {
|
||||
match self {
|
||||
Self::Inactive => HashMap::default(),
|
||||
Self::Active { cursors } => {
|
||||
let mut new_positions = HashMap::default();
|
||||
let mut completed = Vec::new();
|
||||
|
||||
for (id, cursor) in cursors.iter_mut() {
|
||||
let dx = cursor.target_position.x - cursor.current_position.x;
|
||||
let dy = cursor.target_position.y - cursor.current_position.y;
|
||||
|
||||
let distance = (dx.0.powi(2) + dy.0.powi(2)).sqrt();
|
||||
if distance < 0.2 {
|
||||
new_positions.insert(*id, cursor.target_position);
|
||||
completed.push(*id);
|
||||
} else {
|
||||
cursor.current_position.x =
|
||||
Pixels(cursor.current_position.x.0 + dx.0 * DELTA_PERCENT_PER_FRAME);
|
||||
cursor.current_position.y =
|
||||
Pixels(cursor.current_position.y.0 + dy.0 * DELTA_PERCENT_PER_FRAME);
|
||||
new_positions.insert(*id, cursor.current_position);
|
||||
}
|
||||
}
|
||||
|
||||
for id in completed {
|
||||
cursors.remove(&id);
|
||||
}
|
||||
|
||||
if cursors.is_empty() {
|
||||
*self = Self::Inactive;
|
||||
}
|
||||
|
||||
new_positions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user