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