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