From 704ec9cb5cdc1d44f2df2f15de700b0af330b1d7 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Tue, 5 Mar 2024 15:53:59 +0100
Subject: Simplify theming for `TextInput` widget

---
 widget/src/combo_box.rs    |   27 +-
 widget/src/helpers.rs      |    8 +-
 widget/src/overlay/menu.rs |    4 +-
 widget/src/pick_list.rs    |   14 +-
 widget/src/scrollable.rs   |   18 +-
 widget/src/text_input.rs   | 1669 ++++++++++++++++++++++----------------------
 6 files changed, 855 insertions(+), 885 deletions(-)

(limited to 'widget/src')

diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs
index 0cca8d56..2ecf799d 100644
--- a/widget/src/combo_box.rs
+++ b/widget/src/combo_box.rs
@@ -32,7 +32,7 @@ pub struct ComboBox<
     Theme = crate::Theme,
     Renderer = crate::Renderer,
 > where
-    Theme: text_input::StyleSheet + menu::StyleSheet,
+    Theme: text_input::Style + menu::StyleSheet,
     Renderer: text::Renderer,
 {
     state: &'a State<T>,
@@ -51,7 +51,7 @@ pub struct ComboBox<
 impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer>
 where
     T: std::fmt::Display + Clone,
-    Theme: text_input::StyleSheet + menu::StyleSheet,
+    Theme: text_input::Style + menu::StyleSheet,
     Renderer: text::Renderer,
 {
     /// Creates a new [`ComboBox`] with the given list of options, a placeholder,
@@ -121,20 +121,17 @@ where
     // TODO: Define its own `StyleSheet` trait
     pub fn style<S>(mut self, style: S) -> Self
     where
-        S: Into<<Theme as text_input::StyleSheet>::Style>
-            + Into<<Theme as menu::StyleSheet>::Style>
-            + Clone,
+        S: Into<<Theme as menu::StyleSheet>::Style>,
     {
-        self.menu_style = style.clone().into();
-        self.text_input = self.text_input.style(style);
+        self.menu_style = style.into();
         self
     }
 
     /// Sets the style of the [`TextInput`] of the [`ComboBox`].
-    pub fn text_input_style<S>(mut self, style: S) -> Self
-    where
-        S: Into<<Theme as text_input::StyleSheet>::Style> + Clone,
-    {
+    pub fn text_input_style(
+        mut self,
+        style: fn(&Theme, text_input::Status) -> text_input::Appearance,
+    ) -> Self {
         self.text_input = self.text_input.style(style);
         self
     }
@@ -300,8 +297,8 @@ where
     T: Display + Clone + 'static,
     Message: Clone,
     Theme: container::Style
-        + text_input::StyleSheet
-        + scrollable::Tradition
+        + text_input::Style
+        + scrollable::Style
         + menu::StyleSheet,
     Renderer: text::Renderer,
 {
@@ -720,8 +717,8 @@ where
     T: Display + Clone + 'static,
     Message: Clone + 'a,
     Theme: container::Style
-        + text_input::StyleSheet
-        + scrollable::Tradition
+        + text_input::Style
+        + scrollable::Style
         + menu::StyleSheet
         + 'a,
     Renderer: text::Renderer + 'a,
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 3274b8d2..2153ed50 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -104,7 +104,7 @@ pub fn scrollable<'a, Message, Theme, Renderer>(
     content: impl Into<Element<'a, Message, Theme, Renderer>>,
 ) -> Scrollable<'a, Message, Theme, Renderer>
 where
-    Theme: scrollable::Tradition,
+    Theme: scrollable::Style,
     Renderer: core::Renderer,
 {
     Scrollable::new(content)
@@ -209,7 +209,7 @@ pub fn text_input<'a, Message, Theme, Renderer>(
 ) -> TextInput<'a, Message, Theme, Renderer>
 where
     Message: Clone,
-    Theme: text_input::StyleSheet,
+    Theme: text_input::Style,
     Renderer: core::text::Renderer,
 {
     TextInput::new(placeholder, value)
@@ -276,7 +276,7 @@ where
     Message: Clone,
     Renderer: core::text::Renderer,
     Theme: pick_list::StyleSheet
-        + scrollable::Tradition
+        + scrollable::Style
         + overlay::menu::StyleSheet
         + container::Style,
     <Theme as overlay::menu::StyleSheet>::Style:
@@ -296,7 +296,7 @@ pub fn combo_box<'a, T, Message, Theme, Renderer>(
 ) -> ComboBox<'a, T, Message, Theme, Renderer>
 where
     T: std::fmt::Display + Clone,
-    Theme: text_input::StyleSheet + overlay::menu::StyleSheet,
+    Theme: text_input::Style + overlay::menu::StyleSheet,
     Renderer: core::text::Renderer,
 {
     ComboBox::new(state, placeholder, selection, on_selected)
diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs
index b9b735e4..d820592d 100644
--- a/widget/src/overlay/menu.rs
+++ b/widget/src/overlay/menu.rs
@@ -47,7 +47,7 @@ impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer>
 where
     T: ToString + Clone,
     Message: 'a,
-    Theme: StyleSheet + container::Style + scrollable::Tradition + 'a,
+    Theme: StyleSheet + container::Style + scrollable::Style + 'a,
     Renderer: text::Renderer + 'a,
 {
     /// Creates a new [`Menu`] with the given [`State`], a list of options, and
@@ -179,7 +179,7 @@ where
 impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer>
 where
     Message: 'a,
-    Theme: StyleSheet + container::Style + scrollable::Tradition + 'a,
+    Theme: StyleSheet + container::Style + scrollable::Style + 'a,
     Renderer: text::Renderer + 'a,
 {
     pub fn new<T>(
diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs
index b75baa74..aeb0f246 100644
--- a/widget/src/pick_list.rs
+++ b/widget/src/pick_list.rs
@@ -61,10 +61,7 @@ where
     L: Borrow<[T]> + 'a,
     V: Borrow<T> + 'a,
     Message: Clone,
-    Theme: StyleSheet
-        + scrollable::Tradition
-        + menu::StyleSheet
-        + container::Style,
+    Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style,
     <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
     Renderer: text::Renderer,
 {
@@ -176,10 +173,7 @@ where
     L: Borrow<[T]>,
     V: Borrow<T>,
     Message: Clone + 'a,
-    Theme: StyleSheet
-        + scrollable::Tradition
-        + menu::StyleSheet
-        + container::Style,
+    Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style,
     <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
     Renderer: text::Renderer + 'a,
 {
@@ -318,7 +312,7 @@ where
     V: Borrow<T> + 'a,
     Message: Clone + 'a,
     Theme: StyleSheet
-        + scrollable::Tradition
+        + scrollable::Style
         + menu::StyleSheet
         + container::Style
         + 'a,
@@ -628,7 +622,7 @@ where
     T: Clone + ToString,
     Message: 'a,
     Theme: StyleSheet
-        + scrollable::Tradition
+        + scrollable::Style
         + menu::StyleSheet
         + container::Style
         + 'a,
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 8231685b..864fbec8 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -49,7 +49,7 @@ where
         content: impl Into<Element<'a, Message, Theme, Renderer>>,
     ) -> Self
     where
-        Theme: Tradition,
+        Theme: Style,
     {
         Self::with_direction(content, Direction::default())
     }
@@ -60,7 +60,7 @@ where
         direction: Direction,
     ) -> Self
     where
-        Theme: Tradition,
+        Theme: Style,
     {
         let content = content.into();
 
@@ -83,7 +83,7 @@ where
             direction,
             content,
             on_scroll: None,
-            style: Theme::tradition(),
+            style: Theme::style(),
         }
     }
 
@@ -1653,14 +1653,14 @@ pub struct Scroller {
     pub border: Border,
 }
 
-/// The definition of the traditional style of a [`Scrollable`].
-pub trait Tradition {
-    /// Returns the traditional style of a [`Scrollable`].
-    fn tradition() -> fn(&Self, Status) -> Appearance;
+/// The definition of the default style of a [`Scrollable`].
+pub trait Style {
+    /// Returns the default style of a [`Scrollable`].
+    fn style() -> fn(&Self, Status) -> Appearance;
 }
 
-impl Tradition for Theme {
-    fn tradition() -> fn(&Self, Status) -> Appearance {
+impl Style for Theme {
+    fn style() -> fn(&Self, Status) -> Appearance {
         default
     }
 }
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 92c4892c..11b0a5d5 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -27,12 +27,11 @@ use crate::core::widget::operation::{self, Operation};
 use crate::core::widget::tree::{self, Tree};
 use crate::core::window;
 use crate::core::{
-    Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
-    Vector, Widget,
+    Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point,
+    Rectangle, Shell, Size, Vector, Widget,
 };
 use crate::runtime::Command;
-
-pub use iced_style::text_input::{Appearance, StyleSheet};
+use crate::style::Theme;
 
 /// A field that can be filled with text.
 ///
@@ -63,7 +62,6 @@ pub struct TextInput<
     Theme = crate::Theme,
     Renderer = crate::Renderer,
 > where
-    Theme: StyleSheet,
     Renderer: text::Renderer,
 {
     id: Option<Id>,
@@ -79,7 +77,7 @@ pub struct TextInput<
     on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
     on_submit: Option<Message>,
     icon: Option<Icon<Renderer::Font>>,
-    style: Theme::Style,
+    style: fn(&Theme, Status) -> Appearance,
 }
 
 /// The default [`Padding`] of a [`TextInput`].
@@ -88,7 +86,6 @@ pub const DEFAULT_PADDING: Padding = Padding::new(5.0);
 impl<'a, Message, Theme, Renderer> TextInput<'a, Message, Theme, Renderer>
 where
     Message: Clone,
-    Theme: StyleSheet,
     Renderer: text::Renderer,
 {
     /// Creates a new [`TextInput`].
@@ -96,7 +93,10 @@ where
     /// It expects:
     /// - a placeholder,
     /// - the current value
-    pub fn new(placeholder: &str, value: &str) -> Self {
+    pub fn new(placeholder: &str, value: &str) -> Self
+    where
+        Theme: Style,
+    {
         TextInput {
             id: None,
             placeholder: String::from(placeholder),
@@ -111,7 +111,7 @@ where
             on_paste: None,
             on_submit: None,
             icon: None,
-            style: Default::default(),
+            style: Theme::style(),
         }
     }
 
@@ -198,8 +198,8 @@ where
     }
 
     /// Sets the style of the [`TextInput`].
-    pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
-        self.style = style.into();
+    pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
+        self.style = style;
         self
     }
 
@@ -213,20 +213,90 @@ where
         limits: &layout::Limits,
         value: Option<&Value>,
     ) -> layout::Node {
-        layout(
-            renderer,
-            limits,
-            self.width,
-            self.padding,
-            self.size,
-            self.font,
-            self.line_height,
-            self.icon.as_ref(),
-            tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
-            value.unwrap_or(&self.value),
-            &self.placeholder,
-            self.is_secure,
-        )
+        let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
+        let value = value.unwrap_or(&self.value);
+
+        let font = self.font.unwrap_or_else(|| renderer.default_font());
+        let text_size = self.size.unwrap_or_else(|| renderer.default_size());
+        let padding = self.padding.fit(Size::ZERO, limits.max());
+        let height = self.line_height.to_absolute(text_size);
+
+        let limits = limits.width(self.width).shrink(padding);
+        let text_bounds = limits.resolve(self.width, height, Size::ZERO);
+
+        let placeholder_text = Text {
+            font,
+            line_height: self.line_height,
+            content: &self.placeholder,
+            bounds: Size::new(f32::INFINITY, text_bounds.height),
+            size: text_size,
+            horizontal_alignment: alignment::Horizontal::Left,
+            vertical_alignment: alignment::Vertical::Center,
+            shaping: text::Shaping::Advanced,
+        };
+
+        state.placeholder.update(placeholder_text);
+
+        let secure_value = self.is_secure.then(|| value.secure());
+        let value = secure_value.as_ref().unwrap_or(value);
+
+        state.value.update(Text {
+            content: &value.to_string(),
+            ..placeholder_text
+        });
+
+        if let Some(icon) = &self.icon {
+            let icon_text = Text {
+                line_height: self.line_height,
+                content: &icon.code_point.to_string(),
+                font: icon.font,
+                size: icon.size.unwrap_or_else(|| renderer.default_size()),
+                bounds: Size::new(f32::INFINITY, text_bounds.height),
+                horizontal_alignment: alignment::Horizontal::Center,
+                vertical_alignment: alignment::Vertical::Center,
+                shaping: text::Shaping::Advanced,
+            };
+
+            state.icon.update(icon_text);
+
+            let icon_width = state.icon.min_width();
+
+            let (text_position, icon_position) = match icon.side {
+                Side::Left => (
+                    Point::new(
+                        padding.left + icon_width + icon.spacing,
+                        padding.top,
+                    ),
+                    Point::new(padding.left, padding.top),
+                ),
+                Side::Right => (
+                    Point::new(padding.left, padding.top),
+                    Point::new(
+                        padding.left + text_bounds.width - icon_width,
+                        padding.top,
+                    ),
+                ),
+            };
+
+            let text_node = layout::Node::new(
+                text_bounds - Size::new(icon_width + icon.spacing, 0.0),
+            )
+            .move_to(text_position);
+
+            let icon_node =
+                layout::Node::new(Size::new(icon_width, text_bounds.height))
+                    .move_to(icon_position);
+
+            layout::Node::with_children(
+                text_bounds.expand(padding),
+                vec![text_node, icon_node],
+            )
+        } else {
+            let text = layout::Node::new(text_bounds)
+                .move_to(Point::new(padding.left, padding.top));
+
+            layout::Node::with_children(text_bounds.expand(padding), vec![text])
+        }
     }
 
     /// Draws the [`TextInput`] with the given [`Renderer`], overriding its
@@ -243,19 +313,173 @@ where
         value: Option<&Value>,
         viewport: &Rectangle,
     ) {
-        draw(
-            renderer,
-            theme,
-            layout,
-            cursor,
-            tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
-            value.unwrap_or(&self.value),
-            self.on_input.is_none(),
-            self.is_secure,
-            self.icon.as_ref(),
-            &self.style,
-            viewport,
+        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
+        let value = value.unwrap_or(&self.value);
+        let is_disabled = self.on_input.is_none();
+
+        let secure_value = self.is_secure.then(|| value.secure());
+        let value = secure_value.as_ref().unwrap_or(value);
+
+        let bounds = layout.bounds();
+
+        let mut children_layout = layout.children();
+        let text_bounds = children_layout.next().unwrap().bounds();
+
+        let is_mouse_over = cursor.is_over(bounds);
+
+        let status = if is_disabled {
+            Status::Disabled
+        } else if state.is_focused() {
+            Status::Focused
+        } else if is_mouse_over {
+            Status::Hovered
+        } else {
+            Status::Active
+        };
+
+        let appearance = (self.style)(theme, status);
+
+        renderer.fill_quad(
+            renderer::Quad {
+                bounds,
+                border: appearance.border,
+                ..renderer::Quad::default()
+            },
+            appearance.background,
         );
+
+        if self.icon.is_some() {
+            let icon_layout = children_layout.next().unwrap();
+
+            renderer.fill_paragraph(
+                &state.icon,
+                icon_layout.bounds().center(),
+                appearance.icon,
+                *viewport,
+            );
+        }
+
+        let text = value.to_string();
+
+        let (cursor, offset) = if let Some(focus) = state
+            .is_focused
+            .as_ref()
+            .filter(|focus| focus.is_window_focused)
+        {
+            match state.cursor.state(value) {
+                cursor::State::Index(position) => {
+                    let (text_value_width, offset) =
+                        measure_cursor_and_scroll_offset(
+                            &state.value,
+                            text_bounds,
+                            position,
+                        );
+
+                    let is_cursor_visible = ((focus.now - focus.updated_at)
+                        .as_millis()
+                        / CURSOR_BLINK_INTERVAL_MILLIS)
+                        % 2
+                        == 0;
+
+                    let cursor = if is_cursor_visible {
+                        Some((
+                            renderer::Quad {
+                                bounds: Rectangle {
+                                    x: text_bounds.x + text_value_width,
+                                    y: text_bounds.y,
+                                    width: 1.0,
+                                    height: text_bounds.height,
+                                },
+                                ..renderer::Quad::default()
+                            },
+                            appearance.value,
+                        ))
+                    } else {
+                        None
+                    };
+
+                    (cursor, offset)
+                }
+                cursor::State::Selection { start, end } => {
+                    let left = start.min(end);
+                    let right = end.max(start);
+
+                    let (left_position, left_offset) =
+                        measure_cursor_and_scroll_offset(
+                            &state.value,
+                            text_bounds,
+                            left,
+                        );
+
+                    let (right_position, right_offset) =
+                        measure_cursor_and_scroll_offset(
+                            &state.value,
+                            text_bounds,
+                            right,
+                        );
+
+                    let width = right_position - left_position;
+
+                    (
+                        Some((
+                            renderer::Quad {
+                                bounds: Rectangle {
+                                    x: text_bounds.x + left_position,
+                                    y: text_bounds.y,
+                                    width,
+                                    height: text_bounds.height,
+                                },
+                                ..renderer::Quad::default()
+                            },
+                            appearance.selection,
+                        )),
+                        if end == right {
+                            right_offset
+                        } else {
+                            left_offset
+                        },
+                    )
+                }
+            }
+        } else {
+            (None, 0.0)
+        };
+
+        let draw = |renderer: &mut Renderer, viewport| {
+            if let Some((cursor, color)) = cursor {
+                renderer.with_translation(
+                    Vector::new(-offset, 0.0),
+                    |renderer| {
+                        renderer.fill_quad(cursor, color);
+                    },
+                );
+            } else {
+                renderer.with_translation(Vector::ZERO, |_| {});
+            }
+
+            renderer.fill_paragraph(
+                if text.is_empty() {
+                    &state.placeholder
+                } else {
+                    &state.value
+                },
+                Point::new(text_bounds.x, text_bounds.center_y())
+                    - Vector::new(offset, 0.0),
+                if text.is_empty() {
+                    appearance.placeholder
+                } else {
+                    appearance.value
+                },
+                viewport,
+            );
+        };
+
+        if cursor.is_some() {
+            renderer
+                .with_layer(text_bounds, |renderer| draw(renderer, *viewport));
+        } else {
+            draw(renderer, text_bounds);
+        }
     }
 }
 
@@ -263,7 +487,6 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
     for TextInput<'a, Message, Theme, Renderer>
 where
     Message: Clone,
-    Theme: StyleSheet,
     Renderer: text::Renderer,
 {
     fn tag(&self) -> tree::Tag {
@@ -299,20 +522,7 @@ where
         renderer: &Renderer,
         limits: &layout::Limits,
     ) -> layout::Node {
-        layout(
-            renderer,
-            limits,
-            self.width,
-            self.padding,
-            self.size,
-            self.font,
-            self.line_height,
-            self.icon.as_ref(),
-            tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
-            &self.value,
-            &self.placeholder,
-            self.is_secure,
-        )
+        self.layout(tree, renderer, limits, None)
     }
 
     fn operate(
@@ -339,23 +549,468 @@ where
         shell: &mut Shell<'_, Message>,
         _viewport: &Rectangle,
     ) -> event::Status {
-        update(
-            event,
-            layout,
-            cursor,
-            renderer,
-            clipboard,
-            shell,
-            &mut self.value,
-            self.size,
-            self.line_height,
-            self.font,
-            self.is_secure,
-            self.on_input.as_deref(),
-            self.on_paste.as_deref(),
-            &self.on_submit,
-            || tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
-        )
+        let update_cache = |state, value| {
+            replace_paragraph(
+                renderer,
+                state,
+                layout,
+                value,
+                self.font,
+                self.size,
+                self.line_height,
+            );
+        };
+
+        match event {
+            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+            | Event::Touch(touch::Event::FingerPressed { .. }) => {
+                let state = state::<Renderer>(tree);
+
+                let click_position = if self.on_input.is_some() {
+                    cursor.position_over(layout.bounds())
+                } else {
+                    None
+                };
+
+                state.is_focused = if click_position.is_some() {
+                    state.is_focused.or_else(|| {
+                        let now = Instant::now();
+
+                        Some(Focus {
+                            updated_at: now,
+                            now,
+                            is_window_focused: true,
+                        })
+                    })
+                } else {
+                    None
+                };
+
+                if let Some(cursor_position) = click_position {
+                    let text_layout = layout.children().next().unwrap();
+                    let target = cursor_position.x - text_layout.bounds().x;
+
+                    let click =
+                        mouse::Click::new(cursor_position, state.last_click);
+
+                    match click.kind() {
+                        click::Kind::Single => {
+                            let position = if target > 0.0 {
+                                let value = if self.is_secure {
+                                    self.value.secure()
+                                } else {
+                                    self.value.clone()
+                                };
+
+                                find_cursor_position(
+                                    text_layout.bounds(),
+                                    &value,
+                                    state,
+                                    target,
+                                )
+                            } else {
+                                None
+                            }
+                            .unwrap_or(0);
+
+                            if state.keyboard_modifiers.shift() {
+                                state.cursor.select_range(
+                                    state.cursor.start(&self.value),
+                                    position,
+                                );
+                            } else {
+                                state.cursor.move_to(position);
+                            }
+                            state.is_dragging = true;
+                        }
+                        click::Kind::Double => {
+                            if self.is_secure {
+                                state.cursor.select_all(&self.value);
+                            } else {
+                                let position = find_cursor_position(
+                                    text_layout.bounds(),
+                                    &self.value,
+                                    state,
+                                    target,
+                                )
+                                .unwrap_or(0);
+
+                                state.cursor.select_range(
+                                    self.value.previous_start_of_word(position),
+                                    self.value.next_end_of_word(position),
+                                );
+                            }
+
+                            state.is_dragging = false;
+                        }
+                        click::Kind::Triple => {
+                            state.cursor.select_all(&self.value);
+                            state.is_dragging = false;
+                        }
+                    }
+
+                    state.last_click = Some(click);
+
+                    return event::Status::Captured;
+                }
+            }
+            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+            | Event::Touch(touch::Event::FingerLifted { .. })
+            | Event::Touch(touch::Event::FingerLost { .. }) => {
+                state::<Renderer>(tree).is_dragging = false;
+            }
+            Event::Mouse(mouse::Event::CursorMoved { position })
+            | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
+                let state = state::<Renderer>(tree);
+
+                if state.is_dragging {
+                    let text_layout = layout.children().next().unwrap();
+                    let target = position.x - text_layout.bounds().x;
+
+                    let value = if self.is_secure {
+                        self.value.secure()
+                    } else {
+                        self.value.clone()
+                    };
+
+                    let position = find_cursor_position(
+                        text_layout.bounds(),
+                        &value,
+                        state,
+                        target,
+                    )
+                    .unwrap_or(0);
+
+                    state
+                        .cursor
+                        .select_range(state.cursor.start(&value), position);
+
+                    return event::Status::Captured;
+                }
+            }
+            Event::Keyboard(keyboard::Event::KeyPressed {
+                key, text, ..
+            }) => {
+                let state = state::<Renderer>(tree);
+
+                if let Some(focus) = &mut state.is_focused {
+                    let Some(on_input) = &self.on_input else {
+                        return event::Status::Ignored;
+                    };
+
+                    let modifiers = state.keyboard_modifiers;
+                    focus.updated_at = Instant::now();
+
+                    match key.as_ref() {
+                        keyboard::Key::Character("c")
+                            if state.keyboard_modifiers.command() =>
+                        {
+                            if let Some((start, end)) =
+                                state.cursor.selection(&self.value)
+                            {
+                                clipboard.write(
+                                    clipboard::Kind::Standard,
+                                    self.value.select(start, end).to_string(),
+                                );
+                            }
+
+                            return event::Status::Captured;
+                        }
+                        keyboard::Key::Character("x")
+                            if state.keyboard_modifiers.command() =>
+                        {
+                            if let Some((start, end)) =
+                                state.cursor.selection(&self.value)
+                            {
+                                clipboard.write(
+                                    clipboard::Kind::Standard,
+                                    self.value.select(start, end).to_string(),
+                                );
+                            }
+
+                            let mut editor =
+                                Editor::new(&mut self.value, &mut state.cursor);
+                            editor.delete();
+
+                            let message = (on_input)(editor.contents());
+                            shell.publish(message);
+
+                            update_cache(state, &self.value);
+
+                            return event::Status::Captured;
+                        }
+                        keyboard::Key::Character("v")
+                            if state.keyboard_modifiers.command()
+                                && !state.keyboard_modifiers.alt() =>
+                        {
+                            let content = match state.is_pasting.take() {
+                                Some(content) => content,
+                                None => {
+                                    let content: String = clipboard
+                                        .read(clipboard::Kind::Standard)
+                                        .unwrap_or_default()
+                                        .chars()
+                                        .filter(|c| !c.is_control())
+                                        .collect();
+
+                                    Value::new(&content)
+                                }
+                            };
+
+                            let mut editor =
+                                Editor::new(&mut self.value, &mut state.cursor);
+
+                            editor.paste(content.clone());
+
+                            let message = if let Some(paste) = &self.on_paste {
+                                (paste)(editor.contents())
+                            } else {
+                                (on_input)(editor.contents())
+                            };
+                            shell.publish(message);
+
+                            state.is_pasting = Some(content);
+
+                            update_cache(state, &self.value);
+
+                            return event::Status::Captured;
+                        }
+                        keyboard::Key::Character("a")
+                            if state.keyboard_modifiers.command() =>
+                        {
+                            state.cursor.select_all(&self.value);
+
+                            return event::Status::Captured;
+                        }
+                        _ => {}
+                    }
+
+                    if let Some(text) = text {
+                        state.is_pasting = None;
+
+                        if let Some(c) =
+                            text.chars().next().filter(|c| !c.is_control())
+                        {
+                            let mut editor =
+                                Editor::new(&mut self.value, &mut state.cursor);
+
+                            editor.insert(c);
+
+                            let message = (on_input)(editor.contents());
+                            shell.publish(message);
+
+                            focus.updated_at = Instant::now();
+
+                            update_cache(state, &self.value);
+
+                            return event::Status::Captured;
+                        }
+                    }
+
+                    match key.as_ref() {
+                        keyboard::Key::Named(key::Named::Enter) => {
+                            if let Some(on_submit) = self.on_submit.clone() {
+                                shell.publish(on_submit);
+                            }
+                        }
+                        keyboard::Key::Named(key::Named::Backspace) => {
+                            if platform::is_jump_modifier_pressed(modifiers)
+                                && state.cursor.selection(&self.value).is_none()
+                            {
+                                if self.is_secure {
+                                    let cursor_pos =
+                                        state.cursor.end(&self.value);
+                                    state.cursor.select_range(0, cursor_pos);
+                                } else {
+                                    state
+                                        .cursor
+                                        .select_left_by_words(&self.value);
+                                }
+                            }
+
+                            let mut editor =
+                                Editor::new(&mut self.value, &mut state.cursor);
+                            editor.backspace();
+
+                            let message = (on_input)(editor.contents());
+                            shell.publish(message);
+
+                            update_cache(state, &self.value);
+                        }
+                        keyboard::Key::Named(key::Named::Delete) => {
+                            if platform::is_jump_modifier_pressed(modifiers)
+                                && state.cursor.selection(&self.value).is_none()
+                            {
+                                if self.is_secure {
+                                    let cursor_pos =
+                                        state.cursor.end(&self.value);
+                                    state.cursor.select_range(
+                                        cursor_pos,
+                                        self.value.len(),
+                                    );
+                                } else {
+                                    state
+                                        .cursor
+                                        .select_right_by_words(&self.value);
+                                }
+                            }
+
+                            let mut editor =
+                                Editor::new(&mut self.value, &mut state.cursor);
+                            editor.delete();
+
+                            let message = (on_input)(editor.contents());
+                            shell.publish(message);
+
+                            update_cache(state, &self.value);
+                        }
+                        keyboard::Key::Named(key::Named::ArrowLeft) => {
+                            if platform::is_jump_modifier_pressed(modifiers)
+                                && !self.is_secure
+                            {
+                                if modifiers.shift() {
+                                    state
+                                        .cursor
+                                        .select_left_by_words(&self.value);
+                                } else {
+                                    state
+                                        .cursor
+                                        .move_left_by_words(&self.value);
+                                }
+                            } else if modifiers.shift() {
+                                state.cursor.select_left(&self.value);
+                            } else {
+                                state.cursor.move_left(&self.value);
+                            }
+                        }
+                        keyboard::Key::Named(key::Named::ArrowRight) => {
+                            if platform::is_jump_modifier_pressed(modifiers)
+                                && !self.is_secure
+                            {
+                                if modifiers.shift() {
+                                    state
+                                        .cursor
+                                        .select_right_by_words(&self.value);
+                                } else {
+                                    state
+                                        .cursor
+                                        .move_right_by_words(&self.value);
+                                }
+                            } else if modifiers.shift() {
+                                state.cursor.select_right(&self.value);
+                            } else {
+                                state.cursor.move_right(&self.value);
+                            }
+                        }
+                        keyboard::Key::Named(key::Named::Home) => {
+                            if modifiers.shift() {
+                                state.cursor.select_range(
+                                    state.cursor.start(&self.value),
+                                    0,
+                                );
+                            } else {
+                                state.cursor.move_to(0);
+                            }
+                        }
+                        keyboard::Key::Named(key::Named::End) => {
+                            if modifiers.shift() {
+                                state.cursor.select_range(
+                                    state.cursor.start(&self.value),
+                                    self.value.len(),
+                                );
+                            } else {
+                                state.cursor.move_to(self.value.len());
+                            }
+                        }
+                        keyboard::Key::Named(key::Named::Escape) => {
+                            state.is_focused = None;
+                            state.is_dragging = false;
+                            state.is_pasting = None;
+
+                            state.keyboard_modifiers =
+                                keyboard::Modifiers::default();
+                        }
+                        keyboard::Key::Named(
+                            key::Named::Tab
+                            | key::Named::ArrowUp
+                            | key::Named::ArrowDown,
+                        ) => {
+                            return event::Status::Ignored;
+                        }
+                        _ => {}
+                    }
+
+                    return event::Status::Captured;
+                }
+            }
+            Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
+                let state = state::<Renderer>(tree);
+
+                if state.is_focused.is_some() {
+                    match key.as_ref() {
+                        keyboard::Key::Character("v") => {
+                            state.is_pasting = None;
+                        }
+                        keyboard::Key::Named(
+                            key::Named::Tab
+                            | key::Named::ArrowUp
+                            | key::Named::ArrowDown,
+                        ) => {
+                            return event::Status::Ignored;
+                        }
+                        _ => {}
+                    }
+
+                    return event::Status::Captured;
+                }
+
+                state.is_pasting = None;
+            }
+            Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
+                let state = state::<Renderer>(tree);
+
+                state.keyboard_modifiers = modifiers;
+            }
+            Event::Window(_, window::Event::Unfocused) => {
+                let state = state::<Renderer>(tree);
+
+                if let Some(focus) = &mut state.is_focused {
+                    focus.is_window_focused = false;
+                }
+            }
+            Event::Window(_, window::Event::Focused) => {
+                let state = state::<Renderer>(tree);
+
+                if let Some(focus) = &mut state.is_focused {
+                    focus.is_window_focused = true;
+                    focus.updated_at = Instant::now();
+
+                    shell.request_redraw(window::RedrawRequest::NextFrame);
+                }
+            }
+            Event::Window(_, window::Event::RedrawRequested(now)) => {
+                let state = state::<Renderer>(tree);
+
+                if let Some(focus) = &mut state.is_focused {
+                    if focus.is_window_focused {
+                        focus.now = now;
+
+                        let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
+                            - (now - focus.updated_at).as_millis()
+                                % CURSOR_BLINK_INTERVAL_MILLIS;
+
+                        shell.request_redraw(window::RedrawRequest::At(
+                            now + Duration::from_millis(
+                                millis_until_redraw as u64,
+                            ),
+                        ));
+                    }
+                }
+            }
+            _ => {}
+        }
+
+        event::Status::Ignored
     }
 
     fn draw(
@@ -368,19 +1023,7 @@ where
         cursor: mouse::Cursor,
         viewport: &Rectangle,
     ) {
-        draw(
-            renderer,
-            theme,
-            layout,
-            cursor,
-            tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
-            &self.value,
-            self.on_input.is_none(),
-            self.is_secure,
-            self.icon.as_ref(),
-            &self.style,
-            viewport,
-        );
+        self.draw(tree, renderer, theme, layout, cursor, None, viewport);
     }
 
     fn mouse_interaction(
@@ -391,7 +1034,15 @@ where
         _viewport: &Rectangle,
         _renderer: &Renderer,
     ) -> mouse::Interaction {
-        mouse_interaction(layout, cursor, self.on_input.is_none())
+        if cursor.is_over(layout.bounds()) {
+            if self.on_input.is_none() {
+                mouse::Interaction::NotAllowed
+            } else {
+                mouse::Interaction::Text
+            }
+        } else {
+            mouse::Interaction::default()
+        }
     }
 }
 
@@ -399,7 +1050,7 @@ impl<'a, Message, Theme, Renderer> From<TextInput<'a, Message, Theme, Renderer>>
     for Element<'a, Message, Theme, Renderer>
 where
     Message: 'a + Clone,
-    Theme: StyleSheet + 'a,
+    Theme: 'a,
     Renderer: text::Renderer + 'a,
 {
     fn from(
@@ -488,767 +1139,6 @@ pub fn select_all<Message: 'static>(id: Id) -> Command<Message> {
     Command::widget(operation::text_input::select_all(id.0))
 }
 
-/// Computes the layout of a [`TextInput`].
-pub fn layout<Renderer>(
-    renderer: &Renderer,
-    limits: &layout::Limits,
-    width: Length,
-    padding: Padding,
-    size: Option<Pixels>,
-    font: Option<Renderer::Font>,
-    line_height: text::LineHeight,
-    icon: Option<&Icon<Renderer::Font>>,
-    state: &mut State<Renderer::Paragraph>,
-    value: &Value,
-    placeholder: &str,
-    is_secure: bool,
-) -> layout::Node
-where
-    Renderer: text::Renderer,
-{
-    let font = font.unwrap_or_else(|| renderer.default_font());
-    let text_size = size.unwrap_or_else(|| renderer.default_size());
-    let padding = padding.fit(Size::ZERO, limits.max());
-    let height = line_height.to_absolute(text_size);
-
-    let limits = limits.width(width).shrink(padding);
-    let text_bounds = limits.resolve(width, height, Size::ZERO);
-
-    let placeholder_text = Text {
-        font,
-        line_height,
-        content: placeholder,
-        bounds: Size::new(f32::INFINITY, text_bounds.height),
-        size: text_size,
-        horizontal_alignment: alignment::Horizontal::Left,
-        vertical_alignment: alignment::Vertical::Center,
-        shaping: text::Shaping::Advanced,
-    };
-
-    state.placeholder.update(placeholder_text);
-
-    let secure_value = is_secure.then(|| value.secure());
-    let value = secure_value.as_ref().unwrap_or(value);
-
-    state.value.update(Text {
-        content: &value.to_string(),
-        ..placeholder_text
-    });
-
-    if let Some(icon) = icon {
-        let icon_text = Text {
-            line_height,
-            content: &icon.code_point.to_string(),
-            font: icon.font,
-            size: icon.size.unwrap_or_else(|| renderer.default_size()),
-            bounds: Size::new(f32::INFINITY, text_bounds.height),
-            horizontal_alignment: alignment::Horizontal::Center,
-            vertical_alignment: alignment::Vertical::Center,
-            shaping: text::Shaping::Advanced,
-        };
-
-        state.icon.update(icon_text);
-
-        let icon_width = state.icon.min_width();
-
-        let (text_position, icon_position) = match icon.side {
-            Side::Left => (
-                Point::new(
-                    padding.left + icon_width + icon.spacing,
-                    padding.top,
-                ),
-                Point::new(padding.left, padding.top),
-            ),
-            Side::Right => (
-                Point::new(padding.left, padding.top),
-                Point::new(
-                    padding.left + text_bounds.width - icon_width,
-                    padding.top,
-                ),
-            ),
-        };
-
-        let text_node = layout::Node::new(
-            text_bounds - Size::new(icon_width + icon.spacing, 0.0),
-        )
-        .move_to(text_position);
-
-        let icon_node =
-            layout::Node::new(Size::new(icon_width, text_bounds.height))
-                .move_to(icon_position);
-
-        layout::Node::with_children(
-            text_bounds.expand(padding),
-            vec![text_node, icon_node],
-        )
-    } else {
-        let text = layout::Node::new(text_bounds)
-            .move_to(Point::new(padding.left, padding.top));
-
-        layout::Node::with_children(text_bounds.expand(padding), vec![text])
-    }
-}
-
-/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
-/// accordingly.
-pub fn update<'a, Message, Renderer>(
-    event: Event,
-    layout: Layout<'_>,
-    cursor: mouse::Cursor,
-    renderer: &Renderer,
-    clipboard: &mut dyn Clipboard,
-    shell: &mut Shell<'_, Message>,
-    value: &mut Value,
-    size: Option<Pixels>,
-    line_height: text::LineHeight,
-    font: Option<Renderer::Font>,
-    is_secure: bool,
-    on_input: Option<&dyn Fn(String) -> Message>,
-    on_paste: Option<&dyn Fn(String) -> Message>,
-    on_submit: &Option<Message>,
-    state: impl FnOnce() -> &'a mut State<Renderer::Paragraph>,
-) -> event::Status
-where
-    Message: Clone,
-    Renderer: text::Renderer,
-{
-    let update_cache = |state, value| {
-        replace_paragraph(
-            renderer,
-            state,
-            layout,
-            value,
-            font,
-            size,
-            line_height,
-        );
-    };
-
-    match event {
-        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
-        | Event::Touch(touch::Event::FingerPressed { .. }) => {
-            let state = state();
-
-            let click_position = if on_input.is_some() {
-                cursor.position_over(layout.bounds())
-            } else {
-                None
-            };
-
-            state.is_focused = if click_position.is_some() {
-                state.is_focused.or_else(|| {
-                    let now = Instant::now();
-
-                    Some(Focus {
-                        updated_at: now,
-                        now,
-                        is_window_focused: true,
-                    })
-                })
-            } else {
-                None
-            };
-
-            if let Some(cursor_position) = click_position {
-                let text_layout = layout.children().next().unwrap();
-                let target = cursor_position.x - text_layout.bounds().x;
-
-                let click =
-                    mouse::Click::new(cursor_position, state.last_click);
-
-                match click.kind() {
-                    click::Kind::Single => {
-                        let position = if target > 0.0 {
-                            let value = if is_secure {
-                                value.secure()
-                            } else {
-                                value.clone()
-                            };
-
-                            find_cursor_position(
-                                text_layout.bounds(),
-                                &value,
-                                state,
-                                target,
-                            )
-                        } else {
-                            None
-                        }
-                        .unwrap_or(0);
-
-                        if state.keyboard_modifiers.shift() {
-                            state.cursor.select_range(
-                                state.cursor.start(value),
-                                position,
-                            );
-                        } else {
-                            state.cursor.move_to(position);
-                        }
-                        state.is_dragging = true;
-                    }
-                    click::Kind::Double => {
-                        if is_secure {
-                            state.cursor.select_all(value);
-                        } else {
-                            let position = find_cursor_position(
-                                text_layout.bounds(),
-                                value,
-                                state,
-                                target,
-                            )
-                            .unwrap_or(0);
-
-                            state.cursor.select_range(
-                                value.previous_start_of_word(position),
-                                value.next_end_of_word(position),
-                            );
-                        }
-
-                        state.is_dragging = false;
-                    }
-                    click::Kind::Triple => {
-                        state.cursor.select_all(value);
-                        state.is_dragging = false;
-                    }
-                }
-
-                state.last_click = Some(click);
-
-                return event::Status::Captured;
-            }
-        }
-        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
-        | Event::Touch(touch::Event::FingerLifted { .. })
-        | Event::Touch(touch::Event::FingerLost { .. }) => {
-            state().is_dragging = false;
-        }
-        Event::Mouse(mouse::Event::CursorMoved { position })
-        | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
-            let state = state();
-
-            if state.is_dragging {
-                let text_layout = layout.children().next().unwrap();
-                let target = position.x - text_layout.bounds().x;
-
-                let value = if is_secure {
-                    value.secure()
-                } else {
-                    value.clone()
-                };
-
-                let position = find_cursor_position(
-                    text_layout.bounds(),
-                    &value,
-                    state,
-                    target,
-                )
-                .unwrap_or(0);
-
-                state
-                    .cursor
-                    .select_range(state.cursor.start(&value), position);
-
-                return event::Status::Captured;
-            }
-        }
-        Event::Keyboard(keyboard::Event::KeyPressed { key, text, .. }) => {
-            let state = state();
-
-            if let Some(focus) = &mut state.is_focused {
-                let Some(on_input) = on_input else {
-                    return event::Status::Ignored;
-                };
-
-                let modifiers = state.keyboard_modifiers;
-                focus.updated_at = Instant::now();
-
-                match key.as_ref() {
-                    keyboard::Key::Character("c")
-                        if state.keyboard_modifiers.command() =>
-                    {
-                        if let Some((start, end)) =
-                            state.cursor.selection(value)
-                        {
-                            clipboard.write(
-                                clipboard::Kind::Standard,
-                                value.select(start, end).to_string(),
-                            );
-                        }
-
-                        return event::Status::Captured;
-                    }
-                    keyboard::Key::Character("x")
-                        if state.keyboard_modifiers.command() =>
-                    {
-                        if let Some((start, end)) =
-                            state.cursor.selection(value)
-                        {
-                            clipboard.write(
-                                clipboard::Kind::Standard,
-                                value.select(start, end).to_string(),
-                            );
-                        }
-
-                        let mut editor = Editor::new(value, &mut state.cursor);
-                        editor.delete();
-
-                        let message = (on_input)(editor.contents());
-                        shell.publish(message);
-
-                        update_cache(state, value);
-
-                        return event::Status::Captured;
-                    }
-                    keyboard::Key::Character("v")
-                        if state.keyboard_modifiers.command()
-                            && !state.keyboard_modifiers.alt() =>
-                    {
-                        let content = match state.is_pasting.take() {
-                            Some(content) => content,
-                            None => {
-                                let content: String = clipboard
-                                    .read(clipboard::Kind::Standard)
-                                    .unwrap_or_default()
-                                    .chars()
-                                    .filter(|c| !c.is_control())
-                                    .collect();
-
-                                Value::new(&content)
-                            }
-                        };
-
-                        let mut editor = Editor::new(value, &mut state.cursor);
-
-                        editor.paste(content.clone());
-
-                        let message = if let Some(paste) = &on_paste {
-                            (paste)(editor.contents())
-                        } else {
-                            (on_input)(editor.contents())
-                        };
-                        shell.publish(message);
-
-                        state.is_pasting = Some(content);
-
-                        update_cache(state, value);
-
-                        return event::Status::Captured;
-                    }
-                    keyboard::Key::Character("a")
-                        if state.keyboard_modifiers.command() =>
-                    {
-                        state.cursor.select_all(value);
-
-                        return event::Status::Captured;
-                    }
-                    _ => {}
-                }
-
-                if let Some(text) = text {
-                    state.is_pasting = None;
-
-                    if let Some(c) =
-                        text.chars().next().filter(|c| !c.is_control())
-                    {
-                        let mut editor = Editor::new(value, &mut state.cursor);
-
-                        editor.insert(c);
-
-                        let message = (on_input)(editor.contents());
-                        shell.publish(message);
-
-                        focus.updated_at = Instant::now();
-
-                        update_cache(state, value);
-
-                        return event::Status::Captured;
-                    }
-                }
-
-                match key.as_ref() {
-                    keyboard::Key::Named(key::Named::Enter) => {
-                        if let Some(on_submit) = on_submit.clone() {
-                            shell.publish(on_submit);
-                        }
-                    }
-                    keyboard::Key::Named(key::Named::Backspace) => {
-                        if platform::is_jump_modifier_pressed(modifiers)
-                            && state.cursor.selection(value).is_none()
-                        {
-                            if is_secure {
-                                let cursor_pos = state.cursor.end(value);
-                                state.cursor.select_range(0, cursor_pos);
-                            } else {
-                                state.cursor.select_left_by_words(value);
-                            }
-                        }
-
-                        let mut editor = Editor::new(value, &mut state.cursor);
-                        editor.backspace();
-
-                        let message = (on_input)(editor.contents());
-                        shell.publish(message);
-
-                        update_cache(state, value);
-                    }
-                    keyboard::Key::Named(key::Named::Delete) => {
-                        if platform::is_jump_modifier_pressed(modifiers)
-                            && state.cursor.selection(value).is_none()
-                        {
-                            if is_secure {
-                                let cursor_pos = state.cursor.end(value);
-                                state
-                                    .cursor
-                                    .select_range(cursor_pos, value.len());
-                            } else {
-                                state.cursor.select_right_by_words(value);
-                            }
-                        }
-
-                        let mut editor = Editor::new(value, &mut state.cursor);
-                        editor.delete();
-
-                        let message = (on_input)(editor.contents());
-                        shell.publish(message);
-
-                        update_cache(state, value);
-                    }
-                    keyboard::Key::Named(key::Named::ArrowLeft) => {
-                        if platform::is_jump_modifier_pressed(modifiers)
-                            && !is_secure
-                        {
-                            if modifiers.shift() {
-                                state.cursor.select_left_by_words(value);
-                            } else {
-                                state.cursor.move_left_by_words(value);
-                            }
-                        } else if modifiers.shift() {
-                            state.cursor.select_left(value);
-                        } else {
-                            state.cursor.move_left(value);
-                        }
-                    }
-                    keyboard::Key::Named(key::Named::ArrowRight) => {
-                        if platform::is_jump_modifier_pressed(modifiers)
-                            && !is_secure
-                        {
-                            if modifiers.shift() {
-                                state.cursor.select_right_by_words(value);
-                            } else {
-                                state.cursor.move_right_by_words(value);
-                            }
-                        } else if modifiers.shift() {
-                            state.cursor.select_right(value);
-                        } else {
-                            state.cursor.move_right(value);
-                        }
-                    }
-                    keyboard::Key::Named(key::Named::Home) => {
-                        if modifiers.shift() {
-                            state
-                                .cursor
-                                .select_range(state.cursor.start(value), 0);
-                        } else {
-                            state.cursor.move_to(0);
-                        }
-                    }
-                    keyboard::Key::Named(key::Named::End) => {
-                        if modifiers.shift() {
-                            state.cursor.select_range(
-                                state.cursor.start(value),
-                                value.len(),
-                            );
-                        } else {
-                            state.cursor.move_to(value.len());
-                        }
-                    }
-                    keyboard::Key::Named(key::Named::Escape) => {
-                        state.is_focused = None;
-                        state.is_dragging = false;
-                        state.is_pasting = None;
-
-                        state.keyboard_modifiers =
-                            keyboard::Modifiers::default();
-                    }
-                    keyboard::Key::Named(
-                        key::Named::Tab
-                        | key::Named::ArrowUp
-                        | key::Named::ArrowDown,
-                    ) => {
-                        return event::Status::Ignored;
-                    }
-                    _ => {}
-                }
-
-                return event::Status::Captured;
-            }
-        }
-        Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
-            let state = state();
-
-            if state.is_focused.is_some() {
-                match key.as_ref() {
-                    keyboard::Key::Character("v") => {
-                        state.is_pasting = None;
-                    }
-                    keyboard::Key::Named(
-                        key::Named::Tab
-                        | key::Named::ArrowUp
-                        | key::Named::ArrowDown,
-                    ) => {
-                        return event::Status::Ignored;
-                    }
-                    _ => {}
-                }
-
-                return event::Status::Captured;
-            }
-
-            state.is_pasting = None;
-        }
-        Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
-            let state = state();
-
-            state.keyboard_modifiers = modifiers;
-        }
-        Event::Window(_, window::Event::Unfocused) => {
-            let state = state();
-
-            if let Some(focus) = &mut state.is_focused {
-                focus.is_window_focused = false;
-            }
-        }
-        Event::Window(_, window::Event::Focused) => {
-            let state = state();
-
-            if let Some(focus) = &mut state.is_focused {
-                focus.is_window_focused = true;
-                focus.updated_at = Instant::now();
-
-                shell.request_redraw(window::RedrawRequest::NextFrame);
-            }
-        }
-        Event::Window(_, window::Event::RedrawRequested(now)) => {
-            let state = state();
-
-            if let Some(focus) = &mut state.is_focused {
-                if focus.is_window_focused {
-                    focus.now = now;
-
-                    let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
-                        - (now - focus.updated_at).as_millis()
-                            % CURSOR_BLINK_INTERVAL_MILLIS;
-
-                    shell.request_redraw(window::RedrawRequest::At(
-                        now + Duration::from_millis(millis_until_redraw as u64),
-                    ));
-                }
-            }
-        }
-        _ => {}
-    }
-
-    event::Status::Ignored
-}
-
-/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
-/// [`Value`] if provided.
-///
-/// [`Renderer`]: text::Renderer
-pub fn draw<Theme, Renderer>(
-    renderer: &mut Renderer,
-    theme: &Theme,
-    layout: Layout<'_>,
-    cursor: mouse::Cursor,
-    state: &State<Renderer::Paragraph>,
-    value: &Value,
-    is_disabled: bool,
-    is_secure: bool,
-    icon: Option<&Icon<Renderer::Font>>,
-    style: &Theme::Style,
-    viewport: &Rectangle,
-) where
-    Theme: StyleSheet,
-    Renderer: text::Renderer,
-{
-    let secure_value = is_secure.then(|| value.secure());
-    let value = secure_value.as_ref().unwrap_or(value);
-
-    let bounds = layout.bounds();
-
-    let mut children_layout = layout.children();
-    let text_bounds = children_layout.next().unwrap().bounds();
-
-    let is_mouse_over = cursor.is_over(bounds);
-
-    let appearance = if is_disabled {
-        theme.disabled(style)
-    } else if state.is_focused() {
-        theme.focused(style)
-    } else if is_mouse_over {
-        theme.hovered(style)
-    } else {
-        theme.active(style)
-    };
-
-    renderer.fill_quad(
-        renderer::Quad {
-            bounds,
-            border: appearance.border,
-            ..renderer::Quad::default()
-        },
-        appearance.background,
-    );
-
-    if icon.is_some() {
-        let icon_layout = children_layout.next().unwrap();
-
-        renderer.fill_paragraph(
-            &state.icon,
-            icon_layout.bounds().center(),
-            appearance.icon_color,
-            *viewport,
-        );
-    }
-
-    let text = value.to_string();
-
-    let (cursor, offset) = if let Some(focus) = state
-        .is_focused
-        .as_ref()
-        .filter(|focus| focus.is_window_focused)
-    {
-        match state.cursor.state(value) {
-            cursor::State::Index(position) => {
-                let (text_value_width, offset) =
-                    measure_cursor_and_scroll_offset(
-                        &state.value,
-                        text_bounds,
-                        position,
-                    );
-
-                let is_cursor_visible = ((focus.now - focus.updated_at)
-                    .as_millis()
-                    / CURSOR_BLINK_INTERVAL_MILLIS)
-                    % 2
-                    == 0;
-
-                let cursor = if is_cursor_visible {
-                    Some((
-                        renderer::Quad {
-                            bounds: Rectangle {
-                                x: text_bounds.x + text_value_width,
-                                y: text_bounds.y,
-                                width: 1.0,
-                                height: text_bounds.height,
-                            },
-                            ..renderer::Quad::default()
-                        },
-                        theme.value_color(style),
-                    ))
-                } else {
-                    None
-                };
-
-                (cursor, offset)
-            }
-            cursor::State::Selection { start, end } => {
-                let left = start.min(end);
-                let right = end.max(start);
-
-                let (left_position, left_offset) =
-                    measure_cursor_and_scroll_offset(
-                        &state.value,
-                        text_bounds,
-                        left,
-                    );
-
-                let (right_position, right_offset) =
-                    measure_cursor_and_scroll_offset(
-                        &state.value,
-                        text_bounds,
-                        right,
-                    );
-
-                let width = right_position - left_position;
-
-                (
-                    Some((
-                        renderer::Quad {
-                            bounds: Rectangle {
-                                x: text_bounds.x + left_position,
-                                y: text_bounds.y,
-                                width,
-                                height: text_bounds.height,
-                            },
-                            ..renderer::Quad::default()
-                        },
-                        theme.selection_color(style),
-                    )),
-                    if end == right {
-                        right_offset
-                    } else {
-                        left_offset
-                    },
-                )
-            }
-        }
-    } else {
-        (None, 0.0)
-    };
-
-    let draw = |renderer: &mut Renderer, viewport| {
-        if let Some((cursor, color)) = cursor {
-            renderer.with_translation(Vector::new(-offset, 0.0), |renderer| {
-                renderer.fill_quad(cursor, color);
-            });
-        } else {
-            renderer.with_translation(Vector::ZERO, |_| {});
-        }
-
-        renderer.fill_paragraph(
-            if text.is_empty() {
-                &state.placeholder
-            } else {
-                &state.value
-            },
-            Point::new(text_bounds.x, text_bounds.center_y())
-                - Vector::new(offset, 0.0),
-            if text.is_empty() {
-                theme.placeholder_color(style)
-            } else if is_disabled {
-                theme.disabled_color(style)
-            } else {
-                theme.value_color(style)
-            },
-            viewport,
-        );
-    };
-
-    if cursor.is_some() {
-        renderer.with_layer(text_bounds, |renderer| draw(renderer, *viewport));
-    } else {
-        draw(renderer, text_bounds);
-    }
-}
-
-/// Computes the current [`mouse::Interaction`] of the [`TextInput`].
-pub fn mouse_interaction(
-    layout: Layout<'_>,
-    cursor: mouse::Cursor,
-    is_disabled: bool,
-) -> mouse::Interaction {
-    if cursor.is_over(layout.bounds()) {
-        if is_disabled {
-            mouse::Interaction::NotAllowed
-        } else {
-            mouse::Interaction::Text
-        }
-    } else {
-        mouse::Interaction::default()
-    }
-}
-
 /// The state of a [`TextInput`].
 #[derive(Debug, Default, Clone)]
 pub struct State<P: text::Paragraph> {
@@ -1264,6 +1154,12 @@ pub struct State<P: text::Paragraph> {
     // TODO: Add stateful horizontal scrolling offset
 }
 
+fn state<Renderer: text::Renderer>(
+    tree: &mut Tree,
+) -> &mut State<Renderer::Paragraph> {
+    tree.state.downcast_mut::<State<Renderer::Paragraph>>()
+}
+
 #[derive(Debug, Clone, Copy)]
 struct Focus {
     updated_at: Instant,
@@ -1479,3 +1375,86 @@ fn replace_paragraph<Renderer>(
 }
 
 const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
+
+/// The possible status of a [`TextInput`].
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Status {
+    /// The [`TextInput`] can be interacted with.
+    Active,
+    /// The [`TextInput`] is being hovered.
+    Hovered,
+    /// The [`TextInput`] is focused.
+    Focused,
+    /// The [`TextInput`] cannot be interacted with.
+    Disabled,
+}
+
+/// The appearance of a text input.
+#[derive(Debug, Clone, Copy)]
+pub struct Appearance {
+    /// The [`Background`] of the text input.
+    pub background: Background,
+    /// The [`Border`] of the text input.
+    pub border: Border,
+    /// The [`Color`] of the icon of the text input.
+    pub icon: Color,
+    /// The [`Color`] of the placeholder of the text input.
+    pub placeholder: Color,
+    /// The [`Color`] of the value of the text input.
+    pub value: Color,
+    /// The [`Color`] of the selection of the text input.
+    pub selection: Color,
+}
+
+/// The definiton of the default style of a [`TextInput`].
+pub trait Style {
+    /// Returns the default style of a [`TextInput`].
+    fn style() -> fn(&Self, Status) -> Appearance;
+}
+
+impl Style for Theme {
+    fn style() -> fn(&Self, Status) -> Appearance {
+        default
+    }
+}
+
+/// The default style of a [`TextInput`].
+pub fn default(theme: &Theme, status: Status) -> Appearance {
+    let palette = theme.extended_palette();
+
+    let active = Appearance {
+        background: Background::Color(palette.background.base.color),
+        border: Border {
+            radius: 2.0.into(),
+            width: 1.0,
+            color: palette.background.strong.color,
+        },
+        icon: palette.background.weak.text,
+        placeholder: palette.background.strong.color,
+        value: palette.background.base.text,
+        selection: palette.primary.weak.color,
+    };
+
+    match status {
+        Status::Active => active,
+        Status::Hovered => Appearance {
+            border: Border {
+                color: palette.background.base.text,
+                ..active.border
+            },
+            ..active
+        },
+        Status::Focused => Appearance {
+            border: Border {
+                color: palette.primary.strong.color,
+                ..active.border
+            },
+            ..active
+        },
+        Status::Disabled => Appearance {
+            background: Background::Color(palette.background.weak.color),
+            value: active.placeholder,
+            ..active
+        },
+    }
+}
-- 
cgit