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