agg_gui/widgets/
text_field_core.rs1use std::rc::Rc;
5use std::cell::RefCell;
6
7use crate::text::measure_advance;
8use crate::text::Font;
9use crate::undo::UndoRedoCommand;
10
11pub fn prev_char_boundary(s: &str, byte_pos: usize) -> usize {
16 let mut pos = byte_pos;
17 loop {
18 if pos == 0 { return 0; }
19 pos -= 1;
20 if s.is_char_boundary(pos) { return pos; }
21 }
22}
23
24pub fn next_char_boundary(s: &str, byte_pos: usize) -> usize {
25 let mut pos = byte_pos + 1;
26 while pos <= s.len() {
27 if s.is_char_boundary(pos) { return pos; }
28 pos += 1;
29 }
30 s.len()
31}
32
33fn is_word_char(c: char) -> bool { c.is_alphanumeric() || c == '_' }
38
39pub fn next_word_boundary(s: &str, pos: usize) -> usize {
41 let mut chars = s[pos..].char_indices().peekable();
42 let mut advanced = 0usize;
43 while let Some(&(i, c)) = chars.peek() {
45 if !c.is_whitespace() { break; }
46 advanced = i + c.len_utf8();
47 chars.next();
48 }
49 while let Some(&(i, c)) = chars.peek() {
51 if c.is_whitespace() { break; }
52 advanced = i + c.len_utf8();
53 chars.next();
54 }
55 pos + advanced
56}
57
58pub fn prev_word_boundary(s: &str, pos: usize) -> usize {
60 if pos == 0 { return 0; }
61 let chars: Vec<(usize, char)> = s[..pos].char_indices().collect();
62 let mut i = chars.len();
63 while i > 0 && chars[i - 1].1.is_whitespace() { i -= 1; }
64 while i > 0 && !chars[i - 1].1.is_whitespace() { i -= 1; }
65 if i < chars.len() { chars[i].0 } else { 0 }
66}
67
68pub fn word_range_at(s: &str, byte_pos: usize) -> (usize, usize) {
71 let anchor_class = is_word_char(s[byte_pos..].chars().next().unwrap_or(' '));
72 let start = {
74 let mut p = byte_pos;
75 while p > 0 {
76 let prev = prev_char_boundary(s, p);
77 let c = s[prev..p].chars().next().unwrap_or(' ');
78 if is_word_char(c) != anchor_class { break; }
79 p = prev;
80 }
81 p
82 };
83 let end = {
85 let mut p = byte_pos;
86 for (_, c) in s[byte_pos..].char_indices() {
87 if is_word_char(c) != anchor_class { break; }
88 p += c.len_utf8();
89 }
90 p
91 };
92 (start, end)
93}
94
95pub fn byte_at_x(font: &Font, text: &str, font_size: f64, target_x: f64) -> usize {
101 if target_x <= 0.0 { return 0; }
102 let mut prev_x = 0.0f64;
103 let mut prev_pos = 0usize;
104 for (i, c) in text.char_indices() {
105 let x = measure_advance(font, &text[..i], font_size);
106 let mid = (prev_x + x) * 0.5;
107 if target_x < mid { return prev_pos; }
108 prev_x = x;
109 prev_pos = i;
110 let _ = c;
111 }
112 let total = measure_advance(font, text, font_size);
113 let mid = (prev_x + total) * 0.5;
114 if target_x < mid { prev_pos } else { text.len() }
115}
116
117#[derive(Clone, Default)]
123pub struct TextEditState {
124 pub text: String,
125 pub cursor: usize,
126 pub anchor: usize,
127}
128
129pub struct TextEditCommand {
136 pub name: &'static str,
137 pub before: TextEditState,
138 pub after: TextEditState,
139 pub target: Rc<RefCell<TextEditState>>,
140}
141
142impl UndoRedoCommand for TextEditCommand {
143 fn name(&self) -> &str { self.name }
144 fn do_it(&mut self) { *self.target.borrow_mut() = self.after.clone(); }
145 fn undo_it(&mut self) { *self.target.borrow_mut() = self.before.clone(); }
146}