1mod editor;
34mod value;
35
36pub mod cursor;
37
38pub use cursor::Cursor;
39pub use value::Value;
40
41use editor::Editor;
42
43use crate::core::alignment;
44use crate::core::clipboard::{self, Clipboard};
45use crate::core::input_method;
46use crate::core::keyboard;
47use crate::core::keyboard::key;
48use crate::core::layout;
49use crate::core::mouse::{self, click};
50use crate::core::renderer;
51use crate::core::text::paragraph::{self, Paragraph as _};
52use crate::core::text::{self, Text};
53use crate::core::time::{Duration, Instant};
54use crate::core::touch;
55use crate::core::widget;
56use crate::core::widget::operation::{self, Operation};
57use crate::core::widget::tree::{self, Tree};
58use crate::core::window;
59use crate::core::{
60 Background, Border, Color, Element, Event, InputMethod, Layout, Length,
61 Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
62};
63use crate::runtime::Action;
64use crate::runtime::task::{self, Task};
65
66#[allow(missing_debug_implementations)]
99pub struct TextInput<
100 'a,
101 Message,
102 Theme = crate::Theme,
103 Renderer = crate::Renderer,
104> where
105 Theme: Catalog,
106 Renderer: text::Renderer,
107{
108 id: Option<Id>,
109 placeholder: String,
110 value: Value,
111 is_secure: bool,
112 font: Option<Renderer::Font>,
113 width: Length,
114 padding: Padding,
115 size: Option<Pixels>,
116 line_height: text::LineHeight,
117 alignment: alignment::Horizontal,
118 on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
119 on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
120 on_submit: Option<Message>,
121 icon: Option<Icon<Renderer::Font>>,
122 class: Theme::Class<'a>,
123 last_status: Option<Status>,
124}
125
126pub const DEFAULT_PADDING: Padding = Padding::new(5.0);
128
129impl<'a, Message, Theme, Renderer> TextInput<'a, Message, Theme, Renderer>
130where
131 Message: Clone,
132 Theme: Catalog,
133 Renderer: text::Renderer,
134{
135 pub fn new(placeholder: &str, value: &str) -> Self {
138 TextInput {
139 id: None,
140 placeholder: String::from(placeholder),
141 value: Value::new(value),
142 is_secure: false,
143 font: None,
144 width: Length::Fill,
145 padding: DEFAULT_PADDING,
146 size: None,
147 line_height: text::LineHeight::default(),
148 alignment: alignment::Horizontal::Left,
149 on_input: None,
150 on_paste: None,
151 on_submit: None,
152 icon: None,
153 class: Theme::default(),
154 last_status: None,
155 }
156 }
157
158 pub fn id(mut self, id: impl Into<Id>) -> Self {
160 self.id = Some(id.into());
161 self
162 }
163
164 pub fn secure(mut self, is_secure: bool) -> Self {
166 self.is_secure = is_secure;
167 self
168 }
169
170 pub fn on_input(
175 mut self,
176 on_input: impl Fn(String) -> Message + 'a,
177 ) -> Self {
178 self.on_input = Some(Box::new(on_input));
179 self
180 }
181
182 pub fn on_input_maybe(
187 mut self,
188 on_input: Option<impl Fn(String) -> Message + 'a>,
189 ) -> Self {
190 self.on_input = on_input.map(|f| Box::new(f) as _);
191 self
192 }
193
194 pub fn on_submit(mut self, message: Message) -> Self {
197 self.on_submit = Some(message);
198 self
199 }
200
201 pub fn on_submit_maybe(mut self, on_submit: Option<Message>) -> Self {
204 self.on_submit = on_submit;
205 self
206 }
207
208 pub fn on_paste(
211 mut self,
212 on_paste: impl Fn(String) -> Message + 'a,
213 ) -> Self {
214 self.on_paste = Some(Box::new(on_paste));
215 self
216 }
217
218 pub fn on_paste_maybe(
221 mut self,
222 on_paste: Option<impl Fn(String) -> Message + 'a>,
223 ) -> Self {
224 self.on_paste = on_paste.map(|f| Box::new(f) as _);
225 self
226 }
227
228 pub fn font(mut self, font: Renderer::Font) -> Self {
232 self.font = Some(font);
233 self
234 }
235
236 pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self {
238 self.icon = Some(icon);
239 self
240 }
241
242 pub fn width(mut self, width: impl Into<Length>) -> Self {
244 self.width = width.into();
245 self
246 }
247
248 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
250 self.padding = padding.into();
251 self
252 }
253
254 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
256 self.size = Some(size.into());
257 self
258 }
259
260 pub fn line_height(
262 mut self,
263 line_height: impl Into<text::LineHeight>,
264 ) -> Self {
265 self.line_height = line_height.into();
266 self
267 }
268
269 pub fn align_x(
271 mut self,
272 alignment: impl Into<alignment::Horizontal>,
273 ) -> Self {
274 self.alignment = alignment.into();
275 self
276 }
277
278 #[must_use]
280 pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
281 where
282 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
283 {
284 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
285 self
286 }
287
288 #[must_use]
290 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
291 self.class = class.into();
292 self
293 }
294
295 pub fn layout(
299 &self,
300 tree: &mut Tree,
301 renderer: &Renderer,
302 limits: &layout::Limits,
303 value: Option<&Value>,
304 ) -> layout::Node {
305 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
306 let value = value.unwrap_or(&self.value);
307
308 let font = self.font.unwrap_or_else(|| renderer.default_font());
309 let text_size = self.size.unwrap_or_else(|| renderer.default_size());
310 let padding = self.padding.fit(Size::ZERO, limits.max());
311 let height = self.line_height.to_absolute(text_size);
312
313 let limits = limits.width(self.width).shrink(padding);
314 let text_bounds = limits.resolve(self.width, height, Size::ZERO);
315
316 let placeholder_text = Text {
317 font,
318 line_height: self.line_height,
319 content: self.placeholder.as_str(),
320 bounds: Size::new(f32::INFINITY, text_bounds.height),
321 size: text_size,
322 align_x: text::Alignment::Default,
323 align_y: alignment::Vertical::Center,
324 shaping: text::Shaping::Advanced,
325 wrapping: text::Wrapping::default(),
326 };
327
328 state.placeholder.update(placeholder_text);
329
330 let secure_value = self.is_secure.then(|| value.secure());
331 let value = secure_value.as_ref().unwrap_or(value);
332
333 state.value.update(Text {
334 content: &value.to_string(),
335 ..placeholder_text
336 });
337
338 if let Some(icon) = &self.icon {
339 let mut content = [0; 4];
340
341 let icon_text = Text {
342 line_height: self.line_height,
343 content: icon.code_point.encode_utf8(&mut content) as &_,
344 font: icon.font,
345 size: icon.size.unwrap_or_else(|| renderer.default_size()),
346 bounds: Size::new(f32::INFINITY, text_bounds.height),
347 align_x: text::Alignment::Center,
348 align_y: alignment::Vertical::Center,
349 shaping: text::Shaping::Advanced,
350 wrapping: text::Wrapping::default(),
351 };
352
353 state.icon.update(icon_text);
354
355 let icon_width = state.icon.min_width();
356
357 let (text_position, icon_position) = match icon.side {
358 Side::Left => (
359 Point::new(
360 padding.left + icon_width + icon.spacing,
361 padding.top,
362 ),
363 Point::new(padding.left, padding.top),
364 ),
365 Side::Right => (
366 Point::new(padding.left, padding.top),
367 Point::new(
368 padding.left + text_bounds.width - icon_width,
369 padding.top,
370 ),
371 ),
372 };
373
374 let text_node = layout::Node::new(
375 text_bounds - Size::new(icon_width + icon.spacing, 0.0),
376 )
377 .move_to(text_position);
378
379 let icon_node =
380 layout::Node::new(Size::new(icon_width, text_bounds.height))
381 .move_to(icon_position);
382
383 layout::Node::with_children(
384 text_bounds.expand(padding),
385 vec![text_node, icon_node],
386 )
387 } else {
388 let text = layout::Node::new(text_bounds)
389 .move_to(Point::new(padding.left, padding.top));
390
391 layout::Node::with_children(text_bounds.expand(padding), vec![text])
392 }
393 }
394
395 fn input_method<'b>(
396 &self,
397 state: &'b State<Renderer::Paragraph>,
398 layout: Layout<'_>,
399 value: &Value,
400 ) -> InputMethod<&'b str> {
401 let Some(Focus {
402 is_window_focused: true,
403 ..
404 }) = &state.is_focused
405 else {
406 return InputMethod::Disabled;
407 };
408
409 let secure_value = self.is_secure.then(|| value.secure());
410 let value = secure_value.as_ref().unwrap_or(value);
411
412 let text_bounds = layout.children().next().unwrap().bounds();
413
414 let caret_index = match state.cursor.state(value) {
415 cursor::State::Index(position) => position,
416 cursor::State::Selection { start, end } => start.min(end),
417 };
418
419 let text = state.value.raw();
420 let (cursor_x, scroll_offset) =
421 measure_cursor_and_scroll_offset(text, text_bounds, caret_index);
422
423 let alignment_offset = alignment_offset(
424 text_bounds.width,
425 text.min_width(),
426 self.alignment,
427 );
428
429 let x = (text_bounds.x + cursor_x).floor() - scroll_offset
430 + alignment_offset;
431
432 InputMethod::Enabled {
433 position: Point::new(x, text_bounds.y + text_bounds.height),
434 purpose: if self.is_secure {
435 input_method::Purpose::Secure
436 } else {
437 input_method::Purpose::Normal
438 },
439 preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref),
440 }
441 }
442
443 pub fn draw(
448 &self,
449 tree: &Tree,
450 renderer: &mut Renderer,
451 theme: &Theme,
452 layout: Layout<'_>,
453 _cursor: mouse::Cursor,
454 value: Option<&Value>,
455 viewport: &Rectangle,
456 ) {
457 let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
458 let value = value.unwrap_or(&self.value);
459 let is_disabled = self.on_input.is_none();
460
461 let secure_value = self.is_secure.then(|| value.secure());
462 let value = secure_value.as_ref().unwrap_or(value);
463
464 let bounds = layout.bounds();
465
466 let mut children_layout = layout.children();
467 let text_bounds = children_layout.next().unwrap().bounds();
468
469 let style = theme
470 .style(&self.class, self.last_status.unwrap_or(Status::Disabled));
471
472 renderer.fill_quad(
473 renderer::Quad {
474 bounds,
475 border: style.border,
476 ..renderer::Quad::default()
477 },
478 style.background,
479 );
480
481 if self.icon.is_some() {
482 let icon_layout = children_layout.next().unwrap();
483
484 renderer.fill_paragraph(
485 state.icon.raw(),
486 icon_layout.bounds().center(),
487 style.icon,
488 *viewport,
489 );
490 }
491
492 let text = value.to_string();
493
494 let (cursor, offset, is_selecting) = if let Some(focus) = state
495 .is_focused
496 .as_ref()
497 .filter(|focus| focus.is_window_focused)
498 {
499 match state.cursor.state(value) {
500 cursor::State::Index(position) => {
501 let (text_value_width, offset) =
502 measure_cursor_and_scroll_offset(
503 state.value.raw(),
504 text_bounds,
505 position,
506 );
507
508 let is_cursor_visible = !is_disabled
509 && ((focus.now - focus.updated_at).as_millis()
510 / CURSOR_BLINK_INTERVAL_MILLIS)
511 % 2
512 == 0;
513
514 let cursor = if is_cursor_visible {
515 Some((
516 renderer::Quad {
517 bounds: Rectangle {
518 x: (text_bounds.x + text_value_width)
519 .floor(),
520 y: text_bounds.y,
521 width: 1.0,
522 height: text_bounds.height,
523 },
524 ..renderer::Quad::default()
525 },
526 style.value,
527 ))
528 } else {
529 None
530 };
531
532 (cursor, offset, false)
533 }
534 cursor::State::Selection { start, end } => {
535 let left = start.min(end);
536 let right = end.max(start);
537
538 let (left_position, left_offset) =
539 measure_cursor_and_scroll_offset(
540 state.value.raw(),
541 text_bounds,
542 left,
543 );
544
545 let (right_position, right_offset) =
546 measure_cursor_and_scroll_offset(
547 state.value.raw(),
548 text_bounds,
549 right,
550 );
551
552 let width = right_position - left_position;
553
554 (
555 Some((
556 renderer::Quad {
557 bounds: Rectangle {
558 x: text_bounds.x + left_position,
559 y: text_bounds.y,
560 width,
561 height: text_bounds.height,
562 },
563 ..renderer::Quad::default()
564 },
565 style.selection,
566 )),
567 if end == right {
568 right_offset
569 } else {
570 left_offset
571 },
572 true,
573 )
574 }
575 }
576 } else {
577 (None, 0.0, false)
578 };
579
580 let draw = |renderer: &mut Renderer, viewport| {
581 let paragraph = if text.is_empty()
582 && state
583 .preedit
584 .as_ref()
585 .map(|preedit| preedit.content.is_empty())
586 .unwrap_or(true)
587 {
588 state.placeholder.raw()
589 } else {
590 state.value.raw()
591 };
592
593 let alignment_offset = alignment_offset(
594 text_bounds.width,
595 paragraph.min_width(),
596 self.alignment,
597 );
598
599 if let Some((cursor, color)) = cursor {
600 renderer.with_translation(
601 Vector::new(alignment_offset - offset, 0.0),
602 |renderer| {
603 renderer.fill_quad(cursor, color);
604 },
605 );
606 } else {
607 renderer.with_translation(Vector::ZERO, |_| {});
608 }
609
610 renderer.fill_paragraph(
611 paragraph,
612 Point::new(text_bounds.x, text_bounds.center_y())
613 + Vector::new(alignment_offset - offset, 0.0),
614 if text.is_empty() {
615 style.placeholder
616 } else {
617 style.value
618 },
619 viewport,
620 );
621 };
622
623 if is_selecting {
624 renderer
625 .with_layer(text_bounds, |renderer| draw(renderer, *viewport));
626 } else {
627 draw(renderer, text_bounds);
628 }
629 }
630}
631
632impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
633 for TextInput<'_, Message, Theme, Renderer>
634where
635 Message: Clone,
636 Theme: Catalog,
637 Renderer: text::Renderer,
638{
639 fn tag(&self) -> tree::Tag {
640 tree::Tag::of::<State<Renderer::Paragraph>>()
641 }
642
643 fn state(&self) -> tree::State {
644 tree::State::new(State::<Renderer::Paragraph>::new())
645 }
646
647 fn diff(&self, tree: &mut Tree) {
648 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
649
650 if self.on_input.is_none() {
652 state.is_pasting = None;
653 }
654 }
655
656 fn size(&self) -> Size<Length> {
657 Size {
658 width: self.width,
659 height: Length::Shrink,
660 }
661 }
662
663 fn layout(
664 &self,
665 tree: &mut Tree,
666 renderer: &Renderer,
667 limits: &layout::Limits,
668 ) -> layout::Node {
669 self.layout(tree, renderer, limits, None)
670 }
671
672 fn operate(
673 &self,
674 tree: &mut Tree,
675 layout: Layout<'_>,
676 _renderer: &Renderer,
677 operation: &mut dyn Operation,
678 ) {
679 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
680
681 operation.focusable(
682 self.id.as_ref().map(|id| &id.0),
683 layout.bounds(),
684 state,
685 );
686
687 operation.text_input(
688 self.id.as_ref().map(|id| &id.0),
689 layout.bounds(),
690 state,
691 );
692 }
693
694 fn update(
695 &mut self,
696 tree: &mut Tree,
697 event: &Event,
698 layout: Layout<'_>,
699 cursor: mouse::Cursor,
700 renderer: &Renderer,
701 clipboard: &mut dyn Clipboard,
702 shell: &mut Shell<'_, Message>,
703 _viewport: &Rectangle,
704 ) {
705 let update_cache = |state, value| {
706 replace_paragraph(
707 renderer,
708 state,
709 layout,
710 value,
711 self.font,
712 self.size,
713 self.line_height,
714 );
715 };
716
717 match &event {
718 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
719 | Event::Touch(touch::Event::FingerPressed { .. }) => {
720 let state = state::<Renderer>(tree);
721 let cursor_before = state.cursor;
722
723 let click_position = cursor.position_over(layout.bounds());
724
725 state.is_focused = if click_position.is_some() {
726 let now = Instant::now();
727
728 Some(Focus {
729 updated_at: now,
730 now,
731 is_window_focused: true,
732 })
733 } else {
734 None
735 };
736
737 if let Some(cursor_position) = click_position {
738 let text_layout = layout.children().next().unwrap();
739
740 let target = {
741 let text_bounds = text_layout.bounds();
742
743 let alignment_offset = alignment_offset(
744 text_bounds.width,
745 state.value.raw().min_width(),
746 self.alignment,
747 );
748
749 cursor_position.x - text_bounds.x - alignment_offset
750 };
751
752 let click = mouse::Click::new(
753 cursor_position,
754 mouse::Button::Left,
755 state.last_click,
756 );
757
758 match click.kind() {
759 click::Kind::Single => {
760 let position = if target > 0.0 {
761 let value = if self.is_secure {
762 self.value.secure()
763 } else {
764 self.value.clone()
765 };
766
767 find_cursor_position(
768 text_layout.bounds(),
769 &value,
770 state,
771 target,
772 )
773 } else {
774 None
775 }
776 .unwrap_or(0);
777
778 if state.keyboard_modifiers.shift() {
779 state.cursor.select_range(
780 state.cursor.start(&self.value),
781 position,
782 );
783 } else {
784 state.cursor.move_to(position);
785 }
786 state.is_dragging = true;
787 }
788 click::Kind::Double => {
789 if self.is_secure {
790 state.cursor.select_all(&self.value);
791 } else {
792 let position = find_cursor_position(
793 text_layout.bounds(),
794 &self.value,
795 state,
796 target,
797 )
798 .unwrap_or(0);
799
800 state.cursor.select_range(
801 self.value.previous_start_of_word(position),
802 self.value.next_end_of_word(position),
803 );
804 }
805
806 state.is_dragging = false;
807 }
808 click::Kind::Triple => {
809 state.cursor.select_all(&self.value);
810 state.is_dragging = false;
811 }
812 }
813
814 state.last_click = Some(click);
815
816 if cursor_before != state.cursor {
817 shell.request_redraw();
818 }
819
820 shell.capture_event();
821 }
822 }
823 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
824 | Event::Touch(touch::Event::FingerLifted { .. })
825 | Event::Touch(touch::Event::FingerLost { .. }) => {
826 state::<Renderer>(tree).is_dragging = false;
827 }
828 Event::Mouse(mouse::Event::CursorMoved { position })
829 | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
830 let state = state::<Renderer>(tree);
831
832 if state.is_dragging {
833 let text_layout = layout.children().next().unwrap();
834
835 let target = {
836 let text_bounds = text_layout.bounds();
837
838 let alignment_offset = alignment_offset(
839 text_bounds.width,
840 state.value.raw().min_width(),
841 self.alignment,
842 );
843
844 position.x - text_bounds.x - alignment_offset
845 };
846
847 let value = if self.is_secure {
848 self.value.secure()
849 } else {
850 self.value.clone()
851 };
852
853 let position = find_cursor_position(
854 text_layout.bounds(),
855 &value,
856 state,
857 target,
858 )
859 .unwrap_or(0);
860
861 let selection_before = state.cursor.selection(&value);
862
863 state
864 .cursor
865 .select_range(state.cursor.start(&value), position);
866
867 if let Some(focus) = &mut state.is_focused {
868 focus.updated_at = Instant::now();
869 }
870
871 if selection_before != state.cursor.selection(&value) {
872 shell.request_redraw();
873 }
874
875 shell.capture_event();
876 }
877 }
878 Event::Keyboard(keyboard::Event::KeyPressed {
879 key, text, ..
880 }) => {
881 let state = state::<Renderer>(tree);
882
883 if let Some(focus) = &mut state.is_focused {
884 let modifiers = state.keyboard_modifiers;
885
886 match key.as_ref() {
887 keyboard::Key::Character("c")
888 if state.keyboard_modifiers.command()
889 && !self.is_secure =>
890 {
891 if let Some((start, end)) =
892 state.cursor.selection(&self.value)
893 {
894 clipboard.write(
895 clipboard::Kind::Standard,
896 self.value.select(start, end).to_string(),
897 );
898 }
899
900 shell.capture_event();
901 return;
902 }
903 keyboard::Key::Character("x")
904 if state.keyboard_modifiers.command()
905 && !self.is_secure =>
906 {
907 let Some(on_input) = &self.on_input else {
908 return;
909 };
910
911 if let Some((start, end)) =
912 state.cursor.selection(&self.value)
913 {
914 clipboard.write(
915 clipboard::Kind::Standard,
916 self.value.select(start, end).to_string(),
917 );
918 }
919
920 let mut editor =
921 Editor::new(&mut self.value, &mut state.cursor);
922 editor.delete();
923
924 let message = (on_input)(editor.contents());
925 shell.publish(message);
926 shell.capture_event();
927
928 focus.updated_at = Instant::now();
929 update_cache(state, &self.value);
930 return;
931 }
932 keyboard::Key::Character("v")
933 if state.keyboard_modifiers.command()
934 && !state.keyboard_modifiers.alt() =>
935 {
936 let Some(on_input) = &self.on_input else {
937 return;
938 };
939
940 let content = match state.is_pasting.take() {
941 Some(content) => content,
942 None => {
943 let content: String = clipboard
944 .read(clipboard::Kind::Standard)
945 .unwrap_or_default()
946 .chars()
947 .filter(|c| !c.is_control())
948 .collect();
949
950 Value::new(&content)
951 }
952 };
953
954 let mut editor =
955 Editor::new(&mut self.value, &mut state.cursor);
956 editor.paste(content.clone());
957
958 let message = if let Some(paste) = &self.on_paste {
959 (paste)(editor.contents())
960 } else {
961 (on_input)(editor.contents())
962 };
963 shell.publish(message);
964 shell.capture_event();
965
966 state.is_pasting = Some(content);
967 focus.updated_at = Instant::now();
968 update_cache(state, &self.value);
969 return;
970 }
971 keyboard::Key::Character("a")
972 if state.keyboard_modifiers.command() =>
973 {
974 let cursor_before = state.cursor;
975
976 state.cursor.select_all(&self.value);
977
978 if cursor_before != state.cursor {
979 focus.updated_at = Instant::now();
980
981 shell.request_redraw();
982 }
983
984 shell.capture_event();
985 return;
986 }
987 _ => {}
988 }
989
990 if let Some(text) = text {
991 let Some(on_input) = &self.on_input else {
992 return;
993 };
994
995 state.is_pasting = None;
996
997 if let Some(c) =
998 text.chars().next().filter(|c| !c.is_control())
999 {
1000 let mut editor =
1001 Editor::new(&mut self.value, &mut state.cursor);
1002
1003 editor.insert(c);
1004
1005 let message = (on_input)(editor.contents());
1006 shell.publish(message);
1007 shell.capture_event();
1008
1009 focus.updated_at = Instant::now();
1010 update_cache(state, &self.value);
1011 return;
1012 }
1013 }
1014
1015 match key.as_ref() {
1016 keyboard::Key::Named(key::Named::Enter) => {
1017 if let Some(on_submit) = self.on_submit.clone() {
1018 shell.publish(on_submit);
1019 shell.capture_event();
1020 }
1021 }
1022 keyboard::Key::Named(key::Named::Backspace) => {
1023 let Some(on_input) = &self.on_input else {
1024 return;
1025 };
1026
1027 if modifiers.jump()
1028 && state.cursor.selection(&self.value).is_none()
1029 {
1030 if self.is_secure {
1031 let cursor_pos =
1032 state.cursor.end(&self.value);
1033 state.cursor.select_range(0, cursor_pos);
1034 } else {
1035 state
1036 .cursor
1037 .select_left_by_words(&self.value);
1038 }
1039 }
1040
1041 let mut editor =
1042 Editor::new(&mut self.value, &mut state.cursor);
1043 editor.backspace();
1044
1045 let message = (on_input)(editor.contents());
1046 shell.publish(message);
1047 shell.capture_event();
1048
1049 focus.updated_at = Instant::now();
1050 update_cache(state, &self.value);
1051 }
1052 keyboard::Key::Named(key::Named::Delete) => {
1053 let Some(on_input) = &self.on_input else {
1054 return;
1055 };
1056
1057 if modifiers.jump()
1058 && state.cursor.selection(&self.value).is_none()
1059 {
1060 if self.is_secure {
1061 let cursor_pos =
1062 state.cursor.end(&self.value);
1063 state.cursor.select_range(
1064 cursor_pos,
1065 self.value.len(),
1066 );
1067 } else {
1068 state
1069 .cursor
1070 .select_right_by_words(&self.value);
1071 }
1072 }
1073
1074 let mut editor =
1075 Editor::new(&mut self.value, &mut state.cursor);
1076 editor.delete();
1077
1078 let message = (on_input)(editor.contents());
1079 shell.publish(message);
1080 shell.capture_event();
1081
1082 focus.updated_at = Instant::now();
1083 update_cache(state, &self.value);
1084 }
1085 keyboard::Key::Named(key::Named::Home) => {
1086 let cursor_before = state.cursor;
1087
1088 if modifiers.shift() {
1089 state.cursor.select_range(
1090 state.cursor.start(&self.value),
1091 0,
1092 );
1093 } else {
1094 state.cursor.move_to(0);
1095 }
1096
1097 if cursor_before != state.cursor {
1098 focus.updated_at = Instant::now();
1099
1100 shell.request_redraw();
1101 }
1102
1103 shell.capture_event();
1104 }
1105 keyboard::Key::Named(key::Named::End) => {
1106 let cursor_before = state.cursor;
1107
1108 if modifiers.shift() {
1109 state.cursor.select_range(
1110 state.cursor.start(&self.value),
1111 self.value.len(),
1112 );
1113 } else {
1114 state.cursor.move_to(self.value.len());
1115 }
1116
1117 if cursor_before != state.cursor {
1118 focus.updated_at = Instant::now();
1119
1120 shell.request_redraw();
1121 }
1122
1123 shell.capture_event();
1124 }
1125 keyboard::Key::Named(key::Named::ArrowLeft)
1126 if modifiers.macos_command() =>
1127 {
1128 let cursor_before = state.cursor;
1129
1130 if modifiers.shift() {
1131 state.cursor.select_range(
1132 state.cursor.start(&self.value),
1133 0,
1134 );
1135 } else {
1136 state.cursor.move_to(0);
1137 }
1138
1139 if cursor_before != state.cursor {
1140 focus.updated_at = Instant::now();
1141
1142 shell.request_redraw();
1143 }
1144
1145 shell.capture_event();
1146 }
1147 keyboard::Key::Named(key::Named::ArrowRight)
1148 if modifiers.macos_command() =>
1149 {
1150 let cursor_before = state.cursor;
1151
1152 if modifiers.shift() {
1153 state.cursor.select_range(
1154 state.cursor.start(&self.value),
1155 self.value.len(),
1156 );
1157 } else {
1158 state.cursor.move_to(self.value.len());
1159 }
1160
1161 if cursor_before != state.cursor {
1162 focus.updated_at = Instant::now();
1163
1164 shell.request_redraw();
1165 }
1166
1167 shell.capture_event();
1168 }
1169 keyboard::Key::Named(key::Named::ArrowLeft) => {
1170 let cursor_before = state.cursor;
1171
1172 if modifiers.jump() && !self.is_secure {
1173 if modifiers.shift() {
1174 state
1175 .cursor
1176 .select_left_by_words(&self.value);
1177 } else {
1178 state
1179 .cursor
1180 .move_left_by_words(&self.value);
1181 }
1182 } else if modifiers.shift() {
1183 state.cursor.select_left(&self.value);
1184 } else {
1185 state.cursor.move_left(&self.value);
1186 }
1187
1188 if cursor_before != state.cursor {
1189 focus.updated_at = Instant::now();
1190
1191 shell.request_redraw();
1192 }
1193
1194 shell.capture_event();
1195 }
1196 keyboard::Key::Named(key::Named::ArrowRight) => {
1197 let cursor_before = state.cursor;
1198
1199 if modifiers.jump() && !self.is_secure {
1200 if modifiers.shift() {
1201 state
1202 .cursor
1203 .select_right_by_words(&self.value);
1204 } else {
1205 state
1206 .cursor
1207 .move_right_by_words(&self.value);
1208 }
1209 } else if modifiers.shift() {
1210 state.cursor.select_right(&self.value);
1211 } else {
1212 state.cursor.move_right(&self.value);
1213 }
1214
1215 if cursor_before != state.cursor {
1216 focus.updated_at = Instant::now();
1217
1218 shell.request_redraw();
1219 }
1220
1221 shell.capture_event();
1222 }
1223 keyboard::Key::Named(key::Named::Escape) => {
1224 state.is_focused = None;
1225 state.is_dragging = false;
1226 state.is_pasting = None;
1227
1228 state.keyboard_modifiers =
1229 keyboard::Modifiers::default();
1230
1231 shell.capture_event();
1232 }
1233 _ => {}
1234 }
1235 }
1236 }
1237 Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
1238 let state = state::<Renderer>(tree);
1239
1240 if state.is_focused.is_some() {
1241 if let keyboard::Key::Character("v") = key.as_ref() {
1242 state.is_pasting = None;
1243
1244 shell.capture_event();
1245 }
1246 }
1247
1248 state.is_pasting = None;
1249 }
1250 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
1251 let state = state::<Renderer>(tree);
1252
1253 state.keyboard_modifiers = *modifiers;
1254 }
1255 Event::InputMethod(event) => match event {
1256 input_method::Event::Opened | input_method::Event::Closed => {
1257 let state = state::<Renderer>(tree);
1258
1259 state.preedit =
1260 matches!(event, input_method::Event::Opened)
1261 .then(input_method::Preedit::new);
1262
1263 shell.request_redraw();
1264 }
1265 input_method::Event::Preedit(content, selection) => {
1266 let state = state::<Renderer>(tree);
1267
1268 if state.is_focused.is_some() {
1269 state.preedit = Some(input_method::Preedit {
1270 content: content.to_owned(),
1271 selection: selection.clone(),
1272 text_size: self.size,
1273 });
1274
1275 shell.request_redraw();
1276 }
1277 }
1278 input_method::Event::Commit(text) => {
1279 let state = state::<Renderer>(tree);
1280
1281 if let Some(focus) = &mut state.is_focused {
1282 let Some(on_input) = &self.on_input else {
1283 return;
1284 };
1285
1286 let mut editor =
1287 Editor::new(&mut self.value, &mut state.cursor);
1288 editor.paste(Value::new(text));
1289
1290 focus.updated_at = Instant::now();
1291 state.is_pasting = None;
1292
1293 let message = (on_input)(editor.contents());
1294 shell.publish(message);
1295 shell.capture_event();
1296
1297 update_cache(state, &self.value);
1298 }
1299 }
1300 },
1301 Event::Window(window::Event::Unfocused) => {
1302 let state = state::<Renderer>(tree);
1303
1304 if let Some(focus) = &mut state.is_focused {
1305 focus.is_window_focused = false;
1306 }
1307 }
1308 Event::Window(window::Event::Focused) => {
1309 let state = state::<Renderer>(tree);
1310
1311 if let Some(focus) = &mut state.is_focused {
1312 focus.is_window_focused = true;
1313 focus.updated_at = Instant::now();
1314
1315 shell.request_redraw();
1316 }
1317 }
1318 Event::Window(window::Event::RedrawRequested(now)) => {
1319 let state = state::<Renderer>(tree);
1320
1321 if let Some(focus) = &mut state.is_focused {
1322 if focus.is_window_focused {
1323 if matches!(
1324 state.cursor.state(&self.value),
1325 cursor::State::Index(_)
1326 ) {
1327 focus.now = *now;
1328
1329 let millis_until_redraw =
1330 CURSOR_BLINK_INTERVAL_MILLIS
1331 - (*now - focus.updated_at).as_millis()
1332 % CURSOR_BLINK_INTERVAL_MILLIS;
1333
1334 shell.request_redraw_at(
1335 *now + Duration::from_millis(
1336 millis_until_redraw as u64,
1337 ),
1338 );
1339 }
1340
1341 shell.request_input_method(&self.input_method(
1342 state,
1343 layout,
1344 &self.value,
1345 ));
1346 }
1347 }
1348 }
1349 _ => {}
1350 }
1351
1352 let state = state::<Renderer>(tree);
1353 let is_disabled = self.on_input.is_none();
1354
1355 let status = if is_disabled {
1356 Status::Disabled
1357 } else if state.is_focused() {
1358 Status::Focused {
1359 is_hovered: cursor.is_over(layout.bounds()),
1360 }
1361 } else if cursor.is_over(layout.bounds()) {
1362 Status::Hovered
1363 } else {
1364 Status::Active
1365 };
1366
1367 if let Event::Window(window::Event::RedrawRequested(_now)) = event {
1368 self.last_status = Some(status);
1369 } else if self
1370 .last_status
1371 .is_some_and(|last_status| status != last_status)
1372 {
1373 shell.request_redraw();
1374 }
1375 }
1376
1377 fn draw(
1378 &self,
1379 tree: &Tree,
1380 renderer: &mut Renderer,
1381 theme: &Theme,
1382 _style: &renderer::Style,
1383 layout: Layout<'_>,
1384 cursor: mouse::Cursor,
1385 viewport: &Rectangle,
1386 ) {
1387 self.draw(tree, renderer, theme, layout, cursor, None, viewport);
1388 }
1389
1390 fn mouse_interaction(
1391 &self,
1392 _state: &Tree,
1393 layout: Layout<'_>,
1394 cursor: mouse::Cursor,
1395 _viewport: &Rectangle,
1396 _renderer: &Renderer,
1397 ) -> mouse::Interaction {
1398 if cursor.is_over(layout.bounds()) {
1399 if self.on_input.is_none() {
1400 mouse::Interaction::Idle
1401 } else {
1402 mouse::Interaction::Text
1403 }
1404 } else {
1405 mouse::Interaction::default()
1406 }
1407 }
1408}
1409
1410impl<'a, Message, Theme, Renderer> From<TextInput<'a, Message, Theme, Renderer>>
1411 for Element<'a, Message, Theme, Renderer>
1412where
1413 Message: Clone + 'a,
1414 Theme: Catalog + 'a,
1415 Renderer: text::Renderer + 'a,
1416{
1417 fn from(
1418 text_input: TextInput<'a, Message, Theme, Renderer>,
1419 ) -> Element<'a, Message, Theme, Renderer> {
1420 Element::new(text_input)
1421 }
1422}
1423
1424#[derive(Debug, Clone)]
1426pub struct Icon<Font> {
1427 pub font: Font,
1429 pub code_point: char,
1431 pub size: Option<Pixels>,
1433 pub spacing: f32,
1435 pub side: Side,
1437}
1438
1439#[derive(Debug, Clone)]
1441pub enum Side {
1442 Left,
1444 Right,
1446}
1447
1448#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1450pub struct Id(widget::Id);
1451
1452impl Id {
1453 pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
1455 Self(widget::Id::new(id))
1456 }
1457
1458 pub fn unique() -> Self {
1462 Self(widget::Id::unique())
1463 }
1464}
1465
1466impl From<Id> for widget::Id {
1467 fn from(id: Id) -> Self {
1468 id.0
1469 }
1470}
1471
1472impl From<&'static str> for Id {
1473 fn from(id: &'static str) -> Self {
1474 Self::new(id)
1475 }
1476}
1477
1478impl From<String> for Id {
1479 fn from(id: String) -> Self {
1480 Self::new(id)
1481 }
1482}
1483
1484pub fn is_focused(id: impl Into<Id>) -> Task<bool> {
1486 task::widget(operation::focusable::is_focused(id.into().into()))
1487}
1488
1489pub fn focus<T>(id: impl Into<Id>) -> Task<T> {
1491 task::effect(Action::widget(operation::focusable::focus(id.into().0)))
1492}
1493
1494pub fn move_cursor_to_end<T>(id: impl Into<Id>) -> Task<T> {
1497 task::effect(Action::widget(operation::text_input::move_cursor_to_end(
1498 id.into().0,
1499 )))
1500}
1501
1502pub fn move_cursor_to_front<T>(id: impl Into<Id>) -> Task<T> {
1505 task::effect(Action::widget(operation::text_input::move_cursor_to_front(
1506 id.into().0,
1507 )))
1508}
1509
1510pub fn move_cursor_to<T>(id: impl Into<Id>, position: usize) -> Task<T> {
1513 task::effect(Action::widget(operation::text_input::move_cursor_to(
1514 id.into().0,
1515 position,
1516 )))
1517}
1518
1519pub fn select_all<T>(id: impl Into<Id>) -> Task<T> {
1521 task::effect(Action::widget(operation::text_input::select_all(
1522 id.into().0,
1523 )))
1524}
1525
1526#[derive(Debug, Default, Clone)]
1528pub struct State<P: text::Paragraph> {
1529 value: paragraph::Plain<P>,
1530 placeholder: paragraph::Plain<P>,
1531 icon: paragraph::Plain<P>,
1532 is_focused: Option<Focus>,
1533 is_dragging: bool,
1534 is_pasting: Option<Value>,
1535 preedit: Option<input_method::Preedit>,
1536 last_click: Option<mouse::Click>,
1537 cursor: Cursor,
1538 keyboard_modifiers: keyboard::Modifiers,
1539 }
1541
1542fn state<Renderer: text::Renderer>(
1543 tree: &mut Tree,
1544) -> &mut State<Renderer::Paragraph> {
1545 tree.state.downcast_mut::<State<Renderer::Paragraph>>()
1546}
1547
1548#[derive(Debug, Clone)]
1549struct Focus {
1550 updated_at: Instant,
1551 now: Instant,
1552 is_window_focused: bool,
1553}
1554
1555impl<P: text::Paragraph> State<P> {
1556 pub fn new() -> Self {
1558 Self::default()
1559 }
1560
1561 pub fn is_focused(&self) -> bool {
1563 self.is_focused.is_some()
1564 }
1565
1566 pub fn cursor(&self) -> Cursor {
1568 self.cursor
1569 }
1570
1571 pub fn focus(&mut self) {
1573 let now = Instant::now();
1574
1575 self.is_focused = Some(Focus {
1576 updated_at: now,
1577 now,
1578 is_window_focused: true,
1579 });
1580
1581 self.move_cursor_to_end();
1582 }
1583
1584 pub fn unfocus(&mut self) {
1586 self.is_focused = None;
1587 }
1588
1589 pub fn move_cursor_to_front(&mut self) {
1591 self.cursor.move_to(0);
1592 }
1593
1594 pub fn move_cursor_to_end(&mut self) {
1596 self.cursor.move_to(usize::MAX);
1597 }
1598
1599 pub fn move_cursor_to(&mut self, position: usize) {
1601 self.cursor.move_to(position);
1602 }
1603
1604 pub fn select_all(&mut self) {
1606 self.cursor.select_range(0, usize::MAX);
1607 }
1608}
1609
1610impl<P: text::Paragraph> operation::Focusable for State<P> {
1611 fn is_focused(&self) -> bool {
1612 State::is_focused(self)
1613 }
1614
1615 fn focus(&mut self) {
1616 State::focus(self);
1617 }
1618
1619 fn unfocus(&mut self) {
1620 State::unfocus(self);
1621 }
1622}
1623
1624impl<P: text::Paragraph> operation::TextInput for State<P> {
1625 fn move_cursor_to_front(&mut self) {
1626 State::move_cursor_to_front(self);
1627 }
1628
1629 fn move_cursor_to_end(&mut self) {
1630 State::move_cursor_to_end(self);
1631 }
1632
1633 fn move_cursor_to(&mut self, position: usize) {
1634 State::move_cursor_to(self, position);
1635 }
1636
1637 fn select_all(&mut self) {
1638 State::select_all(self);
1639 }
1640}
1641
1642fn offset<P: text::Paragraph>(
1643 text_bounds: Rectangle,
1644 value: &Value,
1645 state: &State<P>,
1646) -> f32 {
1647 if state.is_focused() {
1648 let cursor = state.cursor();
1649
1650 let focus_position = match cursor.state(value) {
1651 cursor::State::Index(i) => i,
1652 cursor::State::Selection { end, .. } => end,
1653 };
1654
1655 let (_, offset) = measure_cursor_and_scroll_offset(
1656 state.value.raw(),
1657 text_bounds,
1658 focus_position,
1659 );
1660
1661 offset
1662 } else {
1663 0.0
1664 }
1665}
1666
1667fn measure_cursor_and_scroll_offset(
1668 paragraph: &impl text::Paragraph,
1669 text_bounds: Rectangle,
1670 cursor_index: usize,
1671) -> (f32, f32) {
1672 let grapheme_position = paragraph
1673 .grapheme_position(0, cursor_index)
1674 .unwrap_or(Point::ORIGIN);
1675
1676 let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0);
1677
1678 (grapheme_position.x, offset)
1679}
1680
1681fn find_cursor_position<P: text::Paragraph>(
1684 text_bounds: Rectangle,
1685 value: &Value,
1686 state: &State<P>,
1687 x: f32,
1688) -> Option<usize> {
1689 let offset = offset(text_bounds, value, state);
1690 let value = value.to_string();
1691
1692 let char_offset = state
1693 .value
1694 .raw()
1695 .hit_test(Point::new(x + offset, text_bounds.height / 2.0))
1696 .map(text::Hit::cursor)?;
1697
1698 Some(
1699 unicode_segmentation::UnicodeSegmentation::graphemes(
1700 &value[..char_offset.min(value.len())],
1701 true,
1702 )
1703 .count(),
1704 )
1705}
1706
1707fn replace_paragraph<Renderer>(
1708 renderer: &Renderer,
1709 state: &mut State<Renderer::Paragraph>,
1710 layout: Layout<'_>,
1711 value: &Value,
1712 font: Option<Renderer::Font>,
1713 text_size: Option<Pixels>,
1714 line_height: text::LineHeight,
1715) where
1716 Renderer: text::Renderer,
1717{
1718 let font = font.unwrap_or_else(|| renderer.default_font());
1719 let text_size = text_size.unwrap_or_else(|| renderer.default_size());
1720
1721 let mut children_layout = layout.children();
1722 let text_bounds = children_layout.next().unwrap().bounds();
1723
1724 state.value = paragraph::Plain::new(Text {
1725 font,
1726 line_height,
1727 content: &value.to_string(),
1728 bounds: Size::new(f32::INFINITY, text_bounds.height),
1729 size: text_size,
1730 align_x: text::Alignment::Default,
1731 align_y: alignment::Vertical::Center,
1732 shaping: text::Shaping::Advanced,
1733 wrapping: text::Wrapping::default(),
1734 });
1735}
1736
1737const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
1738
1739#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1741pub enum Status {
1742 Active,
1744 Hovered,
1746 Focused {
1748 is_hovered: bool,
1750 },
1751 Disabled,
1753}
1754
1755#[derive(Debug, Clone, Copy, PartialEq)]
1757pub struct Style {
1758 pub background: Background,
1760 pub border: Border,
1762 pub icon: Color,
1764 pub placeholder: Color,
1766 pub value: Color,
1768 pub selection: Color,
1770}
1771
1772pub trait Catalog: Sized {
1774 type Class<'a>;
1776
1777 fn default<'a>() -> Self::Class<'a>;
1779
1780 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
1782}
1783
1784pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
1788
1789impl Catalog for Theme {
1790 type Class<'a> = StyleFn<'a, Self>;
1791
1792 fn default<'a>() -> Self::Class<'a> {
1793 Box::new(default)
1794 }
1795
1796 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
1797 class(self, status)
1798 }
1799}
1800
1801pub fn default(theme: &Theme, status: Status) -> Style {
1803 let palette = theme.extended_palette();
1804
1805 let active = Style {
1806 background: Background::Color(palette.background.base.color),
1807 border: Border {
1808 radius: 2.0.into(),
1809 width: 1.0,
1810 color: palette.background.strongest.color,
1811 },
1812 icon: palette.background.weak.text,
1813 placeholder: palette.background.strongest.color,
1814 value: palette.background.base.text,
1815 selection: palette.primary.weak.color,
1816 };
1817
1818 match status {
1819 Status::Active => active,
1820 Status::Hovered => Style {
1821 border: Border {
1822 color: palette.background.base.text,
1823 ..active.border
1824 },
1825 ..active
1826 },
1827 Status::Focused { .. } => Style {
1828 border: Border {
1829 color: palette.primary.strong.color,
1830 ..active.border
1831 },
1832 ..active
1833 },
1834 Status::Disabled => Style {
1835 background: Background::Color(palette.background.weak.color),
1836 value: active.placeholder,
1837 ..active
1838 },
1839 }
1840}
1841
1842fn alignment_offset(
1843 text_bounds_width: f32,
1844 text_min_width: f32,
1845 alignment: alignment::Horizontal,
1846) -> f32 {
1847 if text_min_width > text_bounds_width {
1848 0.0
1849 } else {
1850 match alignment {
1851 alignment::Horizontal::Left => 0.0,
1852 alignment::Horizontal::Center => {
1853 (text_bounds_width - text_min_width) / 2.0
1854 }
1855 alignment::Horizontal::Right => text_bounds_width - text_min_width,
1856 }
1857 }
1858}