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