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