1#![warn(missing_docs)]
25
26use crate::{
27 brush::Brush,
28 core::{
29 algebra::{Point2, Vector2},
30 color::Color,
31 math::Rect,
32 parking_lot::Mutex,
33 pool::Handle,
34 reflect::prelude::*,
35 type_traits::prelude::*,
36 uuid_provider,
37 variable::InheritableVariable,
38 visitor::prelude::*,
39 },
40 define_constructor,
41 draw::{CommandTexture, Draw, DrawingContext},
42 font::FontResource,
43 formatted_text::{FormattedText, FormattedTextBuilder, WrapMode},
44 message::{CursorIcon, KeyCode, MessageDirection, MouseButton, UiMessage},
45 text::TextMessage,
46 widget::{Widget, WidgetBuilder, WidgetMessage},
47 BuildContext, Control, HorizontalAlignment, UiNode, UserInterface, VerticalAlignment,
48};
49use copypasta::ClipboardProvider;
50use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
51use std::{
52 cell::RefCell,
53 fmt::{Debug, Formatter},
54 ops::{Deref, DerefMut},
55 sync::Arc,
56};
57use strum_macros::{AsRefStr, EnumString, VariantNames};
58
59#[derive(Debug, Clone, PartialEq)]
65pub enum TextBoxMessage {
66 SelectionBrush(Brush),
68 CaretBrush(Brush),
70 TextCommitMode(TextCommitMode),
72 Multiline(bool),
74 Editable(bool),
76}
77
78impl TextBoxMessage {
79 define_constructor!(
80 TextBoxMessage:SelectionBrush => fn selection_brush(Brush), layout: false
82 );
83 define_constructor!(
84 TextBoxMessage:CaretBrush => fn caret_brush(Brush), layout: false
86 );
87 define_constructor!(
88 TextBoxMessage:TextCommitMode => fn text_commit_mode(TextCommitMode), layout: false
90 );
91 define_constructor!(
92 TextBoxMessage:Multiline => fn multiline(bool), layout: false
94 );
95 define_constructor!(
96 TextBoxMessage:Editable => fn editable(bool), layout: false
98 );
99}
100
101#[derive(Copy, Clone, PartialEq, Eq)]
103pub enum HorizontalDirection {
104 Left,
106 Right,
108}
109
110#[derive(Copy, Clone, PartialEq, Eq)]
112pub enum VerticalDirection {
113 Down,
115 Up,
117}
118
119pub use crate::formatted_text::Position;
120use crate::style::resource::StyleResourceExt;
121use crate::style::{Style, StyledProperty};
122
123#[derive(
125 Copy,
126 Clone,
127 PartialOrd,
128 PartialEq,
129 Eq,
130 Ord,
131 Hash,
132 Debug,
133 Default,
134 Visit,
135 Reflect,
136 AsRefStr,
137 EnumString,
138 VariantNames,
139 TypeUuidProvider,
140)]
141#[repr(u32)]
142#[type_uuid(id = "5fb7d6f0-c151-4a30-8350-2060749d74c6")]
143pub enum TextCommitMode {
144 Immediate = 0,
146
147 LostFocus = 1,
150
151 #[default]
157 LostFocusPlusEnter = 2,
158
159 Changed = 3,
166}
167
168#[derive(Copy, Clone, PartialEq, Eq, Debug, Visit, Reflect, Default, TypeUuidProvider)]
170#[type_uuid(id = "04c8101b-cb34-47a5-af34-ecfb9b2fc426")]
171pub struct SelectionRange {
172 pub begin: Position,
174 pub end: Position,
176}
177
178impl SelectionRange {
179 #[must_use = "method creates new value which must be used"]
182 pub fn normalized(&self) -> SelectionRange {
183 SelectionRange {
184 begin: self.left(),
185 end: self.right(),
186 }
187 }
188 pub fn range(&self) -> std::ops::Range<Position> {
190 std::ops::Range {
191 start: self.left(),
192 end: self.right(),
193 }
194 }
195 pub fn contains(&self, position: Position) -> bool {
197 (self.begin..=self.end).contains(&position)
198 }
199 pub fn left(&self) -> Position {
201 Position::min(self.begin, self.end)
202 }
203 pub fn right(&self) -> Position {
205 Position::max(self.begin, self.end)
206 }
207}
208
209pub type FilterCallback = dyn FnMut(char) -> bool + Send;
212
213#[derive(Default, Clone, Visit, Reflect, ComponentProvider)]
417pub struct TextBox {
418 pub widget: Widget,
420 pub caret_position: InheritableVariable<Position>,
422 pub caret_visible: InheritableVariable<bool>,
424 pub blink_timer: InheritableVariable<f32>,
426 pub blink_interval: InheritableVariable<f32>,
428 pub formatted_text: RefCell<FormattedText>,
430 pub selection_range: InheritableVariable<Option<SelectionRange>>,
432 pub selecting: bool,
434 #[visit(skip)]
436 pub before_click_position: Position,
437 pub has_focus: bool,
439 pub caret_brush: InheritableVariable<Brush>,
441 pub selection_brush: InheritableVariable<Brush>,
443 #[visit(skip)]
445 #[reflect(hidden)]
446 pub filter: Option<Arc<Mutex<FilterCallback>>>,
447 pub commit_mode: InheritableVariable<TextCommitMode>,
449 pub multiline: InheritableVariable<bool>,
451 pub editable: InheritableVariable<bool>,
453 pub view_position: InheritableVariable<Vector2<f32>>,
455 pub skip_chars: InheritableVariable<Vec<char>>,
457 #[visit(skip)]
459 #[reflect(hidden)]
460 pub recent: Vec<char>,
461}
462
463impl ConstructorProvider<UiNode, UserInterface> for TextBox {
464 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
465 GraphNodeConstructor::new::<Self>()
466 .with_variant("Text Box", |ui| {
467 TextBoxBuilder::new(WidgetBuilder::new().with_name("Text Box"))
468 .with_text("Text")
469 .build(&mut ui.build_ctx())
470 .into()
471 })
472 .with_group("Input")
473 }
474}
475
476impl Debug for TextBox {
477 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
478 f.write_str("TextBox")
479 }
480}
481
482crate::define_widget_deref!(TextBox);
483
484impl TextBox {
485 fn commit_if_changed(&mut self, ui: &mut UserInterface) {
486 let formatted_text = self.formatted_text.borrow();
487 let raw = formatted_text.get_raw_text();
488 if self.recent != raw {
489 self.recent.clear();
490 self.recent.extend(raw);
491 ui.send_message(TextMessage::text(
492 self.handle,
493 MessageDirection::FromWidget,
494 formatted_text.text(),
495 ));
496 }
497 }
498 fn filter_paste_str_multiline(&self, str: &str) -> String {
499 let mut str = str.replace("\r\n", "\n");
500 str.retain(|c| c == '\n' || !c.is_control());
501 if let Some(filter) = self.filter.as_ref() {
502 let filter = &mut *filter.lock();
503 str.retain(filter);
504 }
505 str
506 }
507
508 fn filter_paste_str_single_line(&self, str: &str) -> String {
509 let mut str: String = str
510 .chars()
511 .map(|c| if c == '\n' { ' ' } else { c })
512 .filter(|c| !c.is_control())
513 .collect();
514 if let Some(filter) = self.filter.as_ref() {
515 let filter = &mut *filter.lock();
516 str.retain(filter);
517 }
518 str
519 }
520
521 fn reset_blink(&mut self) {
522 self.caret_visible.set_value_and_mark_modified(true);
523 self.blink_timer.set_value_and_mark_modified(0.0);
524 }
525
526 fn move_caret(&mut self, position: Position, select: bool) {
527 let text = self.formatted_text.borrow();
528 let lines = text.get_lines();
529 if select && !lines.is_empty() {
530 if self.selection_range.is_none() {
531 self.selection_range
532 .set_value_and_mark_modified(Some(SelectionRange {
533 begin: *self.caret_position,
534 end: *self.caret_position,
535 }));
536 }
537 } else {
538 self.selection_range.set_value_and_mark_modified(None);
539 }
540
541 if lines.is_empty() {
542 drop(text);
543 self.set_caret_position(Default::default());
544 return;
545 }
546
547 drop(text);
548
549 if let Some(selection_range) = self.selection_range.as_mut() {
550 if select {
551 if *self.caret_position != selection_range.end {
552 if !selection_range.contains(position) {
553 if position < selection_range.left() {
554 selection_range.begin = selection_range.right();
555 } else {
556 selection_range.begin = selection_range.left();
557 }
558 selection_range.end = position;
559 }
560 } else {
561 selection_range.end = position;
562 }
563 }
564 }
565
566 self.set_caret_position(position);
567 self.ensure_caret_visible();
568 }
569
570 fn move_caret_x(&mut self, offset: isize, select: bool) {
571 let pos = self
572 .formatted_text
573 .borrow()
574 .get_relative_position_x(*self.caret_position, offset);
575 self.move_caret(pos, select);
576 }
577
578 fn move_caret_y(&mut self, offset: isize, select: bool) {
579 let pos = self
580 .formatted_text
581 .borrow()
582 .get_relative_position_y(*self.caret_position, offset);
583 self.move_caret(pos, select);
584 }
585
586 pub fn position_to_char_index_unclamped(&self, position: Position) -> Option<usize> {
591 self.formatted_text
592 .borrow()
593 .position_to_char_index_unclamped(position)
594 }
595
596 pub fn position_to_char_index_clamped(&self, position: Position) -> Option<usize> {
603 self.formatted_text
604 .borrow()
605 .position_to_char_index_clamped(position)
606 }
607
608 pub fn char_index_to_position(&self, i: usize) -> Option<Position> {
610 self.formatted_text.borrow().char_index_to_position(i)
611 }
612
613 pub fn end_position(&self) -> Position {
615 self.formatted_text.borrow().end_position()
616 }
617
618 pub fn find_next_word(&self, from: Position) -> Position {
620 self.position_to_char_index_unclamped(from)
621 .and_then(|i| {
622 self.formatted_text
623 .borrow()
624 .get_raw_text()
625 .iter()
626 .enumerate()
627 .skip(i)
628 .skip_while(|(_, c)| !(c.is_whitespace() || self.skip_chars.contains(*c)))
629 .find(|(_, c)| !(c.is_whitespace() || self.skip_chars.contains(*c)))
630 .and_then(|(n, _)| self.char_index_to_position(n))
631 })
632 .unwrap_or_else(|| self.end_position())
633 }
634
635 pub fn find_prev_word(&self, from: Position) -> Position {
637 self.position_to_char_index_unclamped(from)
638 .and_then(|i| {
639 let text = self.formatted_text.borrow();
640 let len = text.get_raw_text().len();
641 text.get_raw_text()
642 .iter()
643 .enumerate()
644 .rev()
645 .skip(len.saturating_sub(i))
646 .skip_while(|(_, c)| !(c.is_whitespace() || self.skip_chars.contains(*c)))
647 .find(|(_, c)| !(c.is_whitespace() || self.skip_chars.contains(*c)))
648 .and_then(|(n, _)| self.char_index_to_position(n + 1))
649 })
650 .unwrap_or_default()
651 }
652
653 fn insert_char(&mut self, c: char, ui: &UserInterface) {
655 self.remove_before_insert();
656 let position = self
657 .position_to_char_index_unclamped(*self.caret_position)
658 .unwrap_or_default();
659 self.formatted_text
660 .borrow_mut()
661 .insert_char(c, position)
662 .build();
663 self.set_caret_position(
664 self.char_index_to_position(position + 1)
665 .unwrap_or_default(),
666 );
667 if *self.commit_mode == TextCommitMode::Immediate {
668 ui.send_message(TextMessage::text(
669 self.handle,
670 MessageDirection::FromWidget,
671 self.formatted_text.borrow().text(),
672 ));
673 }
674 }
675
676 fn insert_str(&mut self, str: &str, ui: &UserInterface) {
677 if str.is_empty() {
678 return;
679 }
680 let str: String = if *self.multiline {
681 self.filter_paste_str_multiline(str)
682 } else {
683 self.filter_paste_str_single_line(str)
684 };
685 self.remove_before_insert();
686 let position = self
687 .position_to_char_index_unclamped(*self.caret_position)
688 .unwrap_or_default();
689 let mut text = self.formatted_text.borrow_mut();
690 text.insert_str(&str, position);
691 text.build();
692 drop(text);
693 self.set_caret_position(
694 self.char_index_to_position(position + str.chars().count())
695 .unwrap_or_default(),
696 );
697 if *self.commit_mode == TextCommitMode::Immediate {
698 ui.send_message(TextMessage::text(
699 self.handle,
700 MessageDirection::FromWidget,
701 self.formatted_text.borrow().text(),
702 ));
703 }
704 }
705
706 fn remove_before_insert(&mut self) {
707 let Some(selection) = *self.selection_range else {
708 return;
709 };
710 let range = self
711 .formatted_text
712 .borrow()
713 .position_range_to_char_index_range(selection.range());
714 if range.is_empty() {
715 return;
716 }
717 self.formatted_text.borrow_mut().remove_range(range);
718 self.selection_range.set_value_and_mark_modified(None);
719 self.set_caret_position(selection.left());
720 }
721
722 pub fn get_text_len(&self) -> usize {
724 self.formatted_text.borrow_mut().get_raw_text().len()
725 }
726
727 pub fn caret_local_position(&self) -> Vector2<f32> {
729 self.formatted_text
730 .borrow_mut()
731 .position_to_local(*self.caret_position)
732 }
733
734 fn point_to_view_pos(&self, position: Vector2<f32>) -> Vector2<f32> {
735 position - *self.view_position
736 }
737
738 fn rect_to_view_pos(&self, mut rect: Rect<f32>) -> Rect<f32> {
739 rect.position -= *self.view_position;
740 rect
741 }
742
743 fn ensure_caret_visible(&mut self) {
744 let local_bounds = self.bounding_rect();
745 let caret_view_position = self.point_to_view_pos(self.caret_local_position());
746 let spacing_step = self
748 .formatted_text
749 .borrow()
750 .get_font()
751 .state()
752 .data()
753 .map(|font| font.ascender(*self.height))
754 .unwrap_or_default();
755 let spacing = spacing_step * 3.0;
756 let top_left_corner = local_bounds.left_top_corner();
757 let bottom_right_corner = local_bounds.right_bottom_corner();
758 if caret_view_position.x > bottom_right_corner.x {
759 self.view_position.x += caret_view_position.x - bottom_right_corner.x + spacing;
760 }
761 if caret_view_position.x < top_left_corner.x {
762 self.view_position.x -= top_left_corner.x - caret_view_position.x + spacing;
763 }
764 if caret_view_position.y > bottom_right_corner.y {
765 self.view_position.y += bottom_right_corner.y - caret_view_position.y + spacing;
766 }
767 if caret_view_position.y < top_left_corner.y {
768 self.view_position.y -= top_left_corner.y - caret_view_position.y + spacing;
769 }
770 self.view_position.x = self.view_position.x.max(0.0);
771 self.view_position.y = self.view_position.y.max(0.0);
772 }
773
774 fn remove_char(&mut self, direction: HorizontalDirection, ui: &UserInterface) {
775 if let Some(selection) = *self.selection_range {
776 self.remove_range(ui, selection);
777 return;
778 }
779 let Some(position) = self.position_to_char_index_unclamped(*self.caret_position) else {
780 return;
781 };
782
783 let text_len = self.get_text_len();
784 if text_len != 0 {
785 let position = match direction {
786 HorizontalDirection::Left => {
787 if position == 0 {
788 return;
789 }
790 position - 1
791 }
792 HorizontalDirection::Right => {
793 if position >= text_len {
794 return;
795 }
796 position
797 }
798 };
799
800 let mut text = self.formatted_text.borrow_mut();
801 text.remove_at(position);
802 text.build();
803 drop(text);
804
805 if *self.commit_mode == TextCommitMode::Immediate {
806 ui.send_message(TextMessage::text(
807 self.handle(),
808 MessageDirection::FromWidget,
809 self.formatted_text.borrow().text(),
810 ));
811 }
812
813 self.set_caret_position(self.char_index_to_position(position).unwrap_or_default());
814 }
815 }
816
817 fn remove_range(&mut self, ui: &UserInterface, selection: SelectionRange) {
818 let range = self
819 .formatted_text
820 .borrow()
821 .position_range_to_char_index_range(selection.range());
822 if range.is_empty() {
823 return;
824 }
825 self.formatted_text.borrow_mut().remove_range(range);
826 self.formatted_text.borrow_mut().build();
827 self.set_caret_position(selection.left());
828 self.selection_range.set_value_and_mark_modified(None);
829 if *self.commit_mode == TextCommitMode::Immediate {
830 ui.send_message(TextMessage::text(
831 self.handle(),
832 MessageDirection::FromWidget,
833 self.formatted_text.borrow().text(),
834 ));
835 }
836 }
837
838 pub fn is_valid_position(&self, position: Position) -> bool {
840 self.formatted_text
841 .borrow()
842 .get_lines()
843 .get(position.line)
844 .is_some_and(|line| position.offset < line.len())
845 }
846
847 fn set_caret_position(&mut self, position: Position) {
848 self.caret_position.set_value_and_mark_modified(
849 self.formatted_text
850 .borrow()
851 .nearest_valid_position(position),
852 );
853 self.ensure_caret_visible();
854 self.reset_blink();
855 }
856
857 pub fn screen_pos_to_text_pos(&self, screen_point: Vector2<f32>) -> Option<Position> {
859 let point_to_check = self
862 .visual_transform
863 .try_inverse()?
864 .transform_point(&Point2::from(screen_point))
865 .coords;
866
867 Some(
868 self.formatted_text
869 .borrow_mut()
870 .local_to_position(point_to_check),
871 )
872 }
873
874 pub fn text(&self) -> String {
876 self.formatted_text.borrow().text()
877 }
878
879 pub fn wrap_mode(&self) -> WrapMode {
881 self.formatted_text.borrow().wrap_mode()
882 }
883
884 pub fn font(&self) -> FontResource {
886 self.formatted_text.borrow().get_font()
887 }
888
889 pub fn vertical_alignment(&self) -> VerticalAlignment {
891 self.formatted_text.borrow().vertical_alignment()
892 }
893
894 pub fn horizontal_alignment(&self) -> HorizontalAlignment {
896 self.formatted_text.borrow().horizontal_alignment()
897 }
898
899 fn select_word(&mut self, position: Position) {
900 if let Some(index) = self.position_to_char_index_clamped(position) {
901 let text_ref = self.formatted_text.borrow();
902 let text = text_ref.get_raw_text();
903 let search_whitespace = !text[index].is_whitespace();
904
905 let mut left_index = index;
906 while left_index > 0 {
907 let is_whitespace = text[left_index].is_whitespace();
908 if search_whitespace && is_whitespace || !search_whitespace && !is_whitespace {
909 left_index += 1;
910 break;
911 }
912 left_index = left_index.saturating_sub(1);
913 }
914
915 let mut right_index = index;
916 while right_index < text.len() {
917 let is_whitespace = text[right_index].is_whitespace();
918 if search_whitespace && is_whitespace || !search_whitespace && !is_whitespace {
919 break;
920 }
921
922 right_index += 1;
923 }
924
925 drop(text_ref);
926
927 if let (Some(left), Some(right)) = (
928 self.char_index_to_position(left_index),
929 self.char_index_to_position(right_index),
930 ) {
931 self.selection_range
932 .set_value_and_mark_modified(Some(SelectionRange {
933 begin: left,
934 end: right,
935 }));
936 }
937 }
938 }
939}
940
941uuid_provider!(TextBox = "536276f2-a175-4c05-a376-5a7d8bf0d10b");
942
943impl Control for TextBox {
944 fn measure_override(&self, _: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
945 self.formatted_text
946 .borrow_mut()
947 .set_super_sampling_scale(self.visual_max_scaling())
948 .set_constraint(available_size)
949 .build()
950 }
951
952 fn on_visual_transform_changed(&self) {
953 self.formatted_text
954 .borrow_mut()
955 .set_super_sampling_scale(self.visual_max_scaling())
956 .build();
957 }
958
959 fn draw(&self, drawing_context: &mut DrawingContext) {
960 let bounds = self.widget.bounding_rect();
961 drawing_context.push_rect_filled(&bounds, None);
962 drawing_context.commit(
963 self.clip_bounds(),
964 self.widget.background(),
965 CommandTexture::None,
966 None,
967 );
968
969 self.formatted_text
970 .borrow_mut()
971 .set_brush(self.widget.foreground());
972
973 let view_bounds = self.rect_to_view_pos(bounds);
974 if let Some(ref selection_range) = self.selection_range.map(|r| r.normalized()) {
975 let text = self.formatted_text.borrow();
976 let lines = text.get_lines();
977 if selection_range.begin.line == selection_range.end.line {
978 if let Some(line) = lines.get(selection_range.begin.line) {
979 let offset = text
981 .get_range_width(line.begin..(line.begin + selection_range.begin.offset));
982 let width = text.get_range_width(
983 (line.begin + selection_range.begin.offset)
984 ..(line.begin + selection_range.end.offset),
985 );
986 let selection_bounds = Rect::new(
987 view_bounds.x() + line.x_offset + offset,
988 view_bounds.y() + line.y_offset,
989 width,
990 line.height,
991 );
992 drawing_context.push_rect_filled(&selection_bounds, None);
993 }
994 } else {
995 for (i, line) in text.get_lines().iter().enumerate() {
996 if i >= selection_range.begin.line && i <= selection_range.end.line {
997 let selection_bounds = if i == selection_range.begin.line {
998 let offset = text.get_range_width(
1000 line.begin..(line.begin + selection_range.begin.offset),
1001 );
1002 let width = text.get_range_width(
1003 (line.begin + selection_range.begin.offset)..line.end,
1004 );
1005 Rect::new(
1006 view_bounds.x() + line.x_offset + offset,
1007 view_bounds.y() + line.y_offset,
1008 width,
1009 line.height,
1010 )
1011 } else if i == selection_range.end.line {
1012 let width = text.get_range_width(
1014 line.begin..(line.begin + selection_range.end.offset),
1015 );
1016 Rect::new(
1017 view_bounds.x() + line.x_offset,
1018 view_bounds.y() + line.y_offset,
1019 width,
1020 line.height,
1021 )
1022 } else {
1023 Rect::new(
1025 view_bounds.x() + line.x_offset,
1026 view_bounds.y() + line.y_offset,
1027 line.width,
1028 line.height,
1029 )
1030 };
1031 drawing_context.push_rect_filled(&selection_bounds, None);
1032 }
1033 }
1034 }
1035 }
1036 drawing_context.commit(
1037 self.clip_bounds(),
1038 (*self.selection_brush).clone(),
1039 CommandTexture::None,
1040 None,
1041 );
1042
1043 let local_position = self.point_to_view_pos(bounds.position);
1044 drawing_context.draw_text(
1045 self.clip_bounds(),
1046 local_position,
1047 &self.formatted_text.borrow(),
1048 );
1049
1050 if *self.caret_visible {
1051 let caret_pos = self.point_to_view_pos(self.caret_local_position());
1052 let caret_bounds = Rect::new(
1053 caret_pos.x,
1054 caret_pos.y,
1055 2.0,
1056 **self.formatted_text.borrow().font_size(),
1057 );
1058 drawing_context.push_rect_filled(&caret_bounds, None);
1059 drawing_context.commit(
1060 self.clip_bounds(),
1061 (*self.caret_brush).clone(),
1062 CommandTexture::None,
1063 None,
1064 );
1065 }
1066 }
1067
1068 fn update(&mut self, dt: f32, _ui: &mut UserInterface) {
1069 if self.has_focus {
1070 *self.blink_timer += dt;
1071 if *self.blink_timer >= *self.blink_interval {
1072 self.blink_timer.set_value_and_mark_modified(0.0);
1073 self.caret_visible
1074 .set_value_and_mark_modified(!*self.caret_visible);
1075 }
1076 } else {
1077 self.caret_visible.set_value_and_mark_modified(false);
1078 }
1079 }
1080
1081 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
1082 self.widget.handle_routed_message(ui, message);
1083
1084 if message.destination() == self.handle() {
1085 if let Some(msg) = message.data::<WidgetMessage>() {
1086 match msg {
1087 WidgetMessage::Text(text)
1088 if !ui.keyboard_modifiers().control
1089 && !ui.keyboard_modifiers().alt
1090 && *self.editable =>
1091 {
1092 for symbol in text.chars() {
1093 let insert = !symbol.is_control()
1094 && if let Some(filter) = self.filter.as_ref() {
1095 let filter = &mut *filter.lock();
1096 filter(symbol)
1097 } else {
1098 true
1099 };
1100 if insert {
1101 self.insert_char(symbol, ui);
1102 }
1103 }
1104 }
1105 WidgetMessage::KeyDown(code) => {
1106 match code {
1107 KeyCode::ArrowUp if !self.selecting => {
1108 self.move_caret_y(-1, ui.keyboard_modifiers().shift);
1109 }
1110 KeyCode::ArrowDown if !self.selecting => {
1111 self.move_caret_y(1, ui.keyboard_modifiers().shift);
1112 }
1113 KeyCode::ArrowRight if !self.selecting => {
1114 if ui.keyboard_modifiers.control {
1115 self.move_caret(
1116 self.find_next_word(*self.caret_position),
1117 ui.keyboard_modifiers().shift,
1118 );
1119 } else {
1120 self.move_caret_x(1, ui.keyboard_modifiers().shift);
1121 }
1122 }
1123 KeyCode::ArrowLeft if !self.selecting => {
1124 if ui.keyboard_modifiers.control {
1125 self.move_caret(
1126 self.find_prev_word(*self.caret_position),
1127 ui.keyboard_modifiers().shift,
1128 );
1129 } else {
1130 self.move_caret_x(-1, ui.keyboard_modifiers().shift);
1131 }
1132 }
1133 KeyCode::Delete
1134 if !message.handled() && *self.editable && !self.selecting =>
1135 {
1136 self.remove_char(HorizontalDirection::Right, ui);
1137 }
1138 KeyCode::NumpadEnter | KeyCode::Enter if *self.editable => {
1139 if *self.multiline {
1140 self.insert_char('\n', ui);
1141 } else if *self.commit_mode == TextCommitMode::LostFocusPlusEnter {
1142 ui.send_message(TextMessage::text(
1143 self.handle,
1144 MessageDirection::FromWidget,
1145 self.text(),
1146 ));
1147 } else if *self.commit_mode == TextCommitMode::Changed {
1148 self.commit_if_changed(ui);
1149 }
1150 }
1153 KeyCode::Backspace if *self.editable && !self.selecting => {
1154 self.remove_char(HorizontalDirection::Left, ui);
1155 }
1156 KeyCode::End if !self.selecting => {
1157 let select = ui.keyboard_modifiers().shift;
1158 let position = if ui.keyboard_modifiers().control {
1159 self.end_position()
1160 } else {
1161 self.formatted_text
1162 .borrow()
1163 .get_line_range(self.caret_position.line)
1164 .end
1165 };
1166 self.move_caret(position, select);
1167 }
1168 KeyCode::Home if !self.selecting => {
1169 let select = ui.keyboard_modifiers().shift;
1170 let position = if ui.keyboard_modifiers().control {
1171 Position::default()
1172 } else {
1173 self.formatted_text
1174 .borrow()
1175 .get_line_range(self.caret_position.line)
1176 .start
1177 };
1178 self.move_caret(position, select);
1179 }
1180 KeyCode::KeyA if ui.keyboard_modifiers().control => {
1181 let end = self.end_position();
1182 if end != Position::default() {
1183 self.selection_range.set_value_and_mark_modified(Some(
1184 SelectionRange {
1185 begin: Position::default(),
1186 end: self.end_position(),
1187 },
1188 ));
1189 }
1190 }
1191 KeyCode::KeyC if ui.keyboard_modifiers().control => {
1192 if let Some(mut clipboard) = ui.clipboard_mut() {
1193 if let Some(selection_range) = self.selection_range.as_ref() {
1194 let range = self
1195 .formatted_text
1196 .borrow()
1197 .position_range_to_char_index_range(
1198 selection_range.range(),
1199 );
1200 if !range.is_empty() {
1201 let _ = clipboard.set_contents(
1202 self.formatted_text.borrow().text_range(range),
1203 );
1204 }
1205 }
1206 }
1207 }
1208 KeyCode::KeyV if ui.keyboard_modifiers().control => {
1209 if let Some(mut clipboard) = ui.clipboard_mut() {
1210 if let Ok(content) = clipboard.get_contents() {
1211 self.insert_str(&content, ui);
1212 }
1213 }
1214 }
1215 KeyCode::KeyX if ui.keyboard_modifiers().control => {
1216 if let Some(mut clipboard) = ui.clipboard_mut() {
1217 if let Some(selection_range) = self.selection_range.as_ref() {
1218 let range = self
1219 .formatted_text
1220 .borrow()
1221 .position_range_to_char_index_range(
1222 selection_range.range(),
1223 );
1224 if !range.is_empty() {
1225 let _ = clipboard.set_contents(
1226 self.formatted_text.borrow().text_range(range),
1227 );
1228 self.remove_char(HorizontalDirection::Left, ui);
1229 }
1230 }
1231 }
1232 }
1233 _ => (),
1234 }
1235
1236 message.set_handled(true);
1239 }
1240 WidgetMessage::Focus => {
1241 if message.direction() == MessageDirection::FromWidget {
1242 self.reset_blink();
1243 self.has_focus = true;
1244 let end = self.end_position();
1245 if end != Position::default() {
1246 self.set_caret_position(end);
1247 self.selection_range.set_value_and_mark_modified(Some(
1248 SelectionRange {
1249 begin: Position::default(),
1250 end,
1251 },
1252 ));
1253 }
1254 if *self.commit_mode == TextCommitMode::Changed {
1255 self.recent.clear();
1256 self.recent
1257 .extend_from_slice(self.formatted_text.borrow().get_raw_text());
1258 }
1259 }
1260 }
1261 WidgetMessage::Unfocus => {
1262 if message.direction() == MessageDirection::FromWidget {
1263 self.selection_range.set_value_and_mark_modified(None);
1264 self.has_focus = false;
1265
1266 match *self.commit_mode {
1267 TextCommitMode::LostFocus | TextCommitMode::LostFocusPlusEnter => {
1268 ui.send_message(TextMessage::text(
1269 self.handle,
1270 MessageDirection::FromWidget,
1271 self.text(),
1272 ));
1273 }
1274 TextCommitMode::Changed => {
1275 self.commit_if_changed(ui);
1276 }
1277 _ => (),
1278 }
1279 self.recent.clear();
1283 self.recent.shrink_to(0);
1284 }
1285 }
1286 WidgetMessage::MouseDown { pos, button } => {
1287 if *button == MouseButton::Left {
1288 let select = ui.keyboard_modifiers().shift;
1289 if !select {
1290 self.selection_range.set_value_and_mark_modified(None);
1291 }
1292 self.selecting = true;
1293 self.has_focus = true;
1294 self.before_click_position = *self.caret_position;
1295
1296 if let Some(position) = self.screen_pos_to_text_pos(*pos) {
1297 self.move_caret(position, select);
1298 }
1299
1300 ui.capture_mouse(self.handle());
1301 }
1302 }
1303 WidgetMessage::DoubleClick {
1304 button: MouseButton::Left,
1305 } => {
1306 if let Some(position) = self.screen_pos_to_text_pos(ui.cursor_position) {
1307 if position == self.before_click_position {
1308 self.select_word(position);
1309 }
1310 }
1311 }
1312 WidgetMessage::MouseMove { pos, .. } => {
1313 if self.selecting {
1314 if let Some(position) = self.screen_pos_to_text_pos(*pos) {
1315 self.move_caret(position, true);
1316 }
1317 }
1318 }
1319 WidgetMessage::MouseUp { .. } => {
1320 self.selecting = false;
1321 ui.release_mouse_capture();
1322 }
1323 _ => {}
1324 }
1325 } else if let Some(msg) = message.data::<TextMessage>() {
1326 if message.direction() == MessageDirection::ToWidget {
1327 let mut text = self.formatted_text.borrow_mut();
1328
1329 match msg {
1330 TextMessage::Text(new_text) => {
1331 fn text_equals(
1332 formatted_text: &FormattedText,
1333 input_string: &str,
1334 ) -> bool {
1335 let raw_text = formatted_text.get_raw_text();
1336
1337 if raw_text.len() != input_string.chars().count() {
1338 false
1339 } else {
1340 for (raw_char, input_char) in
1341 raw_text.iter().zip(input_string.chars())
1342 {
1343 if *raw_char != input_char {
1344 return false;
1345 }
1346 }
1347
1348 true
1349 }
1350 }
1351 self.selection_range.set_value_and_mark_modified(None);
1352 if !text_equals(&text, new_text) {
1353 text.set_text(new_text);
1354 drop(text);
1355 self.invalidate_layout();
1356 self.formatted_text.borrow_mut().build();
1357
1358 if *self.commit_mode == TextCommitMode::Immediate {
1359 ui.send_message(message.reverse());
1360 }
1361 }
1362 }
1363 TextMessage::Wrap(wrap_mode) => {
1364 if text.wrap_mode() != *wrap_mode {
1365 text.set_wrap(*wrap_mode);
1366 drop(text);
1367 self.invalidate_layout();
1368 ui.send_message(message.reverse());
1369 }
1370 }
1371 TextMessage::Font(font) => {
1372 if &text.get_font() != font {
1373 text.set_font(font.clone());
1374 drop(text);
1375 self.invalidate_layout();
1376 ui.send_message(message.reverse());
1377 }
1378 }
1379 TextMessage::VerticalAlignment(alignment) => {
1380 if &text.vertical_alignment() != alignment {
1381 text.set_vertical_alignment(*alignment);
1382 drop(text);
1383 self.invalidate_layout();
1384 ui.send_message(message.reverse());
1385 }
1386 }
1387 TextMessage::HorizontalAlignment(alignment) => {
1388 if &text.horizontal_alignment() != alignment {
1389 text.set_horizontal_alignment(*alignment);
1390 drop(text);
1391 self.invalidate_layout();
1392 ui.send_message(message.reverse());
1393 }
1394 }
1395 &TextMessage::Shadow(shadow) => {
1396 if *text.shadow != shadow {
1397 text.set_shadow(shadow);
1398 drop(text);
1399 self.invalidate_layout();
1400 ui.send_message(message.reverse());
1401 }
1402 }
1403 TextMessage::ShadowBrush(brush) => {
1404 if &*text.shadow_brush != brush {
1405 text.set_shadow_brush(brush.clone());
1406 drop(text);
1407 self.invalidate_layout();
1408 ui.send_message(message.reverse());
1409 }
1410 }
1411 &TextMessage::ShadowDilation(dilation) => {
1412 if *text.shadow_dilation != dilation {
1413 text.set_shadow_dilation(dilation);
1414 drop(text);
1415 self.invalidate_layout();
1416 ui.send_message(message.reverse());
1417 }
1418 }
1419 &TextMessage::ShadowOffset(offset) => {
1420 if *text.shadow_offset != offset {
1421 text.set_shadow_offset(offset);
1422 drop(text);
1423 self.invalidate_layout();
1424 ui.send_message(message.reverse());
1425 }
1426 }
1427 TextMessage::FontSize(height) => {
1428 if text.font_size() != height {
1429 text.set_font_size(height.clone());
1430 drop(text);
1431 self.invalidate_layout();
1432 ui.send_message(message.reverse());
1433 }
1434 }
1435 }
1436 }
1437 } else if let Some(msg) = message.data::<TextBoxMessage>() {
1438 if message.direction() == MessageDirection::ToWidget {
1439 match msg {
1440 TextBoxMessage::SelectionBrush(brush) => {
1441 if &*self.selection_brush != brush {
1442 self.selection_brush
1443 .set_value_and_mark_modified(brush.clone());
1444 ui.send_message(message.reverse());
1445 }
1446 }
1447 TextBoxMessage::CaretBrush(brush) => {
1448 if &*self.caret_brush != brush {
1449 self.caret_brush.set_value_and_mark_modified(brush.clone());
1450 ui.send_message(message.reverse());
1451 }
1452 }
1453 TextBoxMessage::TextCommitMode(mode) => {
1454 if &*self.commit_mode != mode {
1455 self.commit_mode.set_value_and_mark_modified(*mode);
1456 ui.send_message(message.reverse());
1457 }
1458 }
1459 TextBoxMessage::Multiline(multiline) => {
1460 if &*self.multiline != multiline {
1461 self.multiline.set_value_and_mark_modified(*multiline);
1462 ui.send_message(message.reverse());
1463 }
1464 }
1465 TextBoxMessage::Editable(editable) => {
1466 if &*self.editable != editable {
1467 self.editable.set_value_and_mark_modified(*editable);
1468 ui.send_message(message.reverse());
1469 }
1470 }
1471 }
1472 }
1473 }
1474 }
1475 }
1476}
1477
1478pub struct TextBoxBuilder {
1480 widget_builder: WidgetBuilder,
1481 font: Option<FontResource>,
1482 text: String,
1483 caret_brush: Brush,
1484 selection_brush: Brush,
1485 filter: Option<Arc<Mutex<FilterCallback>>>,
1486 vertical_alignment: VerticalAlignment,
1487 horizontal_alignment: HorizontalAlignment,
1488 wrap: WrapMode,
1489 commit_mode: TextCommitMode,
1490 multiline: bool,
1491 editable: bool,
1492 mask_char: Option<char>,
1493 shadow: bool,
1494 shadow_brush: Brush,
1495 shadow_dilation: f32,
1496 shadow_offset: Vector2<f32>,
1497 skip_chars: Vec<char>,
1498 font_size: Option<StyledProperty<f32>>,
1499}
1500
1501impl TextBoxBuilder {
1502 pub fn new(widget_builder: WidgetBuilder) -> Self {
1504 Self {
1505 widget_builder,
1506 font: None,
1507 text: "".to_owned(),
1508 caret_brush: Brush::Solid(Color::WHITE),
1509 selection_brush: Brush::Solid(Color::opaque(80, 118, 178)),
1510 filter: None,
1511 vertical_alignment: VerticalAlignment::Top,
1512 horizontal_alignment: HorizontalAlignment::Left,
1513 wrap: WrapMode::NoWrap,
1514 commit_mode: TextCommitMode::LostFocusPlusEnter,
1515 multiline: false,
1516 editable: true,
1517 mask_char: None,
1518 shadow: false,
1519 shadow_brush: Brush::Solid(Color::BLACK),
1520 shadow_dilation: 1.0,
1521 shadow_offset: Vector2::new(1.0, 1.0),
1522 skip_chars: Default::default(),
1523 font_size: None,
1524 }
1525 }
1526
1527 pub fn with_font(mut self, font: FontResource) -> Self {
1529 self.font = Some(font);
1530 self
1531 }
1532
1533 pub fn with_text<P: AsRef<str>>(mut self, text: P) -> Self {
1535 text.as_ref().clone_into(&mut self.text);
1536 self
1537 }
1538
1539 pub fn with_caret_brush(mut self, brush: Brush) -> Self {
1541 self.caret_brush = brush;
1542 self
1543 }
1544
1545 pub fn with_selection_brush(mut self, brush: Brush) -> Self {
1547 self.selection_brush = brush;
1548 self
1549 }
1550
1551 pub fn with_filter(mut self, filter: Arc<Mutex<FilterCallback>>) -> Self {
1553 self.filter = Some(filter);
1554 self
1555 }
1556
1557 pub fn with_vertical_text_alignment(mut self, alignment: VerticalAlignment) -> Self {
1559 self.vertical_alignment = alignment;
1560 self
1561 }
1562
1563 pub fn with_horizontal_text_alignment(mut self, alignment: HorizontalAlignment) -> Self {
1565 self.horizontal_alignment = alignment;
1566 self
1567 }
1568
1569 pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
1571 self.wrap = wrap;
1572 self
1573 }
1574
1575 pub fn with_text_commit_mode(mut self, mode: TextCommitMode) -> Self {
1577 self.commit_mode = mode;
1578 self
1579 }
1580
1581 pub fn with_multiline(mut self, multiline: bool) -> Self {
1583 self.multiline = multiline;
1584 self
1585 }
1586
1587 pub fn with_editable(mut self, editable: bool) -> Self {
1589 self.editable = editable;
1590 self
1591 }
1592
1593 pub fn with_font_size(mut self, font_size: StyledProperty<f32>) -> Self {
1595 self.font_size = Some(font_size);
1596 self
1597 }
1598
1599 pub fn with_mask_char(mut self, mask_char: Option<char>) -> Self {
1601 self.mask_char = mask_char;
1602 self
1603 }
1604
1605 pub fn with_shadow(mut self, shadow: bool) -> Self {
1607 self.shadow = shadow;
1608 self
1609 }
1610
1611 pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
1613 self.shadow_brush = brush;
1614 self
1615 }
1616
1617 pub fn with_shadow_dilation(mut self, thickness: f32) -> Self {
1620 self.shadow_dilation = thickness;
1621 self
1622 }
1623
1624 pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
1626 self.shadow_offset = offset;
1627 self
1628 }
1629
1630 pub fn with_skip_chars(mut self, chars: Vec<char>) -> Self {
1635 self.skip_chars = chars;
1636 self
1637 }
1638
1639 pub fn build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
1641 let style = &ctx.style;
1642
1643 if self.widget_builder.foreground.is_none() {
1644 self.widget_builder.foreground = Some(style.property(Style::BRUSH_TEXT));
1645 }
1646 if self.widget_builder.background.is_none() {
1647 self.widget_builder.background = Some(style.property(Style::BRUSH_DARKER));
1648 }
1649 if self.widget_builder.cursor.is_none() {
1650 self.widget_builder.cursor = Some(CursorIcon::Text);
1651 }
1652
1653 let text_box = TextBox {
1654 widget: self
1655 .widget_builder
1656 .with_accepts_input(true)
1657 .with_need_update(true)
1658 .build(ctx),
1659 caret_position: Position::default().into(),
1660 caret_visible: false.into(),
1661 blink_timer: 0.0.into(),
1662 blink_interval: 0.5.into(),
1663 formatted_text: RefCell::new(
1664 FormattedTextBuilder::new(self.font.unwrap_or_else(|| ctx.default_font()))
1665 .with_text(self.text)
1666 .with_horizontal_alignment(self.horizontal_alignment)
1667 .with_vertical_alignment(self.vertical_alignment)
1668 .with_wrap(self.wrap)
1669 .with_mask_char(self.mask_char)
1670 .with_shadow(self.shadow)
1671 .with_shadow_brush(self.shadow_brush)
1672 .with_shadow_dilation(self.shadow_dilation)
1673 .with_shadow_offset(self.shadow_offset)
1674 .with_font_size(
1675 self.font_size
1676 .unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
1677 )
1678 .build(),
1679 ),
1680 selection_range: None.into(),
1681 selecting: false,
1682 before_click_position: Position::default(),
1683 selection_brush: self.selection_brush.into(),
1684 caret_brush: self.caret_brush.into(),
1685 has_focus: false,
1686 filter: self.filter,
1687 commit_mode: self.commit_mode.into(),
1688 multiline: self.multiline.into(),
1689 editable: self.editable.into(),
1690 view_position: Default::default(),
1691 skip_chars: self.skip_chars.into(),
1692 recent: Default::default(),
1693 };
1694
1695 ctx.add_node(UiNode::new(text_box))
1696 }
1697}
1698
1699#[cfg(test)]
1700mod test {
1701 use crate::text_box::TextBoxBuilder;
1702 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
1703
1704 #[test]
1705 fn test_deletion() {
1706 test_widget_deletion(|ctx| TextBoxBuilder::new(WidgetBuilder::new()).build(ctx));
1707 }
1708}