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/container.rs | 170 ++++++++++++++++++++----------------------------
 1 file changed, 72 insertions(+), 98 deletions(-)

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

diff --git a/widget/src/container.rs b/widget/src/container.rs
index 7c133588..21405722 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -9,8 +9,9 @@ use crate::core::renderer;
 use crate::core::widget::tree::{self, Tree};
 use crate::core::widget::{self, Operation};
 use crate::core::{
-    Background, Border, Clipboard, Color, Element, Layout, Length, Padding,
-    Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget,
+    self, Background, Border, Clipboard, Color, Element, Layout, Length,
+    Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,
+    Widget,
 };
 use crate::runtime::Command;
 
@@ -24,7 +25,8 @@ pub struct Container<
     Theme = crate::Theme,
     Renderer = crate::Renderer,
 > where
-    Renderer: crate::core::Renderer,
+    Theme: Catalog,
+    Renderer: core::Renderer,
 {
     id: Option<Id>,
     padding: Padding,
@@ -36,27 +38,17 @@ pub struct Container<
     vertical_alignment: alignment::Vertical,
     clip: bool,
     content: Element<'a, Message, Theme, Renderer>,
-    style: Style<'a, Theme>,
+    class: Theme::Class<'a>,
 }
 
 impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer>
 where
-    Renderer: crate::core::Renderer,
+    Theme: Catalog,
+    Renderer: core::Renderer,
 {
     /// Creates a [`Container`] with the given content.
     pub fn new(
         content: impl Into<Element<'a, Message, Theme, Renderer>>,
-    ) -> Self
-    where
-        Theme: DefaultStyle + 'a,
-    {
-        Self::with_style(content, Theme::default_style)
-    }
-
-    /// Creates a [`Container`] with the given content and style.
-    pub fn with_style(
-        content: impl Into<Element<'a, Message, Theme, Renderer>>,
-        style: impl Fn(&Theme, Status) -> Appearance + 'a,
     ) -> Self {
         let content = content.into();
         let size = content.as_widget().size_hint();
@@ -71,7 +63,7 @@ where
             horizontal_alignment: alignment::Horizontal::Left,
             vertical_alignment: alignment::Vertical::Top,
             clip: false,
-            style: Box::new(style),
+            class: Theme::default(),
             content,
         }
     }
@@ -136,27 +128,37 @@ where
         self
     }
 
-    /// Sets the style of the [`Container`].
-    pub fn style(
-        mut self,
-        style: impl Fn(&Theme, Status) -> Appearance + 'a,
-    ) -> Self {
-        self.style = Box::new(style);
-        self
-    }
-
     /// Sets whether the contents of the [`Container`] should be clipped on
     /// overflow.
     pub fn clip(mut self, clip: bool) -> Self {
         self.clip = clip;
         self
     }
+
+    /// Sets the style of the [`Container`].
+    #[must_use]
+    pub fn style(mut self, style: impl Fn(&Theme) -> 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 [`Container`].
+    #[cfg(feature = "advanced")]
+    #[must_use]
+    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+        self.class = class.into();
+        self
+    }
 }
 
 impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
     for Container<'a, Message, Theme, Renderer>
 where
