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