agg_gui/widgets/
text_field_core.rs1use std::cell::RefCell;
5use std::rc::Rc;
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 {
19 return 0;
20 }
21 pos -= 1;
22 if s.is_char_boundary(pos) {
23 return pos;
24 }
25 }
26}
27
28pub fn next_char_boundary(s: &str, byte_pos: usize) -> usize {
29 let mut pos = byte_pos + 1;
30 while pos <= s.len() {
31 if s.is_char_boundary(pos) {
32 return pos;
33 }
34 pos += 1;
35 }
36 s.len()
37}
38
39fn is_word_char(c: char) -> bool {
44 c.is_alphanumeric() || c == '_'
45}
46
47pub fn next_word_boundary(s: &str, pos: usize) -> usize {
49 let mut chars = s[pos..].char_indices().peekable();
50 let mut advanced = 0usize;
51 while let Some(&(i, c)) = chars.peek() {
53 if !c.is_whitespace() {
54 break;
55 }
56 advanced = i + c.len_utf8();
57 chars.next();
58 }
59 while let Some(&(i, c)) = chars.peek() {
61 if c.is_whitespace() {
62 break;
63 }
64 advanced = i + c.len_utf8();
65 chars.next();
66 }
67 pos + advanced
68}
69
70pub fn prev_word_boundary(s: &str, pos: usize) -> usize {
72 if pos == 0 {
73 return 0;
74 }
75 let chars: Vec<(usize, char)> = s[..pos].char_indices().collect();
76 let mut i = chars.len();
77 while i > 0 && chars[i - 1].1.is_whitespace() {
78 i -= 1;
79 }
80 while i > 0 && !chars[i - 1].1.is_whitespace() {
81 i -= 1;
82 }
83 if i < chars.len() {
84 chars[i].0
85 } else {
86 0
87 }
88}
89
90pub fn word_range_at(s: &str, byte_pos: usize) -> (usize, usize) {
93 let anchor_class = is_word_char(s[byte_pos..].chars().next().unwrap_or(' '));
94 let start = {
96 let mut p = byte_pos;
97 while p > 0 {
98 let prev = prev_char_boundary(s, p);
99 let c = s[prev..p].chars().next().unwrap_or(' ');
100 if is_word_char(c) != anchor_class {
101 break;
102 }
103 p = prev;
104 }
105 p
106 };
107 let end = {
109 let mut p = byte_pos;
110 for (_, c) in s[byte_pos..].char_indices() {
111 if is_word_char(c) != anchor_class {
112 break;
113 }
114 p += c.len_utf8();
115 }
116 p
117 };
118 (start, end)
119}
120
121pub fn byte_at_x(font: &Font, text: &str, font_size: f64, target_x: f64) -> usize {
127 if target_x <= 0.0 {
128 return 0;
129 }
130 let mut prev_x = 0.0f64;
131 let mut prev_pos = 0usize;
132 for (i, c) in text.char_indices() {
133 let x = measure_advance(font, &text[..i], font_size);
134 let mid = (prev_x + x) * 0.5;
135 if target_x < mid {
136 return prev_pos;
137 }
138 prev_x = x;
139 prev_pos = i;
140 let _ = c;
141 }
142 let total = measure_advance(font, text, font_size);
143 let mid = (prev_x + total) * 0.5;
144 if target_x < mid {
145 prev_pos
146 } else {
147 text.len()
148 }
149}
150
151#[derive(Clone, Default)]
157pub struct TextEditState {
158 pub text: String,
159 pub cursor: usize,
160 pub anchor: usize,
161}
162
163pub struct TextEditCommand {
170 pub name: &'static str,
171 pub before: TextEditState,
172 pub after: TextEditState,
173 pub target: Rc<RefCell<TextEditState>>,
174}
175
176impl UndoRedoCommand for TextEditCommand {
177 fn name(&self) -> &str {
178 self.name
179 }
180 fn do_it(&mut self) {
181 *self.target.borrow_mut() = self.after.clone();
182 }
183 fn undo_it(&mut self) {
184 *self.target.borrow_mut() = self.before.clone();
185 }
186}