iced_widget/
text_input.rs

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