gpui: Add `closest_index_for_position` method (#23668)

Jason Lee created

Closes #ISSUE

Release Notes:

- N/A

------------

I just make a little change to improve `index_for_position` to support
return closest index for position.

I need this method to measure for position cursor in multi-line mode
TextInput.

https://github.com/longbridge/gpui-component/pull/583


https://github.com/user-attachments/assets/c69d098e-d2cb-4053-b739-6c7dd666e769

Before this change, GPUI have `LineLayout::closest_index_for_x` method
for unwrapped line case.


https://github.com/zed-industries/zed/blob/d1be419fff415329b38f26aff90488700702c82a/crates/gpui/src/text_system/line_layout.rs#L58-L94

This change is equivalent to making `index_for_position` have a
corresponding method to get the closest index like `index_for_x`.

Change summary

crates/gpui/src/text_system/line_layout.rs | 38 +++++++++++++++++++++--
1 file changed, 34 insertions(+), 4 deletions(-)

Detailed changes

crates/gpui/src/text_system/line_layout.rs 🔗

@@ -278,10 +278,34 @@ impl WrappedLineLayout {
     }
 
     /// The index corresponding to a given position in this layout for the given line height.
+    ///
+    /// See also [`Self::closest_index_for_position`].
     pub fn index_for_position(
+        &self,
+        position: Point<Pixels>,
+        line_height: Pixels,
+    ) -> Result<usize, usize> {
+        self._index_for_position(position, line_height, false)
+    }
+
+    /// The closest index to a given position in this layout for the given line height.
+    ///
+    /// Closest means the character boundary closest to the given position.
+    ///
+    /// See also [`LineLayout::closest_index_for_x`].
+    pub fn closest_index_for_position(
+        &self,
+        position: Point<Pixels>,
+        line_height: Pixels,
+    ) -> Result<usize, usize> {
+        self._index_for_position(position, line_height, true)
+    }
+
+    fn _index_for_position(
         &self,
         mut position: Point<Pixels>,
         line_height: Pixels,
+        closest: bool,
     ) -> Result<usize, usize> {
         let wrapped_line_ix = (position.y / line_height) as usize;
 
@@ -321,10 +345,16 @@ impl WrappedLineLayout {
         } else if position_in_unwrapped_line.x >= wrapped_line_end_x {
             Err(wrapped_line_end_index)
         } else {
-            Ok(self
-                .unwrapped_layout
-                .index_for_x(position_in_unwrapped_line.x)
-                .unwrap())
+            if closest {
+                Ok(self
+                    .unwrapped_layout
+                    .closest_index_for_x(position_in_unwrapped_line.x))
+            } else {
+                Ok(self
+                    .unwrapped_layout
+                    .index_for_x(position_in_unwrapped_line.x)
+                    .unwrap())
+            }
         }
     }