From 68de692290b32d126394e2dc5644361a808a6315 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Fri, 24 Jan 2025 22:57:24 +0800 Subject: [PATCH] input: Fix to position cursor at correct offset on mouse down or select. (#580) Ref: https://github.com/zed-industries/zed/pull/23603 --- ## TODO Here still have a little bug in multiple lines and soft wrapped line. Only the first line can get correct position, the wrapped line is always got the offset +1, so the first char of that soft wrapped line, we can't position the cursor at the first. --- crates/ui/src/input/input.rs | 60 ++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index 1e8daf3b..10d032cf 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -12,7 +12,7 @@ use unicode_segmentation::*; use gpui::prelude::FluentBuilder as _; use gpui::{ actions, div, point, px, AnyElement, AppContext, Bounds, ClickEvent, ClipboardItem, - Context as _, Entity, EventEmitter, FocusHandle, FocusableView, Half, InteractiveElement as _, + Context as _, Entity, EventEmitter, FocusHandle, FocusableView, InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, Model, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, UTF16Selection, ViewContext, ViewInputHandler, WindowContext, @@ -776,9 +776,9 @@ impl TextInput { } if event.modifiers.shift { - self.select_to(self.previous_boundary(offset), cx); + self.select_to(offset, cx); } else { - self.move_to(self.previous_boundary(offset), cx) + self.move_to(offset, cx) } } @@ -896,6 +896,39 @@ impl TextInput { } } + // Fork of `closest_index_for_x` method from `gpui::text::Layout` + // https://github.com/zed-industries/zed/blob/d1be419fff415329b38f26aff90488700702c82a/crates/gpui/src/text_system/line_layout.rs#L74 + fn closest_index_for_x(&self, line: &WrappedLine, x: Pixels) -> usize { + let mut prev_index = 0; + let mut prev_x = px(0.); + + for run in line.unwrapped_layout.runs.iter() { + for glyph in run.glyphs.iter() { + if glyph.position.x >= x { + if glyph.position.x - x < x - prev_x { + return glyph.index; + } else { + return prev_index; + } + } + prev_index = glyph.index; + prev_x = glyph.position.x; + } + } + + // HOTFIX: + // + // Wait https://github.com/zed-industries/zed/pull/23603 + if line.unwrapped_layout.len == 1 { + if x > line.width() / 2.0 { + return 1; + } + return 0; + } + + line.unwrapped_layout.len + } + fn index_for_mouse_position(&self, position: Point, _: &WindowContext) -> usize { // If the text is empty, always return 0 if self.text.is_empty() { @@ -926,16 +959,25 @@ impl TextInput { for line in lines.iter() { let line_origin = self.line_origin_with_y_offset(&mut y_offset, &line, line_height); - let mut pos = inner_position - line_origin; - // Ignore the y position in single line mode, only check x position. + let pos = inner_position - line_origin; + let closest_index = self.closest_index_for_x(line, pos.x); + + // Return offset by use closest_index_for_x if is single line mode. if self.is_single_line() { - pos.y = line_height.half(); + return closest_index; } let index_result = line.index_for_position(pos, line_height); if let Ok(v) = index_result { - // Add 1 for place cursor after the character. - index += v + 1; + // FIXME: If the line have soft wrap, only first line can get correct position (closest_index is correct). + // The second line or more, the position is always at offset +1, + // but if we mouse down on left half or character, we expect the cursor is at offset. + // We need find a way to improve. + if pos.y > line_height / 2. { + index += v + 1; + } else { + index += closest_index; + } break; } else if let Ok(_) = line.index_for_position(point(px(0.), pos.y), line_height) { // Click in the this line but not in the text, move cursor to the end of the line. @@ -1168,7 +1210,7 @@ impl TextInput { } let offset = self.index_for_mouse_position(event.position, cx); - self.select_to(self.previous_boundary(offset), cx); + self.select_to(offset, cx); } fn is_valid_input(&self, new_text: &str) -> bool {