1use super::{
2 events::{EventData, TextFieldEventsType},
3 CharClass, Flags, Selection,
4};
5use crate::prelude::*;
6use crate::utils::GlyphParser;
7
8struct Cursor {
9 pos: usize,
10 start: usize,
11 end: usize,
12}
13
14#[allow(dead_code)]
15const MAX_UNDO_DEPTH: usize = 100;
16
17#[allow(dead_code)]
18#[derive(Copy, Clone)]
19enum LastAction {
20 None,
21 AddChar(CharClass),
22 Delete,
23 Other,
24}
25
26#[allow(dead_code)]
27#[derive(Clone)]
28struct TextSnapshot {
29 glyphs: String,
30 cursor_pos: usize,
31 selection: Selection,
32}
33
34#[CustomControl(overwrite=OnPaint+OnKeyPressed+OnMouseEvent+OnResize+OnFocus, internal=true)]
35pub struct TextField {
36 cursor: Cursor,
37 selection: Selection,
38 glyphs: String,
39 drag_started: bool,
40 flags: Flags,
41 undo_stack: Vec<TextSnapshot>,
42 redo_stack: Vec<TextSnapshot>,
43 last_action: LastAction,
44 history_suspended: bool,
45}
46impl TextField {
47 pub fn new(text: &str, layout: Layout, flags: Flags) -> Self {
62 let mut obj = Self {
63 base: ControlBase::with_status_flags(layout, StatusFlags::Visible | StatusFlags::Enabled | StatusFlags::AcceptInput),
64 cursor: Cursor { pos: 0, start: 0, end: 0 },
65 selection: Selection::NONE,
66 glyphs: String::from(text),
67 drag_started: false,
68 flags,
69 undo_stack: Vec::new(),
70 redo_stack: Vec::new(),
71 last_action: LastAction::None,
72 history_suspended: false,
73 };
74 obj.set_size_bounds(3, 1, u16::MAX, u16::MAX);
75 obj.cursor.pos = obj.glyphs.len();
76 obj
77 }
78
79 #[inline(always)]
81 pub fn is_readonly(&self) -> bool {
82 self.flags.contains(Flags::Readonly)
83 }
84
85 #[inline(always)]
87 pub fn text(&self) -> &str {
88 &self.glyphs
89 }
90
91 #[inline(always)]
93 pub fn set_text(&mut self, text: &str) {
94 self.cursor = Cursor { pos: 0, start: 0, end: 0 };
95 self.selection = Selection::NONE;
96 self.glyphs.clear();
97 self.glyphs.push_str(text);
98 self.undo_stack.clear();
99 self.redo_stack.clear();
100 self.last_action = LastAction::None;
101 self.history_suspended = false;
102 self.move_cursor_to(self.glyphs.len(), false, true);
103 }
104
105 fn update_scroll_view(&mut self, force_end_update: bool) {
106 if (self.cursor.pos >= self.cursor.start) && (self.cursor.pos < self.cursor.end) {
107 if force_end_update {
109 let sz = self.size();
110 let visible_glyphs = ((sz.width as usize) - 2) * (sz.height as usize);
111 self.cursor.end = self.glyphs.next_pos(self.cursor.start, visible_glyphs);
112 }
113 return;
114 }
115 let sz = self.size();
116 let visible_glyphs = (if sz.width > 2 {
117 ((sz.width as usize) - 2) * (sz.height as usize)
118 } else {
119 0
120 })
121 .max(1);
122
123 if self.cursor.pos < self.cursor.start {
124 self.cursor.start = self.cursor.pos;
126 self.cursor.end = self.glyphs.next_pos(self.cursor.pos, visible_glyphs);
127 } else {
128 self.cursor.start = self.glyphs.previous_pos(self.cursor.pos, visible_glyphs - 1);
130 self.cursor.end = self.cursor.pos;
132 }
133 }
134 fn move_cursor_with(&mut self, no_of_glyphs: i32, select: bool) {
135 let new_poz = if no_of_glyphs >= 0 {
136 self.glyphs.next_pos(self.cursor.pos, no_of_glyphs as usize)
137 } else {
138 self.glyphs.previous_pos(self.cursor.pos, (-no_of_glyphs) as usize)
139 };
140 self.move_cursor_to(new_poz, select, false);
141 }
142 fn move_cursor_to(&mut self, new_offset: usize, select: bool, force_end_update: bool) {
143 let current_pos = self.cursor.pos;
144 self.cursor.pos = new_offset.min(self.glyphs.len());
145 self.update_scroll_view(force_end_update);
146 if select {
147 self.selection.update(current_pos, self.cursor.pos);
148 } else {
149 self.selection = Selection::NONE;
150 }
151 }
152 fn move_to_next_word(&mut self, select: bool) {
153 if let Some(char_class) = self.glyphs.glyph(self.cursor.pos).map(|c| CharClass::from(c.0)) {
154 let mut pos = self.cursor.pos;
155 let mut new_char_class = char_class;
156 while let Some((c, size)) = self.glyphs.glyph(pos) {
158 if CharClass::from(c) != char_class {
159 new_char_class = CharClass::from(c);
160 break;
161 }
162 pos += size as usize;
163 }
164 if (new_char_class != char_class) && (new_char_class == CharClass::Space) {
165 while let Some((c, size)) = self.glyphs.glyph(pos) {
167 if CharClass::from(c) != new_char_class {
168 break;
169 }
170 pos += size as usize;
171 }
172 }
173 pos = pos.min(self.glyphs.len());
174 self.move_cursor_to(pos, select, false);
175 }
176 }
177 fn move_to_previous_word(&mut self, select: bool) {
178 if let Some(char_class) = self.glyphs.previous_glyph(self.cursor.pos).map(|c| CharClass::from(c.0)) {
179 let mut pos = self.cursor.pos;
180 let mut new_char_class = char_class;
181 while let Some((c, size)) = self.glyphs.previous_glyph(pos) {
183 if CharClass::from(c) != char_class {
184 new_char_class = CharClass::from(c);
185 break;
186 }
187 if size as usize <= pos {
188 pos -= size as usize;
189 } else {
190 pos = 0;
191 break;
192 }
193 }
194 if (new_char_class != char_class) && (char_class == CharClass::Space) {
195 while let Some((c, size)) = self.glyphs.previous_glyph(pos) {
197 if CharClass::from(c) != new_char_class {
198 break;
199 }
200 if size as usize <= pos {
201 pos -= size as usize;
202 } else {
203 pos = 0;
204 break;
205 }
206 }
207 }
208 self.move_cursor_to(pos, select, false);
209 }
210 }
211 fn copy_text(&mut self) {
212 if !self.selection.is_empty() {
213 RuntimeManager::get()
214 .backend_mut()
215 .set_clipboard_text(&self.glyphs[self.selection.start..self.selection.end]);
216 }
217 }
218 fn paste_text(&mut self) -> bool {
220 if self.is_readonly() {
221 return false;
222 }
223 let had_selection = !self.selection.is_empty();
224 let clipboard_text = RuntimeManager::get().backend().clipboard_text();
225 let has_clipboard_text = clipboard_text.as_ref().map(|txt| !txt.is_empty()).unwrap_or(false);
226 if !(had_selection || has_clipboard_text) {
227 return false;
228 }
229 if !self.history_suspended {
230 self.push_undo_snapshot();
231 }
232 let mut text_was_modified = false;
233 if had_selection {
234 let old_history_suspended = self.history_suspended;
235 self.history_suspended = true;
236 text_was_modified = self.delete_selection();
237 self.history_suspended = old_history_suspended;
238 }
239 if let Some(txt) = clipboard_text {
240 self.glyphs.insert_str(self.cursor.pos, &txt);
241 text_was_modified |= !txt.is_empty();
242 self.move_cursor_to(self.cursor.pos + txt.len(), false, true);
243 }
244 if text_was_modified {
245 self.last_action = LastAction::Other;
246 }
247 text_was_modified
248 }
249 fn cut_text(&mut self) -> bool {
251 if self.is_readonly() {
252 return false;
253 }
254 if !self.selection.is_empty() {
255 if !self.history_suspended {
256 self.push_undo_snapshot();
257 }
258 RuntimeManager::get()
259 .backend_mut()
260 .set_clipboard_text(&self.glyphs[self.selection.start..self.selection.end]);
261 let old_history_suspended = self.history_suspended;
262 self.history_suspended = true;
263 let result = self.delete_selection();
264 self.history_suspended = old_history_suspended;
265 if result {
266 self.last_action = LastAction::Delete;
267 }
268 result
269 } else {
270 false
271 }
272 }
273 fn convert_selection_or_word(&mut self, callback: fn(text: &str) -> String) -> bool {
275 if self.is_readonly() {
276 return false;
277 }
278 if self.selection.is_empty() {
279 self.select_word(self.cursor.pos);
280 }
281
282 if !self.selection.is_empty() {
283 if !self.history_suspended {
284 self.push_undo_snapshot();
285 }
286 let s = callback(&self.glyphs[self.selection.start..self.selection.end]);
287 let text_changed = s != self.glyphs[self.selection.start..self.selection.end];
288 self.glyphs.replace_range(self.selection.start..self.selection.end, &s);
289 let start = self.selection.start;
290 let count = s.count_glyphs();
291 self.selection = Selection::NONE;
292 self.cursor.pos = start;
293 self.move_cursor_with(count as i32, true);
294 if text_changed {
295 self.last_action = LastAction::Other;
296 }
297 text_changed
298 } else {
299 false
300 }
301 }
302
303 fn select_all(&mut self) {
304 self.last_action = LastAction::None;
305 self.selection = Selection::NONE;
306 self.selection.update(0, self.glyphs.len());
307 self.move_cursor_to(self.glyphs.len(), true, false);
308 }
309 fn delete_selection(&mut self) -> bool {
311 if !self.selection.is_empty() {
312 if !self.history_suspended {
313 self.push_undo_snapshot();
314 }
315 let new_pos = self.selection.start;
316 self.glyphs.replace_range(self.selection.start..self.selection.end, "");
317 self.selection = Selection::NONE;
318 self.move_cursor_to(new_pos, false, true);
319 self.last_action = LastAction::Delete;
320 true
321 } else {
322 false
323 }
324 }
325 fn delete_current_character(&mut self) -> bool {
327 if self.is_readonly() {
328 return false;
329 }
330 if self.selection.is_empty() {
331 let next_pos = self.glyphs.next_pos(self.cursor.pos, 1);
332 if self.cursor.pos < next_pos {
333 if !self.history_suspended {
334 self.push_undo_snapshot();
335 }
336 self.glyphs.replace_range(self.cursor.pos..next_pos, "");
337 self.update_scroll_view(true);
338 self.last_action = LastAction::Delete;
339 return true;
340 }
341 false
342 } else {
343 self.delete_selection()
344 }
345 }
346 fn delete_previous_character(&mut self) -> bool {
348 if self.is_readonly() {
349 return false;
350 }
351 if self.selection.is_empty() {
352 let prev_pos = self.glyphs.previous_pos(self.cursor.pos, 1);
353 if prev_pos < self.cursor.pos {
354 if !self.history_suspended {
355 self.push_undo_snapshot();
356 }
357 let end_pos = self.cursor.pos;
358 self.glyphs.replace_range(prev_pos..end_pos, "");
359 self.move_cursor_to(prev_pos, false, true);
360 self.last_action = LastAction::Delete;
361 return true;
362 }
363 false
364 } else {
365 self.delete_selection()
366 }
367 }
368 fn add_char(&mut self, character: char) -> bool {
369 if self.is_readonly() {
370 return false;
371 }
372 let char_class = CharClass::from(character);
373 let can_coalesce = self.selection.is_empty() && matches!(self.last_action, LastAction::AddChar(prev_class) if prev_class == char_class);
374 if !can_coalesce && !self.history_suspended {
375 self.push_undo_snapshot();
376 }
377 if !self.selection.is_empty() {
378 let old_history_suspended = self.history_suspended;
379 self.history_suspended = true;
380 self.delete_selection();
381 self.history_suspended = old_history_suspended;
382 }
383 self.glyphs.insert(self.cursor.pos, character);
384 self.move_cursor_with(1, false);
385 self.last_action = LastAction::AddChar(char_class);
386 true
387 }
388 fn select_word(&mut self, offset: usize) {
389 if let Some((start, end)) = self.glyphs.word_range(offset, |c| CharClass::from(c) == CharClass::Word) {
390 self.selection = Selection::NONE;
391 self.move_cursor_to(start, false, true);
392 self.move_cursor_to(end, true, true);
393 }
394 }
395 fn mouse_pos_to_glyph_offset(&self, x: i32, y: i32, within_control: bool) -> Option<usize> {
396 let sz = self.size();
397 let w = sz.width as i32;
398 let h = sz.height as i32;
399 if within_control && ((x < 1) || (x >= w - 1) || (y < 0) || (y >= h)) {
400 return None;
401 }
402 let glyphs_count = (x - 1) + y * (w - 2);
403 match glyphs_count.cmp(&0) {
404 std::cmp::Ordering::Less => Some(self.glyphs.previous_pos(self.cursor.start, (-glyphs_count) as usize)),
405 std::cmp::Ordering::Equal => Some(self.cursor.start),
406 std::cmp::Ordering::Greater => Some(self.glyphs.next_pos(self.cursor.start, glyphs_count as usize)),
407 }
408 }
409 fn make_snapshot(&self) -> TextSnapshot {
410 TextSnapshot {
411 glyphs: self.glyphs.clone(),
412 cursor_pos: self.cursor.pos,
413 selection: self.selection,
414 }
415 }
416 fn push_snapshot_with_limit(stack: &mut Vec<TextSnapshot>, snapshot: TextSnapshot) {
417 if stack.len() >= MAX_UNDO_DEPTH {
418 stack.remove(0);
419 }
420 stack.push(snapshot);
421 }
422 fn push_undo_snapshot(&mut self) {
423 let snapshot = TextSnapshot {
424 glyphs: self.glyphs.clone(),
425 cursor_pos: self.cursor.pos,
426 selection: self.selection,
427 };
428 Self::push_snapshot_with_limit(&mut self.undo_stack, snapshot);
429 self.redo_stack.clear();
430 }
431 fn restore_snapshot(&mut self, snapshot: TextSnapshot, notify: bool) {
432 self.glyphs = snapshot.glyphs;
433 self.cursor.pos = snapshot.cursor_pos.min(self.glyphs.len());
434 self.selection = snapshot.selection;
435 self.update_scroll_view(true);
436 if notify {
437 self.notify_text_changed();
438 }
439 }
440 pub fn undo(&mut self) {
445 let current = self.make_snapshot();
446 if let Some(snapshot) = self.undo_stack.pop() {
447 Self::push_snapshot_with_limit(&mut self.redo_stack, current);
448 self.last_action = LastAction::None;
449 self.restore_snapshot(snapshot, true);
450 }
451 }
452 pub fn redo(&mut self) {
458 let current = self.make_snapshot();
459 if let Some(snapshot) = self.redo_stack.pop() {
460 Self::push_snapshot_with_limit(&mut self.undo_stack, current);
461 self.last_action = LastAction::None;
462 self.restore_snapshot(snapshot, true);
463 }
464 }
465
466 fn notify_text_changed(&mut self) {
467 self.raise_event(ControlEvent {
468 emitter: self.handle,
469 receiver: self.event_processor,
470 data: ControlEventData::TextField(EventData {
471 evtype: TextFieldEventsType::OnTextChanged,
472 }),
473 });
474 }
475}
476impl OnResize for TextField {
477 fn on_resize(&mut self, _old_size: Size, new_size: Size) {
478 let visible_chars = if new_size.width > 2 {
480 ((new_size.width - 2) as usize) * (new_size.height as usize)
481 } else {
482 0
483 };
484 self.cursor.end = self.glyphs.next_pos(self.cursor.start, visible_chars);
485 self.update_scroll_view(false);
487 }
488}
489impl OnPaint for TextField {
490 fn on_paint(&self, surface: &mut Surface, theme: &Theme) {
491 let attr = match () {
492 _ if !self.is_enabled() => theme.editor.inactive,
493 _ if self.has_focus() => theme.editor.focused,
494 _ if self.is_mouse_over() => theme.editor.hovered,
495 _ => theme.editor.normal,
496 };
497 surface.clear(Character::with_attributes(' ', attr));
498 let show_cursor = self.has_focus();
500 let sz = self.size();
501 let w = (sz.width - 1) as i32;
502 let mut count = (sz.width - 2) * sz.height;
503 let mut pos = self.cursor.start;
504 let mut x = 1;
505 let mut y = 0;
506 let mut ch = Character::with_attributes(' ', attr);
507 let mut ch_selected = Character::with_attributes(' ', theme.editor.pressed_or_selected);
508 while let Some((code, glyph_size)) = self.glyphs.glyph(pos) {
509 if (show_cursor) && self.selection.contains(pos) {
510 ch_selected.code = code;
511 surface.write_char(x, y, ch_selected);
512 } else {
513 ch.code = code;
514 surface.write_char(x, y, ch);
515 }
516 if show_cursor && (pos == self.cursor.pos) {
517 surface.set_cursor(x, y);
518 }
519 x += 1;
520 if x >= w {
521 x = 1;
522 y += 1;
523 }
524 pos += glyph_size as usize;
525 count -= 1;
526 if count == 0 {
527 break;
528 }
529 }
530 if show_cursor && (pos == self.cursor.pos) {
532 if (y == sz.height as i32) && (x == 1) {
534 surface.set_cursor(sz.width as i32 - 1, sz.height as i32 - 1);
535 } else {
536 surface.set_cursor(x, y);
537 }
538 }
539 }
540}
541impl OnKeyPressed for TextField {
542 fn on_key_pressed(&mut self, key: Key, character: char) -> EventProcessStatus {
543 match key.value() {
544 key!("Left") | key!("Shift+Left") => {
545 self.last_action = LastAction::None;
546 self.move_cursor_with(-1, key.modifier.contains(KeyModifier::Shift));
547 return EventProcessStatus::Processed;
548 }
549 key!("Right") | key!("Shift+Right") => {
550 self.last_action = LastAction::None;
551 self.move_cursor_with(1, key.modifier.contains(KeyModifier::Shift));
552 return EventProcessStatus::Processed;
553 }
554 key!("Up") | key!("Shift+Up") => {
555 self.last_action = LastAction::None;
556 self.move_cursor_with(-((self.size().width as i32) - 2), key.modifier.contains(KeyModifier::Shift));
557 return EventProcessStatus::Processed;
558 }
559 key!("Down") | key!("Shift+Down") => {
560 self.last_action = LastAction::None;
561 self.move_cursor_with((self.size().width as i32) - 2, key.modifier.contains(KeyModifier::Shift));
562 return EventProcessStatus::Processed;
563 }
564 key!("Home") | key!("Shift+Home") => {
565 self.last_action = LastAction::None;
566 self.move_cursor_to(0, key.modifier.contains(KeyModifier::Shift), false);
567 return EventProcessStatus::Processed;
568 }
569 key!("End") | key!("Shift+End") => {
570 self.last_action = LastAction::None;
571 self.move_cursor_to(self.glyphs.len(), key.modifier.contains(KeyModifier::Shift), false);
572 return EventProcessStatus::Processed;
573 }
574 key!("Ctrl+Left") | key!("Ctrl+Shift+Left") => {
575 self.last_action = LastAction::None;
576 self.move_to_previous_word(key.modifier.contains(KeyModifier::Shift));
577 return EventProcessStatus::Processed;
578 }
579 key!("Ctrl+Right") | key!("Ctrl+Shift+Right") => {
580 self.last_action = LastAction::None;
581 self.move_to_next_word(key.modifier.contains(KeyModifier::Shift));
582 return EventProcessStatus::Processed;
583 }
584 key!("Ctrl+C") | key!("Ctrl+Insert") => {
586 self.copy_text();
587 return EventProcessStatus::Processed;
588 }
589 key!("Ctrl+X") | key!("Shift+Del") => {
591 if self.cut_text() {
592 self.notify_text_changed();
593 }
594 return EventProcessStatus::Processed;
595 }
596 key!("Ctrl+V") | key!("Shift+Insert") => {
597 if self.paste_text() {
598 self.notify_text_changed();
599 }
600 return EventProcessStatus::Processed;
601 }
602 key!("Ctrl+Shift+U") => {
603 if self.convert_selection_or_word(|s| s.to_uppercase()) {
604 self.notify_text_changed();
605 }
606 return EventProcessStatus::Processed;
607 }
608 key!("Ctrl+U") => {
609 if self.convert_selection_or_word(|s| s.to_lowercase()) {
610 self.notify_text_changed();
611 }
612 return EventProcessStatus::Processed;
613 }
614 key!("Ctrl+A") => {
615 self.select_all();
616 return EventProcessStatus::Processed;
617 }
618 key!("Ctrl+Z") => {
619 self.undo();
620 return EventProcessStatus::Processed;
621 }
622 key!("Ctrl+Y") | key!("Ctrl+Shift+Z") => {
623 self.redo();
624 return EventProcessStatus::Processed;
625 }
626 key!("Delete") => {
627 if self.delete_current_character() {
628 self.notify_text_changed();
629 }
630 return EventProcessStatus::Processed;
631 }
632 key!("Back") => {
633 if self.delete_previous_character() {
634 self.notify_text_changed();
635 }
636 return EventProcessStatus::Processed;
637 }
638 key!("Enter") => {
639 if self.flags.contains(Flags::ProcessEnter) {
640 self.raise_event(ControlEvent {
641 emitter: self.handle,
642 receiver: self.event_processor,
643 data: ControlEventData::TextField(EventData {
644 evtype: TextFieldEventsType::OnValidate,
645 }),
646 });
647 return EventProcessStatus::Processed;
648 }
649 }
650
651 _ => {}
652 }
653 if (character as u32) > 0 {
654 if self.add_char(character) {
655 self.notify_text_changed();
656 }
657 return EventProcessStatus::Processed;
658 }
659 EventProcessStatus::Ignored
660 }
661}
662impl OnFocus for TextField {
663 fn on_focus(&mut self) {
664 if !self.flags.contains(Flags::DisableAutoSelectOnFocus) {
665 self.select_all();
666 }
667 }
668}
669impl OnMouseEvent for TextField {
670 fn on_mouse_event(&mut self, event: &MouseEvent) -> EventProcessStatus {
671 match event {
672 MouseEvent::Enter | MouseEvent::Leave => {
673 self.last_action = LastAction::None;
674 self.drag_started = false;
675 EventProcessStatus::Processed
676 }
677 MouseEvent::Over(_) => EventProcessStatus::Ignored,
678 MouseEvent::Pressed(data) => {
679 if let Some(new_pos) = self.mouse_pos_to_glyph_offset(data.x, data.y, true) {
680 self.last_action = LastAction::None;
681 self.move_cursor_to(new_pos, false, false);
682 self.drag_started = true;
683 }
684 EventProcessStatus::Processed
685 }
686 MouseEvent::Released(_) => {
687 self.last_action = LastAction::None;
688 self.drag_started = false;
689 EventProcessStatus::Processed
690 }
691 MouseEvent::DoubleClick(data) => {
692 if let Some(ofs) = self.mouse_pos_to_glyph_offset(data.x, data.y, true) {
693 self.last_action = LastAction::None;
694 self.select_word(ofs);
695 }
696 EventProcessStatus::Processed
697 }
698 MouseEvent::Drag(data) => {
699 if self.drag_started {
700 if let Some(new_pos) = self.mouse_pos_to_glyph_offset(data.x, data.y, false) {
701 self.last_action = LastAction::None;
702 self.move_cursor_to(new_pos, true, true);
703 }
704 }
705 EventProcessStatus::Processed
706 }
707 MouseEvent::Wheel(_) => EventProcessStatus::Ignored,
708 }
709 }
710}