From f0ae9a0c38c2532220a7460916604914db94c078 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Sun, 24 Mar 2024 05:03:09 +0100
Subject: Use `Catalog` approach for all widgets

---
 widget/src/text_input.rs | 106 +++++++++++++++++++++++++----------------------
 1 file changed, 56 insertions(+), 50 deletions(-)

(limited to 'widget/src/text_input.rs')

diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index b161ec74..dafe2fca 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -60,6 +60,7 @@ pub struct TextInput<
     Theme = crate::Theme,
     Renderer = crate::Renderer,
 > where
+    Theme: Catalog,
     Renderer: text::Renderer,
 {
     id: Option<Id>,
@@ -75,7 +76,7 @@ pub struct TextInput<
     on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
     on_submit: Option<Message>,
     icon: Option<Icon<Renderer::Font>>,
-    style: Style<'a, Theme>,
+    class: Theme::Class<'a>,
 }
 
 /// The default [`Padding`] of a [`TextInput`].
@@ -84,24 +85,12 @@ pub const DEFAULT_PADDING: Padding = Padding::new(5.0);
 impl<'a, Message, Theme, Renderer> TextInput<'a, Message, Theme, Renderer>
 where
     Message: Clone,
+    Theme: Catalog,
     Renderer: text::Renderer,
 {
     /// Creates a new [`TextInput`] with the given placeholder and
     /// its current value.
-    pub fn new(placeholder: &str, value: &str) -> Self
-    where
-        Theme: DefaultStyle + 'a,
-    {
-        Self::with_style(placeholder, value, Theme::default_style)
-    }
-
-    /// Creates a new [`TextInput`] with the given placeholder,
-    /// its current value, and its style.
-    pub fn with_style(
-        placeholder: &str,
-        value: &str,
-        style: impl Fn(&Theme, Status) -> Appearance + 'a,
-    ) -> Self {
+    pub fn new(placeholder: &str, value: &str) -> Self {
         TextInput {
             id: None,
             placeholder: String::from(placeholder),
@@ -116,7 +105,7 @@ where
             on_paste: None,
             on_submit: None,
             icon: None,
-            style: Box::new(style),
+            class: Theme::default(),
         }
     }
 
@@ -203,11 +192,19 @@ where
     }
 
     /// Sets the style of the [`TextInput`].
-    pub fn style(
-        mut self,
-        style: impl Fn(&Theme, Status) -> Appearance + 'a,
-    ) -> Self {
-        self.style = Box::new(style);
+    #[must_use]
+    pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+    where
+        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+    {
+        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+        self
+    }
+
+    /// Sets the style class of the [`TextInput`].
+    #[must_use]
+    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+        self.class = class.into();
         self
     }
 
@@ -345,15 +342,15 @@ where
             Status::Active
         };
 
-        let appearance = (self.style)(theme, status);
+        let style = theme.style(&self.class, status);
 
         renderer.fill_quad(
             renderer::Quad {
                 bounds,
-                border: appearance.border,
+                border: style.border,
                 ..renderer::Quad::default()
             },
-            appearance.background,
+            style.background,
         );
 
         if self.icon.is_some() {
@@ -362,7 +359,7 @@ where
             renderer.fill_paragraph(
                 &state.icon,
                 icon_layout.bounds().center(),
-                appearance.icon,
+                style.icon,
                 *viewport,
             );
         }
@@ -401,7 +398,7 @@ where
                                 },
                                 ..renderer::Quad::default()
                             },
-                            appearance.value,
+                            style.value,
                         ))
                     } else {
                         None
@@ -440,7 +437,7 @@ where
                                 },
                                 ..renderer::Quad::default()
                             },
-                            appearance.selection,
+                            style.selection,
                         )),
                         if end == right {
                             right_offset
@@ -475,9 +472,9 @@ where
                 Point::new(text_bounds.x, text_bounds.center_y())
                     - Vector::new(offset, 0.0),
                 if text.is_empty() {
-                    appearance.placeholder
+                    style.placeholder
                 } else {
-                    appearance.value
+                    style.value
                 },
                 viewport,
             );
@@ -496,6 +493,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
     for TextInput<'a, Message, Theme, Renderer>
 where
     Message: Clone,
