Skip to main content

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