feat: Add rtl support to text input

This commit is contained in:
Hojjat 2026-03-31 14:24:39 -06:00 committed by Ashley Wulber
parent de2982b37e
commit 9b2857083e
6 changed files with 411 additions and 134 deletions

View file

@ -1,10 +1,12 @@
//! Track the cursor of a text input.
use crate::core::text::Affinity;
use crate::text_input::Value;
/// The cursor of a text input.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Cursor {
state: State,
affinity: Affinity,
}
/// The state of a [`Cursor`].
@ -26,6 +28,7 @@ impl Default for Cursor {
fn default() -> Self {
Cursor {
state: State::Index(0),
affinity: Affinity::Before,
}
}
}
@ -186,4 +189,52 @@ impl Cursor {
State::Selection { start, end } => start.max(end),
}
}
/// Returns the current cursor [`Affinity`].
pub fn affinity(&self) -> Affinity {
self.affinity
}
/// Sets the cursor [`Affinity`].
pub fn set_affinity(&mut self, affinity: Affinity) {
self.affinity = affinity;
}
/// Moves the cursor in a visual direction, accounting for RTL text.
///
/// `forward` = `true` is visually rightward
/// RTL text flips the logical direction so that pressing the right-arrow key
/// still moves the caret forward visually.
pub fn move_visual(
&mut self,
forward: bool,
by_words: bool,
rtl: bool,
value: &Value,
) {
match (forward ^ rtl, by_words) {
(true, false) => self.move_right(value),
(true, true) => self.move_right_by_words(value),
(false, false) => self.move_left(value),
(false, true) => self.move_left_by_words(value),
}
}
/// Extends the selection in a visual direction, accounting for RTL text.
///
/// See [`Cursor::move_visual`] for the `forward` / `rtl` semantics.
pub fn select_visual(
&mut self,
forward: bool,
by_words: bool,
rtl: bool,
value: &Value,
) {
match (forward ^ rtl, by_words) {
(true, false) => self.select_right(value),
(true, true) => self.select_right_by_words(value),
(false, false) => self.select_left(value),
(false, true) => self.select_left_by_words(value),
}
}
}

View file

@ -127,6 +127,26 @@ impl Value {
.collect(),
}
}
/// Converts a grapheme index to a byte index in the underlying string.
pub fn byte_index_at_grapheme(&self, grapheme_index: usize) -> usize {
self.graphemes[..grapheme_index.min(self.graphemes.len())]
.iter()
.map(|g| g.len())
.sum()
}
/// Converts a byte index to a grapheme index.
pub fn grapheme_index_at_byte(&self, byte_index: usize) -> usize {
let mut bytes = 0;
for (i, g) in self.graphemes.iter().enumerate() {
if bytes >= byte_index {
return i;
}
bytes += g.len();
}
self.graphemes.len()
}
}
impl std::fmt::Display for Value {