git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commita4107c3adf9aa7f2fa4a9d261b4b192f574712ee
parent138c747621
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-02 18:03
feat(line_edit): display_index, text offset to where it is drawn

The inverse of text_index: a caret or selection edge in `text` as the
offset into display() it is drawn at, which on a masked field is that
many bullets in. What an app needs to draw the caret and selection of a
password field; the test round-trips every boundary through both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/widget/line_edit.rs | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/src/widget/line_edit.rs b/src/widget/line_edit.rs
index b22e1d0..712fc26 100644
--- a/src/widget/line_edit.rs
+++ b/src/widget/line_edit.rs
@@ -150,6 +150,17 @@ impl LineEdit {
         self.text.char_indices().nth(n).map_or(self.text.len(), |(i, _)| i)
     }
 
+    /// The other direction: a byte offset into `text` (the caret, a selection
+    /// edge) as the offset into [`LineEdit::display`] where it is drawn — the
+    /// same offset unless masked, where it is that many bullets in.
+    pub fn display_index(&self, at: usize) -> usize {
+        let at = self.boundary(at);
+        if !self.masked {
+            return at;
+        }
+        self.text[..at].chars().count() * '\u{2022}'.len_utf8()
+    }
+
     /// The end of the selection the caret is not at — what a shift+click
     /// keeps — or the caret itself with nothing selected.
     fn anchor(&self) -> usize {
@@ -460,6 +471,12 @@ mod tests {
         assert_eq!(e.text_index(3 * bullet), 4);
         assert_eq!(e.text_index(99), 4);
         assert_eq!(LineEdit::with_text("abc").text_index(2), 2, "unmasked is the identity");
+        // And back: every text boundary round-trips through the bullets.
+        for at in [0, 1, 3, 4] {
+            assert_eq!(e.text_index(e.display_index(at)), at, "{at}");
+        }
+        assert_eq!(e.display_index(3), 2 * bullet);
+        assert_eq!(LineEdit::with_text("abc").display_index(2), 2);
     }
 
     /// A password must not leave through a chord the user may not have meant.