fyrox_ui/
text_box.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! TextBox is a text widget that allows you to edit text and create specialized input fields. See [`TextBox`] docs for more
22//! info and usage examples.
23
24#![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/// A message that could be used to alternate text box widget's state or receive changes from it.
64///
65/// # Important notes
66///
67/// Text box widget also supports [`TextMessage`] and [`WidgetMessage`].
68#[derive(Debug, Clone, PartialEq)]
69pub enum TextBoxMessage {
70    /// Used to change selection brush of a text box. Use [TextBoxMessage::selection_brush`] to create the message.
71    SelectionBrush(Brush),
72    /// Used to change caret brush of a text box. Use [TextBoxMessage::caret_brush`] to create the message.
73    CaretBrush(Brush),
74    /// Used to change text commit mode of a text box. Use [TextBoxMessage::text_commit_mode`] to create the message.
75    TextCommitMode(TextCommitMode),
76    /// Used to enable or disable multiline mode of a text box. Use [TextBoxMessage::multiline`] to create the message.
77    Multiline(bool),
78    /// Used to enable or disable an ability to edit text box content. Use [TextBoxMessage::editable`] to create the message.
79    Editable(bool),
80}
81
82impl TextBoxMessage {
83    define_constructor!(
84        /// Creates [`TextBoxMessage::SelectionBrush`].
85        TextBoxMessage:SelectionBrush => fn selection_brush(Brush), layout: false
86    );
87    define_constructor!(
88        /// Creates [`TextBoxMessage::CaretBrush`].
89        TextBoxMessage:CaretBrush => fn caret_brush(Brush), layout: false
90    );
91    define_constructor!(
92        /// Creates [`TextBoxMessage::TextCommitMode`].
93        TextBoxMessage:TextCommitMode => fn text_commit_mode(TextCommitMode), layout: false
94    );
95    define_constructor!(
96        /// Creates [`TextBoxMessage::Multiline`].
97        TextBoxMessage:Multiline => fn multiline(bool), layout: false
98    );
99    define_constructor!(
100        /// Creates [`TextBoxMessage::Editable`].
101        TextBoxMessage:Editable => fn editable(bool), layout: false
102    );
103}
104
105/// Specifies a direction on horizontal axis.
106#[derive(Copy, Clone, PartialEq, Eq)]
107pub enum HorizontalDirection {
108    /// Left direction.
109    Left,
110    /// Right direction.
111    Right,
112}
113
114/// Specifies a direction on vertical axis.
115#[derive(Copy, Clone, PartialEq, Eq)]
116pub enum VerticalDirection {
117    /// Down direction.
118    Down,
119    /// Up direction.
120    Up,
121}
122
123pub use crate::formatted_text::Position;
124use crate::style::resource::StyleResourceExt;
125use crate::style::{Style, StyledProperty};
126
127/// Defines the way, how the text box widget will commit the text that was typed in
128#[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    /// Text box will immediately send [`TextMessage::Text`] message after any change (after any pressed button).
149    Immediate = 0,
150
151    /// Text box will send Text message only when it loses focus (when a user "clicks" outside of it or with any other
152    /// event that forces the text box to lose focus).
153    LostFocus = 1,
154
155    /// Text box will send Text message when it loses focus or if Enter key was pressed. This is **default** behavior.
156    ///
157    /// # Notes
158    ///
159    /// In case of multiline text box hitting Enter key won't commit the text!
160    #[default]
161    LostFocusPlusEnter = 2,
162
163    /// Text box will send Text message when it loses focus or if Enter key was pressed, but **only** if the content
164    /// of the text box changed since the last time it gained focus or the text was committed.
165    ///
166    /// # Notes
167    ///
168    /// In case of multiline text box hitting Enter key won't commit the text!
169    Changed = 3,
170}
171
172/// Defines a set of two positions in the text, that forms a specific range.
173#[derive(Copy, Clone, PartialEq, Eq, Debug, Visit, Reflect, Default, TypeUuidProvider)]
174#[type_uuid(id = "04c8101b-cb34-47a5-af34-ecfb9b2fc426")]
175pub struct SelectionRange {
176    /// Position of the beginning.
177    pub begin: Position,
178    /// Position of the end.
179    pub end: Position,
180}
181
182impl SelectionRange {
183    /// Creates a new range, that have its begin always before end. It could be useful in case if user
184    /// selects a range right-to-left.
185    #[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    /// Creates a Range iterator of the positions in this range from left to right, excluding the rightmost position.
193    pub fn range(&self) -> std::ops::Range<Position> {
194        std::ops::Range {
195            start: self.left(),
196            end: self.right(),
197        }
198    }
199    /// `true` if the given position is inside this range, including the beginning and end.
200    pub fn contains(&self, position: Position) -> bool {
201        (self.begin..=self.end).contains(&position)
202    }
203    /// The leftmost position.
204    pub fn left(&self) -> Position {
205        Position::min(self.begin, self.end)
206    }
207    /// The rightmost position.
208    pub fn right(&self) -> Position {
209        Position::max(self.begin, self.end)
210    }
211}
212
213/// Defines a function, that could be used to filter out desired characters. It must return `true` for characters, that pass
214/// the filter, and `false` - otherwise.
215pub type FilterCallback = dyn FnMut(char) -> bool + Send;
216
217/// TextBox is a text widget that allows you to edit text and create specialized input fields. It has various options like
218/// word wrapping, text alignment, and so on.
219///
220/// ## How to create
221///
222/// An instance of the TextBox widget could be created like so:
223///
224/// ```rust,no_run
225/// # use fyrox_ui::{
226/// #     core::pool::Handle,
227/// #     text_box::TextBoxBuilder, widget::WidgetBuilder, UiNode, UserInterface
228/// # };
229/// fn create_text_box(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
230///     TextBoxBuilder::new(WidgetBuilder::new())
231///         .with_text(text)
232///         .build(&mut ui.build_ctx())
233/// }
234/// ```
235///
236/// ## Text alignment and word wrapping
237///
238/// There are various text alignment options for both vertical and horizontal axes. Typical alignment values are:
239/// [`HorizontalAlignment::Left`], [`HorizontalAlignment::Center`], [`HorizontalAlignment::Right`] for horizontal axis,
240/// and [`VerticalAlignment::Top`], [`VerticalAlignment::Center`], [`VerticalAlignment::Bottom`] for vertical axis.
241/// An instance of centered text could be created like so:
242///
243/// ```rust,no_run
244/// # use fyrox_ui::{
245/// #     core::pool::Handle,
246/// #     text_box::TextBoxBuilder, widget::WidgetBuilder, HorizontalAlignment, UiNode, UserInterface,
247/// #     VerticalAlignment,
248/// # };
249/// fn create_centered_text(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
250///     TextBoxBuilder::new(WidgetBuilder::new())
251///         .with_horizontal_text_alignment(HorizontalAlignment::Center)
252///         .with_vertical_text_alignment(VerticalAlignment::Center)
253///     .with_text(text)
254///     .build(&mut ui.build_ctx())
255/// }
256/// ```
257///
258/// Long text is usually needs to wrap on available bounds, there are three possible options for word wrapping:
259/// [`WrapMode::NoWrap`], [`WrapMode::Letter`], [`WrapMode::Word`]. An instance of text with word-based wrapping could be
260/// created like so:
261///
262/// ```rust,no_run
263/// # use fyrox_ui::{
264/// #     core::pool::Handle,
265/// #     formatted_text::WrapMode, text_box::TextBoxBuilder, widget::WidgetBuilder, UiNode,
266/// #     UserInterface,
267/// # };
268/// fn create_text_with_word_wrap(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
269///     TextBoxBuilder::new(WidgetBuilder::new())
270///         .with_wrap(WrapMode::Word)
271///         .with_text(text)
272///         .build(&mut ui.build_ctx())
273/// }
274/// ```
275///
276/// ## Fonts and colors
277///
278/// To set a color of the text just use [`WidgetBuilder::with_foreground`] while building the text instance:
279///
280/// ```rust,no_run
281/// # use fyrox_ui::{
282/// #     core::{color::Color, pool::Handle},
283/// #     brush::Brush, text_box::TextBoxBuilder, widget::WidgetBuilder, UiNode, UserInterface
284/// # };
285/// fn create_text(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
286///     //                  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
287///     TextBoxBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED).into()))
288///         .with_text(text)
289///         .build(&mut ui.build_ctx())
290/// }
291/// ```
292///
293/// By default, text is created with default font, however it is possible to set any custom font:
294///
295/// ```rust,no_run
296/// # use fyrox_resource::manager::ResourceManager;
297/// # use fyrox_ui::{
298/// #     core::{futures::executor::block_on, pool::Handle},
299/// #     text_box::TextBoxBuilder,
300/// #     font::{Font},
301/// #     widget::WidgetBuilder,
302/// #     UiNode, UserInterface,
303/// # };
304///
305/// fn create_text(ui: &mut UserInterface, resource_manager: &ResourceManager, text: &str) -> Handle<UiNode> {
306///     TextBoxBuilder::new(WidgetBuilder::new())
307///         .with_font(resource_manager.request::<Font>("path/to/your/font.ttf"))
308///         .with_text(text)
309///         .with_font_size(20.0f32.into())
310///         .build(&mut ui.build_ctx())
311/// }
312/// ```
313///
314/// Please refer to [`FontResource`] to learn more about fonts.
315///
316/// ### Font size
317///
318/// Use [`TextBoxBuilder::with_font_size`] or send [`TextMessage::font_size`] to your TextBox widget instance
319/// to set the font size of it.
320///
321/// ## Messages
322///
323/// TextBox widget accepts the following list of messages:
324///
325/// - [`TextBoxMessage::SelectionBrush`] - change the brush that is used to highlight selection.
326/// - [`TextBoxMessage::CaretBrush`] - changes the brush of the caret (small blinking vertical line).
327/// - [`TextBoxMessage::TextCommitMode`] - changes the [text commit mode](TextBox#text-commit-mode).
328/// - [`TextBoxMessage::Multiline`] - makes the TextBox either multiline (`true`) or single line (`false`)
329/// - [`TextBoxMessage::Editable`] - enables or disables editing of the text.
330///
331/// **Important:** Please keep in mind, that TextBox widget also accepts [`TextMessage`]s. An example of changing text at
332/// runtime could be something like this:
333///
334/// ```rust,no_run
335/// # use fyrox_ui::{
336/// #     core::pool::Handle,
337/// #     message::{MessageDirection},
338/// #     UiNode, UserInterface,
339/// #     text::TextMessage
340/// # };
341/// fn request_change_text(ui: &UserInterface, text_box_widget_handle: Handle<UiNode>, text: &str) {
342///     ui.send_message(TextMessage::text(
343///         text_box_widget_handle,
344///         MessageDirection::ToWidget,
345///         text.to_owned(),
346///     ))
347/// }
348/// ```
349///
350/// Please keep in mind, that like any other situation when you "changing" something via messages, you should remember
351/// that the change is **not** immediate. The change will be applied on `ui.poll_message(..)` call somewhere in your
352/// code.
353///
354/// ## Shortcuts
355///
356/// There are number of default shortcuts that can be used to speed up text editing:
357///
358/// - `Ctrl+A` - select all
359/// - `Ctrl+C` - copy selected text
360/// - `Ctrl+V` - paste text from clipboard
361/// - `Ctrl+Home` - move caret to the beginning of the text
362/// - `Ctrl+End` - move caret to the beginning of the text
363/// - `Shift+Home` - select everything from current caret position until the beginning of current line
364/// - `Shift+End` - select everything from current caret position until the end of current line
365/// - `Arrows` - move caret accordingly
366/// - `Delete` - deletes next character
367/// - `Backspace` - deletes previous character
368/// - `Enter` - new line (if multiline mode is set) or `commit` message
369///
370/// ## Multiline Text Box
371///
372/// By default, text box will not add new line character to the text if you press `Enter` on keyboard. To enable this
373/// functionality use [`TextBoxBuilder::with_multiline`]
374///
375/// ## Read-only Mode
376///
377/// You can enable or disable content editing by using read-only mode. Use [`TextBoxBuilder::with_editable`] at build stage.
378///
379/// ## Mask Character
380///
381/// You can specify replacement character for every other characters, this is useful option for password fields. Use
382/// [`TextBoxBuilder::with_mask_char`] at build stage. For example, you can set replacement character to asterisk `*` using
383/// `.with_mask_char(Some('*'))`
384///
385/// ## Text Commit Mode
386///
387/// In many situations you don't need the text box to send `new text` message every new character, you either want this
388/// message if `Enter` key is pressed or TextBox has lost keyboard focus (or both). There is [`TextBoxBuilder::with_text_commit_mode`]
389/// on builder specifically for that purpose. Use one of the following modes:
390///
391/// - [`TextCommitMode::Immediate`] - text box will immediately send [`TextMessage::Text`] message after any change.
392/// - [`TextCommitMode::LostFocus`] - text box will send [`TextMessage::Text`] message only when it loses focus.
393/// - [`TextCommitMode::LostFocusPlusEnter`] - text box will send [`TextMessage::Text`] message when it loses focus or if Enter
394/// key was pressed. This is **default** behavior. In case of multiline text box hitting Enter key won't commit text!
395///
396/// ## Filtering
397///
398/// It is possible specify custom input filter, it can be useful if you're creating special input fields like numerical or
399/// phone number. A filter can be specified at build stage like so:
400///
401/// ```rust,no_run
402/// # use fyrox_ui::{
403/// #     core::pool::Handle,
404/// #     text_box::TextBoxBuilder, widget::WidgetBuilder, UiNode, UserInterface
405/// # };
406/// # use std::sync::Arc;
407/// # use fyrox_core::parking_lot::Mutex;
408/// fn create_text_box(ui: &mut UserInterface) -> Handle<UiNode> {
409///     TextBoxBuilder::new(WidgetBuilder::new())
410///         // Specify a filter that will pass only digits.
411///         .with_filter(Arc::new(Mutex::new(|c: char| c.is_ascii_digit())))
412///         .build(&mut ui.build_ctx())
413/// }
414/// ```
415///
416/// ## Style
417///
418/// You can change brush of caret by using [`TextBoxBuilder::with_caret_brush`] and also selection brush by using
419/// [`TextBoxBuilder::with_selection_brush`], it could be useful if you don't like default colors.
420#[derive(Default, Clone, Visit, Reflect, ComponentProvider)]
421#[reflect(derived_type = "UiNode")]
422pub struct TextBox {
423    /// Base widget of the text box.
424    pub widget: Widget,
425    /// Current position of the caret in the text box.
426    pub caret_position: InheritableVariable<Position>,
427    /// Whether the caret is visible or not.
428    pub caret_visible: InheritableVariable<bool>,
429    /// Internal blinking timer.
430    pub blink_timer: InheritableVariable<f32>,
431    /// Blinking interval in seconds.
432    pub blink_interval: InheritableVariable<f32>,
433    /// Formatted text that stores actual text and performs its layout. See [`FormattedText`] docs for more info.
434    pub formatted_text: RefCell<FormattedText>,
435    /// Current selection range.
436    pub selection_range: InheritableVariable<Option<SelectionRange>>,
437    /// `true` if the text box is in selection mode.
438    pub selecting: bool,
439    /// Stores the location of the caret before it was moved by mouse click.
440    #[visit(skip)]
441    pub before_click_position: Position,
442    /// `true` if the text box is focused.
443    pub has_focus: bool,
444    /// Current caret brush of the text box.
445    pub caret_brush: InheritableVariable<Brush>,
446    /// Current selection brush of the text box.
447    pub selection_brush: InheritableVariable<Brush>,
448    /// Current character filter of the text box.
449    #[visit(skip)]
450    #[reflect(hidden)]
451    pub filter: Option<Arc<Mutex<FilterCallback>>>,
452    /// Current text commit mode of the text box.
453    pub commit_mode: InheritableVariable<TextCommitMode>,
454    /// `true` if the the multiline mode is active.
455    pub multiline: InheritableVariable<bool>,
456    /// `true` if the text box is editable.
457    pub editable: InheritableVariable<bool>,
458    /// Position of the local "camera" (viewing rectangle) of the text box.
459    pub view_position: InheritableVariable<Vector2<f32>>,
460    /// A list of custom characters that will be treated as whitespace.
461    pub skip_chars: InheritableVariable<Vec<char>>,
462    /// Stored copy of most recent commit, when `commit_mode` is [TextCommitMode::Changed].
463    #[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    /// Maps input [`Position`] to a linear position in character array.
592    /// The index returned is the index of the character after the position, which may be
593    /// out-of-bounds if thee position is at the end of the text.
594    /// You should check the index before trying to use it to fetch data from inner array of characters.
595    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    /// Maps input [`Position`] to a linear position in character array.
602    /// The index returned is usually the index of the character after the position,
603    /// but if the position is at the end of a line then return the index of the character _before_ the position.
604    /// In other words, the last two positions of each line are mapped to the same character index.
605    /// Output index will always be valid for fetching, if the method returned `Some(index)`.
606    /// The index however cannot be used for text insertion, because it cannot point to a "place after last char".
607    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    /// Maps linear character index (as in string) to its actual location in the text.
614    pub fn char_index_to_position(&self, i: usize) -> Option<Position> {
615        self.formatted_text.borrow().char_index_to_position(i)
616    }
617
618    /// Returns end position of the text.
619    pub fn end_position(&self) -> Position {
620        self.formatted_text.borrow().end_position()
621    }
622
623    /// Returns a position of a next word after the caret in the text.
624    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    /// Returns a position of a next word before the caret in the text.
641    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    /// Inserts given character at current caret position.
659    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    /// Returns current text length in characters.
728    pub fn get_text_len(&self) -> usize {
729        self.formatted_text.borrow_mut().get_raw_text().len()
730    }
731
732    /// Returns current position the caret in the local coordinates.
733    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        // Move view position to contain the caret + add some spacing.
752        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    /// Checks whether the input position is correct (in bounds) or not.
844    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    /// Tries to map screen space position to a position in the text.
863    pub fn screen_pos_to_text_pos(&self, screen_point: Vector2<f32>) -> Option<Position> {
864        // Transform given point into local space of the text box - this way calculations can be done
865        // as usual, without a need for special math.
866        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    /// Returns current text of text box.
880    pub fn text(&self) -> String {
881        self.formatted_text.borrow().text()
882    }
883
884    /// Returns current word wrapping mode of text box.
885    pub fn wrap_mode(&self) -> WrapMode {
886        self.formatted_text.borrow().wrap_mode()
887    }
888
889    /// Returns current font of text box.
890    pub fn font(&self) -> FontResource {
891        self.formatted_text.borrow().get_font()
892    }
893
894    /// Returns current vertical alignment of text box.
895    pub fn vertical_alignment(&self) -> VerticalAlignment {
896        self.formatted_text.borrow().vertical_alignment()
897    }
898
899    /// Returns current horizontal alignment of text box.
900    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                    // Begin line
992                    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                            // Begin line
1011                            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                            // End line
1025                            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                            // Everything between
1036                            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                                // Don't set has_focus = false when enter is pressed.
1166                                // That messes up keyboard navigation.
1167                            }
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                        // TextBox "eats" all input by default, some of the keys are used for input control while
1252                        // others are used directly to enter text.
1253                        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                            // There is no reason to keep the stored recent value in memory
1295                            // while this TextBox does not have focus. Maybe this should be stored globally in UserInterface,
1296                            // since we only ever need one.
1297                            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
1498/// Text box builder creates new [`TextBox`] instances and adds them to the user interface.
1499pub 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    /// Creates new text box widget builder with the base widget builder specified.
1523    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    /// Sets the desired font of the text box.
1548    pub fn with_font(mut self, font: FontResource) -> Self {
1549        self.font = Some(font);
1550        self
1551    }
1552
1553    /// Sets the desired text of the text box.
1554    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    /// Sets the desired caret brush of the text box.
1560    pub fn with_caret_brush(mut self, brush: Brush) -> Self {
1561        self.caret_brush = brush;
1562        self
1563    }
1564
1565    /// Sets the desired selection brush of the text box.
1566    pub fn with_selection_brush(mut self, brush: Brush) -> Self {
1567        self.selection_brush = brush;
1568        self
1569    }
1570
1571    /// Sets the desired character filter of the text box. See [`FilterCallback`] for more info.
1572    pub fn with_filter(mut self, filter: Arc<Mutex<FilterCallback>>) -> Self {
1573        self.filter = Some(filter);
1574        self
1575    }
1576
1577    /// Sets the desired vertical text alignment of the text box.
1578    pub fn with_vertical_text_alignment(mut self, alignment: VerticalAlignment) -> Self {
1579        self.vertical_alignment = alignment;
1580        self
1581    }
1582
1583    /// Sets the desired horizontal text alignment of the text box.
1584    pub fn with_horizontal_text_alignment(mut self, alignment: HorizontalAlignment) -> Self {
1585        self.horizontal_alignment = alignment;
1586        self
1587    }
1588
1589    /// Sets the desired word wrapping of the text box.
1590    pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
1591        self.wrap = wrap;
1592        self
1593    }
1594
1595    /// Sets the desired text commit mode of the text box.
1596    pub fn with_text_commit_mode(mut self, mode: TextCommitMode) -> Self {
1597        self.commit_mode = mode;
1598        self
1599    }
1600
1601    /// Enables or disables multiline mode of the text box.
1602    pub fn with_multiline(mut self, multiline: bool) -> Self {
1603        self.multiline = multiline;
1604        self
1605    }
1606
1607    /// Enables or disables editing of the text box.
1608    pub fn with_editable(mut self, editable: bool) -> Self {
1609        self.editable = editable;
1610        self
1611    }
1612
1613    /// Sets the desired height of the text.
1614    pub fn with_font_size(mut self, font_size: StyledProperty<f32>) -> Self {
1615        self.font_size = Some(font_size);
1616        self
1617    }
1618
1619    /// Sets the desired masking character of the text box.
1620    pub fn with_mask_char(mut self, mask_char: Option<char>) -> Self {
1621        self.mask_char = mask_char;
1622        self
1623    }
1624
1625    /// Whether the shadow enabled or not.
1626    pub fn with_shadow(mut self, shadow: bool) -> Self {
1627        self.shadow = shadow;
1628        self
1629    }
1630
1631    /// Sets desired shadow brush. It will be used to render the shadow.
1632    pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
1633        self.shadow_brush = brush;
1634        self
1635    }
1636
1637    /// Sets desired shadow dilation in units. Keep in mind that the dilation is absolute,
1638    /// not percentage-based.
1639    pub fn with_shadow_dilation(mut self, thickness: f32) -> Self {
1640        self.shadow_dilation = thickness;
1641        self
1642    }
1643
1644    /// Sets desired shadow offset in units.
1645    pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
1646        self.shadow_offset = offset;
1647        self
1648    }
1649
1650    /// Sets desired set of characters that will be treated like whitespace during Ctrl+Arrow navigation
1651    /// (Ctrl+Left Arrow and Ctrl+Right Arrow). This could be useful to treat underscores like whitespaces,
1652    /// which in its turn could be useful for in-game consoles where commands usually separated using
1653    /// underscores (`like_this_one`).
1654    pub fn with_skip_chars(mut self, chars: Vec<char>) -> Self {
1655        self.skip_chars = chars;
1656        self
1657    }
1658
1659    /// Creates a new [`TextBox`] instance and adds it to the user interface.
1660    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}