1use repose_core::*;
2use std::ops::Range;
3use std::rc::Rc;
4use std::time::Duration;
5use std::{cell::RefCell, time::Instant};
6
7use ab_glyph::{Font, FontArc, PxScale, ScaleFont};
8use fontdb::Database;
9use std::sync::OnceLock;
10
11pub const TF_FONT_PX: f32 = 16.0;
12pub const TF_PADDING_X: f32 = 8.0;
13
14use unicode_segmentation::UnicodeSegmentation;
15
16pub struct TextMetrics {
17 pub positions: Vec<f32>,
19 pub byte_offsets: Vec<usize>,
21}
22
23pub fn measure_text(text: &str, px: u32) -> TextMetrics {
24 let m = repose_text::metrics_for_textfield(text, px as f32);
25 TextMetrics {
26 positions: m.positions,
27 byte_offsets: m.byte_offsets,
28 }
29}
30
31pub fn byte_to_char_index(m: &TextMetrics, byte: usize) -> usize {
32 match m.byte_offsets.binary_search(&byte) {
34 Ok(i) | Err(i) => i,
35 }
36}
37
38pub fn index_for_x_bytes(text: &str, px: u32, x: f32) -> usize {
39 let m = measure_text(text, px);
40 let mut best_i = 0usize;
42 let mut best_d = f32::INFINITY;
43 for i in 0..m.positions.len() {
44 let d = (m.positions[i] - x).abs();
45 if d < best_d {
46 best_d = d;
47 best_i = i;
48 }
49 }
50 m.byte_offsets[best_i]
51}
52
53fn prev_grapheme_boundary(text: &str, byte: usize) -> usize {
55 let mut last = 0usize;
56 for (i, _) in text.grapheme_indices(true) {
57 if i >= byte {
58 break;
59 }
60 last = i;
61 }
62 last
63}
64
65fn next_grapheme_boundary(text: &str, byte: usize) -> usize {
66 for (i, _) in text.grapheme_indices(true) {
67 if i > byte {
68 return i;
69 }
70 }
71 text.len()
72}
73
74pub fn positions_for(text: &str, px: u32) -> Vec<f32> {
76 repose_text::metrics_for_textfield(text, px as f32).positions
77}
78
79pub fn index_for_x(text: &str, px: u32, x: f32) -> usize {
81 let m = repose_text::metrics_for_textfield(text, px as f32);
82 let mut best = 0usize;
84 let mut dmin = f32::INFINITY;
85 for (i, &xx) in m.positions.iter().enumerate() {
86 let d = (xx - x).abs();
87 if d < dmin {
88 dmin = d;
89 best = i;
90 }
91 }
92 best
93}
94
95#[derive(Clone, Debug)]
96pub struct TextFieldState {
97 pub text: String,
98 pub selection: Range<usize>,
99 pub composition: Option<Range<usize>>, pub scroll_offset: f32,
101 pub drag_anchor: Option<usize>, pub blink_start: Instant, pub inner_width: f32,
104}
105
106impl TextFieldState {
107 pub fn new() -> Self {
108 Self {
109 text: String::new(),
110 selection: 0..0,
111 composition: None,
112 scroll_offset: 0.0,
113 drag_anchor: None,
114 blink_start: Instant::now(),
115 inner_width: 0.0,
116 }
117 }
118
119 pub fn insert_text(&mut self, text: &str) {
120 let start = self.selection.start.min(self.text.len());
121 let end = self.selection.end.min(self.text.len());
122
123 self.text.replace_range(start..end, text);
124 let new_pos = start + text.len();
125 self.selection = new_pos..new_pos;
126 self.reset_caret_blink();
127 }
128
129 pub fn delete_backward(&mut self) {
130 if self.selection.start == self.selection.end {
131 let pos = self.selection.start.min(self.text.len());
132 if pos > 0 {
133 let prev = prev_grapheme_boundary(&self.text, pos);
134 self.text.replace_range(prev..pos, "");
135 self.selection = prev..prev;
136 }
137 } else {
138 self.insert_text("");
139 }
140 self.reset_caret_blink();
141 }
142
143 pub fn delete_forward(&mut self) {
144 if self.selection.start == self.selection.end {
145 let pos = self.selection.start.min(self.text.len());
146 if pos < self.text.len() {
147 let next = next_grapheme_boundary(&self.text, pos);
148 self.text.replace_range(pos..next, "");
149 }
150 } else {
151 self.insert_text("");
152 }
153 self.reset_caret_blink();
154 }
155
156 pub fn move_cursor(&mut self, delta: isize, extend_selection: bool) {
157 let mut pos = self.selection.end.min(self.text.len());
158 if delta < 0 {
159 for _ in 0..delta.unsigned_abs() {
160 pos = prev_grapheme_boundary(&self.text, pos);
161 }
162 } else if delta > 0 {
163 for _ in 0..(delta as usize) {
164 pos = next_grapheme_boundary(&self.text, pos);
165 }
166 }
167 if extend_selection {
168 self.selection.end = pos;
169 } else {
170 self.selection = pos..pos;
171 }
172 self.reset_caret_blink();
173 }
174
175 pub fn selected_text(&self) -> String {
176 if self.selection.start == self.selection.end {
177 String::new()
178 } else {
179 self.text[self.selection.clone()].to_string()
180 }
181 }
182
183 pub fn set_composition(&mut self, text: String, cursor: Option<(usize, usize)>) {
184 if let Some(range) = &self.composition {
185 self.text.replace_range(range.clone(), &text);
187 } else {
188 let pos = self.selection.start;
190 self.text.insert_str(pos, &text);
191 }
192
193 let start = self.selection.start;
194 self.composition = Some(start..start + text.len());
195
196 if let Some((cursor_start, cursor_end)) = cursor {
197 self.selection = (start + cursor_start)..(start + cursor_end);
198 }
199 self.reset_caret_blink();
200 }
201
202 pub fn commit_composition(&mut self, text: String) {
203 if let Some(range) = self.composition.take() {
204 self.text.replace_range(range.clone(), &text);
205 let new_pos = range.start + text.len();
206 self.selection = new_pos..new_pos;
207 }
208 self.reset_caret_blink();
209 }
210
211 pub fn cancel_composition(&mut self) {
212 if let Some(range) = self.composition.take() {
213 self.text.replace_range(range, "");
214 }
215 self.reset_caret_blink();
216 }
217
218 pub fn delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) {
219 if self.selection.start != self.selection.end {
221 let start = self.selection.start.min(self.text.len());
223 let end = self.selection.end.min(self.text.len());
224 self.text.replace_range(start..end, "");
225 self.selection = start..start;
226 return;
227 }
228
229 let caret = self.selection.end.min(self.text.len());
230 let start = caret.saturating_sub(before_bytes);
231 let end = (caret + after_bytes).min(self.text.len());
232 if start < end {
233 self.text.replace_range(start..end, "");
234 self.selection = start..start;
235 }
236 }
237
238 pub fn begin_drag(&mut self, idx_byte: usize, extend: bool) {
240 let idx = idx_byte.min(self.text.len());
241 if extend {
242 let anchor = self.selection.start;
243 self.selection = anchor.min(idx)..anchor.max(idx);
244 self.drag_anchor = Some(anchor);
245 } else {
246 self.selection = idx..idx;
247 self.drag_anchor = Some(idx);
248 }
249 self.reset_caret_blink();
250 }
251
252 pub fn drag_to(&mut self, idx_byte: usize) {
253 if let Some(anchor) = self.drag_anchor {
254 let i = idx_byte.min(self.text.len());
255 self.selection = anchor.min(i)..anchor.max(i);
256 }
257 self.reset_caret_blink();
258 }
259 pub fn end_drag(&mut self) {
260 self.drag_anchor = None;
261 }
262
263 pub fn caret_index(&self) -> usize {
264 self.selection.end
265 }
266
267 pub fn ensure_caret_visible(&mut self, caret_x: f32, inner_width: f32) {
269 let inset = 2.0;
271 let left = self.scroll_offset + inset;
272 let right = self.scroll_offset + inner_width - inset;
273 if caret_x < left {
274 self.scroll_offset = (caret_x - inset).max(0.0);
275 } else if caret_x > right {
276 self.scroll_offset = (caret_x - inner_width + inset).max(0.0);
277 }
278 }
279
280 pub fn reset_caret_blink(&mut self) {
281 self.blink_start = Instant::now();
282 }
283 pub fn caret_visible(&self) -> bool {
284 const PERIOD: Duration = Duration::from_millis(500);
285 ((Instant::now() - self.blink_start).as_millis() / PERIOD.as_millis() as u128) % 2 == 0
286 }
287
288 pub fn set_inner_width(&mut self, w: f32) {
289 self.inner_width = w.max(0.0);
290 }
291}
292
293pub fn TextField(
295 hint: impl Into<String>,
296 modifier: Modifier,
297 on_change: impl Fn(String) + 'static,
298) -> View {
299 View::new(
300 0,
301 ViewKind::TextField {
302 state_key: 0,
303 hint: hint.into(),
304 on_change: Some(Rc::new(on_change)),
305 },
306 )
307 .modifier(modifier)
308 .semantics(Semantics {
309 role: Role::TextField,
310 label: None,
311 focused: false,
312 enabled: true,
313 })
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn test_textfield_insert() {
322 let mut state = TextFieldState::new();
323 state.insert_text("Hello");
324 assert_eq!(state.text, "Hello");
325 assert_eq!(state.selection, 5..5);
326 }
327
328 #[test]
329 fn test_textfield_delete_backward() {
330 let mut state = TextFieldState::new();
331 state.insert_text("Hello");
332 state.delete_backward();
333 assert_eq!(state.text, "Hell");
334 assert_eq!(state.selection, 4..4);
335 }
336
337 #[test]
338 fn test_textfield_selection() {
339 let mut state = TextFieldState::new();
340 state.insert_text("Hello World");
341 state.selection = 0..5; state.insert_text("Hi");
343 assert_eq!(state.text, "Hi World");
344 assert_eq!(state.selection, 2..2);
345 }
346
347 #[test]
348 fn test_textfield_ime_composition() {
349 let mut state = TextFieldState::new();
350 state.insert_text("Test ");
351 state.set_composition("日本".to_string(), Some((0, 2)));
352 assert_eq!(state.text, "Test 日本");
353 assert!(state.composition.is_some());
354
355 state.commit_composition("日本語".to_string());
356 assert_eq!(state.text, "Test 日本語");
357 assert!(state.composition.is_none());
358 }
359
360 #[test]
361 fn test_textfield_cursor_movement() {
362 let mut state = TextFieldState::new();
363 state.insert_text("Hello");
364 state.move_cursor(-2, false);
365 assert_eq!(state.selection, 3..3);
366
367 state.move_cursor(1, false);
368 assert_eq!(state.selection, 4..4);
369 }
370
371 #[test]
372 fn test_delete_surrounding() {
373 let mut state = TextFieldState::new();
374 state.insert_text("Hello");
375 state.delete_surrounding(2, 1); assert_eq!(state.text, "Hel");
378 assert_eq!(state.selection, 3..3);
379 }
380
381 #[test]
382 fn test_grapheme_delete_and_move() {
383 let mut st = TextFieldState::new();
385 st.insert_text("A👍🏽B");
386 assert_eq!(st.text, "A👍🏽B");
387 st.move_cursor(-1, false);
389 assert_eq!(st.selection.end, "A👍🏽".len());
390 st.delete_backward();
391 assert_eq!(st.text, "AB");
392 assert_eq!(st.selection, "A".len().."A".len());
393 }
394
395 #[test]
396 fn test_index_for_x_bytes_grapheme() {
397 let t = "A👍🏽B";
399 let px = 16u32;
400 let m = measure_text(t, px);
401 for i in 0..m.byte_offsets.len() - 1 {
403 let b = m.byte_offsets[i];
404 let _ = &t[..b];
406 }
407 }
408}