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