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