+    Theme: Catalog,
     Renderer: text::Renderer,
 {
     fn tag(&self) -> tree::Tag {
@@ -1058,8 +1056,8 @@ where
 impl<'a, Message, Theme, Renderer> From<TextInput<'a, Message, Theme, Renderer>>
     for Element<'a, Message, Theme, Renderer>
 where
-    Message: 'a + Clone,
-    Theme: 'a,
+    Message: Clone + 'a,
+    Theme: Catalog + 'a,
     Renderer: text::Renderer + 'a,
 {
     fn from(
@@ -1400,7 +1398,7 @@ pub enum Status {
 
 /// The appearance of a text input.
 #[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
     /// The [`Background`] of the text input.
     pub background: Background,
     /// The [`Border`] of the text input.
@@ -1415,32 +1413,40 @@ pub struct Appearance {
     pub selection: Color,
 }
 
-/// The style of a [`TextInput`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of a [`TextInput`].
+pub trait Catalog: Sized {
+    /// The item class of the [`Catalog`].
+    type Class<'a>;
 
-/// The default style of a [`TextInput`].
-pub trait DefaultStyle {
-    /// Returns the default style of a [`TextInput`].
-    fn default_style(&self, status: Status) -> Appearance;
+    /// The default class produced by the [`Catalog`].
+    fn default<'a>() -> Self::Class<'a>;
+
+    /// The [`Style`] of a class with the given status.
+    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
 }
 
-impl DefaultStyle for Theme {
-    fn default_style(&self, status: Status) -> Appearance {
-        default(self, status)
+/// A styling function for a [`TextInput`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+    type Class<'a> = StyleFn<'a, Self>;
+
+    fn default<'a>() -> Self::Class<'a> {
+        Box::new(default)
     }
-}
 
-impl DefaultStyle for Appearance {
-    fn default_style(&self, _status: Status) -> Appearance {
-        *self
+    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+        class(self, status)
     }
 }
 
 /// The default style of a [`TextInput`].
-pub fn default(theme: &Theme, status: Status) -> Appearance {
+pub fn default(theme: &Theme, status: Status) -> Style {
     let palette = theme.extended_palette();
 
-    let active = Appearance {
+    let active = Style {
         background: Background::Color(palette.background.base.color),
         border: Border {
             radius: 2.0.into(),
@@ -1455,21 +1461,21 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
 
     match status {
         Status::Active => active,
-        Status::Hovered => Appearance {
+        Status::Hovered => Style {
             border: Border {
                 color: palette.background.base.text,
                 ..active.border
             },
             ..active
         },
-        Status::Focused => Appearance {
+        Status::Focused => Style {
             border: Border {
                 color: palette.primary.strong.color,
                 ..active.border
             },
             ..active
         },
-        Status::Disabled => Appearance {
+        Status::Disabled => Style {
             background: Background::Color(palette.background.weak.color),
             value: active.placeholder,
             ..active
-- 
cgit 


From 6216c513d5e5853bf1d43342094e91a74981f4f2 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Mon, 1 Apr 2024 11:30:01 +0200
Subject: Use generic `Content` in `Text` to avoid reallocation in `fill_text`

---
 widget/src/text_input.rs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

(limited to 'widget/src/text_input.rs')

diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index dafe2fca..8cfb0408 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -232,7 +232,7 @@ where
         let placeholder_text = Text {
             font,
             line_height: self.line_height,
-            content: &self.placeholder,
+            content: self.placeholder.as_str(),
             bounds: Size::new(f32::INFINITY, text_bounds.height),
             size: text_size,
             horizontal_alignment: alignment::Horizontal::Left,
@@ -251,9 +251,11 @@ where
         });
 
         if let Some(icon) = &self.icon {
+            let mut content = [0; 4];
+
             let icon_text = Text {
                 line_height: self.line_height,
-                content: &icon.code_point.to_string(),
+                content: icon.code_point.encode_utf8(&mut content) as &_,
                 font: icon.font,
                 size: icon.size.unwrap_or_else(|| renderer.default_size()),
                 bounds: Size::new(f32::INFINITY, text_bounds.height),
-- 
cgit 


From 31d1d5fecbef50fa319cabd5d4194f1e4aaefa21 Mon Sep 17 00:00:00 2001
From: Aaron McGuire <89317236+Aaron-McGuire@users.noreply.github.com>
Date: Tue, 2 Apr 2024 03:46:35 -0500
Subject: Check is_secure before a copy/cut from TextInput (#2366)

* Check is_secure before copy/cut on text_input

* run cargo fmt
---
 widget/src/text_input.rs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

(limited to 'widget/src/text_input.rs')

diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 8cfb0408..a814df78 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -712,7 +712,8 @@ where
 
                     match key.as_ref() {
                         keyboard::Key::Character("c")
-                            if state.keyboard_modifiers.command() =>
+                            if state.keyboard_modifiers.command()
+                                && !self.is_secure =>
                         {
                             if let Some((start, end)) =
                                 state.cursor.selection(&self.value)
@@ -726,7 +727,8 @@ where
                             return event::Status::Captured;
                         }
                         keyboard::Key::Character("x")
-                            if state.keyboard_modifiers.command() =>
+                            if state.keyboard_modifiers.command()
+                                && !self.is_secure =>
                         {
                             if let Some((start, end)) =
                                 state.cursor.selection(&self.value)
-- 
cgit 


From 6d3e1d835e1688fbc58622a03a784ed25ed3f0e1 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 5 Apr 2024 23:59:21 +0200
Subject: Decouple caching from layering and simplify everything

---
 widget/src/text_input.rs | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

(limited to 'widget/src/text_input.rs')

diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 8cfb0408..05dd87b1 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -368,7 +368,7 @@ where
 
         let text = value.to_string();
 
-        let (cursor, offset) = if let Some(focus) = state
+        let (cursor, offset, is_selecting) = if let Some(focus) = state
             .is_focused
             .as_ref()
             .filter(|focus| focus.is_window_focused)
@@ -406,7 +406,7 @@ where
                         None
                     };
 
-                    (cursor, offset)
+                    (cursor, offset, false)
                 }
                 cursor::State::Selection { start, end } => {
                     let left = start.min(end);
@@ -446,11 +446,12 @@ where
                         } else {
                             left_offset
                         },
+                        true,
                     )
                 }
             }
         } else {
-            (None, 0.0)
+            (None, 0.0, false)
         };
 
         let draw = |renderer: &mut Renderer, viewport| {
@@ -482,7 +483,7 @@ where
             );
         };
 
-        if cursor.is_some() {
+        if is_selecting {
             renderer
                 .with_layer(text_bounds, |renderer| draw(renderer, *viewport));
         } else {
-- 
cgit