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