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.fill_quad(renderer::Quad::default(), Color::TRANSPARENT);
566 }
567
568 renderer.fill_paragraph(
569 paragraph,
570 text_bounds.anchor(paragraph.min_bounds(), Alignment::Start, Alignment::Center)
571 + Vector::new(alignment_offset - offset, 0.0),
572 if text.is_empty() {
573 style.placeholder
574 } else {
575 style.value
576 },
577 viewport,
578 );
579 };
580
581 if is_selecting {
582 renderer.with_layer(text_bounds, |renderer| draw(renderer, *viewport));
583 } else {
584 draw(renderer, text_bounds);
585 }
586 }
587}
588
589impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
590 for TextInput<'_, Message, Theme, Renderer>
591where
592 Message: Clone,
593 Theme: Catalog,
594 Renderer: text::Renderer,
595{
596 fn tag(&self) -> tree::Tag {
597 tree::Tag::of::<State<Renderer::Paragraph>>()
598 }
599
600 fn state(&self) -> tree::State {
601 tree::State::new(State::<Renderer::Paragraph>::new())
602 }
603
604 fn diff(&self, tree: &mut Tree) {
605 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
606
607 if self.on_input.is_none() {
609 state.is_pasting = None;
610 }
611 }
612
613 fn size(&self) -> Size<Length> {
614 Size {
615 width: self.width,
616 height: Length::Shrink,
617 }
618 }
619
620 fn layout(
621 &mut self,
622 tree: &mut Tree,
623 renderer: &Renderer,
624 limits: &layout::Limits,
625 ) -> layout::Node {
626 self.layout(tree, renderer, limits, None)
627 }
628
629 fn operate(
630 &mut self,
631 tree: &mut Tree,
632 layout: Layout<'_>,
633 _renderer: &Renderer,
634 operation: &mut dyn Operation,
635 ) {
636 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
637
638 operation.text_input(self.id.as_ref(), layout.bounds(), state);
639 operation.focusable(self.id.as_ref(), layout.bounds(), state);
640 }
641
642 fn update(
643 &mut self,
644 tree: &mut Tree,
645 event: &Event,
646 layout: Layout<'_>,
647 cursor: mouse::Cursor,
648 renderer: &Renderer,
649 clipboard: &mut dyn Clipboard,
650 shell: &mut Shell<'_, Message>,
651 _viewport: &Rectangle,
652 ) {
653 let update_cache = |state, value| {
654 replace_paragraph(
655 renderer,
656 state,
657 layout,
658 value,
659 self.font,
660 self.size,
661 self.line_height,
662 );
663 };
664
665 match &event {
666 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
667 | Event::Touch(touch::Event::FingerPressed { .. }) => {
668 let state = state::<Renderer>(tree);
669 let cursor_before = state.cursor;
670
671 let click_position = cursor.position_over(layout.bounds());
672
673 state.is_focused = if click_position.is_some() {
674 let now = Instant::now();
675
676 Some(Focus {
677 updated_at: now,
678 now,
679 is_window_focused: true,
680 })
681 } else {
682 None
683 };
684
685 if let Some(cursor_position) = click_position {
686 let text_layout = layout.children().next().unwrap();
687
688 let target = {
689 let text_bounds = text_layout.bounds();
690
691 let alignment_offset = alignment_offset(
692 text_bounds.width,
693 state.value.raw().min_width(),
694 self.alignment,
695 );
696
697 cursor_position.x - text_bounds.x - alignment_offset
698 };
699
700 let click =
701 mouse::Click::new(cursor_position, mouse::Button::Left, state.last_click);
702
703 match click.kind() {
704 click::Kind::Single => {
705 let position = if target > 0.0 {
706 let value = if self.is_secure {
707 self.value.secure()
708 } else {
709 self.value.clone()
710 };
711
712 find_cursor_position(text_layout.bounds(), &value, state, target)
713 } else {
714 None
715 }
716 .unwrap_or(0);
717
718 if state.keyboard_modifiers.shift() {
719 state
720 .cursor
721 .select_range(state.cursor.start(&self.value), position);
722 } else {
723 state.cursor.move_to(position);
724 }
725
726 state.is_dragging = Some(Drag::Select);
727 }
728 click::Kind::Double => {
729 if self.is_secure {
730 state.cursor.select_all(&self.value);
731
732 state.is_dragging = None;
733 } else {
734 let position = find_cursor_position(
735 text_layout.bounds(),
736 &self.value,
737 state,
738 target,
739 )
740 .unwrap_or(0);
741
742 state.cursor.select_range(
743 self.value.previous_start_of_word(position),
744 self.value.next_end_of_word(position),
745 );
746
747 state.is_dragging = Some(Drag::SelectWords { anchor: position });
748 }
749 }
750 click::Kind::Triple => {
751 state.cursor.select_all(&self.value);
752 state.is_dragging = None;
753 }
754 }
755
756 state.last_click = Some(click);
757
758 if cursor_before != state.cursor {
759 shell.request_redraw();
760 }
761
762 shell.capture_event();
763 }
764 }
765 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
766 | Event::Touch(touch::Event::FingerLifted { .. })
767 | Event::Touch(touch::Event::FingerLost { .. }) => {
768 state::<Renderer>(tree).is_dragging = None;
769 }
770 Event::Mouse(mouse::Event::CursorMoved { position })
771 | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
772 let state = state::<Renderer>(tree);
773
774 if let Some(is_dragging) = &state.is_dragging {
775 let text_layout = layout.children().next().unwrap();
776
777 let target = {
778 let text_bounds = text_layout.bounds();
779
780 let alignment_offset = alignment_offset(
781 text_bounds.width,
782 state.value.raw().min_width(),
783 self.alignment,
784 );
785
786 position.x - text_bounds.x - alignment_offset
787 };
788
789 let value = if self.is_secure {
790 self.value.secure()
791 } else {
792 self.value.clone()
793 };
794
795 let position =
796 find_cursor_position(text_layout.bounds(), &value, state, target)
797 .unwrap_or(0);
798
799 let selection_before = state.cursor.selection(&value);
800
801 match is_dragging {
802 Drag::Select => {
803 state
804 .cursor
805 .select_range(state.cursor.start(&value), position);
806 }
807 Drag::SelectWords { anchor } => {
808 if position < *anchor {
809 state.cursor.select_range(
810 self.value.previous_start_of_word(position),
811 self.value.next_end_of_word(*anchor),
812 );
813 } else {
814 state.cursor.select_range(
815 self.value.previous_start_of_word(*anchor),
816 self.value.next_end_of_word(position),
817 );
818 }
819 }
820 }
821
822 if let Some(focus) = &mut state.is_focused {
823 focus.updated_at = Instant::now();
824 }
825
826 if selection_before != state.cursor.selection(&value) {
827 shell.request_redraw();
828 }
829
830 shell.capture_event();
831 }
832 }
833 Event::Keyboard(keyboard::Event::KeyPressed {
834 key,
835 text,
836 modified_key,
837 physical_key,
838 ..
839 }) => {
840 let state = state::<Renderer>(tree);
841
842 if let Some(focus) = &mut state.is_focused {
843 let modifiers = state.keyboard_modifiers;
844
845 match key.to_latin(*physical_key) {
846 Some('c') if state.keyboard_modifiers.command() && !self.is_secure => {
847 if let Some((start, end)) = state.cursor.selection(&self.value) {
848 clipboard.write(
849 clipboard::Kind::Standard,
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 clipboard.write(
864 clipboard::Kind::Standard,
865 self.value.select(start, end).to_string(),
866 );
867 }
868
869 let mut editor = Editor::new(&mut self.value, &mut state.cursor);
870 editor.delete();
871
872 let message = (on_input)(editor.contents());
873 shell.publish(message);
874 shell.capture_event();
875
876 focus.updated_at = Instant::now();
877 update_cache(state, &self.value);
878 return;
879 }
880 Some('v')
881 if state.keyboard_modifiers.command()
882 && !state.keyboard_modifiers.alt() =>
883 {
884 let Some(on_input) = &self.on_input else {
885 return;
886 };
887
888 let content = match state.is_pasting.take() {
889 Some(content) => content,
890 None => {
891 let content: String = clipboard
892 .read(clipboard::Kind::Standard)
893 .unwrap_or_default()
894 .chars()
895 .filter(|c| !c.is_control())
896 .collect();
897
898 Value::new(&content)
899 }
900 };
901
902 let mut editor = Editor::new(&mut self.value, &mut state.cursor);
903 editor.paste(content.clone());
904
905 let message = if let Some(paste) = &self.on_paste {
906 (paste)(editor.contents())
907 } else {
908 (on_input)(editor.contents())
909 };
910 shell.publish(message);
911 shell.capture_event();
912
913 state.is_pasting = Some(content);
914 focus.updated_at = Instant::now();
915 update_cache(state, &self.value);
916 return;
917 }
918 Some('a') if state.keyboard_modifiers.command() => {
919 let cursor_before = state.cursor;
920
921 state.cursor.select_all(&self.value);
922
923 if cursor_before != state.cursor {
924 focus.updated_at = Instant::now();
925
926 shell.request_redraw();
927 }
928
929 shell.capture_event();
930 return;
931 }
932 _ => {}
933 }
934
935 if let Some(text) = text {
936 let Some(on_input) = &self.on_input else {
937 return;
938 };
939
940 state.is_pasting = None;
941
942 if let Some(c) = text.chars().next().filter(|c| !c.is_control()) {
943 let mut editor = Editor::new(&mut self.value, &mut state.cursor);
944
945 editor.insert(c);
946
947 let message = (on_input)(editor.contents());
948 shell.publish(message);
949 shell.capture_event();
950
951 focus.updated_at = Instant::now();
952 update_cache(state, &self.value);
953 return;
954 }
955 }
956
957 #[cfg(target_os = "macos")]
958 let macos_shortcut = crate::text_editor::convert_macos_shortcut(key, modifiers);
959
960 #[cfg(target_os = "macos")]
961 let modified_key = macos_shortcut.as_ref().unwrap_or(modified_key);
962
963 match modified_key.as_ref() {
964 keyboard::Key::Named(key::Named::Enter) => {
965 if let Some(on_submit) = self.on_submit.clone() {
966 shell.publish(on_submit);
967 shell.capture_event();
968 }
969 }
970 keyboard::Key::Named(key::Named::Backspace) => {
971 let Some(on_input) = &self.on_input else {
972 return;
973 };
974
975 if state.cursor.selection(&self.value).is_none() {
976 if (self.is_secure && modifiers.jump()) || modifiers.macos_command()
977 {
978 state
979 .cursor
980 .select_range(state.cursor.start(&self.value), 0);
981 } else if modifiers.jump() {
982 state.cursor.select_left_by_words(&self.value);
983 }
984 }
985
986 let mut editor = Editor::new(&mut self.value, &mut state.cursor);
987 editor.backspace();
988
989 let message = (on_input)(editor.contents());
990 shell.publish(message);
991 shell.capture_event();
992
993 focus.updated_at = Instant::now();
994 update_cache(state, &self.value);
995 }
996 keyboard::Key::Named(key::Named::Delete) => {
997 let Some(on_input) = &self.on_input else {
998 return;
999 };
1000
1001 if state.cursor.selection(&self.value).is_none() {
1002 if (self.is_secure && modifiers.jump()) || modifiers.macos_command()
1003 {
1004 state.cursor.select_range(
1005 state.cursor.start(&self.value),
1006 self.value.len(),
1007 );
1008 } else if modifiers.jump() {
1009 state.cursor.select_right_by_words(&self.value);
1010 }
1011 }
1012
1013 let mut editor = Editor::new(&mut self.value, &mut state.cursor);
1014 editor.delete();
1015
1016 let message = (on_input)(editor.contents());
1017 shell.publish(message);
1018 shell.capture_event();
1019
1020 focus.updated_at = Instant::now();
1021 update_cache(state, &self.value);
1022 }
1023 keyboard::Key::Named(key::Named::Home) => {
1024 let cursor_before = state.cursor;
1025
1026 if modifiers.shift() {
1027 state
1028 .cursor
1029 .select_range(state.cursor.start(&self.value), 0);
1030 } else {
1031 state.cursor.move_to(0);
1032 }
1033
1034 if cursor_before != state.cursor {
1035 focus.updated_at = Instant::now();
1036
1037 shell.request_redraw();
1038 }
1039
1040 shell.capture_event();
1041 }
1042 keyboard::Key::Named(key::Named::End) => {
1043 let cursor_before = state.cursor;
1044
1045 if modifiers.shift() {
1046 state.cursor.select_range(
1047 state.cursor.start(&self.value),
1048 self.value.len(),
1049 );
1050 } else {
1051 state.cursor.move_to(self.value.len());
1052 }
1053
1054 if cursor_before != state.cursor {
1055 focus.updated_at = Instant::now();
1056
1057 shell.request_redraw();
1058 }
1059
1060 shell.capture_event();
1061 }
1062 keyboard::Key::Named(key::Named::ArrowLeft) => {
1063 let cursor_before = state.cursor;
1064
1065 if (self.is_secure && modifiers.jump()) || modifiers.macos_command() {
1066 if modifiers.shift() {
1067 state
1068 .cursor
1069 .select_range(state.cursor.start(&self.value), 0);
1070 } else {
1071 state.cursor.move_to(0);
1072 }
1073 } else if modifiers.jump() {
1074 if modifiers.shift() {
1075 state.cursor.select_left_by_words(&self.value);
1076 } else {
1077 state.cursor.move_left_by_words(&self.value);
1078 }
1079 } else if modifiers.shift() {
1080 state.cursor.select_left(&self.value);
1081 } else {
1082 state.cursor.move_left(&self.value);
1083 }
1084
1085 if cursor_before != state.cursor {
1086 focus.updated_at = Instant::now();
1087
1088 shell.request_redraw();
1089 }
1090
1091 shell.capture_event();
1092 }
1093 keyboard::Key::Named(key::Named::ArrowRight) => {
1094 let cursor_before = state.cursor;
1095
1096 if (self.is_secure && modifiers.jump()) || modifiers.macos_command() {
1097 if modifiers.shift() {
1098 state.cursor.select_range(
1099 state.cursor.start(&self.value),
1100 self.value.len(),
1101 );
1102 } else {
1103 state.cursor.move_to(self.value.len());
1104 }
1105 } else if modifiers.jump() {
1106 if modifiers.shift() {
1107 state.cursor.select_right_by_words(&self.value);
1108 } else {
1109 state.cursor.move_right_by_words(&self.value);
1110 }
1111 } else if modifiers.shift() {
1112 state.cursor.select_right(&self.value);
1113 } else {
1114 state.cursor.move_right(&self.value);
1115 }
1116
1117 if cursor_before != state.cursor {
1118 focus.updated_at = Instant::now();
1119
1120 shell.request_redraw();
1121 }
1122
1123 shell.capture_event();
1124 }
1125 keyboard::Key::Named(key::Named::Escape) => {
1126 state.is_focused = None;
1127 state.is_dragging = None;
1128 state.is_pasting = None;
1129
1130 state.keyboard_modifiers = keyboard::Modifiers::default();
1131
1132 shell.capture_event();
1133 }
1134 _ => {}
1135 }
1136 }
1137 }
1138 Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
1139 let state = state::<Renderer>(tree);
1140
1141 if state.is_focused.is_some()
1142 && let keyboard::Key::Character("v") = key.as_ref()
1143 {
1144 state.is_pasting = None;
1145
1146 shell.capture_event();
1147 }
1148
1149 state.is_pasting = None;
1150 }
1151 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
1152 let state = state::<Renderer>(tree);
1153
1154 state.keyboard_modifiers = *modifiers;
1155 }
1156 Event::InputMethod(event) => match event {
1157 input_method::Event::Opened | input_method::Event::Closed => {
1158 let state = state::<Renderer>(tree);
1159
1160 state.preedit = matches!(event, input_method::Event::Opened)
1161 .then(input_method::Preedit::new);
1162
1163 shell.request_redraw();
1164 }
1165 input_method::Event::Preedit(content, selection) => {
1166 let state = state::<Renderer>(tree);
1167
1168 if state.is_focused.is_some() {
1169 state.preedit = Some(input_method::Preedit {
1170 content: content.to_owned(),
1171 selection: selection.clone(),
1172 text_size: self.size,
1173 });
1174
1175 shell.request_redraw();
1176 }
1177 }
1178 input_method::Event::Commit(text) => {
1179 let state = state::<Renderer>(tree);
1180
1181 if let Some(focus) = &mut state.is_focused {
1182 let Some(on_input) = &self.on_input else {
1183 return;
1184 };
1185
1186 let mut editor = Editor::new(&mut self.value, &mut state.cursor);
1187 editor.paste(Value::new(text));
1188
1189 focus.updated_at = Instant::now();
1190 state.is_pasting = None;
1191
1192 let message = (on_input)(editor.contents());
1193 shell.publish(message);
1194 shell.capture_event();
1195
1196 update_cache(state, &self.value);
1197 }
1198 }
1199 },
1200 Event::Window(window::Event::Unfocused) => {
1201 let state = state::<Renderer>(tree);
1202
1203 if let Some(focus) = &mut state.is_focused {
1204 focus.is_window_focused = false;
1205 }
1206 }
1207 Event::Window(window::Event::Focused) => {
1208 let state = state::<Renderer>(tree);
1209
1210 if let Some(focus) = &mut state.is_focused {
1211 focus.is_window_focused = true;
1212 focus.updated_at = Instant::now();
1213
1214 shell.request_redraw();
1215 }
1216 }
1217 Event::Window(window::Event::RedrawRequested(now)) => {
1218 let state = state::<Renderer>(tree);
1219
1220 if let Some(focus) = &mut state.is_focused
1221 && focus.is_window_focused
1222 {
1223 if matches!(state.cursor.state(&self.value), cursor::State::Index(_)) {
1224 focus.now = *now;
1225
1226 let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
1227 - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;
1228
1229 shell.request_redraw_at(
1230 *now + Duration::from_millis(millis_until_redraw as u64),
1231 );
1232 }
1233
1234 shell.request_input_method(&self.input_method(state, layout, &self.value));
1235 }
1236 }
1237 _ => {}
1238 }
1239
1240 let state = state::<Renderer>(tree);
1241 let is_disabled = self.on_input.is_none();
1242
1243 let status = if is_disabled {
1244 Status::Disabled
1245 } else if state.is_focused() {
1246 Status::Focused {
1247 is_hovered: cursor.is_over(layout.bounds()),
1248 }
1249 } else if cursor.is_over(layout.bounds()) {
1250 Status::Hovered
1251 } else {
1252 Status::Active
1253 };
1254
1255 if let Event::Window(window::Event::RedrawRequested(_now)) = event {
1256 self.last_status = Some(status);
1257 } else if self
1258 .last_status
1259 .is_some_and(|last_status| status != last_status)
1260 {
1261 shell.request_redraw();
1262 }
1263 }
1264
1265 fn draw(
1266 &self,
1267 tree: &Tree,
1268 renderer: &mut Renderer,
1269 theme: &Theme,
1270 _style: &renderer::Style,
1271 layout: Layout<'_>,
1272 cursor: mouse::Cursor,
1273 viewport: &Rectangle,
1274 ) {
1275 self.draw(tree, renderer, theme, layout, cursor, None, viewport);
1276 }
1277
1278 fn mouse_interaction(
1279 &self,
1280 _tree: &Tree,
1281 layout: Layout<'_>,
1282 cursor: mouse::Cursor,
1283 _viewport: &Rectangle,
1284 _renderer: &Renderer,
1285 ) -> mouse::Interaction {
1286 if cursor.is_over(layout.bounds()) {
1287 if self.on_input.is_none() {
1288 mouse::Interaction::Idle
1289 } else {
1290 mouse::Interaction::Text
1291 }
1292 } else {
1293 mouse::Interaction::default()
1294 }
1295 }
1296}
1297
1298impl<'a, Message, Theme, Renderer> From<TextInput<'a, Message, Theme, Renderer>>
1299 for Element<'a, Message, Theme, Renderer>
1300where
1301 Message: Clone + 'a,
1302 Theme: Catalog + 'a,
1303 Renderer: text::Renderer + 'a,
1304{
1305 fn from(
1306 text_input: TextInput<'a, Message, Theme, Renderer>,
1307 ) -> Element<'a, Message, Theme, Renderer> {
1308 Element::new(text_input)
1309 }
1310}
1311
1312#[derive(Debug, Clone)]
1314pub struct Icon<Font> {
1315 pub font: Font,
1317 pub code_point: char,
1319 pub size: Option<Pixels>,
1321 pub spacing: f32,
1323 pub side: Side,
1325}
1326
1327#[derive(Debug, Clone)]
1329pub enum Side {
1330 Left,
1332 Right,
1334}
1335
1336#[derive(Debug, Default, Clone)]
1338pub struct State<P: text::Paragraph> {
1339 value: paragraph::Plain<P>,
1340 placeholder: paragraph::Plain<P>,
1341 icon: paragraph::Plain<P>,
1342 is_focused: Option<Focus>,
1343 is_dragging: Option<Drag>,
1344 is_pasting: Option<Value>,
1345 preedit: Option<input_method::Preedit>,
1346 last_click: Option<mouse::Click>,
1347 cursor: Cursor,
1348 keyboard_modifiers: keyboard::Modifiers,
1349 }
1351
1352fn state<Renderer: text::Renderer>(tree: &mut Tree) -> &mut State<Renderer::Paragraph> {
1353 tree.state.downcast_mut::<State<Renderer::Paragraph>>()
1354}
1355
1356#[derive(Debug, Clone)]
1357struct Focus {
1358 updated_at: Instant,
1359 now: Instant,
1360 is_window_focused: bool,
1361}
1362
1363#[derive(Debug, Clone)]
1364enum Drag {
1365 Select,
1366 SelectWords { anchor: usize },
1367}
1368
1369impl<P: text::Paragraph> State<P> {
1370 pub fn new() -> Self {
1372 Self::default()
1373 }
1374
1375 pub fn is_focused(&self) -> bool {
1377 self.is_focused.is_some()
1378 }
1379
1380 pub fn cursor(&self) -> Cursor {
1382 self.cursor
1383 }
1384
1385 pub fn focus(&mut self) {
1387 let now = Instant::now();
1388
1389 self.is_focused = Some(Focus {
1390 updated_at: now,
1391 now,
1392 is_window_focused: true,
1393 });
1394
1395 self.move_cursor_to_end();
1396 }
1397
1398 pub fn unfocus(&mut self) {
1400 self.is_focused = None;
1401 }
1402
1403 pub fn move_cursor_to_front(&mut self) {
1405 self.cursor.move_to(0);
1406 }
1407
1408 pub fn move_cursor_to_end(&mut self) {
1410 self.cursor.move_to(usize::MAX);
1411 }
1412
1413 pub fn move_cursor_to(&mut self, position: usize) {
1415 self.cursor.move_to(position);
1416 }
1417
1418 pub fn select_all(&mut self) {
1420 self.cursor.select_range(0, usize::MAX);
1421 }
1422
1423 pub fn select_range(&mut self, start: usize, end: usize) {
1425 self.cursor.select_range(start, end);
1426 }
1427}
1428
1429impl<P: text::Paragraph> operation::Focusable for State<P> {
1430 fn is_focused(&self) -> bool {
1431 State::is_focused(self)
1432 }
1433
1434 fn focus(&mut self) {
1435 State::focus(self);
1436 }
1437
1438 fn unfocus(&mut self) {
1439 State::unfocus(self);
1440 }
1441}
1442
1443impl<P: text::Paragraph> operation::TextInput for State<P> {
1444 fn text(&self) -> &str {
1445 if self.value.content().is_empty() {
1446 self.placeholder.content()
1447 } else {
1448 self.value.content()
1449 }
1450 }
1451
1452 fn move_cursor_to_front(&mut self) {
1453 State::move_cursor_to_front(self);
1454 }
1455
1456 fn move_cursor_to_end(&mut self) {
1457 State::move_cursor_to_end(self);
1458 }
1459
1460 fn move_cursor_to(&mut self, position: usize) {
1461 State::move_cursor_to(self, position);
1462 }
1463
1464 fn select_all(&mut self) {
1465 State::select_all(self);
1466 }
1467
1468 fn select_range(&mut self, start: usize, end: usize) {
1469 State::select_range(self, start, end);
1470 }
1471}
1472
1473fn offset<P: text::Paragraph>(text_bounds: Rectangle, value: &Value, state: &State<P>) -> f32 {
1474 if state.is_focused() {
1475 let cursor = state.cursor();
1476
1477 let focus_position = match cursor.state(value) {
1478 cursor::State::Index(i) => i,
1479 cursor::State::Selection { end, .. } => end,
1480 };
1481
1482 let (_, offset) =
1483 measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, focus_position);
1484
1485 offset
1486 } else {
1487 0.0
1488 }
1489}
1490
1491fn measure_cursor_and_scroll_offset(
1492 paragraph: &impl text::Paragraph,
1493 text_bounds: Rectangle,
1494 cursor_index: usize,
1495) -> (f32, f32) {
1496 let grapheme_position = paragraph
1497 .grapheme_position(0, cursor_index)
1498 .unwrap_or(Point::ORIGIN);
1499
1500 let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0);
1501
1502 (grapheme_position.x, offset)
1503}
1504
1505fn find_cursor_position<P: text::Paragraph>(
1508 text_bounds: Rectangle,
1509 value: &Value,
1510 state: &State<P>,
1511 x: f32,
1512) -> Option<usize> {
1513 let offset = offset(text_bounds, value, state);
1514 let value = value.to_string();
1515
1516 let char_offset = state
1517 .value
1518 .raw()
1519 .hit_test(Point::new(x + offset, text_bounds.height / 2.0))
1520 .map(text::Hit::cursor)?;
1521
1522 Some(
1523 unicode_segmentation::UnicodeSegmentation::graphemes(
1524 &value[..char_offset.min(value.len())],
1525 true,
1526 )
1527 .count(),
1528 )
1529}
1530
1531fn replace_paragraph<Renderer>(
1532 renderer: &Renderer,
1533 state: &mut State<Renderer::Paragraph>,
1534 layout: Layout<'_>,
1535 value: &Value,
1536 font: Option<Renderer::Font>,
1537 text_size: Option<Pixels>,
1538 line_height: text::LineHeight,
1539) where
1540 Renderer: text::Renderer,
1541{
1542 let font = font.unwrap_or_else(|| renderer.default_font());
1543 let text_size = text_size.unwrap_or_else(|| renderer.default_size());
1544
1545 let mut children_layout = layout.children();
1546 let text_bounds = children_layout.next().unwrap().bounds();
1547
1548 state.value = paragraph::Plain::new(Text {
1549 font,
1550 line_height,
1551 content: value.to_string(),
1552 bounds: Size::new(f32::INFINITY, text_bounds.height),
1553 size: text_size,
1554 align_x: text::Alignment::Default,
1555 align_y: alignment::Vertical::Center,
1556 shaping: text::Shaping::Advanced,
1557 wrapping: text::Wrapping::default(),
1558 hint_factor: renderer.scale_factor(),
1559 });
1560}
1561
1562const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
1563
1564#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1566pub enum Status {
1567 Active,
1569 Hovered,
1571 Focused {
1573 is_hovered: bool,
1575 },
1576 Disabled,
1578}
1579
1580#[derive(Debug, Clone, Copy, PartialEq)]
1582pub struct Style {
1583 pub background: Background,
1585 pub border: Border,
1587 pub icon: Color,
1589 pub placeholder: Color,
1591 pub value: Color,
1593 pub selection: Color,
1595}
1596
1597pub trait Catalog: Sized {
1599 type Class<'a>;
1601
1602 fn default<'a>() -> Self::Class<'a>;
1604
1605 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
1607}
1608
1609pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
1613
1614impl Catalog for Theme {
1615 type Class<'a> = StyleFn<'a, Self>;
1616
1617 fn default<'a>() -> Self::Class<'a> {
1618 Box::new(default)
1619 }
1620
1621 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
1622 class(self, status)
1623 }
1624}
1625
1626pub fn default(theme: &Theme, status: Status) -> Style {
1628 let palette = theme.extended_palette();
1629
1630 let active = Style {
1631 background: Background::Color(palette.background.base.color),
1632 border: Border {
1633 radius: 2.0.into(),
1634 width: 1.0,
1635 color: palette.background.strong.color,
1636 },
1637 icon: palette.background.weak.text,
1638 placeholder: palette.secondary.base.color,
1639 value: palette.background.base.text,
1640 selection: palette.primary.weak.color,
1641 };
1642
1643 match status {
1644 Status::Active => active,
1645 Status::Hovered => Style {
1646 border: Border {
1647 color: palette.background.base.text,
1648 ..active.border
1649 },
1650 ..active
1651 },
1652 Status::Focused { .. } => Style {
1653 border: Border {
1654 color: palette.primary.strong.color,
1655 ..active.border
1656 },
1657 ..active
1658 },
1659 Status::Disabled => Style {
1660 background: Background::Color(palette.background.weak.color),
1661 value: active.placeholder,
1662 placeholder: palette.background.strongest.color,
1663 ..active
1664 },
1665 }
1666}
1667
1668fn alignment_offset(
1669 text_bounds_width: f32,
1670 text_min_width: f32,
1671 alignment: alignment::Horizontal,
1672) -> f32 {
1673 if text_min_width > text_bounds_width {
1674 0.0
1675 } else {
1676 match alignment {
1677 alignment::Horizontal::Left => 0.0,
1678 alignment::Horizontal::Center => (text_bounds_width - text_min_width) / 2.0,
1679 alignment::Horizontal::Right => text_bounds_width - text_min_width,
1680 }
1681 }
1682}