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