-    Renderer: crate::core::Renderer,
+    Theme: Catalog,
+    Renderer: core::Renderer,
 {
     fn tag(&self) -> tree::Tag {
         self.content.as_widget().tag()
@@ -272,14 +274,7 @@ where
         viewport: &Rectangle,
     ) {
         let bounds = layout.bounds();
-
-        let status = if cursor.is_over(bounds) {
-            Status::Hovered
-        } else {
-            Status::Idle
-        };
-
-        let style = (self.style)(theme, status);
+        let style = theme.style(&self.class);
 
         if let Some(clipped_viewport) = bounds.intersection(viewport) {
             draw_background(renderer, &style, bounds);
@@ -324,8 +319,8 @@ impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
     for Element<'a, Message, Theme, Renderer>
 where
     Message: 'a,
-    Theme: 'a,
-    Renderer: 'a + crate::core::Renderer,
+    Theme: Catalog + 'a,
+    Renderer: core::Renderer + 'a,
 {
     fn from(
         column: Container<'a, Message, Theme, Renderer>,
@@ -362,25 +357,25 @@ pub fn layout(
     )
 }
 
-/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`.
+/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
 pub fn draw_background<Renderer>(
     renderer: &mut Renderer,
-    appearance: &Appearance,
+    style: &Style,
     bounds: Rectangle,
 ) where
-    Renderer: crate::core::Renderer,
+    Renderer: core::Renderer,
 {
-    if appearance.background.is_some()
-        || appearance.border.width > 0.0
-        || appearance.shadow.color.a > 0.0
+    if style.background.is_some()
+        || style.border.width > 0.0
+        || style.shadow.color.a > 0.0
     {
         renderer.fill_quad(
             renderer::Quad {
                 bounds,
-                border: appearance.border,
-                shadow: appearance.shadow,
+                border: style.border,
+                shadow: style.shadow,
             },
-            appearance
+            style
                 .background
                 .unwrap_or(Background::Color(Color::TRANSPARENT)),
         );
@@ -502,7 +497,7 @@ pub fn visible_bounds(id: Id) -> Command<Option<Rectangle>> {
 
 /// The appearance of a container.
 #[derive(Debug, Clone, Copy, Default)]
-pub struct Appearance {
+pub struct Style {
     /// The text [`Color`] of the container.
     pub text_color: Option<Color>,
     /// The [`Background`] of the container.
@@ -513,8 +508,8 @@ pub struct Appearance {
     pub shadow: Shadow,
 }
 
-impl Appearance {
-    /// Updates the border of the [`Appearance`] with the given [`Color`] and `width`.
+impl Style {
+    /// Updates the border of the [`Style`] with the given [`Color`] and `width`.
     pub fn with_border(
         self,
         color: impl Into<Color>,
@@ -530,7 +525,7 @@ impl Appearance {
         }
     }
 
-    /// Updates the background of the [`Appearance`].
+    /// Updates the background of the [`Style`].
     pub fn with_background(self, background: impl Into<Background>) -> Self {
         Self {
             background: Some(background.into()),
@@ -539,99 +534,78 @@ impl Appearance {
     }
 }
 
-impl From<Color> for Appearance {
+impl From<Color> for Style {
     fn from(color: Color) -> Self {
         Self::default().with_background(color)
     }
 }
 
-impl From<Gradient> for Appearance {
+impl From<Gradient> for Style {
     fn from(gradient: Gradient) -> Self {
         Self::default().with_background(gradient)
     }
 }
 
-impl From<gradient::Linear> for Appearance {
+impl From<gradient::Linear> for Style {
     fn from(gradient: gradient::Linear) -> Self {
         Self::default().with_background(gradient)
     }
 }
 
-/// The possible status of a [`Container`].
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Status {
-    /// The [`Container`] is idle.
-    Idle,
-    /// The [`Container`] is being hovered.
-    Hovered,
-}
+/// The theme catalog of a [`Container`].
+pub trait Catalog {
+    /// The item class of the [`Catalog`].
+    type Class<'a>;
 
-/// The style of a [`Container`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+    /// The default class produced by the [`Catalog`].
+    fn default<'a>() -> Self::Class<'a>;
 
-/// The default style of a [`Container`].
-pub trait DefaultStyle {
-    /// Returns the default style of a [`Container`].
-    fn default_style(&self, status: Status) -> Appearance;
+    /// The [`Style`] of a class with the given status.
+    fn style(&self, class: &Self::Class<'_>) -> Style;
 }
 
-impl DefaultStyle for Theme {
-    fn default_style(&self, status: Status) -> Appearance {
-        transparent(self, status)
-    }
-}
+/// A styling function for a [`Container`].
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
 
-impl DefaultStyle for Appearance {
-    fn default_style(&self, _status: Status) -> Appearance {
-        *self
-    }
-}
+impl Catalog for Theme {
+    type Class<'a> = StyleFn<'a, Self>;
 
-impl DefaultStyle for Color {
-    fn default_style(&self, _status: Status) -> Appearance {
-        Appearance::from(*self)
+    fn default<'a>() -> Self::Class<'a> {
+        Box::new(transparent)
     }
-}
-
-impl DefaultStyle for Gradient {
-    fn default_style(&self, _status: Status) -> Appearance {
-        Appearance::from(*self)
-    }
-}
 
-impl DefaultStyle for gradient::Linear {
-    fn default_style(&self, _status: Status) -> Appearance {
-        Appearance::from(*self)
+    fn style(&self, class: &Self::Class<'_>) -> Style {
+        class(self)
     }
 }
 
 /// A transparent [`Container`].
-pub fn transparent<Theme>(_theme: &Theme, _status: Status) -> Appearance {
-    Appearance::default()
+pub fn transparent<Theme>(_theme: &Theme) -> Style {
+    Style::default()
 }
 
 /// A rounded [`Container`] with a background.
-pub fn rounded_box(theme: &Theme, _status: Status) -> Appearance {
+pub fn rounded_box(theme: &Theme) -> Style {
     let palette = theme.extended_palette();
 
-    Appearance {
+    Style {
         background: Some(palette.background.weak.color.into()),
         border: Border::rounded(2),
-        ..Appearance::default()
+        ..Style::default()
     }
 }
 
 /// A bordered [`Container`] with a background.
-pub fn bordered_box(theme: &Theme, _status: Status) -> Appearance {
+pub fn bordered_box(theme: &Theme) -> Style {
     let palette = theme.extended_palette();
 
-    Appearance {
+    Style {
         background: Some(palette.background.weak.color.into()),
         border: Border {
             width: 1.0,
             radius: 0.0.into(),
             color: palette.background.strong.color,
         },
-        ..Appearance::default()
+        ..Style::default()
     }
 }
-- 
cgit 


From 15057a05c118dafcb8cf90d4119e66caaa6026c5 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 3 May 2024 09:11:46 +0200
Subject: Introduce `center` widget helper

... and also make `center_x` and `center_y` set
`width` and `height` to `Length::Fill`, respectively.

This targets the most common use case when centering
things and removes a bunch of boilerplate as a result.
---
 widget/src/container.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 60 insertions(+), 2 deletions(-)

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

diff --git a/widget/src/container.rs b/widget/src/container.rs
index 21405722..8b6638d4 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -92,6 +92,49 @@ where
         self
     }
 
+    /// Sets the [`Container`] to fill the available space in the horizontal axis.
+    ///
+    /// This can be useful to quickly position content when chained with
+    /// alignment functions—like [`center_x`].
+    ///
+    /// Calling this method is equivalent to calling [`width`] with a
+    /// [`Length::Fill`].
+    ///
+    /// [`center_x`]: Self::center_x
+    /// [`width`]: Self::width
+    pub fn fill_x(self) -> Self {
+        self.width(Length::Fill)
+    }
+
+    /// Sets the [`Container`] to fill the available space in the vetical axis.
+    ///
+    /// This can be useful to quickly position content when chained with
+    /// alignment functions—like [`center_y`].
+    ///
+    /// Calling this method is equivalent to calling [`height`] with a
+    /// [`Length::Fill`].
+    ///
+    /// [`center_y`]: Self::center_x
+    /// [`height`]: Self::height
+    pub fn fill_y(self) -> Self {
+        self.height(Length::Fill)
+    }
+
+    /// Sets the [`Container`] to fill all the available space.
+    ///
+    /// This can be useful to quickly position content when chained with
+    /// alignment functions—like [`center`].
+    ///
+    /// Calling this method is equivalent to chaining [`fill_x`] and
+    /// [`fill_y`].
+    ///
+    /// [`center`]: Self::center
+    /// [`fill_x`]: Self::fill_x
+    /// [`fill_y`]: Self::fill_y
+    pub fn fill(self) -> Self {
+        self.width(Length::Fill).height(Length::Fill)
+    }
+
     /// Sets the maximum width of the [`Container`].
     pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
         self.max_width = max_width.into().0;
@@ -116,18 +159,33 @@ where
         self
     }
 
-    /// Centers the contents in the horizontal axis of the [`Container`].
+    /// Sets the [`Container`] to fill the available space in the horizontal axis
+    /// and centers its contents there.
     pub fn center_x(mut self) -> Self {
+        self.width = Length::Fill;
         self.horizontal_alignment = alignment::Horizontal::Center;
         self
     }
 
-    /// Centers the contents in the vertical axis of the [`Container`].
+    /// Sets the [`Container`] to fill the available space in the vertical axis
+    /// and centers its contents there.
     pub fn center_y(mut self) -> Self {
+        self.height = Length::Fill;
         self.vertical_alignment = alignment::Vertical::Center;
         self
     }
 
+    /// Centers the contents in both the horizontal and vertical axes of the
+    /// [`Container`].
+    ///
+    /// This is equivalent to chaining [`center_x`] and [`center_y`].
+    ///
+    /// [`center_x`]: Self::center_x
+    /// [`center_y`]: Self::center_y
+    pub fn center(self) -> Self {
+        self.center_x().center_y()
+    }
+
     /// Sets whether the contents of the [`Container`] should be clipped on
     /// overflow.
     pub fn clip(mut self, clip: bool) -> Self {
-- 
cgit 


From 05f69f495e9b52e9a7d7bada420558d4c4f6730c Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Mon, 13 May 2024 17:56:02 +0200
Subject: Ask for explicit `Length` in `center_*` methods

---
 widget/src/container.rs | 27 ++++++++++-----------------
 1 file changed, 10 insertions(+), 17 deletions(-)

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

diff --git a/widget/src/container.rs b/widget/src/container.rs
index 8b6638d4..51967707 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -122,9 +122,6 @@ where
 
     /// Sets the [`Container`] to fill all the available space.
     ///
-    /// This can be useful to quickly position content when chained with
-    /// alignment functions—like [`center`].
-    ///
     /// Calling this method is equivalent to chaining [`fill_x`] and
     /// [`fill_y`].
     ///
@@ -159,20 +156,14 @@ where
         self
     }
 
-    /// Sets the [`Container`] to fill the available space in the horizontal axis
-    /// and centers its contents there.
-    pub fn center_x(mut self) -> Self {
-        self.width = Length::Fill;
-        self.horizontal_alignment = alignment::Horizontal::Center;
-        self
+    /// Sets the width of the [`Container`] and centers its contents horizontally.
+    pub fn center_x(self, width: impl Into<Length>) -> Self {
+        self.width(width).align_x(alignment::Horizontal::Center)
     }
 
-    /// Sets the [`Container`] to fill the available space in the vertical axis
-    /// and centers its contents there.
-    pub fn center_y(mut self) -> Self {
-        self.height = Length::Fill;
-        self.vertical_alignment = alignment::Vertical::Center;
-        self
+    /// Sets the height of the [`Container`] and centers its contents vertically.
+    pub fn center_y(self, height: impl Into<Length>) -> Self {
+        self.height(height).align_y(alignment::Vertical::Center)
     }
 
     /// Centers the contents in both the horizontal and vertical axes of the
@@ -182,8 +173,10 @@ where
     ///
     /// [`center_x`]: Self::center_x
     /// [`center_y`]: Self::center_y
-    pub fn center(self) -> Self {
-        self.center_x().center_y()
+    pub fn center(self, length: impl Into<Length>) -> Self {
+        let length = length.into();
+
+        self.center_x(length).center_y(length)
     }
 
     /// Sets whether the contents of the [`Container`] should be clipped on
-- 
cgit