rg3d_ui/
text_box.rs

1use crate::{
2    brush::Brush,
3    core::{algebra::Vector2, color::Color, math::Rect, pool::Handle},
4    define_constructor,
5    draw::{CommandTexture, Draw, DrawingContext},
6    formatted_text::{FormattedText, FormattedTextBuilder, WrapMode},
7    message::{CursorIcon, KeyCode, MessageDirection, MouseButton, UiMessage},
8    ttf::SharedFont,
9    widget::{Widget, WidgetBuilder, WidgetMessage},
10    BuildContext, Control, HorizontalAlignment, UiNode, UserInterface, VerticalAlignment,
11    BRUSH_DARKER, BRUSH_TEXT,
12};
13use copypasta::ClipboardProvider;
14use std::{
15    any::{Any, TypeId},
16    cell::RefCell,
17    cmp::{self, Ordering},
18    fmt::{Debug, Formatter},
19    ops::{Deref, DerefMut},
20    rc::Rc,
21    sync::mpsc::Sender,
22};
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum TextBoxMessage {
26    Text(String),
27}
28
29impl TextBoxMessage {
30    define_constructor!(TextBoxMessage:Text => fn text(String), layout: false);
31}
32
33#[derive(Copy, Clone, PartialEq, Eq)]
34pub enum HorizontalDirection {
35    Left,
36    Right,
37}
38
39#[derive(Copy, Clone, PartialEq, Eq)]
40pub enum VerticalDirection {
41    Down,
42    Up,
43}
44
45#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
46pub struct Position {
47    // Line index.
48    line: usize,
49
50    // Offset from beginning of a line.
51    offset: usize,
52}
53
54#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)]
55#[repr(u32)]
56pub enum TextCommitMode {
57    /// Text box will immediately send Text message after any change.
58    Immediate = 0,
59
60    /// Text box will send Text message only when it loses focus.
61    LostFocus = 1,
62
63    /// Text box will send Text message when it loses focus or if Enter
64    /// key was pressed. This is **default** behavior.
65    ///
66    /// # Notes
67    ///
68    /// In case of multiline text box hitting Enter key won't commit text!
69    LostFocusPlusEnter = 2,
70}
71
72#[derive(Copy, Clone, PartialEq, Eq, Debug)]
73pub struct SelectionRange {
74    begin: Position,
75    end: Position,
76}
77
78impl SelectionRange {
79    #[must_use = "method creates new value which must be used"]
80    pub fn normalized(&self) -> SelectionRange {
81        match self.begin.line.cmp(&self.end.line) {
82            Ordering::Less => *self,
83            Ordering::Equal => {
84                if self.begin.offset > self.end.offset {
85                    SelectionRange {
86                        begin: self.end,
87                        end: self.begin,
88                    }
89                } else {
90                    *self
91                }
92            }
93            Ordering::Greater => SelectionRange {
94                begin: self.end,
95                end: self.begin,
96            },
97        }
98    }
99}
100
101pub type FilterCallback = dyn FnMut(char) -> bool;
102
103#[derive(Clone)]
104pub struct TextBox {
105    widget: Widget,
106    caret_position: Position,
107    caret_visible: bool,
108    blink_timer: f32,
109    blink_interval: f32,
110    formatted_text: RefCell<FormattedText>,
111    selection_range: Option<SelectionRange>,
112    selecting: bool,
113    has_focus: bool,
114    caret_brush: Brush,
115    selection_brush: Brush,
116    filter: Option<Rc<RefCell<FilterCallback>>>,
117    commit_mode: TextCommitMode,
118    multiline: bool,
119    editable: bool,
120}
121
122impl Debug for TextBox {
123    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
124        f.write_str("TextBox")
125    }
126}
127
128crate::define_widget_deref!(TextBox);
129
130impl TextBox {
131    pub fn reset_blink(&mut self) {
132        self.caret_visible = true;
133        self.blink_timer = 0.0;
134    }
135
136    pub fn move_caret_x(
137        &mut self,
138        mut offset: usize,
139        direction: HorizontalDirection,
140        select: bool,
141    ) {
142        if select {
143            if self.selection_range.is_none() {
144                self.selection_range = Some(SelectionRange {
145                    begin: self.caret_position,
146                    end: self.caret_position,
147                });
148            }
149        } else {
150            self.selection_range = None;
151        }
152
153        self.reset_blink();
154
155        let text = self.formatted_text.borrow();
156        let lines = text.get_lines();
157
158        if lines.is_empty() {
159            self.caret_position = Default::default();
160            return;
161        }
162
163        while offset > 0 {
164            match direction {
165                HorizontalDirection::Left => {
166                    if self.caret_position.offset > 0 {
167                        self.caret_position.offset -= 1
168                    } else if self.caret_position.line > 0 {
169                        self.caret_position.line -= 1;
170                        self.caret_position.offset = lines[self.caret_position.line].len();
171                    } else {
172                        self.caret_position.offset = 0;
173                        break;
174                    }
175                }
176                HorizontalDirection::Right => {
177                    let line = lines.get(self.caret_position.line).unwrap();
178                    if self.caret_position.offset < line.len() {
179                        self.caret_position.offset += 1;
180                    } else if self.caret_position.line < lines.len() - 1 {
181                        self.caret_position.line += 1;
182                        self.caret_position.offset = 0;
183                    } else {
184                        self.caret_position.offset = line.len();
185                        break;
186                    }
187                }
188            }
189            offset -= 1;
190        }
191
192        if let Some(selection_range) = self.selection_range.as_mut() {
193            if select {
194                selection_range.end = self.caret_position;
195            }
196        }
197    }
198
199    pub fn move_caret_y(&mut self, offset: usize, direction: VerticalDirection, select: bool) {
200        if select {
201            if self.selection_range.is_none() {
202                self.selection_range = Some(SelectionRange {
203                    begin: self.caret_position,
204                    end: self.caret_position,
205                });
206            }
207        } else {
208            self.selection_range = None;
209        }
210
211        let text = self.formatted_text.borrow();
212        let lines = text.get_lines();
213
214        if lines.is_empty() {
215            return;
216        }
217
218        let line_count = lines.len();
219
220        match direction {
221            VerticalDirection::Down => {
222                if self.caret_position.line + offset >= line_count {
223                    self.caret_position.line = line_count - 1;
224                } else {
225                    self.caret_position.line += offset;
226                }
227            }
228            VerticalDirection::Up => {
229                if self.caret_position.line > offset {
230                    self.caret_position.line -= offset;
231                } else {
232                    self.caret_position.line = 0;
233                }
234            }
235        }
236
237        if let Some(selection_range) = self.selection_range.as_mut() {
238            if select {
239                selection_range.end = self.caret_position;
240            }
241        }
242    }
243
244    pub fn get_absolute_position(&self, position: Position) -> Option<usize> {
245        self.formatted_text
246            .borrow()
247            .get_lines()
248            .get(position.line)
249            .map(|line| line.begin + cmp::min(position.offset, line.len()))
250    }
251
252    /// Inserts given character at current caret position.
253    fn insert_char(&mut self, c: char, ui: &UserInterface) {
254        if !c.is_control() {
255            let position = self.get_absolute_position(self.caret_position).unwrap_or(0);
256            self.formatted_text
257                .borrow_mut()
258                .insert_char(c, position)
259                .build();
260            self.move_caret_x(1, HorizontalDirection::Right, false);
261            ui.send_message(TextBoxMessage::text(
262                self.handle,
263                MessageDirection::ToWidget,
264                self.formatted_text.borrow().text(),
265            ));
266        }
267    }
268
269    fn insert_str(&mut self, str: &str, ui: &UserInterface) {
270        let position = self.get_absolute_position(self.caret_position).unwrap_or(0);
271        self.formatted_text.borrow_mut().insert_str(str, position);
272        self.move_caret_x(str.chars().count(), HorizontalDirection::Right, false);
273        ui.send_message(TextBoxMessage::text(
274            self.handle,
275            MessageDirection::ToWidget,
276            self.formatted_text.borrow().text(),
277        ));
278    }
279
280    pub fn get_text_len(&self) -> usize {
281        self.formatted_text.borrow_mut().get_raw_text().len()
282    }
283
284    fn remove_char(&mut self, direction: HorizontalDirection, ui: &UserInterface) {
285        if let Some(position) = self.get_absolute_position(self.caret_position) {
286            let text_len = self.get_text_len();
287            if text_len != 0 {
288                let position = match direction {
289                    HorizontalDirection::Left => {
290                        if position == 0 {
291                            return;
292                        }
293                        position - 1
294                    }
295                    HorizontalDirection::Right => {
296                        if position >= text_len {
297                            return;
298                        }
299                        position
300                    }
301                };
302                self.formatted_text.borrow_mut().remove_at(position);
303                self.formatted_text.borrow_mut().build();
304
305                ui.send_message(TextBoxMessage::text(
306                    self.handle(),
307                    MessageDirection::ToWidget,
308                    self.formatted_text.borrow().text(),
309                ));
310
311                if direction == HorizontalDirection::Left {
312                    self.move_caret_x(1, direction, false);
313                }
314            }
315        }
316    }
317
318    fn remove_range(&mut self, ui: &UserInterface, selection: SelectionRange) {
319        let selection = selection.normalized();
320        if let Some(begin) = self.get_absolute_position(selection.begin) {
321            if let Some(end) = self.get_absolute_position(selection.end) {
322                self.formatted_text.borrow_mut().remove_range(begin..end);
323                self.formatted_text.borrow_mut().build();
324
325                ui.send_message(TextBoxMessage::text(
326                    self.handle(),
327                    MessageDirection::ToWidget,
328                    self.formatted_text.borrow().text(),
329                ));
330
331                self.caret_position = selection.begin;
332            }
333        }
334    }
335
336    pub fn screen_pos_to_text_pos(&self, screen_pos: Vector2<f32>) -> Option<Position> {
337        let caret_pos = self.widget.screen_position;
338        let font = self.formatted_text.borrow().get_font();
339        let font = font.0.lock().unwrap();
340        for (line_index, line) in self.formatted_text.borrow().get_lines().iter().enumerate() {
341            let line_bounds = Rect::new(
342                caret_pos.x + line.x_offset,
343                caret_pos.y + line.y_offset,
344                line.width,
345                font.ascender(),
346            );
347            if line_bounds.contains(screen_pos) {
348                let mut x = line_bounds.x();
349                // Check each character in line.
350                for (offset, index) in (line.begin..line.end).enumerate() {
351                    let character = self.formatted_text.borrow().get_raw_text()[index];
352                    let (width, height, advance) =
353                        if let Some(glyph) = font.glyphs().get(character.char_code as usize) {
354                            (
355                                glyph.bitmap_width as f32,
356                                glyph.bitmap_height as f32,
357                                glyph.advance,
358                            )
359                        } else {
360                            // Stub
361                            let h = font.height();
362                            (h, h, h)
363                        };
364                    let char_bounds = Rect::new(x, line_bounds.y(), width, height);
365                    if char_bounds.contains(screen_pos) {
366                        return Some(Position {
367                            line: line_index,
368                            offset,
369                        });
370                    }
371                    x += advance;
372                }
373            }
374        }
375        None
376    }
377
378    pub fn text(&self) -> String {
379        self.formatted_text.borrow().text()
380    }
381
382    pub fn set_wrap(&mut self, wrap: WrapMode) -> &mut Self {
383        self.formatted_text.borrow_mut().set_wrap(wrap);
384        self
385    }
386
387    pub fn wrap_mode(&self) -> WrapMode {
388        self.formatted_text.borrow().wrap_mode()
389    }
390
391    pub fn set_font(&mut self, font: SharedFont) -> &mut Self {
392        self.formatted_text.borrow_mut().set_font(font);
393        self
394    }
395
396    pub fn font(&self) -> SharedFont {
397        self.formatted_text.borrow().get_font()
398    }
399
400    pub fn set_vertical_alignment(&mut self, valign: VerticalAlignment) -> &mut Self {
401        self.formatted_text
402            .borrow_mut()
403            .set_vertical_alignment(valign);
404        self
405    }
406
407    pub fn vertical_alignment(&self) -> VerticalAlignment {
408        self.formatted_text.borrow().vertical_alignment()
409    }
410
411    pub fn set_horizontal_alignment(&mut self, halign: HorizontalAlignment) -> &mut Self {
412        self.formatted_text
413            .borrow_mut()
414            .set_horizontal_alignment(halign);
415        self
416    }
417
418    pub fn horizontal_alignment(&self) -> HorizontalAlignment {
419        self.formatted_text.borrow().horizontal_alignment()
420    }
421}
422
423impl Control for TextBox {
424    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
425        if type_id == TypeId::of::<Self>() {
426            Some(self)
427        } else {
428            None
429        }
430    }
431
432    fn measure_override(&self, _: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
433        self.formatted_text
434            .borrow_mut()
435            .set_constraint(available_size)
436            .build()
437    }
438
439    fn draw(&self, drawing_context: &mut DrawingContext) {
440        let bounds = self.widget.screen_bounds();
441        drawing_context.push_rect_filled(&bounds, None);
442        drawing_context.commit(
443            self.clip_bounds(),
444            self.widget.background(),
445            CommandTexture::None,
446            None,
447        );
448
449        self.formatted_text
450            .borrow_mut()
451            .set_constraint(Vector2::new(bounds.w(), bounds.h()))
452            .set_brush(self.widget.foreground())
453            .build();
454
455        if let Some(ref selection_range) = self.selection_range.map(|r| r.normalized()) {
456            let text = self.formatted_text.borrow();
457            let lines = text.get_lines();
458            if selection_range.begin.line == selection_range.end.line {
459                let line = lines[selection_range.begin.line];
460                // Begin line
461                let offset =
462                    text.get_range_width(line.begin..(line.begin + selection_range.begin.offset));
463                let width = text.get_range_width(
464                    (line.begin + selection_range.begin.offset)
465                        ..(line.begin + selection_range.end.offset),
466                );
467                let bounds = Rect::new(
468                    bounds.x() + line.x_offset + offset,
469                    bounds.y() + line.y_offset,
470                    width,
471                    line.height,
472                );
473                drawing_context.push_rect_filled(&bounds, None);
474            } else {
475                for (i, line) in text.get_lines().iter().enumerate() {
476                    if i >= selection_range.begin.line && i <= selection_range.end.line {
477                        let bounds = if i == selection_range.begin.line {
478                            // Begin line
479                            let offset = text.get_range_width(
480                                line.begin..(line.begin + selection_range.begin.offset),
481                            );
482                            let width = text.get_range_width(
483                                (line.begin + selection_range.begin.offset)..line.end,
484                            );
485                            Rect::new(
486                                bounds.x() + line.x_offset + offset,
487                                bounds.y() + line.y_offset,
488                                width,
489                                line.height,
490                            )
491                        } else if i == selection_range.end.line {
492                            // End line
493                            let width = text.get_range_width(
494                                line.begin..(line.begin + selection_range.end.offset),
495                            );
496                            Rect::new(
497                                bounds.x() + line.x_offset,
498                                bounds.y() + line.y_offset,
499                                width,
500                                line.height,
501                            )
502                        } else {
503                            // Everything between
504                            Rect::new(
505                                bounds.x() + line.x_offset,
506                                bounds.y() + line.y_offset,
507                                line.width,
508                                line.height,
509                            )
510                        };
511                        drawing_context.push_rect_filled(&bounds, None);
512                    }
513                }
514            }
515        }
516        drawing_context.commit(
517            self.clip_bounds(),
518            self.selection_brush.clone(),
519            CommandTexture::None,
520            None,
521        );
522
523        let screen_position = bounds.position;
524        drawing_context.draw_text(bounds, screen_position, &self.formatted_text.borrow());
525
526        if self.caret_visible {
527            let text = self.formatted_text.borrow();
528
529            let font = text.get_font();
530            let mut caret_pos = screen_position;
531
532            let font = font.0.lock().unwrap();
533            if let Some(line) = text.get_lines().get(self.caret_position.line) {
534                let text = text.get_raw_text();
535                caret_pos += Vector2::new(line.x_offset, line.y_offset);
536                for (offset, char_index) in (line.begin..line.end).enumerate() {
537                    if offset >= self.caret_position.offset {
538                        break;
539                    }
540                    if let Some(glyph) = font.glyphs().get(text[char_index].glyph_index as usize) {
541                        caret_pos.x += glyph.advance;
542                    } else {
543                        caret_pos.x += font.height();
544                    }
545                }
546            }
547
548            let caret_bounds = Rect::new(caret_pos.x, caret_pos.y, 2.0, font.height());
549            drawing_context.push_rect_filled(&caret_bounds, None);
550            drawing_context.commit(
551                self.clip_bounds(),
552                self.caret_brush.clone(),
553                CommandTexture::None,
554                None,
555            );
556        }
557    }
558
559    fn update(&mut self, dt: f32, _sender: &Sender<UiMessage>) {
560        if self.has_focus {
561            self.blink_timer += dt;
562            if self.blink_timer >= self.blink_interval {
563                self.blink_timer = 0.0;
564                self.caret_visible = !self.caret_visible;
565            }
566        } else {
567            self.caret_visible = false;
568        }
569    }
570
571    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
572        self.widget.handle_routed_message(ui, message);
573
574        if message.destination() == self.handle() {
575            if let Some(msg) = message.data::<WidgetMessage>() {
576                match msg {
577                    &WidgetMessage::Text(symbol)
578                        if !ui.keyboard_modifiers().control
579                            && !ui.keyboard_modifiers().alt
580                            && self.editable =>
581                    {
582                        let insert = if let Some(filter) = self.filter.as_ref() {
583                            let filter = &mut *filter.borrow_mut();
584                            filter(symbol)
585                        } else {
586                            true
587                        };
588                        if insert {
589                            if let Some(range) = self.selection_range {
590                                self.remove_range(ui, range);
591                                self.selection_range = None;
592                            }
593                            self.insert_char(symbol, ui);
594                        }
595                    }
596                    WidgetMessage::KeyDown(code) => match code {
597                        KeyCode::Up => {
598                            self.move_caret_y(
599                                1,
600                                VerticalDirection::Up,
601                                ui.keyboard_modifiers().shift,
602                            );
603                        }
604                        KeyCode::Down => {
605                            self.move_caret_y(
606                                1,
607                                VerticalDirection::Down,
608                                ui.keyboard_modifiers().shift,
609                            );
610                        }
611                        KeyCode::Right => {
612                            self.move_caret_x(
613                                1,
614                                HorizontalDirection::Right,
615                                ui.keyboard_modifiers().shift,
616                            );
617                        }
618                        KeyCode::Left => {
619                            self.move_caret_x(
620                                1,
621                                HorizontalDirection::Left,
622                                ui.keyboard_modifiers().shift,
623                            );
624                        }
625                        KeyCode::Delete if !message.handled() && self.editable => {
626                            if let Some(range) = self.selection_range {
627                                self.remove_range(ui, range);
628                                self.selection_range = None;
629                            } else {
630                                self.remove_char(HorizontalDirection::Right, ui);
631                            }
632                            message.set_handled(true);
633                        }
634                        KeyCode::NumpadEnter | KeyCode::Return if self.editable => {
635                            if self.multiline {
636                                self.insert_char('\n', ui);
637                            } else if self.commit_mode == TextCommitMode::LostFocusPlusEnter {
638                                ui.send_message(TextBoxMessage::text(
639                                    self.handle,
640                                    MessageDirection::FromWidget,
641                                    self.text(),
642                                ));
643                                self.has_focus = false;
644                            }
645                        }
646                        KeyCode::Backspace if self.editable => {
647                            if let Some(range) = self.selection_range {
648                                self.remove_range(ui, range);
649                                self.selection_range = None;
650                            } else {
651                                self.remove_char(HorizontalDirection::Left, ui);
652                            }
653                        }
654                        KeyCode::End => {
655                            let text = self.formatted_text.borrow();
656                            let line = &text.get_lines()[self.caret_position.line];
657                            if ui.keyboard_modifiers().control {
658                                self.caret_position.line = text.get_lines().len() - 1;
659                                self.caret_position.offset = line.end - line.begin;
660                                self.selection_range = None;
661                            } else if ui.keyboard_modifiers().shift {
662                                let prev_position = self.caret_position;
663                                self.caret_position.offset = line.end - line.begin;
664                                self.selection_range = Some(SelectionRange {
665                                    begin: prev_position,
666                                    end: Position {
667                                        line: self.caret_position.line,
668                                        offset: self.caret_position.offset - 1,
669                                    },
670                                });
671                            } else {
672                                self.caret_position.offset = line.end - line.begin;
673                                self.selection_range = None;
674                            }
675                        }
676                        KeyCode::Home => {
677                            if ui.keyboard_modifiers().control {
678                                self.caret_position.line = 0;
679                                self.caret_position.offset = 0;
680                                self.selection_range = None;
681                            } else if ui.keyboard_modifiers().shift {
682                                let prev_position = self.caret_position;
683                                self.caret_position.line = 0;
684                                self.caret_position.offset = 0;
685                                self.selection_range = Some(SelectionRange {
686                                    begin: self.caret_position,
687                                    end: Position {
688                                        line: prev_position.line,
689                                        offset: prev_position.offset.saturating_sub(1),
690                                    },
691                                });
692                            } else {
693                                self.caret_position.offset = 0;
694                                self.selection_range = None;
695                            }
696                        }
697                        KeyCode::A if ui.keyboard_modifiers().control => {
698                            let text = self.formatted_text.borrow();
699                            if let Some(last_line) = &text.get_lines().last() {
700                                self.selection_range = Some(SelectionRange {
701                                    begin: Position { line: 0, offset: 0 },
702                                    end: Position {
703                                        line: text.get_lines().len() - 1,
704                                        offset: last_line.end - last_line.begin,
705                                    },
706                                });
707                            }
708                        }
709                        KeyCode::C if ui.keyboard_modifiers().control => {
710                            if let Some(clipboard) = ui.clipboard_mut() {
711                                if let Some(selection_range) = self.selection_range.as_ref() {
712                                    if let (Some(begin), Some(end)) = (
713                                        self.get_absolute_position(selection_range.begin),
714                                        self.get_absolute_position(selection_range.end),
715                                    ) {
716                                        let _ = clipboard
717                                            .set_contents(String::from(&self.text()[begin..end]));
718                                    }
719                                }
720                            }
721                        }
722                        KeyCode::V if ui.keyboard_modifiers().control => {
723                            if let Some(clipboard) = ui.clipboard_mut() {
724                                if let Ok(content) = clipboard.get_contents() {
725                                    if let Some(selection_range) = self.selection_range {
726                                        self.remove_range(ui, selection_range);
727                                        self.selection_range = None;
728                                    }
729
730                                    self.insert_str(&content, ui);
731                                }
732                            }
733                        }
734                        _ => (),
735                    },
736                    WidgetMessage::GotFocus => {
737                        self.reset_blink();
738                        self.selection_range = None;
739                        self.has_focus = true;
740                    }
741                    WidgetMessage::LostFocus => {
742                        self.selection_range = None;
743                        self.has_focus = false;
744
745                        if self.commit_mode == TextCommitMode::LostFocus
746                            || self.commit_mode == TextCommitMode::LostFocusPlusEnter
747                        {
748                            ui.send_message(TextBoxMessage::text(
749                                self.handle,
750                                MessageDirection::FromWidget,
751                                self.text(),
752                            ));
753                        }
754                    }
755                    WidgetMessage::MouseDown { pos, button } => {
756                        if *button == MouseButton::Left {
757                            self.selection_range = None;
758                            self.selecting = true;
759                            self.has_focus = true;
760
761                            if let Some(position) = self.screen_pos_to_text_pos(*pos) {
762                                self.caret_position = position;
763
764                                self.selection_range = Some(SelectionRange {
765                                    begin: position,
766                                    end: position,
767                                })
768                            }
769
770                            ui.capture_mouse(self.handle());
771                        }
772                    }
773                    WidgetMessage::MouseMove { pos, .. } => {
774                        if self.selecting {
775                            if let Some(position) = self.screen_pos_to_text_pos(*pos) {
776                                if let Some(ref mut sel_range) = self.selection_range {
777                                    if position.offset > sel_range.begin.offset {
778                                        sel_range.end = Position {
779                                            line: position.line,
780                                            offset: position.offset + 1,
781                                        };
782                                    } else {
783                                        sel_range.end = position;
784                                    }
785                                }
786                            }
787                        }
788                    }
789                    WidgetMessage::MouseUp { .. } => {
790                        self.selecting = false;
791
792                        ui.release_mouse_capture();
793                    }
794                    _ => {}
795                }
796            } else if let Some(TextBoxMessage::Text(new_text)) = message.data::<TextBoxMessage>() {
797                if message.direction() == MessageDirection::ToWidget {
798                    let mut equals = false;
799                    for (&old, new) in self
800                        .formatted_text
801                        .borrow()
802                        .get_raw_text()
803                        .iter()
804                        .zip(new_text.chars())
805                    {
806                        if old.char_code != new as u32 {
807                            equals = false;
808                            break;
809                        }
810                    }
811                    if !equals {
812                        self.formatted_text.borrow_mut().set_text(new_text);
813                        self.invalidate_layout();
814
815                        if self.commit_mode == TextCommitMode::Immediate {
816                            ui.send_message(message.reverse());
817                        }
818                    }
819                }
820            }
821        }
822    }
823}
824
825pub struct TextBoxBuilder {
826    widget_builder: WidgetBuilder,
827    font: Option<SharedFont>,
828    text: String,
829    caret_brush: Brush,
830    selection_brush: Brush,
831    filter: Option<Rc<RefCell<FilterCallback>>>,
832    vertical_alignment: VerticalAlignment,
833    horizontal_alignment: HorizontalAlignment,
834    wrap: WrapMode,
835    commit_mode: TextCommitMode,
836    multiline: bool,
837    editable: bool,
838    mask_char: Option<char>,
839}
840
841impl TextBoxBuilder {
842    pub fn new(widget_builder: WidgetBuilder) -> Self {
843        Self {
844            widget_builder,
845            font: None,
846            text: "".to_owned(),
847            caret_brush: Brush::Solid(Color::WHITE),
848            selection_brush: Brush::Solid(Color::opaque(80, 118, 178)),
849            filter: None,
850            vertical_alignment: VerticalAlignment::Top,
851            horizontal_alignment: HorizontalAlignment::Left,
852            wrap: WrapMode::NoWrap,
853            commit_mode: TextCommitMode::LostFocusPlusEnter,
854            multiline: false,
855            editable: true,
856            mask_char: None,
857        }
858    }
859
860    pub fn with_font(mut self, font: SharedFont) -> Self {
861        self.font = Some(font);
862        self
863    }
864
865    pub fn with_text<P: AsRef<str>>(mut self, text: P) -> Self {
866        self.text = text.as_ref().to_owned();
867        self
868    }
869
870    pub fn with_caret_brush(mut self, brush: Brush) -> Self {
871        self.caret_brush = brush;
872        self
873    }
874
875    pub fn with_selection_brush(mut self, brush: Brush) -> Self {
876        self.selection_brush = brush;
877        self
878    }
879
880    pub fn with_filter(mut self, filter: Rc<RefCell<FilterCallback>>) -> Self {
881        self.filter = Some(filter);
882        self
883    }
884
885    pub fn with_vertical_text_alignment(mut self, alignment: VerticalAlignment) -> Self {
886        self.vertical_alignment = alignment;
887        self
888    }
889
890    pub fn with_horizontal_text_alignment(mut self, alignment: HorizontalAlignment) -> Self {
891        self.horizontal_alignment = alignment;
892        self
893    }
894
895    pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
896        self.wrap = wrap;
897        self
898    }
899
900    pub fn with_text_commit_mode(mut self, mode: TextCommitMode) -> Self {
901        self.commit_mode = mode;
902        self
903    }
904
905    pub fn with_multiline(mut self, multiline: bool) -> Self {
906        self.multiline = multiline;
907        self
908    }
909
910    pub fn with_editable(mut self, editable: bool) -> Self {
911        self.editable = editable;
912        self
913    }
914
915    pub fn with_mask_char(mut self, mask_char: Option<char>) -> Self {
916        self.mask_char = mask_char;
917        self
918    }
919
920    pub fn build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
921        if self.widget_builder.foreground.is_none() {
922            self.widget_builder.foreground = Some(BRUSH_TEXT);
923        }
924        if self.widget_builder.background.is_none() {
925            self.widget_builder.background = Some(BRUSH_DARKER);
926        }
927        if self.widget_builder.cursor.is_none() {
928            self.widget_builder.cursor = Some(CursorIcon::Text);
929        }
930
931        let text_box = TextBox {
932            widget: self.widget_builder.build(),
933            caret_position: Position::default(),
934            caret_visible: false,
935            blink_timer: 0.0,
936            blink_interval: 0.5,
937            formatted_text: RefCell::new(
938                FormattedTextBuilder::new()
939                    .with_text(self.text)
940                    .with_font(self.font.unwrap_or_else(|| crate::DEFAULT_FONT.clone()))
941                    .with_horizontal_alignment(self.horizontal_alignment)
942                    .with_vertical_alignment(self.vertical_alignment)
943                    .with_wrap(self.wrap)
944                    .with_mask_char(self.mask_char)
945                    .build(),
946            ),
947            selection_range: None,
948            selecting: false,
949            selection_brush: self.selection_brush,
950            caret_brush: self.caret_brush,
951            has_focus: false,
952            filter: self.filter,
953            commit_mode: self.commit_mode,
954            multiline: self.multiline,
955            editable: self.editable,
956        };
957
958        ctx.add_node(UiNode::new(text_box))
959    }
960}