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