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