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