diff options
| author | 2024-03-05 15:53:59 +0100 | |
|---|---|---|
| committer | 2024-03-05 15:53:59 +0100 | |
| commit | 704ec9cb5cdc1d44f2df2f15de700b0af330b1d7 (patch) | |
| tree | 27aeef02032c47327e74c9b3e48e83e11cde7fb2 /widget/src | |
| parent | d681aaa57e3106cf0ce90b74ade040ca7bb97832 (diff) | |
| download | iced-704ec9cb5cdc1d44f2df2f15de700b0af330b1d7.tar.gz iced-704ec9cb5cdc1d44f2df2f15de700b0af330b1d7.tar.bz2 iced-704ec9cb5cdc1d44f2df2f15de700b0af330b1d7.zip  | |
Simplify theming for `TextInput` widget
Diffstat (limited to 'widget/src')
| -rw-r--r-- | widget/src/combo_box.rs | 27 | ||||
| -rw-r--r-- | widget/src/helpers.rs | 8 | ||||
| -rw-r--r-- | widget/src/overlay/menu.rs | 4 | ||||
| -rw-r--r-- | widget/src/pick_list.rs | 14 | ||||
| -rw-r--r-- | widget/src/scrollable.rs | 18 | ||||
| -rw-r--r-- | widget/src/text_input.rs | 1669 | 
6 files changed, 855 insertions, 885 deletions
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 +        }, +    } +}  | 
