Skip to main content

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::{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/// A message that could be used to alternate text box widget's state or receive changes from it.
63///
64/// # Important notes
65///
66/// Text box widget also supports [`TextMessage`] and [`WidgetMessage`].
67#[derive(Debug, Clone, PartialEq)]
68pub enum TextBoxMessage {
69    /// Used to change selection brush of a text box.
70    SelectionBrush(Brush),
71    /// Used to change caret brush of a text box.
72    CaretBrush(Brush),
73    /// Used to change text commit mode of a text box.
74    TextCommitMode(TextCommitMode),
75    /// Used to enable or disable multiline mode of a text box.
76    Multiline(bool),
77    /// Used to enable or disable the ability to edit text box content.
78    Editable(bool),
79    /// Used to set new padding for a text box.
80    Padding(Thickness),
81    /// Used to set new corner radius.
82    CornerRadius(f32),
83}
84impl MessageData for TextBoxMessage {}
85
86/// Specifies a direction on horizontal axis.
87#[derive(Copy, Clone, PartialEq, Eq)]
88pub enum HorizontalDirection {
89    /// Left direction.
90    Left,
91    /// Right direction.
92    Right,
93}
94
95/// Specifies a direction on vertical axis.
96#[derive(Copy, Clone, PartialEq, Eq)]
97pub enum VerticalDirection {
98    /// Down direction.
99    Down,
100    /// Up direction.
101    Up,
102}
103
104pub use crate::formatted_text::Position;
105
106/// Defines the way, how the text box widget will commit the text that was typed in
107#[derive(
108    Copy,
109    Clone,
110    PartialOrd,
111    PartialEq,
112    Eq,
113    Ord,
114    Hash,
115    Debug,
116    Default,
117    Visit,
118    Reflect,
119    AsRefStr,
120    EnumString,
121    VariantNames,
122    TypeUuidProvider,
123)]
124#[repr(u32)]
125#[type_uuid(id = "5fb7d6f0-c151-4a30-8350-2060749d74c6")]
126pub enum TextCommitMode {
127    /// Text box will immediately send [`TextMessage::Text`] message after any change (after any pressed button).
128    Immediate = 0,
129
130    /// Text box will send [`TextMessage::Text`] message only when it loses focus (when a user "clicks" outside of it or with any other
131    /// event that forces the text box to lose focus).
132    LostFocus = 1,
133
134    /// Text box will send [`TextMessage::Text`] message when it loses focus or if `Enter` key was pressed. This is **default** behavior.
135    ///
136    /// # Notes
137    ///
138    /// In case of multiline text box hitting Enter key won't commit the text!
139    #[default]
140    LostFocusPlusEnter = 2,
141
142    /// Text box will send Text message when it loses focus or if `Enter` key was pressed, but **only** if the content
143    /// of the text box changed since the last time it gained focus or the text was committed.
144    ///
145    /// # Notes
146    ///
147    /// In case of multiline text box hitting `Enter` key won't commit the text!
148    Changed = 3,
149}
150
151/// Defines a set of two positions in the text, that forms a specific range.
152#[derive(Copy, Clone, PartialEq, Eq, Debug, Visit, Reflect, Default, TypeUuidProvider)]
153#[type_uuid(id = "04c8101b-cb34-47a5-af34-ecfb9b2fc426")]
154pub struct SelectionRange {
155    /// Position of the beginning.
156    pub begin: Position,
157    /// Position of the end.
158    pub end: Position,
159}
160
161impl SelectionRange {
162    /// Creates a new range, that have its beginning always before end. It could be useful in case if a user
163    /// selects a range right-to-left.
164    #[must_use = "method creates new value which must be used"]
165    pub fn normalized(&self) -> SelectionRange {
166        SelectionRange {
167            begin: self.left(),
168            end: self.right(),
169        }
170    }
171    /// Creates a Range iterator of the positions in this range from left to right, excluding the rightmost position.
172    pub fn range(&self) -> std::ops::Range<Position> {
173        std::ops::Range {
174            start: self.left(),
175            end: self.right(),
176        }
177    }
178    /// `true` if the given position is inside this range, including the beginning and end.
179    pub fn contains(&self, position: Position) -> bool {
180        (self.begin..=self.end).contains(&position)
181    }
182    /// The leftmost position.
183    pub fn left(&self) -> Position {
184        Position::min(self.begin, self.end)
185    }
186    /// The rightmost position.
187    pub fn right(&self) -> Position {
188        Position::max(self.begin, self.end)
189    }
190
191    /// Returns `true` if beginning of the selection equals to its ending.
192    pub fn is_collapsed(&self) -> bool {
193        self.begin == self.end
194    }
195}
196
197/// Defines a function, that could be used to filter out desired characters. It must return `true` for characters, that pass
198/// the filter, and `false` - otherwise.
199pub type FilterCallback = dyn FnMut(char) -> bool + Send;
200
201/// TextBox is a text widget that allows you to edit text and create specialized input fields. It has various options like
202/// word wrapping, text alignment, and so on.
203///
204/// ## How to create
205///
206/// An instance of the TextBox widget could be created like so:
207///
208/// ```rust,no_run
209/// # use fyrox_ui::{
210/// #     core::pool::Handle,
211/// #     text_box::{TextBox, TextBoxBuilder}, widget::WidgetBuilder, UiNode, UserInterface
212/// # };
213/// fn create_text_box(ui: &mut UserInterface, text: &str) -> Handle<TextBox> {
214///     TextBoxBuilder::new(WidgetBuilder::new())
215///         .with_text(text)
216///         .build(&mut ui.build_ctx())
217/// }
218/// ```
219///
220/// ## Text alignment and word wrapping
221///
222/// There are various text alignment options for both vertical and horizontal axes. Typical alignment values are:
223/// [`HorizontalAlignment::Left`], [`HorizontalAlignment::Center`], [`HorizontalAlignment::Right`] for horizontal axis,
224/// and [`VerticalAlignment::Top`], [`VerticalAlignment::Center`], [`VerticalAlignment::Bottom`] for vertical axis.
225/// An instance of centered text could be created like so:
226///
227/// ```rust,no_run
228/// # use fyrox_ui::{
229/// #     core::pool::Handle,
230/// #     text_box::{TextBox, TextBoxBuilder}, widget::WidgetBuilder, HorizontalAlignment, UiNode, UserInterface,
231/// #     VerticalAlignment,
232/// # };
233/// fn create_centered_text(ui: &mut UserInterface, text: &str) -> Handle<TextBox> {
234///     TextBoxBuilder::new(WidgetBuilder::new())
235///         .with_horizontal_text_alignment(HorizontalAlignment::Center)
236///         .with_vertical_text_alignment(VerticalAlignment::Center)
237///     .with_text(text)
238///     .build(&mut ui.build_ctx())
239/// }
240/// ```
241///
242/// Long text is usually needs to wrap on available bounds, there are three possible options for word wrapping:
243/// [`WrapMode::NoWrap`], [`WrapMode::Letter`], [`WrapMode::Word`]. An instance of text with word-based wrapping could be
244/// created like so:
245///
246/// ```rust,no_run
247/// # use fyrox_ui::{
248/// #     core::pool::Handle,
249/// #     formatted_text::WrapMode, text_box::{TextBox, TextBoxBuilder}, widget::WidgetBuilder, UiNode,
250/// #     UserInterface,
251/// # };
252/// fn create_text_with_word_wrap(ui: &mut UserInterface, text: &str) -> Handle<TextBox> {
253///     TextBoxBuilder::new(WidgetBuilder::new())
254///         .with_wrap(WrapMode::Word)
255///         .with_text(text)
256///         .build(&mut ui.build_ctx())
257/// }
258/// ```
259///
260/// ## Fonts and colors
261///
262/// To set a color of the text, just use [`WidgetBuilder::with_foreground`] while building the text instance:
263///
264/// ```rust,no_run
265/// # use fyrox_ui::{
266/// #     core::{color::Color, pool::Handle},
267/// #     brush::Brush, text_box::{TextBox, TextBoxBuilder}, widget::WidgetBuilder, UiNode, UserInterface
268/// # };
269/// fn create_text(ui: &mut UserInterface, text: &str) -> Handle<TextBox> {
270///     //                  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
271///     TextBoxBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED).into()))
272///         .with_text(text)
273///         .build(&mut ui.build_ctx())
274/// }
275/// ```
276///
277/// By default, text is created with default font, however it is possible to set any custom font:
278///
279/// ```rust,no_run
280/// # use fyrox_resource::manager::ResourceManager;
281/// # use fyrox_ui::{
282/// #     core::{futures::executor::block_on, pool::Handle},
283/// #     text_box::{TextBox, TextBoxBuilder},
284/// #     font::{Font},
285/// #     widget::WidgetBuilder,
286/// #     UiNode, UserInterface,
287/// # };
288///
289/// fn create_text(ui: &mut UserInterface, resource_manager: &ResourceManager, text: &str) -> Handle<TextBox> {
290///     TextBoxBuilder::new(WidgetBuilder::new())
291///         .with_font(resource_manager.request::<Font>("path/to/your/font.ttf"))
292///         .with_text(text)
293///         .with_font_size(20.0f32.into())
294///         .build(&mut ui.build_ctx())
295/// }
296/// ```
297///
298/// Please refer to [`FontResource`] to learn more about fonts.
299///
300/// ### Font size
301///
302/// Use [`TextBoxBuilder::with_font_size`] or send [`TextMessage::FontSize`] to your TextBox widget instance
303/// to set the font size of it.
304///
305/// ## Messages
306///
307/// TextBox widget accepts the following list of messages:
308///
309/// - [`TextBoxMessage::SelectionBrush`] - change the brush that is used to highlight selection.
310/// - [`TextBoxMessage::CaretBrush`] - changes the brush of the caret (small blinking vertical line).
311/// - [`TextBoxMessage::TextCommitMode`] - changes the [text commit mode](TextBox#text-commit-mode).
312/// - [`TextBoxMessage::Multiline`] - makes the TextBox either multiline (`true`) or single line (`false`)
313/// - [`TextBoxMessage::Editable`] - enables or disables editing of the text.
314///
315/// **Important:** Please keep in mind, that TextBox widget also accepts [`TextMessage`]s. An example of changing text at
316/// runtime could be something like this:
317///
318/// ```rust,no_run
319/// # use fyrox_ui::{
320/// #     core::pool::Handle,
321/// #     UiNode, UserInterface,
322/// #     text::TextMessage
323/// # };
324/// fn request_change_text(ui: &UserInterface, text_box_widget_handle: Handle<UiNode>, text: &str) {
325///     ui.send(text_box_widget_handle, TextMessage::Text(text.to_owned()))
326/// }
327/// ```
328///
329/// Please keep in mind, that like any other situation when you "changing" something via messages, you should remember
330/// that the change is **not** immediate. The change will be applied on `ui.poll_message(...)` call somewhere in your
331/// code.
332///
333/// ## Shortcuts
334///
335/// There are a number of default shortcuts that can be used to speed up text editing:
336///
337/// - `Ctrl+A` - select all
338/// - `Ctrl+C` - copy selected text
339/// - `Ctrl+V` - paste text from clipboard
340/// - `Ctrl+Home` - move caret to the beginning of the text
341/// - `Ctrl+End` - move caret to the beginning of the text
342/// - `Shift+Home` - select everything from current caret position until the beginning of current line
343/// - `Shift+End` - select everything from current caret position until the end of current line
344/// - `Arrows` - move caret accordingly
345/// - `Delete` - deletes next character
346/// - `Backspace` - deletes previous character
347/// - `Enter` - new line (if multiline mode is set) or `commit` message
348///
349/// ## Multiline Text Box
350///
351/// By default, text box will not add a new line character to the text if you press `Enter` on keyboard. To enable this
352/// functionality use [`TextBoxBuilder::with_multiline`]
353///
354/// ## Read-only Mode
355///
356/// You can enable or disable content editing by using read-only mode. Use [`TextBoxBuilder::with_editable`] at the build stage.
357///
358/// ## Mask Character
359///
360/// You can specify replacement character for every other characters, this is a useful option for password fields. Use
361/// [`TextBoxBuilder::with_mask_char`] at build stage. For example, you can set replacement character to asterisk `*` using
362/// `.with_mask_char(Some('*'))`
363///
364/// ## Text Commit Mode
365///
366/// In many situations you don't need the text box to send `new text` message every new character, you either want this
367/// message if `Enter` key is pressed or TextBox has lost keyboard focus (or both). There is [`TextBoxBuilder::with_text_commit_mode`]
368/// on builder specifically for that purpose. Use one of the following modes:
369///
370/// - [`TextCommitMode::Immediate`] - text box will immediately send [`TextMessage::Text`] message after any change.
371/// - [`TextCommitMode::LostFocus`] - text box will send [`TextMessage::Text`] message only when it loses focus.
372/// - [`TextCommitMode::LostFocusPlusEnter`] - text box will send [`TextMessage::Text`] message when it loses focus or if Enter
373/// key was pressed. This is **default** behavior. In case of multiline text box hitting Enter key won't commit text!
374///
375/// ## Filtering
376///
377/// It is possible to specify custom input filter, it can be useful if you're creating special input fields like numerical or
378/// phone number. A filter can be specified at build stage like so:
379///
380/// ```rust,no_run
381/// # use fyrox_ui::{
382/// #     core::pool::Handle,
383/// #     text_box::{TextBox, TextBoxBuilder}, widget::WidgetBuilder, UiNode, UserInterface
384/// # };
385/// # use std::sync::Arc;
386/// # use fyrox_core::parking_lot::Mutex;
387/// fn create_text_box(ui: &mut UserInterface) -> Handle<TextBox> {
388///     TextBoxBuilder::new(WidgetBuilder::new())
389///         // Specify a filter that will pass only digits.
390///         .with_filter(Arc::new(Mutex::new(|c: char| c.is_ascii_digit())))
391///         .build(&mut ui.build_ctx())
392/// }
393/// ```
394///
395/// ## Style
396///
397/// You can change brush of caret by using [`TextBoxBuilder::with_caret_brush`] and also selection brush by using
398/// [`TextBoxBuilder::with_selection_brush`], it could be useful if you don't like default colors.
399#[derive(Default, Clone, Visit, Reflect, ComponentProvider)]
400#[reflect(derived_type = "UiNode")]
401pub struct TextBox {
402    /// Base widget of the text box.
403    pub widget: Widget,
404    /// Current position of the caret in the text box.
405    pub caret_position: InheritableVariable<Position>,
406    /// Whether the caret is visible or not.
407    pub caret_visible: InheritableVariable<bool>,
408    /// Internal blinking timer.
409    pub blink_timer: InheritableVariable<f32>,
410    /// Blinking interval in seconds.
411    pub blink_interval: InheritableVariable<f32>,
412    /// Formatted text that stores actual text and performs its layout. See [`FormattedText`] docs for more info.
413    pub formatted_text: RefCell<FormattedText>,
414    /// Current selection range.
415    pub selection_range: InheritableVariable<Option<SelectionRange>>,
416    /// `true` if the text box is in selection mode.
417    pub selecting: bool,
418    /// Stores the location of the caret before it was moved by mouse click.
419    #[visit(skip)]
420    pub before_click_position: Position,
421    /// `true` if the text box is focused.
422    pub has_focus: bool,
423    /// Current caret brush of the text box.
424    pub caret_brush: InheritableVariable<Brush>,
425    /// Current selection brush of the text box.
426    pub selection_brush: InheritableVariable<Brush>,
427    /// Current character filter of the text box.
428    #[visit(skip)]
429    #[reflect(hidden)]
430    pub filter: Option<Arc<Mutex<FilterCallback>>>,
431    /// Current text commit mode of the text box.
432    pub commit_mode: InheritableVariable<TextCommitMode>,
433    /// `true` if the multiline mode is active.
434    pub multiline: InheritableVariable<bool>,
435    /// `true` if the text box is editable.
436    pub editable: InheritableVariable<bool>,
437    /// Position of the local "camera" (viewing rectangle) of the text box.
438    pub view_position: InheritableVariable<Vector2<f32>>,
439    /// A list of custom characters that will be treated as whitespace.
440    pub skip_chars: InheritableVariable<Vec<char>>,
441    /// Stored copy of most recent commit, when `commit_mode` is [TextCommitMode::Changed].
442    #[visit(skip)]
443    #[reflect(hidden)]
444    pub recent: Vec<char>,
445    /// Placeholder widget when the text box is empty.
446    #[visit(optional)]
447    pub placeholder: Handle<UiNode>,
448    /// Corner radius of the text box.
449    #[visit(optional)]
450    pub corner_radius: InheritableVariable<f32>,
451}
452
453impl ConstructorProvider<UiNode, UserInterface> for TextBox {
454    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
455        GraphNodeConstructor::new::<Self>()
456            .with_variant("Text Box", |ui| {
457                TextBoxBuilder::new(WidgetBuilder::new().with_name("Text Box"))
458                    .with_text("Text")
459                    .build(&mut ui.build_ctx())
460                    .to_base()
461                    .into()
462            })
463            .with_group("Input")
464    }
465}
466
467impl Debug for TextBox {
468    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
469        f.write_str("TextBox")
470    }
471}
472
473crate::define_widget_deref!(TextBox);
474
475impl TextBox {
476    fn commit_if_changed(&mut self, ui: &mut UserInterface) {
477        let formatted_text = self.formatted_text.borrow();
478        let raw = formatted_text.get_raw_text();
479        if self.recent != raw {
480            self.recent.clear();
481            self.recent.extend(raw);
482            ui.post(self.handle, TextMessage::Text(formatted_text.text()));
483        }
484    }
485    fn filter_paste_str_multiline(&self, str: &str) -> String {
486        let mut str = str.replace("\r\n", "\n");
487        str.retain(|c| c == '\n' || !c.is_control());
488        if let Some(filter) = self.filter.as_ref() {
489            let filter = &mut *filter.safe_lock();
490            str.retain(filter);
491        }
492        str
493    }
494
495    fn filter_paste_str_single_line(&self, str: &str) -> String {
496        let mut str: String = str
497            .chars()
498            .map(|c| if c == '\n' { ' ' } else { c })
499            .filter(|c| !c.is_control())
500            .collect();
501        if let Some(filter) = self.filter.as_ref() {
502            let filter = &mut *filter.safe_lock();
503            str.retain(filter);
504        }
505        str
506    }
507
508    fn reset_blink(&mut self) {
509        self.caret_visible.set_value_and_mark_modified(true);
510        self.blink_timer.set_value_and_mark_modified(0.0);
511    }
512
513    fn move_caret(&mut self, position: Position, select: bool) {
514        let text = self.formatted_text.borrow();
515        let lines = text.get_lines();
516        if select && !lines.is_empty() {
517            if self.selection_range.is_none() {
518                self.selection_range
519                    .set_value_and_mark_modified(Some(SelectionRange {
520                        begin: *self.caret_position,
521                        end: *self.caret_position,
522                    }));
523                self.invalidate_visual();
524            }
525        } else {
526            self.selection_range.set_value_and_mark_modified(None);
527            self.invalidate_visual();
528        }
529
530        if lines.is_empty() {
531            drop(text);
532            self.set_caret_position(Default::default());
533            return;
534        }
535
536        drop(text);
537
538        if let Some(selection_range) = self.selection_range.as_mut() {
539            if select {
540                if *self.caret_position != selection_range.end {
541                    if !selection_range.contains(position) {
542                        if position < selection_range.left() {
543                            selection_range.begin = selection_range.right();
544                        } else {
545                            selection_range.begin = selection_range.left();
546                        }
547                        selection_range.end = position;
548                    }
549                } else {
550                    selection_range.end = position;
551                }
552            }
553        }
554
555        self.set_caret_position(position);
556        self.ensure_caret_visible();
557    }
558
559    fn move_caret_x(&mut self, offset: isize, select: bool) {
560        let pos = self
561            .formatted_text
562            .borrow()
563            .get_relative_position_x(*self.caret_position, offset);
564        self.move_caret(pos, select);
565    }
566
567    fn move_caret_y(&mut self, offset: isize, select: bool) {
568        let pos = self
569            .formatted_text
570            .borrow()
571            .get_relative_position_y(*self.caret_position, offset);
572        self.move_caret(pos, select);
573    }
574
575    /// Maps input [`Position`] to a linear position in character array.
576    /// The index returned is the index of the character after the position, which may be
577    /// out-of-bounds if the position is at the end of the text.
578    /// You should check the index before trying to use it to fetch data from inner array of characters.
579    pub fn position_to_char_index_unclamped(&self, position: Position) -> Option<usize> {
580        self.formatted_text
581            .borrow()
582            .position_to_char_index_unclamped(position)
583    }
584
585    /// Maps input [`Position`] to a linear position in character array.
586    /// The index returned is usually the index of the character after the position,
587    /// but if the position is at the end of a line, then return the index of the character _before_ the position.
588    /// In other words, the last two positions of each line are mapped to the same character index.
589    /// Output index will always be valid for fetching if the method returned `Some(index)`.
590    /// The index, however, cannot be used for text insertion because it cannot point to a "place after last char".
591    pub fn position_to_char_index_clamped(&self, position: Position) -> Option<usize> {
592        self.formatted_text
593            .borrow()
594            .position_to_char_index_clamped(position)
595    }
596
597    /// Maps linear character index (as in string) to its actual location in the text.
598    pub fn char_index_to_position(&self, i: usize) -> Option<Position> {
599        self.formatted_text.borrow().char_index_to_position(i)
600    }
601
602    /// Returns end position of the text.
603    pub fn end_position(&self) -> Position {
604        self.formatted_text.borrow().end_position()
605    }
606
607    /// Returns a position of the next word after the caret in the text.
608    pub fn find_next_word(&self, from: Position) -> Position {
609        self.position_to_char_index_unclamped(from)
610            .and_then(|i| {
611                self.formatted_text
612                    .borrow()
613                    .get_raw_text()
614                    .iter()
615                    .enumerate()
616                    .skip(i)
617                    .skip_while(|(_, c)| !(c.is_whitespace() || self.skip_chars.contains(*c)))
618                    .find(|(_, c)| !(c.is_whitespace() || self.skip_chars.contains(*c)))
619                    .and_then(|(n, _)| self.char_index_to_position(n))
620            })
621            .unwrap_or_else(|| self.end_position())
622    }
623
624    /// Returns a position of a next word before the caret in the text.
625    pub fn find_prev_word(&self, from: Position) -> Position {
626        self.position_to_char_index_unclamped(from)
627            .and_then(|i| {
628                let text = self.formatted_text.borrow();
629                let len = text.get_raw_text().len();
630                text.get_raw_text()
631                    .iter()
632                    .enumerate()
633                    .rev()
634                    .skip(len.saturating_sub(i))
635                    .skip_while(|(_, c)| !(c.is_whitespace() || self.skip_chars.contains(*c)))
636                    .find(|(_, c)| !(c.is_whitespace() || self.skip_chars.contains(*c)))
637                    .and_then(|(n, _)| self.char_index_to_position(n + 1))
638            })
639            .unwrap_or_default()
640    }
641
642    /// Inserts given character at current caret position.
643    fn insert_char(&mut self, c: char, ui: &UserInterface) {
644        self.remove_before_insert();
645        let position = self
646            .position_to_char_index_unclamped(*self.caret_position)
647            .unwrap_or_default();
648        self.formatted_text
649            .borrow_mut()
650            .insert_char(c, position)
651            .measure_and_arrange();
652        self.set_caret_position(
653            self.char_index_to_position(position + 1)
654                .unwrap_or_default(),
655        );
656        self.invalidate_layout();
657
658        self.on_text_changed(ui);
659    }
660
661    fn insert_str(&mut self, str: &str, ui: &UserInterface) {
662        if str.is_empty() {
663            return;
664        }
665        let str: String = if *self.multiline {
666            self.filter_paste_str_multiline(str)
667        } else {
668            self.filter_paste_str_single_line(str)
669        };
670        self.remove_before_insert();
671        let position = self
672            .position_to_char_index_unclamped(*self.caret_position)
673            .unwrap_or_default();
674        let mut text = self.formatted_text.borrow_mut();
675        text.insert_str(&str, position);
676        text.measure();
677        drop(text);
678        self.set_caret_position(
679            self.char_index_to_position(position + str.chars().count())
680                .unwrap_or_default(),
681        );
682        self.invalidate_layout();
683        self.on_text_changed(ui);
684    }
685
686    fn remove_before_insert(&mut self) {
687        let Some(selection) = *self.selection_range else {
688            return;
689        };
690        let range = self
691            .formatted_text
692            .borrow()
693            .position_range_to_char_index_range(selection.range());
694        if range.is_empty() {
695            return;
696        }
697        self.formatted_text.borrow_mut().remove_range(range);
698        self.selection_range.set_value_and_mark_modified(None);
699        self.set_caret_position(selection.left());
700        self.invalidate_layout();
701    }
702
703    /// Returns current text length in characters.
704    pub fn get_text_len(&self) -> usize {
705        self.formatted_text.borrow().get_raw_text().len()
706    }
707
708    /// Returns current position the caret in the local coordinates.
709    pub fn caret_local_position(&self) -> Vector2<f32> {
710        self.formatted_text
711            .borrow()
712            .position_to_local(*self.caret_position)
713    }
714
715    fn point_to_view_pos(&self, position: Vector2<f32>) -> Vector2<f32> {
716        position - *self.view_position
717    }
718
719    fn rect_to_view_pos(&self, mut rect: Rect<f32>) -> Rect<f32> {
720        rect.position -= *self.view_position;
721        rect
722    }
723
724    fn ensure_caret_visible(&mut self) {
725        let local_bounds = self.bounding_rect();
726        let caret_view_position = self.point_to_view_pos(self.caret_local_position());
727        // Move view position to contain the caret + add some spacing.
728        let spacing_step = self
729            .formatted_text
730            .borrow()
731            .get_font()
732            .state()
733            .data()
734            .map(|font| font.ascender(*self.height))
735            .unwrap_or_default();
736        let spacing = spacing_step * 3.0;
737        let top_left_corner = local_bounds.left_top_corner();
738        let bottom_right_corner = local_bounds.right_bottom_corner();
739        if caret_view_position.x > bottom_right_corner.x {
740            self.view_position.x += caret_view_position.x - bottom_right_corner.x + spacing;
741        }
742        if caret_view_position.x < top_left_corner.x {
743            self.view_position.x -= top_left_corner.x - caret_view_position.x + spacing;
744        }
745        if caret_view_position.y > bottom_right_corner.y {
746            self.view_position.y += bottom_right_corner.y - caret_view_position.y + spacing;
747        }
748        if caret_view_position.y < top_left_corner.y {
749            self.view_position.y -= top_left_corner.y - caret_view_position.y + spacing;
750        }
751        self.view_position.x = self.view_position.x.max(0.0);
752        self.view_position.y = self.view_position.y.max(0.0);
753    }
754
755    fn remove_char(&mut self, direction: HorizontalDirection, ui: &UserInterface) {
756        if let Some(selection) = *self.selection_range {
757            if !selection.is_collapsed() {
758                self.remove_range(ui, selection);
759                return;
760            }
761        }
762        let Some(position) = self.position_to_char_index_unclamped(*self.caret_position) else {
763            return;
764        };
765
766        let text_len = self.get_text_len();
767        if text_len != 0 {
768            let position = match direction {
769                HorizontalDirection::Left => {
770                    if position == 0 {
771                        return;
772                    }
773                    position - 1
774                }
775                HorizontalDirection::Right => {
776                    if position >= text_len {
777                        return;
778                    }
779                    position
780                }
781            };
782
783            let mut text = self.formatted_text.borrow_mut();
784            text.remove_at(position);
785            text.measure_and_arrange();
786            drop(text);
787            self.invalidate_layout();
788            self.on_text_changed(ui);
789            self.set_caret_position(self.char_index_to_position(position).unwrap_or_default());
790        }
791    }
792
793    fn remove_range(&mut self, ui: &UserInterface, selection: SelectionRange) {
794        let range = self
795            .formatted_text
796            .borrow()
797            .position_range_to_char_index_range(selection.range());
798        if range.is_empty() {
799            return;
800        }
801        self.formatted_text.borrow_mut().remove_range(range);
802        self.formatted_text.borrow_mut().measure_and_arrange();
803        self.set_caret_position(selection.left());
804        self.selection_range.set_value_and_mark_modified(None);
805        self.invalidate_layout();
806        self.on_text_changed(ui);
807    }
808
809    /// Checks whether the input position is correct (in bounds) or not.
810    pub fn is_valid_position(&self, position: Position) -> bool {
811        self.formatted_text
812            .borrow()
813            .get_lines()
814            .get(position.line)
815            .is_some_and(|line| position.offset < line.len())
816    }
817
818    fn set_caret_position(&mut self, position: Position) {
819        self.caret_position.set_value_and_mark_modified(
820            self.formatted_text
821                .borrow()
822                .nearest_valid_position(position),
823        );
824        self.ensure_caret_visible();
825        self.reset_blink();
826        self.invalidate_visual();
827    }
828
829    /// Tries to map screen space position to a position in the text.
830    pub fn screen_pos_to_text_pos(&self, screen_point: Vector2<f32>) -> Option<Position> {
831        // Transform given point into local space of the text box - this way calculations can be done
832        // as usual, without a need for special math.
833        let point_to_check = self
834            .visual_transform
835            .try_inverse()?
836            .transform_point(&Point2::from(screen_point))
837            .coords;
838
839        Some(
840            self.formatted_text
841                .borrow()
842                .local_to_position(point_to_check),
843        )
844    }
845
846    /// Returns current text of text box.
847    pub fn text(&self) -> String {
848        self.formatted_text.borrow().text()
849    }
850
851    /// Returns current word wrapping mode of text box.
852    pub fn wrap_mode(&self) -> WrapMode {
853        self.formatted_text.borrow().wrap_mode()
854    }
855
856    /// Returns current font of text box.
857    pub fn font(&self) -> FontResource {
858        self.formatted_text.borrow().get_font()
859    }
860
861    /// Returns current vertical alignment of text box.
862    pub fn vertical_alignment(&self) -> VerticalAlignment {
863        self.formatted_text.borrow().vertical_alignment()
864    }
865
866    /// Returns current horizontal alignment of text box.
867    pub fn horizontal_alignment(&self) -> HorizontalAlignment {
868        self.formatted_text.borrow().horizontal_alignment()
869    }
870
871    fn select_word(&mut self, position: Position) {
872        if let Some(index) = self.position_to_char_index_clamped(position) {
873            let text_ref = self.formatted_text.borrow();
874            let text = text_ref.get_raw_text();
875            let search_whitespace = !some_or_return!(text.get(index)).is_whitespace();
876
877            let mut left_index = index;
878            while left_index > 0 {
879                let is_whitespace = text[left_index].is_whitespace();
880                if search_whitespace && is_whitespace || !search_whitespace && !is_whitespace {
881                    left_index += 1;
882                    break;
883                }
884                left_index = left_index.saturating_sub(1);
885            }
886
887            let mut right_index = index;
888            while right_index < text.len() {
889                let is_whitespace = text[right_index].is_whitespace();
890                if search_whitespace && is_whitespace || !search_whitespace && !is_whitespace {
891                    break;
892                }
893
894                right_index += 1;
895            }
896
897            drop(text_ref);
898
899            if let (Some(left), Some(right)) = (
900                self.char_index_to_position(left_index),
901                self.char_index_to_position(right_index),
902            ) {
903                self.selection_range
904                    .set_value_and_mark_modified(Some(SelectionRange {
905                        begin: left,
906                        end: right,
907                    }));
908                self.invalidate_visual();
909            }
910        }
911    }
912
913    fn on_text_changed(&self, ui: &UserInterface) {
914        if self.placeholder.is_some() {
915            ui.send(
916                self.placeholder,
917                WidgetMessage::Visibility(self.formatted_text.borrow().text.is_empty()),
918            );
919        }
920        if *self.commit_mode == TextCommitMode::Immediate {
921            ui.post(
922                self.handle,
923                TextMessage::Text(self.formatted_text.borrow().text()),
924            );
925        }
926    }
927}
928
929uuid_provider!(TextBox = "536276f2-a175-4c05-a376-5a7d8bf0d10b");
930
931impl Control for TextBox {
932    fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
933        let text_size = self
934            .formatted_text
935            .borrow_mut()
936            .set_super_sampling_scale(self.visual_max_scaling())
937            .set_constraint(available_size)
938            .measure();
939        let children_size = self.widget.measure_override(ui, available_size);
940        text_size.sup(&children_size)
941    }
942
943    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
944        self.formatted_text.borrow_mut().arrange(final_size);
945        self.widget.arrange_override(ui, final_size)
946    }
947
948    fn draw(&self, drawing_context: &mut DrawingContext) {
949        let bounds = self.widget.bounding_rect();
950        drawing_context.push_rounded_rect_filled(&bounds, *self.corner_radius, 4);
951        drawing_context.commit(
952            self.clip_bounds(),
953            self.widget.background(),
954            CommandTexture::None,
955            &self.material,
956            None,
957        );
958
959        self.formatted_text
960            .borrow_mut()
961            .set_brush(self.widget.foreground());
962
963        let view_bounds = self.rect_to_view_pos(bounds);
964        if let Some(ref selection_range) = self.selection_range.map(|r| r.normalized()) {
965            let text = self.formatted_text.borrow();
966            let lines = text.get_lines();
967            if selection_range.begin.line == selection_range.end.line {
968                if let Some(line) = lines.get(selection_range.begin.line) {
969                    // Begin line
970                    let offset = text
971                        .get_range_width(line.begin..(line.begin + selection_range.begin.offset));
972                    let width = text.get_range_width(
973                        (line.begin + selection_range.begin.offset)
974                            ..(line.begin + selection_range.end.offset),
975                    );
976                    let selection_bounds = Rect::new(
977                        view_bounds.x() + line.x_offset + offset,
978                        view_bounds.y() + line.y_offset,
979                        width,
980                        line.height,
981                    );
982                    drawing_context.push_rect_filled(&selection_bounds, None);
983                }
984            } else {
985                for (i, line) in text.get_lines().iter().enumerate() {
986                    if i >= selection_range.begin.line && i <= selection_range.end.line {
987                        let selection_bounds = if i == selection_range.begin.line {
988                            // Begin line
989                            let offset = text.get_range_width(
990                                line.begin..(line.begin + selection_range.begin.offset),
991                            );
992                            let width = text.get_range_width(
993                                (line.begin + selection_range.begin.offset)..line.end,
994                            );
995                            Rect::new(
996                                view_bounds.x() + line.x_offset + offset,
997                                view_bounds.y() + line.y_offset,
998                                width,
999                                line.height,
1000                            )
1001                        } else if i == selection_range.end.line {
1002                            // End line
1003                            let width = text.get_range_width(
1004                                line.begin..(line.begin + selection_range.end.offset),
1005                            );
1006                            Rect::new(
1007                                view_bounds.x() + line.x_offset,
1008                                view_bounds.y() + line.y_offset,
1009                                width,
1010                                line.height,
1011                            )
1012                        } else {
1013                            // Everything between
1014                            Rect::new(
1015                                view_bounds.x() + line.x_offset,
1016                                view_bounds.y() + line.y_offset,
1017                                line.width,
1018                                line.height,
1019                            )
1020                        };
1021                        drawing_context.push_rect_filled(&selection_bounds, None);
1022                    }
1023                }
1024            }
1025        }
1026        drawing_context.commit(
1027            self.clip_bounds(),
1028            (*self.selection_brush).clone(),
1029            CommandTexture::None,
1030            &self.material,
1031            None,
1032        );
1033
1034        let local_position = self.point_to_view_pos(bounds.position);
1035        drawing_context.draw_text(
1036            self.clip_bounds(),
1037            local_position,
1038            &self.material,
1039            &self.formatted_text.borrow(),
1040        );
1041
1042        if *self.caret_visible {
1043            let caret_pos = self.point_to_view_pos(self.caret_local_position());
1044            let caret_bounds = Rect::new(
1045                caret_pos.x,
1046                caret_pos.y,
1047                2.0,
1048                **self.formatted_text.borrow().font_size(),
1049            );
1050            drawing_context.push_rect_filled(&caret_bounds, None);
1051            drawing_context.commit(
1052                self.clip_bounds(),
1053                (*self.caret_brush).clone(),
1054                CommandTexture::None,
1055                &self.material,
1056                None,
1057            );
1058        }
1059    }
1060
1061    fn on_visual_transform_changed(
1062        &self,
1063        _old_transform: &Matrix3<f32>,
1064        _new_transform: &Matrix3<f32>,
1065    ) {
1066        let mut text = self.formatted_text.borrow_mut();
1067        let new_super_sampling_scale = self.visual_max_scaling();
1068        if new_super_sampling_scale != text.super_sampling_scale() {
1069            text.set_super_sampling_scale(new_super_sampling_scale)
1070                .measure_and_arrange();
1071        }
1072    }
1073
1074    fn update(&mut self, dt: f32, _ui: &mut UserInterface) {
1075        if self.has_focus {
1076            *self.blink_timer += dt;
1077            if *self.blink_timer >= *self.blink_interval {
1078                self.blink_timer.set_value_and_mark_modified(0.0);
1079                self.caret_visible
1080                    .set_value_and_mark_modified(!*self.caret_visible);
1081            }
1082        } else {
1083            self.caret_visible.set_value_and_mark_modified(false);
1084        }
1085    }
1086
1087    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
1088        self.widget.handle_routed_message(ui, message);
1089
1090        if message.destination() == self.handle() {
1091            if let Some(msg) = message.data::<WidgetMessage>() {
1092                match msg {
1093                    WidgetMessage::Text(text)
1094                        if !ui.keyboard_modifiers().control
1095                            && !ui.keyboard_modifiers().alt
1096                            && *self.editable =>
1097                    {
1098                        for symbol in text.chars() {
1099                            let insert = !symbol.is_control()
1100                                && if let Some(filter) = self.filter.as_ref() {
1101                                    let filter = &mut *filter.safe_lock();
1102                                    filter(symbol)
1103                                } else {
1104                                    true
1105                                };
1106                            if insert {
1107                                self.insert_char(symbol, ui);
1108                            }
1109                        }
1110                    }
1111                    WidgetMessage::KeyDown(code) => {
1112                        match code {
1113                            KeyCode::ArrowUp if !self.selecting => {
1114                                self.move_caret_y(-1, ui.keyboard_modifiers().shift);
1115                            }
1116                            KeyCode::ArrowDown if !self.selecting => {
1117                                self.move_caret_y(1, ui.keyboard_modifiers().shift);
1118                            }
1119                            KeyCode::ArrowRight if !self.selecting => {
1120                                if ui.keyboard_modifiers.control {
1121                                    self.move_caret(
1122                                        self.find_next_word(*self.caret_position),
1123                                        ui.keyboard_modifiers().shift,
1124                                    );
1125                                } else {
1126                                    self.move_caret_x(1, ui.keyboard_modifiers().shift);
1127                                }
1128                            }
1129                            KeyCode::ArrowLeft if !self.selecting => {
1130                                if ui.keyboard_modifiers.control {
1131                                    self.move_caret(
1132                                        self.find_prev_word(*self.caret_position),
1133                                        ui.keyboard_modifiers().shift,
1134                                    );
1135                                } else {
1136                                    self.move_caret_x(-1, ui.keyboard_modifiers().shift);
1137                                }
1138                            }
1139                            KeyCode::Delete
1140                                if !message.handled() && *self.editable && !self.selecting =>
1141                            {
1142                                self.remove_char(HorizontalDirection::Right, ui);
1143                            }
1144                            KeyCode::NumpadEnter | KeyCode::Enter if *self.editable => {
1145                                if *self.multiline && !ui.keyboard_modifiers.shift {
1146                                    self.insert_char('\n', ui);
1147                                } else if *self.commit_mode == TextCommitMode::LostFocusPlusEnter {
1148                                    ui.post(self.handle, TextMessage::Text(self.text()));
1149                                } else if *self.commit_mode == TextCommitMode::Changed {
1150                                    self.commit_if_changed(ui);
1151                                }
1152                                // Don't set has_focus = false when enter is pressed.
1153                                // That messes up keyboard navigation.
1154                            }
1155                            KeyCode::Backspace if *self.editable && !self.selecting => {
1156                                self.remove_char(HorizontalDirection::Left, ui);
1157                            }
1158                            KeyCode::End if !self.selecting => {
1159                                let select = ui.keyboard_modifiers().shift;
1160                                let position = if ui.keyboard_modifiers().control {
1161                                    self.end_position()
1162                                } else {
1163                                    self.formatted_text
1164                                        .borrow()
1165                                        .get_line_range(self.caret_position.line)
1166                                        .end
1167                                };
1168                                self.move_caret(position, select);
1169                            }
1170                            KeyCode::Home if !self.selecting => {
1171                                let select = ui.keyboard_modifiers().shift;
1172                                let position = if ui.keyboard_modifiers().control {
1173                                    Position::default()
1174                                } else {
1175                                    self.formatted_text
1176                                        .borrow()
1177                                        .get_line_range(self.caret_position.line)
1178                                        .start
1179                                };
1180                                self.move_caret(position, select);
1181                            }
1182                            KeyCode::KeyA if ui.keyboard_modifiers().control => {
1183                                let end = self.end_position();
1184                                if end != Position::default() {
1185                                    self.selection_range.set_value_and_mark_modified(Some(
1186                                        SelectionRange {
1187                                            begin: Position::default(),
1188                                            end: self.end_position(),
1189                                        },
1190                                    ));
1191                                    self.invalidate_visual();
1192                                }
1193                            }
1194                            KeyCode::KeyC if ui.keyboard_modifiers().control => {
1195                                if let Some(mut clipboard) = ui.clipboard_mut() {
1196                                    if let Some(selection_range) = self.selection_range.as_ref() {
1197                                        let range = self
1198                                            .formatted_text
1199                                            .borrow()
1200                                            .position_range_to_char_index_range(
1201                                                selection_range.range(),
1202                                            );
1203                                        if !range.is_empty() {
1204                                            let _ = clipboard.set_contents(
1205                                                self.formatted_text.borrow().text_range(range),
1206                                            );
1207                                        }
1208                                    }
1209                                }
1210                            }
1211                            KeyCode::KeyV if ui.keyboard_modifiers().control => {
1212                                if let Some(mut clipboard) = ui.clipboard_mut() {
1213                                    if let Ok(content) = clipboard.get_contents() {
1214                                        self.insert_str(&content, ui);
1215                                    }
1216                                }
1217                            }
1218                            KeyCode::KeyX if ui.keyboard_modifiers().control => {
1219                                if let Some(mut clipboard) = ui.clipboard_mut() {
1220                                    if let Some(selection_range) = self.selection_range.as_ref() {
1221                                        let range = self
1222                                            .formatted_text
1223                                            .borrow()
1224                                            .position_range_to_char_index_range(
1225                                                selection_range.range(),
1226                                            );
1227                                        if !range.is_empty() {
1228                                            let _ = clipboard.set_contents(
1229                                                self.formatted_text.borrow().text_range(range),
1230                                            );
1231                                            self.remove_char(HorizontalDirection::Left, ui);
1232                                        }
1233                                    }
1234                                }
1235                            }
1236                            _ => (),
1237                        }
1238
1239                        // TextBox "eats" all input by default, some of the keys are used for input control while
1240                        // others are used directly to enter text.
1241                        message.set_handled(true);
1242                    }
1243                    WidgetMessage::Focus => {
1244                        if message.direction() == MessageDirection::FromWidget {
1245                            self.reset_blink();
1246                            self.has_focus = true;
1247                            let end = self.end_position();
1248                            if end != Position::default() {
1249                                self.set_caret_position(end);
1250                                self.selection_range.set_value_and_mark_modified(Some(
1251                                    SelectionRange {
1252                                        begin: Position::default(),
1253                                        end,
1254                                    },
1255                                ));
1256                                self.invalidate_visual();
1257                            }
1258                            if *self.commit_mode == TextCommitMode::Changed {
1259                                self.recent.clear();
1260                                self.recent
1261                                    .extend_from_slice(self.formatted_text.borrow().get_raw_text());
1262                            }
1263                        }
1264                    }
1265                    WidgetMessage::Unfocus => {
1266                        if message.direction() == MessageDirection::FromWidget {
1267                            self.selection_range.set_value_and_mark_modified(None);
1268                            self.invalidate_visual();
1269                            self.has_focus = false;
1270
1271                            match *self.commit_mode {
1272                                TextCommitMode::LostFocus | TextCommitMode::LostFocusPlusEnter => {
1273                                    ui.post(self.handle, TextMessage::Text(self.text()));
1274                                }
1275                                TextCommitMode::Changed => {
1276                                    self.commit_if_changed(ui);
1277                                }
1278                                _ => (),
1279                            }
1280                            // There is no reason to keep the stored recent value in memory
1281                            // while this TextBox does not have focus. Maybe this should be stored globally in UserInterface,
1282                            // since we only ever need one.
1283                            self.recent.clear();
1284                            self.recent.shrink_to(0);
1285                        }
1286                    }
1287                    WidgetMessage::MouseDown { pos, button } => {
1288                        if *button == MouseButton::Left {
1289                            let select = ui.keyboard_modifiers().shift;
1290                            if !select {
1291                                self.selection_range.set_value_and_mark_modified(None);
1292                                self.invalidate_visual();
1293                            }
1294                            self.selecting = true;
1295                            self.has_focus = true;
1296                            self.before_click_position = *self.caret_position;
1297
1298                            if let Some(position) = self.screen_pos_to_text_pos(*pos) {
1299                                self.move_caret(position, select);
1300                            }
1301
1302                            ui.capture_mouse(self.handle());
1303                        }
1304                    }
1305                    WidgetMessage::DoubleClick {
1306                        button: MouseButton::Left,
1307                    } => {
1308                        if let Some(position) = self.screen_pos_to_text_pos(ui.cursor_position) {
1309                            if position == self.before_click_position {
1310                                self.select_word(position);
1311                            }
1312                        }
1313                    }
1314                    WidgetMessage::MouseMove { pos, .. } => {
1315                        if self.selecting {
1316                            if let Some(position) = self.screen_pos_to_text_pos(*pos) {
1317                                self.move_caret(position, true);
1318                            }
1319                        }
1320                    }
1321                    WidgetMessage::MouseUp { .. } => {
1322                        self.selecting = false;
1323                        ui.release_mouse_capture();
1324                    }
1325                    _ => {}
1326                }
1327            } else if let Some(msg) = message.data::<TextMessage>() {
1328                if message.direction() == MessageDirection::ToWidget {
1329                    let mut text = self.formatted_text.borrow_mut();
1330
1331                    match msg {
1332                        TextMessage::BBCode(_) => (),
1333                        TextMessage::Text(new_text) => {
1334                            fn text_equals(
1335                                formatted_text: &FormattedText,
1336                                input_string: &str,
1337                            ) -> bool {
1338                                let raw_text = formatted_text.get_raw_text();
1339
1340                                if raw_text.len() != input_string.chars().count() {
1341                                    false
1342                                } else {
1343                                    for (raw_char, input_char) in
1344                                        raw_text.iter().zip(input_string.chars())
1345                                    {
1346                                        if *raw_char != input_char {
1347                                            return false;
1348                                        }
1349                                    }
1350
1351                                    true
1352                                }
1353                            }
1354                            self.selection_range.set_value_and_mark_modified(None);
1355                            self.invalidate_visual();
1356                            if !text_equals(&text, new_text) {
1357                                text.set_text(new_text);
1358                                drop(text);
1359                                self.invalidate_layout();
1360                                self.formatted_text.borrow_mut().measure_and_arrange();
1361                                self.on_text_changed(ui);
1362                            }
1363                        }
1364                        TextMessage::Wrap(wrap_mode) => {
1365                            if text.wrap_mode() != *wrap_mode {
1366                                text.set_wrap(*wrap_mode);
1367                                drop(text);
1368                                self.invalidate_layout();
1369                                ui.try_send_response(message);
1370                            }
1371                        }
1372                        TextMessage::Font(font) => {
1373                            if &text.get_font() != font {
1374                                text.set_font(font.clone());
1375                                drop(text);
1376                                self.invalidate_layout();
1377                                ui.try_send_response(message);
1378                            }
1379                        }
1380                        TextMessage::VerticalAlignment(alignment) => {
1381                            if &text.vertical_alignment() != alignment {
1382                                text.set_vertical_alignment(*alignment);
1383                                drop(text);
1384                                self.invalidate_layout();
1385                                ui.try_send_response(message);
1386                            }
1387                        }
1388                        TextMessage::HorizontalAlignment(alignment) => {
1389                            if &text.horizontal_alignment() != alignment {
1390                                text.set_horizontal_alignment(*alignment);
1391                                drop(text);
1392                                self.invalidate_layout();
1393                                ui.try_send_response(message);
1394                            }
1395                        }
1396                        &TextMessage::Shadow(shadow) => {
1397                            if *text.shadow != shadow {
1398                                text.set_shadow(shadow);
1399                                drop(text);
1400                                self.invalidate_layout();
1401                                ui.try_send_response(message);
1402                            }
1403                        }
1404                        TextMessage::ShadowBrush(brush) => {
1405                            if &*text.shadow_brush != brush {
1406                                text.set_shadow_brush(brush.clone());
1407                                drop(text);
1408                                self.invalidate_layout();
1409                                ui.try_send_response(message);
1410                            }
1411                        }
1412                        &TextMessage::ShadowDilation(dilation) => {
1413                            if *text.shadow_dilation != dilation {
1414                                text.set_shadow_dilation(dilation);
1415                                drop(text);
1416                                self.invalidate_layout();
1417                                ui.try_send_response(message);
1418                            }
1419                        }
1420                        &TextMessage::ShadowOffset(offset) => {
1421                            if *text.shadow_offset != offset {
1422                                text.set_shadow_offset(offset);
1423                                drop(text);
1424                                self.invalidate_layout();
1425                                ui.try_send_response(message);
1426                            }
1427                        }
1428                        TextMessage::FontSize(height) => {
1429                            if text.font_size() != height {
1430                                text.set_font_size(height.clone());
1431                                drop(text);
1432                                self.invalidate_layout();
1433                                ui.try_send_response(message);
1434                            }
1435                        }
1436                        TextMessage::Runs(runs) => {
1437                            text.set_runs(runs.clone());
1438                            drop(text);
1439                            self.invalidate_layout();
1440                        }
1441                    }
1442                }
1443            } else if let Some(msg) = message.data::<TextBoxMessage>() {
1444                if message.direction() == MessageDirection::ToWidget {
1445                    match msg {
1446                        TextBoxMessage::SelectionBrush(brush) => {
1447                            if &*self.selection_brush != brush {
1448                                self.selection_brush
1449                                    .set_value_and_mark_modified(brush.clone());
1450                                ui.try_send_response(message);
1451                            }
1452                        }
1453                        TextBoxMessage::CaretBrush(brush) => {
1454                            if &*self.caret_brush != brush {
1455                                self.caret_brush.set_value_and_mark_modified(brush.clone());
1456                                ui.try_send_response(message);
1457                            }
1458                        }
1459                        TextBoxMessage::TextCommitMode(mode) => {
1460                            if &*self.commit_mode != mode {
1461                                self.commit_mode.set_value_and_mark_modified(*mode);
1462                                ui.try_send_response(message);
1463                            }
1464                        }
1465                        TextBoxMessage::Multiline(multiline) => {
1466                            if &*self.multiline != multiline {
1467                                self.multiline.set_value_and_mark_modified(*multiline);
1468                                ui.try_send_response(message);
1469                            }
1470                        }
1471                        TextBoxMessage::Editable(editable) => {
1472                            if &*self.editable != editable {
1473                                self.editable.set_value_and_mark_modified(*editable);
1474                                ui.try_send_response(message);
1475                            }
1476                        }
1477                        TextBoxMessage::Padding(padding) => {
1478                            let mut formatted_text = self.formatted_text.borrow_mut();
1479                            if &*formatted_text.padding != padding {
1480                                formatted_text.padding.set_value_and_mark_modified(*padding);
1481                                ui.try_send_response(message);
1482                            }
1483                        }
1484                        TextBoxMessage::CornerRadius(corner_radius) => {
1485                            if *self.corner_radius != *corner_radius {
1486                                self.corner_radius
1487                                    .set_value_and_mark_modified(*corner_radius);
1488                                ui.try_send_response(message);
1489                                self.invalidate_visual();
1490                            }
1491                        }
1492                    }
1493                }
1494            }
1495        }
1496    }
1497}
1498
1499/// Placeholder builder.
1500pub enum EmptyTextPlaceholder<'a> {
1501    /// No placeholder is required.
1502    None,
1503    /// Simple constructor for text-based placeholders.
1504    Text(&'a str),
1505    /// Arbitrary widget (or any hierarchy of widgets).
1506    Widget(Handle<UiNode>),
1507}
1508
1509impl<'a> EmptyTextPlaceholder<'a> {
1510    fn build(self, main_text: &str, ctx: &mut BuildContext) -> Handle<UiNode> {
1511        match self {
1512            EmptyTextPlaceholder::None => Handle::NONE,
1513            EmptyTextPlaceholder::Text(text) => TextBuilder::new(
1514                WidgetBuilder::new()
1515                    .with_visibility(main_text.is_empty())
1516                    .with_foreground(ctx.style.property(Style::BRUSH_LIGHTER)),
1517            )
1518            .with_text(text)
1519            .with_vertical_text_alignment(VerticalAlignment::Center)
1520            .build(ctx)
1521            .to_base(),
1522            EmptyTextPlaceholder::Widget(widget) => widget,
1523        }
1524    }
1525}
1526
1527/// Text box builder creates new [`TextBox`] instances and adds them to the user interface.
1528pub struct TextBoxBuilder<'a> {
1529    widget_builder: WidgetBuilder,
1530    font: Option<FontResource>,
1531    text: String,
1532    caret_brush: Brush,
1533    selection_brush: Brush,
1534    filter: Option<Arc<Mutex<FilterCallback>>>,
1535    vertical_alignment: VerticalAlignment,
1536    horizontal_alignment: HorizontalAlignment,
1537    wrap: WrapMode,
1538    commit_mode: TextCommitMode,
1539    multiline: bool,
1540    editable: bool,
1541    mask_char: Option<char>,
1542    shadow: bool,
1543    shadow_brush: Brush,
1544    shadow_dilation: f32,
1545    shadow_offset: Vector2<f32>,
1546    skip_chars: Vec<char>,
1547    font_size: Option<StyledProperty<f32>>,
1548    padding: Thickness,
1549    placeholder: EmptyTextPlaceholder<'a>,
1550    corner_radius: f32,
1551    trim_text: bool,
1552}
1553
1554impl<'a> TextBoxBuilder<'a> {
1555    /// Creates new text box widget builder with the base widget builder specified.
1556    pub fn new(widget_builder: WidgetBuilder) -> Self {
1557        Self {
1558            widget_builder,
1559            font: None,
1560            text: "".to_owned(),
1561            caret_brush: Brush::Solid(Color::WHITE),
1562            selection_brush: Brush::Solid(Color::opaque(80, 118, 178)),
1563            filter: None,
1564            vertical_alignment: VerticalAlignment::Top,
1565            horizontal_alignment: HorizontalAlignment::Left,
1566            wrap: WrapMode::NoWrap,
1567            commit_mode: TextCommitMode::LostFocusPlusEnter,
1568            multiline: false,
1569            editable: true,
1570            mask_char: None,
1571            shadow: false,
1572            shadow_brush: Brush::Solid(Color::BLACK),
1573            shadow_dilation: 1.0,
1574            shadow_offset: Vector2::new(1.0, 1.0),
1575            skip_chars: Default::default(),
1576            font_size: None,
1577            padding: Thickness {
1578                left: 5.0,
1579                top: 2.0,
1580                right: 5.0,
1581                bottom: 2.0,
1582            },
1583            placeholder: EmptyTextPlaceholder::None,
1584            corner_radius: 3.0,
1585            trim_text: false,
1586        }
1587    }
1588
1589    /// Sets the desired font of the text box.
1590    pub fn with_font(mut self, font: FontResource) -> Self {
1591        self.font = Some(font);
1592        self
1593    }
1594
1595    /// Sets the desired padding of the text box.
1596    pub fn with_padding(mut self, padding: Thickness) -> Self {
1597        self.padding = padding;
1598        self
1599    }
1600
1601    /// Sets the desired text of the text box.
1602    pub fn with_text<P: AsRef<str>>(mut self, text: P) -> Self {
1603        text.as_ref().clone_into(&mut self.text);
1604        self
1605    }
1606
1607    /// Sets the desired caret brush of the text box.
1608    pub fn with_caret_brush(mut self, brush: Brush) -> Self {
1609        self.caret_brush = brush;
1610        self
1611    }
1612
1613    /// Sets the desired selection brush of the text box.
1614    pub fn with_selection_brush(mut self, brush: Brush) -> Self {
1615        self.selection_brush = brush;
1616        self
1617    }
1618
1619    /// Sets the desired character filter of the text box. See [`FilterCallback`] for more info.
1620    pub fn with_filter(mut self, filter: Arc<Mutex<FilterCallback>>) -> Self {
1621        self.filter = Some(filter);
1622        self
1623    }
1624
1625    /// Sets the desired vertical text alignment of the text box.
1626    pub fn with_vertical_text_alignment(mut self, alignment: VerticalAlignment) -> Self {
1627        self.vertical_alignment = alignment;
1628        self
1629    }
1630
1631    /// Sets the desired horizontal text alignment of the text box.
1632    pub fn with_horizontal_text_alignment(mut self, alignment: HorizontalAlignment) -> Self {
1633        self.horizontal_alignment = alignment;
1634        self
1635    }
1636
1637    /// Sets the desired word wrapping of the text box.
1638    pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
1639        self.wrap = wrap;
1640        self
1641    }
1642
1643    /// Sets the desired text commit mode of the text box.
1644    pub fn with_text_commit_mode(mut self, mode: TextCommitMode) -> Self {
1645        self.commit_mode = mode;
1646        self
1647    }
1648
1649    /// Enables or disables multiline mode of the text box.
1650    pub fn with_multiline(mut self, multiline: bool) -> Self {
1651        self.multiline = multiline;
1652        self
1653    }
1654
1655    /// Enables or disables editing of the text box.
1656    pub fn with_editable(mut self, editable: bool) -> Self {
1657        self.editable = editable;
1658        self
1659    }
1660
1661    /// Sets the desired height of the text.
1662    pub fn with_font_size(mut self, font_size: StyledProperty<f32>) -> Self {
1663        self.font_size = Some(font_size);
1664        self
1665    }
1666
1667    /// Sets the desired masking character of the text box.
1668    pub fn with_mask_char(mut self, mask_char: Option<char>) -> Self {
1669        self.mask_char = mask_char;
1670        self
1671    }
1672
1673    /// Whether the shadow enabled or not.
1674    pub fn with_shadow(mut self, shadow: bool) -> Self {
1675        self.shadow = shadow;
1676        self
1677    }
1678
1679    /// Sets desired shadow brush. It will be used to render the shadow.
1680    pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
1681        self.shadow_brush = brush;
1682        self
1683    }
1684
1685    /// Sets desired shadow dilation in units. Keep in mind that the dilation is absolute,
1686    /// not percentage-based.
1687    pub fn with_shadow_dilation(mut self, thickness: f32) -> Self {
1688        self.shadow_dilation = thickness;
1689        self
1690    }
1691
1692    /// Sets desired shadow offset in units.
1693    pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
1694        self.shadow_offset = offset;
1695        self
1696    }
1697
1698    /// Sets desired set of characters that will be treated like whitespace during Ctrl+Arrow navigation
1699    /// (Ctrl+Left Arrow and Ctrl+Right Arrow). This could be useful to treat underscores like whitespaces,
1700    /// which in its turn could be useful for in-game consoles where commands usually separated using
1701    /// underscores (`like_this_one`).
1702    pub fn with_skip_chars(mut self, chars: Vec<char>) -> Self {
1703        self.skip_chars = chars;
1704        self
1705    }
1706
1707    /// Sets the desired placeholder widget when the search bar is empty.
1708    pub fn with_empty_text_placeholder(mut self, placeholder: EmptyTextPlaceholder<'a>) -> Self {
1709        self.placeholder = placeholder;
1710        self
1711    }
1712
1713    /// Sets the desired corner radius of the text box.
1714    pub fn with_corner_radius(mut self, corner_radius: f32) -> Self {
1715        self.corner_radius = corner_radius;
1716        self
1717    }
1718
1719    /// A flag, that defines whether the formatted text should add ellipsis (…) to lines that goes
1720    /// outside provided bounds.
1721    pub fn with_trim_text(mut self, trim: bool) -> Self {
1722        self.trim_text = trim;
1723        self
1724    }
1725
1726    /// Creates a new [`TextBox`] instance and adds it to the user interface.
1727    pub fn build(mut self, ctx: &mut BuildContext) -> Handle<TextBox> {
1728        let style = &ctx.style;
1729
1730        if self.widget_builder.foreground.is_none() {
1731            self.widget_builder.foreground = Some(style.property(Style::BRUSH_TEXT));
1732        }
1733        if self.widget_builder.background.is_none() {
1734            self.widget_builder.background = Some(style.property(Style::BRUSH_DARKER));
1735        }
1736        if self.widget_builder.cursor.is_none() {
1737            self.widget_builder.cursor = Some(CursorIcon::Text);
1738        }
1739        let placeholder = self.placeholder.build(&self.text, ctx);
1740        if let Ok(placeholder_ref) = ctx.try_get_node_mut(placeholder) {
1741            placeholder_ref
1742                .hit_test_visibility
1743                .set_value_and_mark_modified(false);
1744            placeholder_ref.set_margin(self.padding);
1745        }
1746
1747        let text_box = TextBox {
1748            widget: self
1749                .widget_builder
1750                .with_child(placeholder)
1751                .with_accepts_input(true)
1752                .with_need_update(true)
1753                .build(ctx),
1754            caret_position: Position::default().into(),
1755            caret_visible: false.into(),
1756            blink_timer: 0.0.into(),
1757            blink_interval: 0.5.into(),
1758            formatted_text: RefCell::new(
1759                FormattedTextBuilder::new(self.font.unwrap_or_else(|| ctx.default_font()))
1760                    .with_text(self.text)
1761                    .with_horizontal_alignment(self.horizontal_alignment)
1762                    .with_vertical_alignment(self.vertical_alignment)
1763                    .with_wrap(self.wrap)
1764                    .with_mask_char(self.mask_char)
1765                    .with_shadow(self.shadow)
1766                    .with_shadow_brush(self.shadow_brush)
1767                    .with_shadow_dilation(self.shadow_dilation)
1768                    .with_shadow_offset(self.shadow_offset)
1769                    .with_padding(self.padding)
1770                    .with_trim_text(self.trim_text)
1771                    .with_font_size(
1772                        self.font_size
1773                            .unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
1774                    )
1775                    .build(),
1776            ),
1777            selection_range: None.into(),
1778            selecting: false,
1779            before_click_position: Position::default(),
1780            selection_brush: self.selection_brush.into(),
1781            caret_brush: self.caret_brush.into(),
1782            has_focus: false,
1783            filter: self.filter,
1784            commit_mode: self.commit_mode.into(),
1785            multiline: self.multiline.into(),
1786            editable: self.editable.into(),
1787            view_position: Default::default(),
1788            skip_chars: self.skip_chars.into(),
1789            recent: Default::default(),
1790            placeholder,
1791            corner_radius: self.corner_radius.into(),
1792        };
1793
1794        ctx.add(text_box)
1795    }
1796}
1797
1798#[cfg(test)]
1799mod test {
1800    use crate::text_box::TextBoxBuilder;
1801    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
1802
1803    #[test]
1804    fn test_deletion() {
1805        test_widget_deletion(|ctx| TextBoxBuilder::new(WidgetBuilder::new()).build(ctx));
1806    }
1807}