Skip to main content

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