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: usize,
49
50 offset: usize,
52}
53
54#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)]
55#[repr(u32)]
56pub enum TextCommitMode {
57 Immediate = 0,
59
60 LostFocus = 1,
62
63 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 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 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 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 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 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 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 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}