iced_widget/
text_input.rs

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