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