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