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