diff options
Diffstat (limited to '')
| -rw-r--r-- | widget/src/button.rs | 143 | ||||
| -rw-r--r-- | widget/src/checkbox.rs | 99 | ||||
| -rw-r--r-- | widget/src/combo_box.rs | 130 | ||||
| -rw-r--r-- | widget/src/container.rs | 170 | ||||
| -rw-r--r-- | widget/src/helpers.rs | 39 | ||||
| -rw-r--r-- | widget/src/overlay/menu.rs | 177 | ||||
| -rw-r--r-- | widget/src/pane_grid.rs | 108 | ||||
| -rw-r--r-- | widget/src/pane_grid/content.rs | 59 | ||||
| -rw-r--r-- | widget/src/pane_grid/title_bar.rs | 64 | ||||
| -rw-r--r-- | widget/src/pick_list.rs | 106 | ||||
| -rw-r--r-- | widget/src/progress_bar.rs | 103 | ||||
| -rw-r--r-- | widget/src/qr_code.rs | 94 | ||||
| -rw-r--r-- | widget/src/radio.rs | 87 | ||||
| -rw-r--r-- | widget/src/rule.rs | 109 | ||||
| -rw-r--r-- | widget/src/scrollable.rs | 131 | ||||
| -rw-r--r-- | widget/src/slider.rs | 93 | ||||
| -rw-r--r-- | widget/src/svg.rs | 91 | ||||
| -rw-r--r-- | widget/src/text_editor.rs | 94 | ||||
| -rw-r--r-- | widget/src/text_input.rs | 106 | ||||
| -rw-r--r-- | widget/src/themer.rs | 4 | ||||
| -rw-r--r-- | widget/src/toggler.rs | 85 | ||||
| -rw-r--r-- | widget/src/tooltip.rs | 41 | ||||
| -rw-r--r-- | widget/src/vertical_slider.rs | 52 | 
23 files changed, 1238 insertions, 947 deletions
diff --git a/widget/src/button.rs b/widget/src/button.rs index 5790f811..dc949671 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -49,6 +49,7 @@ use crate::core::{  pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>  where      Renderer: crate::core::Renderer, +    Theme: Catalog,  {      content: Element<'a, Message, Theme, Renderer>,      on_press: Option<Message>, @@ -56,20 +57,18 @@ where      height: Length,      padding: Padding,      clip: bool, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer>  where      Renderer: crate::core::Renderer, +    Theme: Catalog,  {      /// Creates a new [`Button`] with the given content.      pub fn new(          content: impl Into<Element<'a, Message, Theme, Renderer>>, -    ) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    ) -> Self {          let content = content.into();          let size = content.as_widget().size_hint(); @@ -80,7 +79,7 @@ where              height: size.height.fluid(),              padding: DEFAULT_PADDING,              clip: false, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -119,21 +118,30 @@ where          self      } -    /// Sets the style variant of this [`Button`]. -    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 [`Button`] should be clipped on      /// overflow.      pub fn clip(mut self, clip: bool) -> Self {          self.clip = clip;          self      } + +    /// Sets the style of the [`Button`]. +    #[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 [`Button`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into(); +        self +    }  }  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -146,6 +154,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>  where      Message: 'a + Clone,      Renderer: 'a + crate::core::Renderer, +    Theme: Catalog,  {      fn tag(&self) -> tree::Tag {          tree::Tag::of::<State>() @@ -304,19 +313,19 @@ where              Status::Active          }; -        let styling = (self.style)(theme, status); +        let style = theme.style(&self.class, status); -        if styling.background.is_some() -            || styling.border.width > 0.0 -            || styling.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: styling.border, -                    shadow: styling.shadow, +                    border: style.border, +                    shadow: style.shadow,                  }, -                styling +                style                      .background                      .unwrap_or(Background::Color(Color::TRANSPARENT)),              ); @@ -333,7 +342,7 @@ where              renderer,              theme,              &renderer::Style { -                text_color: styling.text_color, +                text_color: style.text_color,              },              content_layout,              cursor, @@ -378,7 +387,7 @@ impl<'a, Message, Theme, Renderer> From<Button<'a, Message, Theme, Renderer>>      for Element<'a, Message, Theme, Renderer>  where      Message: Clone + 'a, -    Theme: 'a, +    Theme: Catalog + 'a,      Renderer: crate::core::Renderer + 'a,  {      fn from(button: Button<'a, Message, Theme, Renderer>) -> Self { @@ -407,9 +416,9 @@ pub enum Status {      Disabled,  } -/// The appearance of a button. +/// The style of a button.  #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { +pub struct Style {      /// The [`Background`] of the button.      pub background: Option<Background>,      /// The text [`Color`] of the button. @@ -420,8 +429,8 @@ pub struct Appearance {      pub shadow: Shadow,  } -impl Appearance { -    /// Updates the [`Appearance`] with the given [`Background`]. +impl Style { +    /// Updates the [`Style`] with the given [`Background`].      pub fn with_background(self, background: impl Into<Background>) -> Self {          Self {              background: Some(background.into()), @@ -430,7 +439,7 @@ impl Appearance {      }  } -impl std::default::Default for Appearance { +impl Default for Style {      fn default() -> Self {          Self {              background: None, @@ -441,41 +450,41 @@ impl std::default::Default for Appearance {      }  } -/// The style of a [`Button`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of a [`Button`]. +pub trait Catalog { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -/// The default style of a [`Button`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`Button`]. -    fn default_style(&self, status: Status) -> Appearance; -} +    /// The default class produced by the [`Catalog`]. +    fn default<'a>() -> Self::Class<'a>; -impl DefaultStyle for Theme { -    fn default_style(&self, status: Status) -> Appearance { -        primary(self, status) -    } +    /// The [`Style`] of a class with the given status. +    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;  } -impl DefaultStyle for Appearance { -    fn default_style(&self, _status: Status) -> Appearance { -        *self +/// A styling function for a [`Button`]. +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(primary)      } -} -impl DefaultStyle for Color { -    fn default_style(&self, _status: Status) -> Appearance { -        Appearance::default().with_background(*self) +    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { +        class(self, status)      }  }  /// A primary button; denoting a main action. -pub fn primary(theme: &Theme, status: Status) -> Appearance { +pub fn primary(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      let base = styled(palette.primary.strong);      match status {          Status::Active | Status::Pressed => base, -        Status::Hovered => Appearance { +        Status::Hovered => Style {              background: Some(Background::Color(palette.primary.base.color)),              ..base          }, @@ -484,13 +493,13 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance {  }  /// A secondary button; denoting a complementary action. -pub fn secondary(theme: &Theme, status: Status) -> Appearance { +pub fn secondary(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      let base = styled(palette.secondary.base);      match status {          Status::Active | Status::Pressed => base, -        Status::Hovered => Appearance { +        Status::Hovered => Style {              background: Some(Background::Color(palette.secondary.strong.color)),              ..base          }, @@ -499,13 +508,13 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance {  }  /// A success button; denoting a good outcome. -pub fn success(theme: &Theme, status: Status) -> Appearance { +pub fn success(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      let base = styled(palette.success.base);      match status {          Status::Active | Status::Pressed => base, -        Status::Hovered => Appearance { +        Status::Hovered => Style {              background: Some(Background::Color(palette.success.strong.color)),              ..base          }, @@ -514,13 +523,13 @@ pub fn success(theme: &Theme, status: Status) -> Appearance {  }  /// A danger button; denoting a destructive action. -pub fn danger(theme: &Theme, status: Status) -> Appearance { +pub fn danger(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      let base = styled(palette.danger.base);      match status {          Status::Active | Status::Pressed => base, -        Status::Hovered => Appearance { +        Status::Hovered => Style {              background: Some(Background::Color(palette.danger.strong.color)),              ..base          }, @@ -529,17 +538,17 @@ pub fn danger(theme: &Theme, status: Status) -> Appearance {  }  /// A text button; useful for links. -pub fn text(theme: &Theme, status: Status) -> Appearance { +pub fn text(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette(); -    let base = Appearance { +    let base = Style {          text_color: palette.background.base.text, -        ..Appearance::default() +        ..Style::default()      };      match status {          Status::Active | Status::Pressed => base, -        Status::Hovered => Appearance { +        Status::Hovered => Style {              text_color: palette.background.base.text.scale_alpha(0.8),              ..base          }, @@ -547,21 +556,21 @@ pub fn text(theme: &Theme, status: Status) -> Appearance {      }  } -fn styled(pair: palette::Pair) -> Appearance { -    Appearance { +fn styled(pair: palette::Pair) -> Style { +    Style {          background: Some(Background::Color(pair.color)),          text_color: pair.text,          border: Border::rounded(2), -        ..Appearance::default() +        ..Style::default()      }  } -fn disabled(appearance: Appearance) -> Appearance { -    Appearance { -        background: appearance +fn disabled(style: Style) -> Style { +    Style { +        background: style              .background              .map(|background| background.scale_alpha(0.5)), -        text_color: appearance.text_color.scale_alpha(0.5), -        ..appearance +        text_color: style.text_color.scale_alpha(0.5), +        ..style      }  } diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 15fb8f58..48f6abf6 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -39,6 +39,7 @@ pub struct Checkbox<      Renderer = crate::Renderer,  > where      Renderer: text::Renderer, +    Theme: Catalog,  {      is_checked: bool,      on_toggle: Option<Box<dyn Fn(bool) -> Message + 'a>>, @@ -51,12 +52,13 @@ pub struct Checkbox<      text_shaping: text::Shaping,      font: Option<Renderer::Font>,      icon: Icon<Renderer::Font>, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer>  where      Renderer: text::Renderer, +    Theme: Catalog,  {      /// The default size of a [`Checkbox`].      const DEFAULT_SIZE: f32 = 16.0; @@ -69,10 +71,7 @@ where      /// It expects:      ///   * the label of the [`Checkbox`]      ///   * a boolean describing whether the [`Checkbox`] is checked or not -    pub fn new(label: impl Into<String>, is_checked: bool) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    pub fn new(label: impl Into<String>, is_checked: bool) -> Self {          Checkbox {              is_checked,              on_toggle: None, @@ -91,7 +90,7 @@ where                  line_height: text::LineHeight::default(),                  shaping: text::Shaping::Basic,              }, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -174,11 +173,20 @@ where      }      /// Sets the style of the [`Checkbox`]. -    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 [`Checkbox`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into();          self      }  } @@ -187,6 +195,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for Checkbox<'a, Message, Theme, Renderer>  where      Renderer: text::Renderer, +    Theme: Catalog,  {      fn tag(&self) -> tree::Tag {          tree::Tag::of::<widget::text::State<Renderer::Paragraph>>() @@ -285,7 +294,7 @@ where          tree: &Tree,          renderer: &mut Renderer,          theme: &Theme, -        style: &renderer::Style, +        defaults: &renderer::Style,          layout: Layout<'_>,          cursor: mouse::Cursor,          viewport: &Rectangle, @@ -304,7 +313,7 @@ where              Status::Active { is_checked }          }; -        let appearance = (self.style)(theme, status); +        let style = theme.style(&self.class, status);          {              let layout = children.next().unwrap(); @@ -313,10 +322,10 @@ where              renderer.fill_quad(                  renderer::Quad {                      bounds, -                    border: appearance.border, +                    border: style.border,                      ..renderer::Quad::default()                  }, -                appearance.background, +                style.background,              );              let Icon { @@ -341,7 +350,7 @@ where                          shaping: *shaping,                      },                      bounds.center(), -                    appearance.icon_color, +                    style.icon_color,                      *viewport,                  );              } @@ -352,11 +361,11 @@ where              crate::text::draw(                  renderer, -                style, +                defaults,                  label_layout,                  tree.state.downcast_ref(), -                crate::text::Appearance { -                    color: appearance.text_color, +                crate::text::Style { +                    color: style.text_color,                  },                  viewport,              ); @@ -368,7 +377,7 @@ impl<'a, Message, Theme, Renderer> From<Checkbox<'a, Message, Theme, Renderer>>      for Element<'a, Message, Theme, Renderer>  where      Message: 'a, -    Theme: 'a, +    Theme: 'a + Catalog,      Renderer: 'a + text::Renderer,  {      fn from( @@ -413,9 +422,9 @@ pub enum Status {      },  } -/// The appearance of a checkbox. +/// The style of a checkbox.  #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style {      /// The [`Background`] of the checkbox.      pub background: Background,      /// The icon [`Color`] of the checkbox. @@ -426,29 +435,37 @@ pub struct Appearance {      pub text_color: Option<Color>,  } -/// The style of a [`Checkbox`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of a [`Checkbox`]. +pub trait Catalog: Sized { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -/// The default style of a [`Checkbox`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`Checkbox`]. -    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 { -        primary(self, status) +/// A styling function for a [`Checkbox`]. +/// +/// 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(primary)      } -} -impl DefaultStyle for Appearance { -    fn default_style(&self, _status: Status) -> Appearance { -        *self +    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { +        class(self, status)      }  }  /// A primary checkbox; denoting a main toggle. -pub fn primary(theme: &Theme, status: Status) -> Appearance { +pub fn primary(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      match status { @@ -474,7 +491,7 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance {  }  /// A secondary checkbox; denoting a complementary toggle. -pub fn secondary(theme: &Theme, status: Status) -> Appearance { +pub fn secondary(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      match status { @@ -500,7 +517,7 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance {  }  /// A success checkbox; denoting a positive toggle. -pub fn success(theme: &Theme, status: Status) -> Appearance { +pub fn success(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      match status { @@ -526,7 +543,7 @@ pub fn success(theme: &Theme, status: Status) -> Appearance {  }  /// A danger checkbox; denoting a negaive toggle. -pub fn danger(theme: &Theme, status: Status) -> Appearance { +pub fn danger(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      match status { @@ -556,8 +573,8 @@ fn styled(      base: palette::Pair,      accent: palette::Pair,      is_checked: bool, -) -> Appearance { -    Appearance { +) -> Style { +    Style {          background: Background::Color(if is_checked {              accent.color          } else { diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index ee24d742..e4f4a41f 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -32,6 +32,7 @@ pub struct ComboBox<      Theme = crate::Theme,      Renderer = crate::Renderer,  > where +    Theme: Catalog,      Renderer: text::Renderer,  {      state: &'a State<T>, @@ -42,7 +43,7 @@ pub struct ComboBox<      on_option_hovered: Option<Box<dyn Fn(T) -> Message>>,      on_close: Option<Message>,      on_input: Option<Box<dyn Fn(String) -> Message>>, -    menu_style: menu::Style<'a, Theme>, +    menu_class: <Theme as menu::Catalog>::Class<'a>,      padding: Padding,      size: Option<f32>,  } @@ -50,6 +51,7 @@ pub struct ComboBox<  impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer>  where      T: std::fmt::Display + Clone, +    Theme: Catalog,      Renderer: text::Renderer,  {      /// Creates a new [`ComboBox`] with the given list of options, a placeholder, @@ -60,18 +62,10 @@ where          placeholder: &str,          selection: Option<&T>,          on_selected: impl Fn(T) -> Message + 'static, -    ) -> Self -    where -        Theme: DefaultStyle + 'a, -    { -        let style = Theme::default_style(); - -        let text_input = TextInput::with_style( -            placeholder, -            &state.value(), -            style.text_input, -        ) -        .on_input(TextInputEvent::TextChanged); +    ) -> Self { +        let text_input = TextInput::new(placeholder, &state.value()) +            .on_input(TextInputEvent::TextChanged) +            .class(Theme::default_input());          let selection = selection.map(T::to_string).unwrap_or_default(); @@ -84,7 +78,7 @@ where              on_option_hovered: None,              on_input: None,              on_close: None, -            menu_style: style.menu, +            menu_class: <Theme as Catalog>::default_menu(),              padding: text_input::DEFAULT_PADDING,              size: None,          } @@ -124,18 +118,6 @@ where          self      } -    /// Sets the style of the [`ComboBox`]. -    pub fn style(mut self, style: impl Into<Style<'a, Theme>>) -> Self -    where -        Theme: 'a, -    { -        let style = style.into(); - -        self.text_input = self.text_input.style(style.text_input); -        self.menu_style = style.menu; -        self -    } -      /// Sets the [`Renderer::Font`] of the [`ComboBox`].      ///      /// [`Renderer::Font`]: text::Renderer @@ -173,6 +155,55 @@ where              ..self          }      } + +    /// Sets the style of the input of the [`ComboBox`]. +    #[must_use] +    pub fn input_style( +        mut self, +        style: impl Fn(&Theme, text_input::Status) -> text_input::Style + 'a, +    ) -> Self +    where +        <Theme as text_input::Catalog>::Class<'a>: +            From<text_input::StyleFn<'a, Theme>>, +    { +        self.text_input = self.text_input.style(style); +        self +    } + +    /// Sets the style of the menu of the [`ComboBox`]. +    #[must_use] +    pub fn menu_style( +        mut self, +        style: impl Fn(&Theme) -> menu::Style + 'a, +    ) -> Self +    where +        <Theme as menu::Catalog>::Class<'a>: From<menu::StyleFn<'a, Theme>>, +    { +        self.menu_class = (Box::new(style) as menu::StyleFn<'a, Theme>).into(); +        self +    } + +    /// Sets the style class of the input of the [`ComboBox`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn input_class( +        mut self, +        class: impl Into<<Theme as text_input::Catalog>::Class<'a>>, +    ) -> Self { +        self.text_input = self.text_input.class(class); +        self +    } + +    /// Sets the style class of the menu of the [`ComboBox`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn menu_class( +        mut self, +        class: impl Into<<Theme as menu::Catalog>::Class<'a>>, +    ) -> Self { +        self.menu_class = class.into(); +        self +    }  }  /// The local state of a [`ComboBox`]. @@ -296,6 +327,7 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>  where      T: Display + Clone + 'static,      Message: Clone, +    Theme: Catalog,      Renderer: text::Renderer,  {      fn size(&self) -> Size<Length> { @@ -686,7 +718,7 @@ where                      (self.on_selected)(x)                  },                  self.on_option_hovered.as_deref(), -                &self.menu_style, +                &self.menu_class,              )              .width(bounds.width)              .padding(self.padding); @@ -712,7 +744,7 @@ impl<'a, T, Message, Theme, Renderer>  where      T: Display + Clone + 'static,      Message: Clone + 'a, -    Theme: 'a, +    Theme: Catalog + 'a,      Renderer: text::Renderer + 'a,  {      fn from(combo_box: ComboBox<'a, T, Message, Theme, Renderer>) -> Self { @@ -720,6 +752,21 @@ where      }  } +/// The theme catalog of a [`ComboBox`]. +pub trait Catalog: text_input::Catalog + menu::Catalog { +    /// The default class for the text input of the [`ComboBox`]. +    fn default_input<'a>() -> <Self as text_input::Catalog>::Class<'a> { +        <Self as text_input::Catalog>::default() +    } + +    /// The default class for the menu of the [`ComboBox`]. +    fn default_menu<'a>() -> <Self as menu::Catalog>::Class<'a> { +        <Self as menu::Catalog>::default() +    } +} + +impl Catalog for Theme {} +  fn search<'a, T, A>(      options: impl IntoIterator<Item = T> + 'a,      option_matchers: impl IntoIterator<Item = &'a A> + 'a, @@ -762,30 +809,3 @@ where          })          .collect()  } - -/// The style of a [`ComboBox`]. -#[allow(missing_debug_implementations)] -pub struct Style<'a, Theme> { -    /// The style of the [`TextInput`] of the [`ComboBox`]. -    pub text_input: text_input::Style<'a, Theme>, - -    /// The style of the [`Menu`] of the [`ComboBox`]. -    /// -    /// [`Menu`]: menu::Menu -    pub menu: menu::Style<'a, Theme>, -} - -/// The default style of a [`ComboBox`]. -pub trait DefaultStyle: Sized { -    /// Returns the default style of a [`ComboBox`]. -    fn default_style() -> Style<'static, Self>; -} - -impl DefaultStyle for Theme { -    fn default_style() -> Style<'static, Self> { -        Style { -            text_input: Box::new(text_input::default), -            menu: menu::DefaultStyle::default_style(), -        } -    } -} 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()      }  } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 4863e550..77b30882 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -7,6 +7,7 @@ use crate::core;  use crate::core::widget::operation;  use crate::core::{Element, Length, Pixels};  use crate::keyed; +use crate::overlay;  use crate::pick_list::{self, PickList};  use crate::progress_bar::{self, ProgressBar};  use crate::radio::{self, Radio}; @@ -58,7 +59,7 @@ pub fn container<'a, Message, Theme, Renderer>(      content: impl Into<Element<'a, Message, Theme, Renderer>>,  ) -> Container<'a, Message, Theme, Renderer>  where -    Theme: container::DefaultStyle + 'a, +    Theme: container::Catalog + 'a,      Renderer: core::Renderer,  {      Container::new(content) @@ -104,7 +105,7 @@ pub fn scrollable<'a, Message, Theme, Renderer>(      content: impl Into<Element<'a, Message, Theme, Renderer>>,  ) -> Scrollable<'a, Message, Theme, Renderer>  where -    Theme: scrollable::DefaultStyle + 'a, +    Theme: scrollable::Catalog + 'a,      Renderer: core::Renderer,  {      Scrollable::new(content) @@ -117,7 +118,7 @@ pub fn button<'a, Message, Theme, Renderer>(      content: impl Into<Element<'a, Message, Theme, Renderer>>,  ) -> Button<'a, Message, Theme, Renderer>  where -    Theme: button::DefaultStyle + 'a, +    Theme: button::Catalog + 'a,      Renderer: core::Renderer,  {      Button::new(content) @@ -134,7 +135,7 @@ pub fn tooltip<'a, Message, Theme, Renderer>(      position: tooltip::Position,  ) -> crate::Tooltip<'a, Message, Theme, Renderer>  where -    Theme: container::DefaultStyle + 'a, +    Theme: container::Catalog + 'a,      Renderer: core::text::Renderer,  {      Tooltip::new(content, tooltip, position) @@ -147,7 +148,7 @@ pub fn text<'a, Theme, Renderer>(      text: impl ToString,  ) -> Text<'a, Theme, Renderer>  where -    Theme: text::DefaultStyle + 'a, +    Theme: text::Catalog + 'a,      Renderer: core::text::Renderer,  {      Text::new(text.to_string()) @@ -161,7 +162,7 @@ pub fn checkbox<'a, Message, Theme, Renderer>(      is_checked: bool,  ) -> Checkbox<'a, Message, Theme, Renderer>  where -    Theme: checkbox::DefaultStyle + 'a, +    Theme: checkbox::Catalog + 'a,      Renderer: core::text::Renderer,  {      Checkbox::new(label, is_checked) @@ -178,7 +179,7 @@ pub fn radio<'a, Message, Theme, Renderer, V>(  ) -> Radio<'a, Message, Theme, Renderer>  where      Message: Clone, -    Theme: radio::DefaultStyle + 'a, +    Theme: radio::Catalog + 'a,      Renderer: core::text::Renderer,      V: Copy + Eq,  { @@ -194,7 +195,7 @@ pub fn toggler<'a, Message, Theme, Renderer>(      f: impl Fn(bool) -> Message + 'a,  ) -> Toggler<'a, Message, Theme, Renderer>  where -    Theme: toggler::DefaultStyle + 'a, +    Theme: toggler::Catalog + 'a,      Renderer: core::text::Renderer,  {      Toggler::new(label, is_checked, f) @@ -209,7 +210,7 @@ pub fn text_input<'a, Message, Theme, Renderer>(  ) -> TextInput<'a, Message, Theme, Renderer>  where      Message: Clone, -    Theme: text_input::DefaultStyle + 'a, +    Theme: text_input::Catalog + 'a,      Renderer: core::text::Renderer,  {      TextInput::new(placeholder, value) @@ -223,7 +224,7 @@ pub fn text_editor<'a, Message, Theme, Renderer>(  ) -> TextEditor<'a, core::text::highlighter::PlainText, Message, Theme, Renderer>  where      Message: Clone, -    Theme: text_editor::DefaultStyle + 'a, +    Theme: text_editor::Catalog + 'a,      Renderer: core::text::Renderer,  {      TextEditor::new(content) @@ -240,7 +241,7 @@ pub fn slider<'a, T, Message, Theme>(  where      T: Copy + From<u8> + std::cmp::PartialOrd,      Message: Clone, -    Theme: slider::DefaultStyle + 'a, +    Theme: slider::Catalog + 'a,  {      Slider::new(range, value, on_change)  } @@ -256,7 +257,7 @@ pub fn vertical_slider<'a, T, Message, Theme>(  where      T: Copy + From<u8> + std::cmp::PartialOrd,      Message: Clone, -    Theme: vertical_slider::DefaultStyle + 'a, +    Theme: vertical_slider::Catalog + 'a,  {      VerticalSlider::new(range, value, on_change)  } @@ -274,7 +275,7 @@ where      L: Borrow<[T]> + 'a,      V: Borrow<T> + 'a,      Message: Clone, -    Theme: pick_list::DefaultStyle, +    Theme: pick_list::Catalog + overlay::menu::Catalog,      Renderer: core::text::Renderer,  {      PickList::new(options, selected, on_selected) @@ -291,7 +292,7 @@ pub fn combo_box<'a, T, Message, Theme, Renderer>(  ) -> ComboBox<'a, T, Message, Theme, Renderer>  where      T: std::fmt::Display + Clone, -    Theme: combo_box::DefaultStyle + 'a, +    Theme: combo_box::Catalog + 'a,      Renderer: core::text::Renderer,  {      ComboBox::new(state, placeholder, selection, on_selected) @@ -318,7 +319,7 @@ pub fn vertical_space() -> Space {  /// [`Rule`]: crate::Rule  pub fn horizontal_rule<'a, Theme>(height: impl Into<Pixels>) -> Rule<'a, Theme>  where -    Theme: rule::DefaultStyle + 'a, +    Theme: rule::Catalog + 'a,  {      Rule::horizontal(height)  } @@ -328,7 +329,7 @@ where  /// [`Rule`]: crate::Rule  pub fn vertical_rule<'a, Theme>(width: impl Into<Pixels>) -> Rule<'a, Theme>  where -    Theme: rule::DefaultStyle + 'a, +    Theme: rule::Catalog + 'a,  {      Rule::vertical(width)  } @@ -345,7 +346,7 @@ pub fn progress_bar<'a, Theme>(      value: f32,  ) -> ProgressBar<'a, Theme>  where -    Theme: progress_bar::DefaultStyle + 'a, +    Theme: progress_bar::Catalog + 'a,  {      ProgressBar::new(range, value)  } @@ -367,7 +368,7 @@ pub fn svg<'a, Theme>(      handle: impl Into<core::svg::Handle>,  ) -> crate::Svg<'a, Theme>  where -    Theme: crate::svg::DefaultStyle + 'a, +    Theme: crate::svg::Catalog,  {      crate::Svg::new(handle)  } @@ -395,7 +396,7 @@ pub fn qr_code<'a, Theme>(      data: &'a crate::qr_code::Data,  ) -> crate::QRCode<'a, Theme>  where -    Theme: crate::qr_code::DefaultStyle + 'a, +    Theme: crate::qr_code::Catalog + 'a,  {      crate::QRCode::new(data)  } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 0364f980..d76caa8a 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -1,5 +1,4 @@  //! Build and show dropdown menus. -use crate::container::{self, Container};  use crate::core::alignment;  use crate::core::event::{self, Event};  use crate::core::layout::{self, Layout}; @@ -20,12 +19,15 @@ use crate::scrollable::{self, Scrollable};  #[allow(missing_debug_implementations)]  pub struct Menu<      'a, +    'b,      T,      Message,      Theme = crate::Theme,      Renderer = crate::Renderer,  > where +    Theme: Catalog,      Renderer: text::Renderer, +    'b: 'a,  {      state: &'a mut State,      options: &'a [T], @@ -38,15 +40,17 @@ pub struct Menu<      text_line_height: text::LineHeight,      text_shaping: text::Shaping,      font: Option<Renderer::Font>, -    style: &'a Style<'a, Theme>, +    class: &'a <Theme as Catalog>::Class<'b>,  } -impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer> +impl<'a, 'b, T, Message, Theme, Renderer> +    Menu<'a, 'b, T, Message, Theme, Renderer>  where      T: ToString + Clone,      Message: 'a, -    Theme: 'a, +    Theme: Catalog + 'a,      Renderer: text::Renderer + 'a, +    'b: 'a,  {      /// Creates a new [`Menu`] with the given [`State`], a list of options,      /// the message to produced when an option is selected, and its [`Style`]. @@ -56,7 +60,7 @@ where          hovered_option: &'a mut Option<usize>,          on_selected: impl FnMut(T) -> Message + 'a,          on_option_hovered: Option<&'a dyn Fn(T) -> Message>, -        style: &'a Style<'a, Theme>, +        class: &'a <Theme as Catalog>::Class<'b>,      ) -> Self {          Menu {              state, @@ -70,7 +74,7 @@ where              text_line_height: text::LineHeight::default(),              text_shaping: text::Shaping::Basic,              font: None, -            style, +            class,          }      } @@ -153,27 +157,29 @@ impl Default for State {      }  } -struct Overlay<'a, Message, Theme, Renderer> +struct Overlay<'a, 'b, Message, Theme, Renderer>  where +    Theme: Catalog,      Renderer: crate::core::Renderer,  {      position: Point,      state: &'a mut Tree, -    container: Container<'a, Message, Theme, Renderer>, +    list: Scrollable<'a, Message, Theme, Renderer>,      width: f32,      target_height: f32, -    style: &'a Style<'a, Theme>, +    class: &'a <Theme as Catalog>::Class<'b>,  } -impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> +impl<'a, 'b, Message, Theme, Renderer> Overlay<'a, 'b, Message, Theme, Renderer>  where      Message: 'a, -    Theme: 'a, +    Theme: Catalog + scrollable::Catalog + 'a,      Renderer: text::Renderer + 'a, +    'b: 'a,  {      pub fn new<T>(          position: Point, -        menu: Menu<'a, T, Message, Theme, Renderer>, +        menu: Menu<'a, 'b, T, Message, Theme, Renderer>,          target_height: f32,      ) -> Self      where @@ -191,46 +197,43 @@ where              text_size,              text_line_height,              text_shaping, -            style, +            class,          } = menu; -        let container = Container::with_style( -            Scrollable::with_direction_and_style( -                List { -                    options, -                    hovered_option, -                    on_selected, -                    on_option_hovered, -                    font, -                    text_size, -                    text_line_height, -                    text_shaping, -                    padding, -                    style: &style.list, -                }, -                scrollable::Direction::default(), -                &style.scrollable, -            ), -            container::transparent, +        let list = Scrollable::with_direction( +            List { +                options, +                hovered_option, +                on_selected, +                on_option_hovered, +                font, +                text_size, +                text_line_height, +                text_shaping, +                padding, +                class, +            }, +            scrollable::Direction::default(),          ); -        state.tree.diff(&container as &dyn Widget<_, _, _>); +        state.tree.diff(&list as &dyn Widget<_, _, _>);          Self {              position,              state: &mut state.tree, -            container, +            list,              width,              target_height, -            style, +            class,          }      }  } -impl<'a, Message, Theme, Renderer> +impl<'a, 'b, Message, Theme, Renderer>      crate::core::Overlay<Message, Theme, Renderer> -    for Overlay<'a, Message, Theme, Renderer> +    for Overlay<'a, 'b, Message, Theme, Renderer>  where +    Theme: Catalog,      Renderer: text::Renderer,  {      fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { @@ -251,7 +254,7 @@ where          )          .width(self.width); -        let node = self.container.layout(self.state, renderer, &limits); +        let node = self.list.layout(self.state, renderer, &limits);          let size = node.size();          node.move_to(if space_below > space_above { @@ -272,7 +275,7 @@ where      ) -> event::Status {          let bounds = layout.bounds(); -        self.container.on_event( +        self.list.on_event(              self.state, event, layout, cursor, renderer, clipboard, shell,              &bounds,          ) @@ -285,7 +288,7 @@ where          viewport: &Rectangle,          renderer: &Renderer,      ) -> mouse::Interaction { -        self.container +        self.list              .mouse_interaction(self.state, layout, cursor, viewport, renderer)      } @@ -293,30 +296,32 @@ where          &self,          renderer: &mut Renderer,          theme: &Theme, -        style: &renderer::Style, +        defaults: &renderer::Style,          layout: Layout<'_>,          cursor: mouse::Cursor,      ) {          let bounds = layout.bounds(); -        let appearance = (self.style.list)(theme); +        let style = Catalog::style(theme, self.class);          renderer.fill_quad(              renderer::Quad {                  bounds, -                border: appearance.border, +                border: style.border,                  ..renderer::Quad::default()              }, -            appearance.background, +            style.background,          ); -        self.container -            .draw(self.state, renderer, theme, style, layout, cursor, &bounds); +        self.list.draw( +            self.state, renderer, theme, defaults, layout, cursor, &bounds, +        );      }  } -struct List<'a, T, Message, Theme, Renderer> +struct List<'a, 'b, T, Message, Theme, Renderer>  where +    Theme: Catalog,      Renderer: text::Renderer,  {      options: &'a [T], @@ -328,13 +333,14 @@ where      text_line_height: text::LineHeight,      text_shaping: text::Shaping,      font: Option<Renderer::Font>, -    style: &'a dyn Fn(&Theme) -> Appearance, +    class: &'a <Theme as Catalog>::Class<'b>,  } -impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> -    for List<'a, T, Message, Theme, Renderer> +impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> +    for List<'a, 'b, T, Message, Theme, Renderer>  where      T: Clone + ToString, +    Theme: Catalog,      Renderer: text::Renderer,  {      fn size(&self) -> Size<Length> { @@ -477,7 +483,7 @@ where          _cursor: mouse::Cursor,          viewport: &Rectangle,      ) { -        let appearance = (self.style)(theme); +        let style = Catalog::style(theme, self.class);          let bounds = layout.bounds();          let text_size = @@ -507,14 +513,14 @@ where                  renderer.fill_quad(                      renderer::Quad {                          bounds: Rectangle { -                            x: bounds.x + appearance.border.width, -                            width: bounds.width - appearance.border.width * 2.0, +                            x: bounds.x + style.border.width, +                            width: bounds.width - style.border.width * 2.0,                              ..bounds                          }, -                        border: Border::rounded(appearance.border.radius), +                        border: Border::rounded(style.border.radius),                          ..renderer::Quad::default()                      }, -                    appearance.selected_background, +                    style.selected_background,                  );              } @@ -531,9 +537,9 @@ where                  },                  Point::new(bounds.x + self.padding.left, bounds.center_y()),                  if is_selected { -                    appearance.selected_text_color +                    style.selected_text_color                  } else { -                    appearance.text_color +                    style.text_color                  },                  *viewport,              ); @@ -541,23 +547,24 @@ where      }  } -impl<'a, T, Message, Theme, Renderer> -    From<List<'a, T, Message, Theme, Renderer>> +impl<'a, 'b, T, Message, Theme, Renderer> +    From<List<'a, 'b, T, Message, Theme, Renderer>>      for Element<'a, Message, Theme, Renderer>  where      T: ToString + Clone,      Message: 'a, -    Theme: 'a, +    Theme: 'a + Catalog,      Renderer: 'a + text::Renderer, +    'b: 'a,  { -    fn from(list: List<'a, T, Message, Theme, Renderer>) -> Self { +    fn from(list: List<'a, 'b, T, Message, Theme, Renderer>) -> Self {          Element::new(list)      }  }  /// The appearance of a [`Menu`].  #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style {      /// The [`Background`] of the menu.      pub background: Background,      /// The [`Border`] of the menu. @@ -570,35 +577,43 @@ pub struct Appearance {      pub selected_background: Background,  } -/// The style of the different parts of a [`Menu`]. -#[allow(missing_debug_implementations)] -pub struct Style<'a, Theme> { -    /// The style of the list of the [`Menu`]. -    pub list: Box<dyn Fn(&Theme) -> Appearance + 'a>, -    /// The style of the [`Scrollable`] of the [`Menu`]. -    pub scrollable: scrollable::Style<'a, Theme>, -} +/// The theme catalog of a [`Menu`]. +pub trait Catalog: scrollable::Catalog { +    /// The item class of the [`Catalog`]. +    type Class<'a>; + +    /// The default class produced by the [`Catalog`]. +    fn default<'a>() -> <Self as Catalog>::Class<'a>; -/// The default style of a [`Menu`]. -pub trait DefaultStyle: Sized { -    /// Returns the default style of a [`Menu`]. -    fn default_style() -> Style<'static, Self>; +    /// The default class for the scrollable of the [`Menu`]. +    fn default_scrollable<'a>() -> <Self as scrollable::Catalog>::Class<'a> { +        <Self as scrollable::Catalog>::default() +    } + +    /// The [`Style`] of a class with the given status. +    fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style;  } -impl DefaultStyle for Theme { -    fn default_style() -> Style<'static, Self> { -        Style { -            list: Box::new(default), -            scrollable: Box::new(scrollable::default), -        } +/// A styling function for a [`Menu`]. +pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>; + +impl Catalog for Theme { +    type Class<'a> = StyleFn<'a, Self>; + +    fn default<'a>() -> StyleFn<'a, Self> { +        Box::new(default) +    } + +    fn style(&self, class: &StyleFn<'_, Self>) -> Style { +        class(self)      }  }  /// The default style of the list of a [`Menu`]. -pub fn default(theme: &Theme) -> Appearance { +pub fn default(theme: &Theme) -> Style {      let palette = theme.extended_palette(); -    Appearance { +    Style {          background: palette.background.weak.color.into(),          border: Border {              width: 1.0, diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index beac0bd8..acfa9d44 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -30,6 +30,7 @@ pub use split::Split;  pub use state::State;  pub use title_bar::TitleBar; +use crate::container;  use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse; @@ -39,8 +40,8 @@ use crate::core::touch;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree};  use crate::core::{ -    Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, -    Point, Rectangle, Shell, Size, Theme, Vector, Widget, +    self, Background, Border, Clipboard, Color, Element, Layout, Length, +    Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,  };  const DRAG_DEADBAND_DISTANCE: f32 = 10.0; @@ -101,7 +102,8 @@ pub struct PaneGrid<      Theme = crate::Theme,      Renderer = crate::Renderer,  > where -    Renderer: crate::core::Renderer, +    Theme: Catalog, +    Renderer: core::Renderer,  {      contents: Contents<'a, Content<'a, Message, Theme, Renderer>>,      width: Length, @@ -110,12 +112,13 @@ pub struct PaneGrid<      on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,      on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,      on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, -    style: Style<'a, Theme>, +    class: <Theme as Catalog>::Class<'a>,  }  impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer>  where -    Renderer: crate::core::Renderer, +    Theme: Catalog, +    Renderer: core::Renderer,  {      /// Creates a [`PaneGrid`] with the given [`State`] and view function.      /// @@ -124,10 +127,7 @@ where      pub fn new<T>(          state: &'a State<T>,          view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>, -    ) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    ) -> Self {          let contents = if let Some((pane, pane_state)) =              state.maximized.and_then(|pane| {                  state.panes.get(&pane).map(|pane_state| (pane, pane_state)) @@ -158,7 +158,7 @@ where              on_click: None,              on_drag: None,              on_resize: None, -            style: Box::new(Theme::default_style), +            class: <Theme as Catalog>::default(),          }      } @@ -218,8 +218,23 @@ where      }      /// Sets the style of the [`PaneGrid`]. -    pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self { -        self.style = Box::new(style); +    #[must_use] +    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self +    where +        <Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme>>, +    { +        self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); +        self +    } + +    /// Sets the style class of the [`PaneGrid`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class( +        mut self, +        class: impl Into<<Theme as Catalog>::Class<'a>>, +    ) -> Self { +        self.class = class.into();          self      } @@ -233,7 +248,8 @@ where  impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for PaneGrid<'a, Message, Theme, Renderer>  where -    Renderer: crate::core::Renderer, +    Theme: Catalog, +    Renderer: core::Renderer,  {      fn tag(&self) -> tree::Tag {          tree::Tag::of::<state::Action>() @@ -596,7 +612,7 @@ where          tree: &Tree,          renderer: &mut Renderer,          theme: &Theme, -        style: &renderer::Style, +        defaults: &renderer::Style,          layout: Layout<'_>,          cursor: mouse::Cursor,          viewport: &Rectangle, @@ -677,7 +693,7 @@ where              None          }; -        let appearance = (self.style)(theme); +        let style = Catalog::style(theme, &self.class);          for ((id, (content, tree)), pane_layout) in              contents.zip(layout.children()) @@ -692,7 +708,7 @@ where                          tree,                          renderer,                          theme, -                        style, +                        defaults,                          pane_layout,                          pane_cursor,                          viewport, @@ -710,10 +726,10 @@ where                              renderer.fill_quad(                                  renderer::Quad {                                      bounds, -                                    border: appearance.hovered_region.border, +                                    border: style.hovered_region.border,                                      ..renderer::Quad::default()                                  }, -                                appearance.hovered_region.background, +                                style.hovered_region.background,                              );                          }                      } @@ -723,7 +739,7 @@ where                          tree,                          renderer,                          theme, -                        style, +                        defaults,                          pane_layout,                          pane_cursor,                          viewport, @@ -738,10 +754,10 @@ where              renderer.fill_quad(                  renderer::Quad {                      bounds, -                    border: appearance.hovered_region.border, +                    border: style.hovered_region.border,                      ..renderer::Quad::default()                  }, -                appearance.hovered_region.background, +                style.hovered_region.background,              );          } @@ -759,7 +775,7 @@ where                              tree,                              renderer,                              theme, -                            style, +                            defaults,                              layout,                              pane_cursor,                              viewport, @@ -772,9 +788,9 @@ where          if picked_pane.is_none() {              if let Some((axis, split_region, is_picked)) = picked_split {                  let highlight = if is_picked { -                    appearance.picked_split +                    style.picked_split                  } else { -                    appearance.hovered_split +                    style.hovered_split                  };                  renderer.fill_quad( @@ -832,8 +848,8 @@ impl<'a, Message, Theme, Renderer> From<PaneGrid<'a, Message, Theme, Renderer>>      for Element<'a, Message, Theme, Renderer>  where      Message: 'a, -    Theme: 'a, -    Renderer: crate::core::Renderer + 'a, +    Theme: Catalog + 'a, +    Renderer: core::Renderer + 'a,  {      fn from(          pane_grid: PaneGrid<'a, Message, Theme, Renderer>, @@ -1116,7 +1132,7 @@ impl<'a, T> Contents<'a, T> {  /// The appearance of a [`PaneGrid`].  #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { +pub struct Style {      /// The appearance of a hovered region highlight.      pub hovered_region: Highlight,      /// The appearance of a picked split. @@ -1145,32 +1161,40 @@ pub struct Line {      pub width: f32,  } -/// The style of a [`PaneGrid`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>; +/// The theme catalog of a [`PaneGrid`]. +pub trait Catalog: container::Catalog { +    /// The item class of this [`Catalog`]. +    type Class<'a>; -/// The default style of a [`PaneGrid`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`PaneGrid`]. -    fn default_style(&self) -> Appearance; +    /// The default class produced by this [`Catalog`]. +    fn default<'a>() -> <Self as Catalog>::Class<'a>; + +    /// The [`Style`] of a class with the given status. +    fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style;  } -impl DefaultStyle for Theme { -    fn default_style(&self) -> Appearance { -        default(self) +/// A styling function for a [`PaneGrid`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>; + +impl Catalog for Theme { +    type Class<'a> = StyleFn<'a, Self>; + +    fn default<'a>() -> StyleFn<'a, Self> { +        Box::new(default)      } -} -impl DefaultStyle for Appearance { -    fn default_style(&self) -> Appearance { -        *self +    fn style(&self, class: &StyleFn<'_, Self>) -> Style { +        class(self)      }  }  /// The default style of a [`PaneGrid`]. -pub fn default(theme: &Theme) -> Appearance { +pub fn default(theme: &Theme) -> Style {      let palette = theme.extended_palette(); -    Appearance { +    Style {          hovered_region: Highlight {              background: Background::Color(Color {                  a: 0.5, diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 98f4f99a..30ad52ca 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -6,7 +6,7 @@ use crate::core::overlay;  use crate::core::renderer;  use crate::core::widget::{self, Tree};  use crate::core::{ -    Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector, +    self, Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector,  };  use crate::pane_grid::{Draggable, TitleBar}; @@ -20,30 +20,29 @@ pub struct Content<      Theme = crate::Theme,      Renderer = crate::Renderer,  > where -    Renderer: crate::core::Renderer, +    Theme: container::Catalog, +    Renderer: core::Renderer,  {      title_bar: Option<TitleBar<'a, Message, Theme, Renderer>>,      body: Element<'a, Message, Theme, Renderer>, -    style: container::Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>  where -    Renderer: crate::core::Renderer, +    Theme: container::Catalog, +    Renderer: core::Renderer,  {      /// Creates a new [`Content`] with the provided body. -    pub fn new(body: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self -    where -        Theme: container::DefaultStyle + 'a, -    { +    pub fn new(body: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {          Self {              title_bar: None,              body: body.into(), -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } -    /// Sets the [`TitleBar`] of this [`Content`]. +    /// Sets the [`TitleBar`] of the [`Content`].      pub fn title_bar(          mut self,          title_bar: TitleBar<'a, Message, Theme, Renderer>, @@ -53,18 +52,31 @@ where      }      /// Sets the style of the [`Content`]. +    #[must_use]      pub fn style(          mut self, -        style: impl Fn(&Theme, container::Status) -> container::Appearance + 'a, -    ) -> Self { -        self.style = Box::new(style); +        style: impl Fn(&Theme) -> container::Style + 'a, +    ) -> Self +    where +        Theme::Class<'a>: From<container::StyleFn<'a, Theme>>, +    { +        self.class = (Box::new(style) as container::StyleFn<'a, Theme>).into(); +        self +    } + +    /// Sets the style class of the [`Content`]. +    #[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> Content<'a, Message, Theme, Renderer>  where -    Renderer: crate::core::Renderer, +    Theme: container::Catalog, +    Renderer: core::Renderer,  {      pub(super) fn state(&self) -> Tree {          let children = if let Some(title_bar) = self.title_bar.as_ref() { @@ -93,7 +105,7 @@ where      /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].      /// -    /// [`Renderer`]: crate::core::Renderer +    /// [`Renderer`]: core::Renderer      pub fn draw(          &self,          tree: &Tree, @@ -107,15 +119,7 @@ where          let bounds = layout.bounds();          { -            let style = { -                let status = if cursor.is_over(bounds) { -                    container::Status::Hovered -                } else { -                    container::Status::Idle -                }; - -                (self.style)(theme, status) -            }; +            let style = theme.style(&self.class);              container::draw_background(renderer, &style, bounds);          } @@ -381,7 +385,8 @@ where  impl<'a, Message, Theme, Renderer> Draggable      for &Content<'a, Message, Theme, Renderer>  where -    Renderer: crate::core::Renderer, +    Theme: container::Catalog, +    Renderer: core::Renderer,  {      fn can_be_dragged_at(          &self, @@ -403,8 +408,8 @@ impl<'a, T, Message, Theme, Renderer> From<T>      for Content<'a, Message, Theme, Renderer>  where      T: Into<Element<'a, Message, Theme, Renderer>>, -    Theme: container::DefaultStyle + 'a, -    Renderer: crate::core::Renderer, +    Theme: container::Catalog + 'a, +    Renderer: core::Renderer,  {      fn from(element: T) -> Self {          Self::new(element) diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 8dfea6e3..c2eeebb7 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -6,7 +6,8 @@ use crate::core::overlay;  use crate::core::renderer;  use crate::core::widget::{self, Tree};  use crate::core::{ -    Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, Vector, +    self, Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, +    Vector,  };  /// The title bar of a [`Pane`]. @@ -19,32 +20,31 @@ pub struct TitleBar<      Theme = crate::Theme,      Renderer = crate::Renderer,  > where -    Renderer: crate::core::Renderer, +    Theme: container::Catalog, +    Renderer: core::Renderer,  {      content: Element<'a, Message, Theme, Renderer>,      controls: Option<Element<'a, Message, Theme, Renderer>>,      padding: Padding,      always_show_controls: bool, -    style: container::Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer>  where -    Renderer: crate::core::Renderer, +    Theme: container::Catalog, +    Renderer: core::Renderer,  {      /// Creates a new [`TitleBar`] with the given content.      pub fn new(          content: impl Into<Element<'a, Message, Theme, Renderer>>, -    ) -> Self -    where -        Theme: container::DefaultStyle + 'a, -    { +    ) -> Self {          Self {              content: content.into(),              controls: None,              padding: Padding::ZERO,              always_show_controls: false, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -63,15 +63,6 @@ where          self      } -    /// Sets the style of the [`TitleBar`]. -    pub fn style( -        mut self, -        style: impl Fn(&Theme, container::Status) -> container::Appearance + 'a, -    ) -> Self { -        self.style = Box::new(style); -        self -    } -      /// Sets whether or not the [`controls`] attached to this [`TitleBar`] are      /// always visible.      /// @@ -84,11 +75,33 @@ where          self.always_show_controls = true;          self      } + +    /// Sets the style of the [`TitleBar`]. +    #[must_use] +    pub fn style( +        mut self, +        style: impl Fn(&Theme) -> container::Style + 'a, +    ) -> Self +    where +        Theme::Class<'a>: From<container::StyleFn<'a, Theme>>, +    { +        self.class = (Box::new(style) as container::StyleFn<'a, Theme>).into(); +        self +    } + +    /// Sets the style class of the [`TitleBar`]. +    #[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> TitleBar<'a, Message, Theme, Renderer>  where -    Renderer: crate::core::Renderer, +    Theme: container::Catalog, +    Renderer: core::Renderer,  {      pub(super) fn state(&self) -> Tree {          let children = if let Some(controls) = self.controls.as_ref() { @@ -117,7 +130,7 @@ where      /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].      /// -    /// [`Renderer`]: crate::core::Renderer +    /// [`Renderer`]: core::Renderer      pub fn draw(          &self,          tree: &Tree, @@ -130,16 +143,7 @@ where          show_controls: bool,      ) {          let bounds = layout.bounds(); - -        let style = { -            let status = if cursor.is_over(bounds) { -                container::Status::Hovered -            } else { -                container::Status::Idle -            }; - -            (self.style)(theme, status) -        }; +        let style = theme.style(&self.class);          let inherited_style = renderer::Style {              text_color: style.text_color.unwrap_or(inherited_style.text_color), diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 52d54397..801e792b 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -32,6 +32,7 @@ pub struct PickList<      T: ToString + PartialEq + Clone,      L: Borrow<[T]> + 'a,      V: Borrow<T> + 'a, +    Theme: Catalog,      Renderer: text::Renderer,  {      on_select: Box<dyn Fn(T) -> Message + 'a>, @@ -47,7 +48,8 @@ pub struct PickList<      text_shaping: text::Shaping,      font: Option<Renderer::Font>,      handle: Handle<Renderer::Font>, -    style: Style<'a, Theme>, +    class: <Theme as Catalog>::Class<'a>, +    menu_class: <Theme as menu::Catalog>::Class<'a>,  }  impl<'a, T, L, V, Message, Theme, Renderer> @@ -57,6 +59,7 @@ where      L: Borrow<[T]> + 'a,      V: Borrow<T> + 'a,      Message: Clone, +    Theme: Catalog,      Renderer: text::Renderer,  {      /// Creates a new [`PickList`] with the given list of options, the current @@ -65,10 +68,7 @@ where          options: L,          selected: Option<V>,          on_select: impl Fn(T) -> Message + 'a, -    ) -> Self -    where -        Theme: DefaultStyle, -    { +    ) -> Self {          Self {              on_select: Box::new(on_select),              on_open: None, @@ -83,7 +83,8 @@ where              text_shaping: text::Shaping::Basic,              font: None,              handle: Handle::default(), -            style: Theme::default_style(), +            class: <Theme as Catalog>::default(), +            menu_class: <Theme as Catalog>::default_menu(),          }      } @@ -151,8 +152,23 @@ where      }      /// Sets the style of the [`PickList`]. -    pub fn style(mut self, style: impl Into<Style<'a, Theme>>) -> Self { -        self.style = style.into(); +    #[must_use] +    pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self +    where +        <Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme>>, +    { +        self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); +        self +    } + +    /// Sets the style class of the [`PickList`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class( +        mut self, +        class: impl Into<<Theme as Catalog>::Class<'a>>, +    ) -> Self { +        self.class = class.into();          self      }  } @@ -164,6 +180,7 @@ where      L: Borrow<[T]>,      V: Borrow<T>,      Message: Clone + 'a, +    Theme: Catalog + 'a,      Renderer: text::Renderer + 'a,  {      fn tag(&self) -> tree::Tag { @@ -409,15 +426,15 @@ where              Status::Active          }; -        let appearance = (self.style.field)(theme, status); +        let style = Catalog::style(theme, &self.class, status);          renderer.fill_quad(              renderer::Quad {                  bounds, -                border: appearance.border, +                border: style.border,                  ..renderer::Quad::default()              }, -            appearance.background, +            style.background,          );          let handle = match &self.handle { @@ -478,7 +495,7 @@ where                      bounds.x + bounds.width - self.padding.right,                      bounds.center_y(),                  ), -                appearance.handle_color, +                style.handle_color,                  *viewport,              );          } @@ -505,9 +522,9 @@ where                  },                  Point::new(bounds.x + self.padding.left, bounds.center_y()),                  if is_selected { -                    appearance.text_color +                    style.text_color                  } else { -                    appearance.placeholder_color +                    style.placeholder_color                  },                  *viewport,              ); @@ -539,7 +556,7 @@ where                      (on_select)(option)                  },                  None, -                &self.style.menu, +                &self.menu_class,              )              .width(bounds.width)              .padding(self.padding) @@ -565,7 +582,7 @@ where      L: Borrow<[T]> + 'a,      V: Borrow<T> + 'a,      Message: Clone + 'a, -    Theme: 'a, +    Theme: Catalog + 'a,      Renderer: text::Renderer + 'a,  {      fn from( @@ -662,7 +679,7 @@ pub enum Status {  /// The appearance of a pick list.  #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style {      /// The text [`Color`] of the pick list.      pub text_color: Color,      /// The placeholder [`Color`] of the pick list. @@ -675,36 +692,49 @@ pub struct Appearance {      pub border: Border,  } -/// The styles of the different parts of a [`PickList`]. -#[allow(missing_debug_implementations)] -pub struct Style<'a, Theme> { -    /// The style of the [`PickList`] itself. -    pub field: Box<dyn Fn(&Theme, Status) -> Appearance + 'a>, +/// The theme catalog of a [`PickList`]. +pub trait Catalog: menu::Catalog { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -    /// The style of the [`Menu`] of the pick list. -    pub menu: menu::Style<'a, Theme>, -} +    /// The default class produced by the [`Catalog`]. +    fn default<'a>() -> <Self as Catalog>::Class<'a>; -/// The default style of a [`PickList`]. -pub trait DefaultStyle: Sized { -    /// Returns the default style of a [`PickList`]. -    fn default_style() -> Style<'static, Self>; +    /// The default class for the menu of the [`PickList`]. +    fn default_menu<'a>() -> <Self as menu::Catalog>::Class<'a> { +        <Self as menu::Catalog>::default() +    } + +    /// The [`Style`] of a class with the given status. +    fn style( +        &self, +        class: &<Self as Catalog>::Class<'_>, +        status: Status, +    ) -> Style;  } -impl DefaultStyle for Theme { -    fn default_style() -> Style<'static, Self> { -        Style { -            field: Box::new(default), -            menu: menu::DefaultStyle::default_style(), -        } +/// A styling function for a [`PickList`]. +/// +/// 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>() -> StyleFn<'a, Self> { +        Box::new(default) +    } + +    fn style(&self, class: &StyleFn<'_, Self>, status: Status) -> Style { +        class(self, status)      }  }  /// The default style of the field of a [`PickList`]. -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 {          text_color: palette.background.weak.text,          background: palette.background.weak.color.into(),          placeholder_color: palette.background.strong.color, @@ -718,7 +748,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {      match status {          Status::Active => active, -        Status::Hovered | Status::Opened => Appearance { +        Status::Hovered | Status::Opened => Style {              border: Border {                  color: palette.primary.strong.color,                  ..active.border diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 38d8da85..e7821b43 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -4,7 +4,8 @@ use crate::core::mouse;  use crate::core::renderer;  use crate::core::widget::Tree;  use crate::core::{ -    Background, Border, Element, Layout, Length, Rectangle, Size, Theme, Widget, +    self, Background, Border, Element, Layout, Length, Rectangle, Size, Theme, +    Widget,  };  use std::ops::RangeInclusive; @@ -22,15 +23,21 @@ use std::ops::RangeInclusive;  ///  ///   #[allow(missing_debug_implementations)] -pub struct ProgressBar<'a, Theme = crate::Theme> { +pub struct ProgressBar<'a, Theme = crate::Theme> +where +    Theme: Catalog, +{      range: RangeInclusive<f32>,      value: f32,      width: Length,      height: Option<Length>, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  } -impl<'a, Theme> ProgressBar<'a, Theme> { +impl<'a, Theme> ProgressBar<'a, Theme> +where +    Theme: Catalog, +{      /// The default height of a [`ProgressBar`].      pub const DEFAULT_HEIGHT: f32 = 30.0; @@ -39,16 +46,13 @@ impl<'a, Theme> ProgressBar<'a, Theme> {      /// It expects:      ///   * an inclusive range of possible values      ///   * the current value of the [`ProgressBar`] -    pub fn new(range: RangeInclusive<f32>, value: f32) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {          ProgressBar {              value: value.clamp(*range.start(), *range.end()),              range,              width: Length::Fill,              height: None, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -65,8 +69,20 @@ impl<'a, Theme> ProgressBar<'a, Theme> {      }      /// Sets the style of the [`ProgressBar`]. -    pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self { -        self.style = Box::new(style); +    #[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 [`ProgressBar`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into();          self      }  } @@ -74,7 +90,8 @@ impl<'a, Theme> ProgressBar<'a, Theme> {  impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for ProgressBar<'a, Theme>  where -    Renderer: crate::core::Renderer, +    Theme: Catalog, +    Renderer: core::Renderer,  {      fn size(&self) -> Size<Length> {          Size { @@ -116,15 +133,15 @@ where                  / (range_end - range_start)          }; -        let appearance = (self.style)(theme); +        let style = theme.style(&self.class);          renderer.fill_quad(              renderer::Quad {                  bounds: Rectangle { ..bounds }, -                border: appearance.border, +                border: style.border,                  ..renderer::Quad::default()              }, -            appearance.background, +            style.background,          );          if active_progress_width > 0.0 { @@ -134,10 +151,10 @@ where                          width: active_progress_width,                          ..bounds                      }, -                    border: Border::rounded(appearance.border.radius), +                    border: Border::rounded(style.border.radius),                      ..renderer::Quad::default()                  }, -                appearance.bar, +                style.bar,              );          }      } @@ -147,8 +164,8 @@ impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>      for Element<'a, Message, Theme, Renderer>  where      Message: 'a, -    Theme: 'a, -    Renderer: 'a + crate::core::Renderer, +    Theme: 'a + Catalog, +    Renderer: 'a + core::Renderer,  {      fn from(          progress_bar: ProgressBar<'a, Theme>, @@ -159,7 +176,7 @@ where  /// The appearance of a progress bar.  #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style {      /// The [`Background`] of the progress bar.      pub background: Background,      /// The [`Background`] of the bar of the progress bar. @@ -168,29 +185,37 @@ pub struct Appearance {      pub border: Border,  } -/// The style of a [`ProgressBar`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>; +/// The theme catalog of a [`ProgressBar`]. +pub trait Catalog: Sized { +    /// The item class of the [`Catalog`]. +    type Class<'a>; + +    /// The default class produced by the [`Catalog`]. +    fn default<'a>() -> Self::Class<'a>; -/// The default style of a [`ProgressBar`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`ProgressBar`]. -    fn default_style(&self) -> 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) -> Appearance { -        primary(self) +/// A styling function for a [`ProgressBar`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>; + +impl Catalog for Theme { +    type Class<'a> = StyleFn<'a, Self>; + +    fn default<'a>() -> Self::Class<'a> { +        Box::new(primary)      } -} -impl DefaultStyle for Appearance { -    fn default_style(&self) -> Appearance { -        *self +    fn style(&self, class: &Self::Class<'_>) -> Style { +        class(self)      }  }  /// The primary style of a [`ProgressBar`]. -pub fn primary(theme: &Theme) -> Appearance { +pub fn primary(theme: &Theme) -> Style {      let palette = theme.extended_palette();      styled( @@ -200,7 +225,7 @@ pub fn primary(theme: &Theme) -> Appearance {  }  /// The secondary style of a [`ProgressBar`]. -pub fn secondary(theme: &Theme) -> Appearance { +pub fn secondary(theme: &Theme) -> Style {      let palette = theme.extended_palette();      styled( @@ -210,14 +235,14 @@ pub fn secondary(theme: &Theme) -> Appearance {  }  /// The success style of a [`ProgressBar`]. -pub fn success(theme: &Theme) -> Appearance { +pub fn success(theme: &Theme) -> Style {      let palette = theme.extended_palette();      styled(palette.background.strong.color, palette.success.base.color)  }  /// The danger style of a [`ProgressBar`]. -pub fn danger(theme: &Theme) -> Appearance { +pub fn danger(theme: &Theme) -> Style {      let palette = theme.extended_palette();      styled(palette.background.strong.color, palette.danger.base.color) @@ -226,8 +251,8 @@ pub fn danger(theme: &Theme) -> Appearance {  fn styled(      background: impl Into<Background>,      bar: impl Into<Background>, -) -> Appearance { -    Appearance { +) -> Style { +    Style {          background: background.into(),          bar: bar.into(),          border: Border::rounded(2), diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 601e5808..300028b9 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -19,22 +19,25 @@ const QUIET_ZONE: usize = 2;  /// A type of matrix barcode consisting of squares arranged in a grid which  /// can be read by an imaging device, such as a camera.  #[allow(missing_debug_implementations)] -pub struct QRCode<'a, Theme = crate::Theme> { +pub struct QRCode<'a, Theme = crate::Theme> +where +    Theme: Catalog, +{      data: &'a Data,      cell_size: u16, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  } -impl<'a, Theme> QRCode<'a, Theme> { +impl<'a, Theme> QRCode<'a, Theme> +where +    Theme: Catalog, +{      /// Creates a new [`QRCode`] with the provided [`Data`]. -    pub fn new(data: &'a Data) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    pub fn new(data: &'a Data) -> Self {          Self {              data,              cell_size: DEFAULT_CELL_SIZE, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -45,14 +48,27 @@ impl<'a, Theme> QRCode<'a, Theme> {      }      /// Sets the style of the [`QRCode`]. -    pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self { -        self.style = Box::new(style); +    #[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 [`QRCode`]. +    #[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> Widget<Message, Theme, Renderer> -    for QRCode<'a, Theme> +impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a, Theme> +where +    Theme: Catalog,  {      fn tag(&self) -> tree::Tag {          tree::Tag::of::<State>() @@ -96,13 +112,13 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>          let bounds = layout.bounds();          let side_length = self.data.width + 2 * QUIET_ZONE; -        let appearance = (self.style)(theme); -        let mut last_appearance = state.last_appearance.borrow_mut(); +        let style = theme.style(&self.class); +        let mut last_style = state.last_style.borrow_mut(); -        if Some(appearance) != *last_appearance { +        if Some(style) != *last_style {              self.data.cache.clear(); -            *last_appearance = Some(appearance); +            *last_style = Some(style);          }          // Reuse cache if possible @@ -114,7 +130,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>              frame.fill_rectangle(                  Point::ORIGIN,                  Size::new(side_length as f32, side_length as f32), -                appearance.background, +                style.background,              );              // Avoid drawing on the quiet zone @@ -133,7 +149,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>                      frame.fill_rectangle(                          Point::new(column as f32, row as f32),                          Size::UNIT, -                        appearance.cell, +                        style.cell,                      );                  });          }); @@ -152,7 +168,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>  impl<'a, Message, Theme> From<QRCode<'a, Theme>>      for Element<'a, Message, Theme, Renderer>  where -    Theme: 'a, +    Theme: Catalog + 'a,  {      fn from(qr_code: QRCode<'a, Theme>) -> Self {          Self::new(qr_code) @@ -324,44 +340,50 @@ impl From<qrcode::types::QrError> for Error {  #[derive(Default)]  struct State { -    last_appearance: RefCell<Option<Appearance>>, +    last_style: RefCell<Option<Style>>,  }  /// The appearance of a QR code.  #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { +pub struct Style {      /// The color of the QR code data cells      pub cell: Color,      /// The color of the QR code background      pub background: Color,  } -/// The style of a [`QRCode`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>; +/// The theme catalog of a [`QRCode`]. +pub trait Catalog { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -/// The default style of a [`QRCode`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`QRCode`]. -    fn default_style(&self) -> 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<'_>) -> Style;  } -impl DefaultStyle for Theme { -    fn default_style(&self) -> Appearance { -        default(self) +/// A styling function for a [`QRCode`]. +pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> 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) -> Appearance { -        *self +    fn style(&self, class: &Self::Class<'_>) -> Style { +        class(self)      }  }  /// The default style of a [`QRCode`]. -pub fn default(theme: &Theme) -> Appearance { +pub fn default(theme: &Theme) -> Style {      let palette = theme.palette(); -    Appearance { +    Style {          cell: palette.text,          background: palette.background,      } diff --git a/widget/src/radio.rs b/widget/src/radio.rs index a7b7dd03..6b22961d 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -69,6 +69,7 @@ use crate::core::{  #[allow(missing_debug_implementations)]  pub struct Radio<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>  where +    Theme: Catalog,      Renderer: text::Renderer,  {      is_selected: bool, @@ -81,12 +82,13 @@ where      text_line_height: text::LineHeight,      text_shaping: text::Shaping,      font: Option<Renderer::Font>, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer>  where      Message: Clone, +    Theme: Catalog,      Renderer: text::Renderer,  {      /// The default size of a [`Radio`] button. @@ -110,7 +112,6 @@ where          f: F,      ) -> Self      where -        Theme: DefaultStyle + 'a,          V: Eq + Copy,          F: FnOnce(V) -> Message,      { @@ -125,7 +126,7 @@ where              text_line_height: text::LineHeight::default(),              text_shaping: text::Shaping::Basic,              font: None, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -175,11 +176,20 @@ where      }      /// Sets the style of the [`Radio`] button. -    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 [`Radio`] button. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into();          self      }  } @@ -188,6 +198,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for Radio<'a, Message, Theme, Renderer>  where      Message: Clone, +    Theme: Catalog,      Renderer: text::Renderer,  {      fn tag(&self) -> tree::Tag { @@ -284,7 +295,7 @@ where          tree: &Tree,          renderer: &mut Renderer,          theme: &Theme, -        style: &renderer::Style, +        defaults: &renderer::Style,          layout: Layout<'_>,          cursor: mouse::Cursor,          viewport: &Rectangle, @@ -300,7 +311,7 @@ where              Status::Active { is_selected }          }; -        let appearance = (self.style)(theme, status); +        let style = theme.style(&self.class, status);          {              let layout = children.next().unwrap(); @@ -314,12 +325,12 @@ where                      bounds,                      border: Border {                          radius: (size / 2.0).into(), -                        width: appearance.border_width, -                        color: appearance.border_color, +                        width: style.border_width, +                        color: style.border_color,                      },                      ..renderer::Quad::default()                  }, -                appearance.background, +                style.background,              );              if self.is_selected { @@ -334,7 +345,7 @@ where                          border: Border::rounded(dot_size / 2.0),                          ..renderer::Quad::default()                      }, -                    appearance.dot_color, +                    style.dot_color,                  );              }          } @@ -344,11 +355,11 @@ where              crate::text::draw(                  renderer, -                style, +                defaults,                  label_layout,                  tree.state.downcast_ref(), -                crate::text::Appearance { -                    color: appearance.text_color, +                crate::text::Style { +                    color: style.text_color,                  },                  viewport,              ); @@ -360,7 +371,7 @@ impl<'a, Message, Theme, Renderer> From<Radio<'a, Message, Theme, Renderer>>      for Element<'a, Message, Theme, Renderer>  where      Message: 'a + Clone, -    Theme: 'a, +    Theme: 'a + Catalog,      Renderer: 'a + text::Renderer,  {      fn from( @@ -387,7 +398,7 @@ pub enum Status {  /// The appearance of a radio button.  #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style {      /// The [`Background`] of the radio button.      pub background: Background,      /// The [`Color`] of the dot of the radio button. @@ -400,32 +411,38 @@ pub struct Appearance {      pub text_color: Option<Color>,  } -/// The style of a [`Radio`] button. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of a [`Radio`]. +pub trait Catalog { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -/// The default style of a [`Radio`] button. -pub trait DefaultStyle { -    /// Returns the default style of a [`Radio`] button. -    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 [`Radio`]. +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 [`Radio`] button. -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: Color::TRANSPARENT.into(),          dot_color: palette.primary.strong.color,          border_width: 1.0, @@ -435,7 +452,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {      match status {          Status::Active { .. } => active, -        Status::Hovered { .. } => Appearance { +        Status::Hovered { .. } => Style {              dot_color: palette.primary.strong.color,              background: palette.primary.weak.color.into(),              ..active diff --git a/widget/src/rule.rs b/widget/src/rule.rs index 9fa5f74f..1a536d2f 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -1,4 +1,5 @@  //! Display a horizontal or vertical rule for dividing content. +use crate::core;  use crate::core::border::{self, Border};  use crate::core::layout;  use crate::core::mouse; @@ -10,43 +11,55 @@ use crate::core::{  /// Display a horizontal or vertical rule for dividing content.  #[allow(missing_debug_implementations)] -pub struct Rule<'a, Theme = crate::Theme> { +pub struct Rule<'a, Theme = crate::Theme> +where +    Theme: Catalog, +{      width: Length,      height: Length,      is_horizontal: bool, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  } -impl<'a, Theme> Rule<'a, Theme> { +impl<'a, Theme> Rule<'a, Theme> +where +    Theme: Catalog, +{      /// Creates a horizontal [`Rule`] with the given height. -    pub fn horizontal(height: impl Into<Pixels>) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    pub fn horizontal(height: impl Into<Pixels>) -> Self {          Rule {              width: Length::Fill,              height: Length::Fixed(height.into().0),              is_horizontal: true, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      }      /// Creates a vertical [`Rule`] with the given width. -    pub fn vertical(width: impl Into<Pixels>) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    pub fn vertical(width: impl Into<Pixels>) -> Self {          Rule {              width: Length::Fixed(width.into().0),              height: Length::Fill,              is_horizontal: false, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      }      /// Sets the style of the [`Rule`]. -    pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self { -        self.style = Box::new(style); +    #[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 [`Rule`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into();          self      }  } @@ -54,7 +67,8 @@ impl<'a, Theme> Rule<'a, Theme> {  impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for Rule<'a, Theme>  where -    Renderer: crate::core::Renderer, +    Renderer: core::Renderer, +    Theme: Catalog,  {      fn size(&self) -> Size<Length> {          Size { @@ -83,35 +97,34 @@ where          _viewport: &Rectangle,      ) {          let bounds = layout.bounds(); -        let appearance = (self.style)(theme); +        let style = theme.style(&self.class);          let bounds = if self.is_horizontal {              let line_y = (bounds.y + (bounds.height / 2.0) -                - (appearance.width as f32 / 2.0)) +                - (style.width as f32 / 2.0))                  .round(); -            let (offset, line_width) = appearance.fill_mode.fill(bounds.width); +            let (offset, line_width) = style.fill_mode.fill(bounds.width);              let line_x = bounds.x + offset;              Rectangle {                  x: line_x,                  y: line_y,                  width: line_width, -                height: appearance.width as f32, +                height: style.width as f32,              }          } else {              let line_x = (bounds.x + (bounds.width / 2.0) -                - (appearance.width as f32 / 2.0)) +                - (style.width as f32 / 2.0))                  .round(); -            let (offset, line_height) = -                appearance.fill_mode.fill(bounds.height); +            let (offset, line_height) = style.fill_mode.fill(bounds.height);              let line_y = bounds.y + offset;              Rectangle {                  x: line_x,                  y: line_y, -                width: appearance.width as f32, +                width: style.width as f32,                  height: line_height,              }          }; @@ -119,10 +132,10 @@ where          renderer.fill_quad(              renderer::Quad {                  bounds, -                border: Border::rounded(appearance.radius), +                border: Border::rounded(style.radius),                  ..renderer::Quad::default()              }, -            appearance.color, +            style.color,          );      }  } @@ -131,8 +144,8 @@ impl<'a, Message, Theme, Renderer> From<Rule<'a, Theme>>      for Element<'a, Message, Theme, Renderer>  where      Message: 'a, -    Theme: 'a, -    Renderer: 'a + crate::core::Renderer, +    Theme: 'a + Catalog, +    Renderer: 'a + core::Renderer,  {      fn from(rule: Rule<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {          Element::new(rule) @@ -141,7 +154,7 @@ where  /// The appearance of a rule.  #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style {      /// The color of the rule.      pub color: Color,      /// The width (thickness) of the rule line. @@ -216,32 +229,40 @@ impl FillMode {      }  } -/// The style of a [`Rule`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>; +/// The theme catalog of a [`Rule`]. +pub trait Catalog: Sized { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -/// The default style of a [`Rule`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`Rule`]. -    fn default_style(&self) -> 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<'_>) -> Style;  } -impl DefaultStyle for Theme { -    fn default_style(&self) -> Appearance { -        default(self) +/// A styling function for a [`Rule`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> 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) -> Appearance { -        *self +    fn style(&self, class: &Self::Class<'_>) -> Style { +        class(self)      }  }  /// The default styling of a [`Rule`]. -pub fn default(theme: &Theme) -> Appearance { +pub fn default(theme: &Theme) -> Style {      let palette = theme.extended_palette(); -    Appearance { +    Style {          color: palette.background.strong.color,          width: 1,          radius: 0.0.into(), diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index c03bbb7d..84e9ac15 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -12,8 +12,8 @@ use crate::core::widget;  use crate::core::widget::operation::{self, Operation};  use crate::core::widget::tree::{self, Tree};  use crate::core::{ -    Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, -    Point, Rectangle, Shell, Size, Theme, Vector, Widget, +    self, Background, Border, Clipboard, Color, Element, Layout, Length, +    Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,  };  use crate::runtime::Command; @@ -28,7 +28,8 @@ pub struct Scrollable<      Theme = crate::Theme,      Renderer = crate::Renderer,  > where -    Renderer: crate::core::Renderer, +    Theme: Catalog, +    Renderer: core::Renderer,  {      id: Option<Id>,      width: Length, @@ -36,20 +37,18 @@ pub struct Scrollable<      direction: Direction,      content: Element<'a, Message, Theme, Renderer>,      on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>  where -    Renderer: crate::core::Renderer, +    Theme: Catalog, +    Renderer: core::Renderer,  {      /// Creates a new vertical [`Scrollable`].      pub fn new(          content: impl Into<Element<'a, Message, Theme, Renderer>>, -    ) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    ) -> Self {          Self::with_direction(content, Direction::default())      } @@ -57,18 +56,6 @@ where      pub fn with_direction(          content: impl Into<Element<'a, Message, Theme, Renderer>>,          direction: Direction, -    ) -> Self -    where -        Theme: DefaultStyle + 'a, -    { -        Self::with_direction_and_style(content, direction, Theme::default_style) -    } - -    /// Creates a new [`Scrollable`] with the given [`Direction`] and style. -    pub fn with_direction_and_style( -        content: impl Into<Element<'a, Message, Theme, Renderer>>, -        direction: Direction, -        style: impl Fn(&Theme, Status) -> Appearance + 'a,      ) -> Self {          let content = content.into(); @@ -91,7 +78,7 @@ where              direction,              content,              on_scroll: None, -            style: Box::new(style), +            class: Theme::default(),          }      } @@ -121,12 +108,21 @@ where          self      } -    /// Sets the style of the [`Scrollable`] . -    pub fn style( -        mut self, -        style: impl Fn(&Theme, Status) -> Appearance + 'a, -    ) -> Self { -        self.style = Box::new(style); +    /// Sets the style of this [`Scrollable`]. +    #[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 [`Scrollable`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into();          self      }  } @@ -237,7 +233,8 @@ pub enum Alignment {  impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for Scrollable<'a, Message, Theme, Renderer>  where -    Renderer: crate::core::Renderer, +    Theme: Catalog, +    Renderer: core::Renderer,  {      fn tag(&self) -> tree::Tag {          tree::Tag::of::<State>() @@ -651,7 +648,7 @@ where          tree: &Tree,          renderer: &mut Renderer,          theme: &Theme, -        style: &renderer::Style, +        defaults: &renderer::Style,          layout: Layout<'_>,          cursor: mouse::Cursor,          _viewport: &Rectangle, @@ -701,13 +698,9 @@ where              Status::Active          }; -        let appearance = (self.style)(theme, status); +        let style = theme.style(&self.class, status); -        container::draw_background( -            renderer, -            &appearance.container, -            layout.bounds(), -        ); +        container::draw_background(renderer, &style.container, layout.bounds());          // Draw inner content          if scrollbars.active() { @@ -719,7 +712,7 @@ where                              &tree.children[0],                              renderer,                              theme, -                            style, +                            defaults,                              content_layout,                              cursor,                              &Rectangle { @@ -782,7 +775,7 @@ where                      if let Some(scrollbar) = scrollbars.y {                          draw_scrollbar(                              renderer, -                            appearance.vertical_scrollbar, +                            style.vertical_scrollbar,                              &scrollbar,                          );                      } @@ -790,14 +783,14 @@ where                      if let Some(scrollbar) = scrollbars.x {                          draw_scrollbar(                              renderer, -                            appearance.horizontal_scrollbar, +                            style.horizontal_scrollbar,                              &scrollbar,                          );                      }                      if let (Some(x), Some(y)) = (scrollbars.x, scrollbars.y) {                          let background = -                            appearance.gap.or(appearance.container.background); +                            style.gap.or(style.container.background);                          if let Some(background) = background {                              renderer.fill_quad( @@ -821,7 +814,7 @@ where                  &tree.children[0],                  renderer,                  theme, -                style, +                defaults,                  content_layout,                  cursor,                  &Rectangle { @@ -916,8 +909,8 @@ impl<'a, Message, Theme, Renderer>      for Element<'a, Message, Theme, Renderer>  where      Message: 'a, -    Theme: 'a, -    Renderer: 'a + crate::core::Renderer, +    Theme: 'a + Catalog, +    Renderer: 'a + core::Renderer,  {      fn from(          text_input: Scrollable<'a, Message, Theme, Renderer>, @@ -1570,9 +1563,9 @@ pub enum Status {  /// The appearance of a scrolable.  #[derive(Debug, Clone, Copy)] -pub struct Appearance { -    /// The [`container::Appearance`] of a scrollable. -    pub container: container::Appearance, +pub struct Style { +    /// The [`container::Style`] of a scrollable. +    pub container: container::Style,      /// The vertical [`Scrollbar`] appearance.      pub vertical_scrollbar: Scrollbar,      /// The horizontal [`Scrollbar`] appearance. @@ -1601,29 +1594,35 @@ pub struct Scroller {      pub border: Border,  } -/// The style of a [`Scrollable`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of a [`Scrollable`]. +pub trait Catalog { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -/// The default style of a [`Scrollable`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`Scrollable`]. -    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 [`Scrollable`]. +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 [`Scrollable`]. -pub fn default(theme: &Theme, status: Status) -> Appearance { +pub fn default(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      let scrollbar = Scrollbar { @@ -1636,8 +1635,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {      };      match status { -        Status::Active => Appearance { -            container: container::Appearance::default(), +        Status::Active => Style { +            container: container::Style::default(),              vertical_scrollbar: scrollbar,              horizontal_scrollbar: scrollbar,              gap: None, @@ -1654,8 +1653,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {                  ..scrollbar              }; -            Appearance { -                container: container::Appearance::default(), +            Style { +                container: container::Style::default(),                  vertical_scrollbar: if is_vertical_scrollbar_hovered {                      hovered_scrollbar                  } else { @@ -1681,8 +1680,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {                  ..scrollbar              }; -            Appearance { -                container: container::Appearance::default(), +            Style { +                container: container::Style::default(),                  vertical_scrollbar: if is_vertical_scrollbar_dragged {                      dragged_scrollbar                  } else { diff --git a/widget/src/slider.rs b/widget/src/slider.rs index d3b46a98..a8f1d192 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -9,7 +9,7 @@ use crate::core::renderer;  use crate::core::touch;  use crate::core::widget::tree::{self, Tree};  use crate::core::{ -    Border, Clipboard, Color, Element, Layout, Length, Pixels, Point, +    self, Border, Clipboard, Color, Element, Layout, Length, Pixels, Point,      Rectangle, Shell, Size, Theme, Widget,  }; @@ -39,7 +39,10 @@ use std::ops::RangeInclusive;  ///  ///   #[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message, Theme = crate::Theme> { +pub struct Slider<'a, T, Message, Theme = crate::Theme> +where +    Theme: Catalog, +{      range: RangeInclusive<T>,      step: T,      shift_step: Option<T>, @@ -49,13 +52,14 @@ pub struct Slider<'a, T, Message, Theme = crate::Theme> {      on_release: Option<Message>,      width: Length,      height: f32, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>  where      T: Copy + From<u8> + PartialOrd,      Message: Clone, +    Theme: Catalog,  {      /// The default height of a [`Slider`].      pub const DEFAULT_HEIGHT: f32 = 16.0; @@ -70,7 +74,6 @@ where      ///   `Message`.      pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self      where -        Theme: DefaultStyle + 'a,          F: 'a + Fn(T) -> Message,      {          let value = if value >= *range.start() { @@ -95,7 +98,7 @@ where              on_release: None,              width: Length::Fill,              height: Self::DEFAULT_HEIGHT, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -130,15 +133,6 @@ where          self      } -    /// Sets the style of the [`Slider`]. -    pub fn style( -        mut self, -        style: impl Fn(&Theme, Status) -> Appearance + 'a, -    ) -> Self { -        self.style = Box::new(style); -        self -    } -      /// Sets the step size of the [`Slider`].      pub fn step(mut self, step: impl Into<T>) -> Self {          self.step = step.into(); @@ -152,6 +146,24 @@ where          self.shift_step = Some(shift_step.into());          self      } + +    /// Sets the style of the [`Slider`]. +    #[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 [`Slider`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into(); +        self +    }  }  impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> @@ -159,7 +171,8 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>  where      T: Copy + Into<f64> + num_traits::FromPrimitive,      Message: Clone, -    Renderer: crate::core::Renderer, +    Theme: Catalog, +    Renderer: core::Renderer,  {      fn tag(&self) -> tree::Tag {          tree::Tag::of::<State>() @@ -349,8 +362,8 @@ where          let bounds = layout.bounds();          let is_mouse_over = cursor.is_over(bounds); -        let style = (self.style)( -            theme, +        let style = theme.style( +            &self.class,              if state.is_dragging {                  Status::Dragged              } else if is_mouse_over { @@ -461,8 +474,8 @@ impl<'a, T, Message, Theme, Renderer> From<Slider<'a, T, Message, Theme>>  where      T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,      Message: Clone + 'a, -    Theme: 'a, -    Renderer: crate::core::Renderer + 'a, +    Theme: Catalog + 'a, +    Renderer: core::Renderer + 'a,  {      fn from(          slider: Slider<'a, T, Message, Theme>, @@ -490,15 +503,15 @@ pub enum Status {  /// The appearance of a slider.  #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style {      /// The colors of the rail of the slider.      pub rail: Rail,      /// The appearance of the [`Handle`] of the slider.      pub handle: Handle,  } -impl Appearance { -    /// Changes the [`HandleShape`] of the [`Appearance`] to a circle +impl Style { +    /// Changes the [`HandleShape`] of the [`Style`] to a circle      /// with the given radius.      pub fn with_circular_handle(mut self, radius: impl Into<Pixels>) -> Self {          self.handle.shape = HandleShape::Circle { @@ -549,29 +562,35 @@ pub enum HandleShape {      },  } -/// The style of a [`Slider`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of a [`Slider`]. +pub trait Catalog: Sized { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -/// The default style of a [`Slider`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`Slider`]. -    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 [`Slider`]. +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 [`Slider`]. -pub fn default(theme: &Theme, status: Status) -> Appearance { +pub fn default(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      let color = match status { @@ -580,7 +599,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {          Status::Dragged => palette.primary.strong.color,      }; -    Appearance { +    Style {          rail: Rail {              colors: (color, palette.secondary.base.color),              width: 4.0, diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 53c45bcb..eb142189 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -20,36 +20,36 @@ pub use crate::core::svg::Handle;  /// [`Svg`] images can have a considerable rendering cost when resized,  /// specially when they are complex.  #[allow(missing_debug_implementations)] -pub struct Svg<'a, Theme = crate::Theme> { +pub struct Svg<'a, Theme = crate::Theme> +where +    Theme: Catalog, +{      handle: Handle,      width: Length,      height: Length,      content_fit: ContentFit, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  } -impl<'a, Theme> Svg<'a, Theme> { +impl<'a, Theme> Svg<'a, Theme> +where +    Theme: Catalog, +{      /// Creates a new [`Svg`] from the given [`Handle`]. -    pub fn new(handle: impl Into<Handle>) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    pub fn new(handle: impl Into<Handle>) -> Self {          Svg {              handle: handle.into(),              width: Length::Fill,              height: Length::Shrink,              content_fit: ContentFit::Contain, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      }      /// Creates a new [`Svg`] that will display the contents of the file at the      /// provided path.      #[must_use] -    pub fn from_path(path: impl Into<PathBuf>) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    pub fn from_path(path: impl Into<PathBuf>) -> Self {          Self::new(Handle::from_path(path))      } @@ -78,13 +78,21 @@ impl<'a, Theme> Svg<'a, Theme> {          }      } -    /// Sets the style variant of this [`Svg`]. +    /// Sets the style of the [`Svg`]. +    #[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 [`Svg`]. +    #[cfg(feature = "advanced")]      #[must_use] -    pub fn style( -        mut self, -        style: impl Fn(&Theme, Status) -> Appearance + 'a, -    ) -> Self { -        self.style = Box::new(style); +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into();          self      }  } @@ -93,6 +101,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for Svg<'a, Theme>  where      Renderer: svg::Renderer, +    Theme: Catalog,  {      fn size(&self) -> Size<Length> {          Size { @@ -167,11 +176,11 @@ where                  Status::Idle              }; -            let appearance = (self.style)(theme, status); +            let style = theme.style(&self.class, status);              renderer.draw_svg(                  self.handle.clone(), -                appearance.color, +                style.color,                  drawing_bounds + offset,              );          }; @@ -189,7 +198,7 @@ where  impl<'a, Message, Theme, Renderer> From<Svg<'a, Theme>>      for Element<'a, Message, Theme, Renderer>  where -    Theme: 'a, +    Theme: Catalog + 'a,      Renderer: svg::Renderer + 'a,  {      fn from(icon: Svg<'a, Theme>) -> Element<'a, Message, Theme, Renderer> { @@ -208,7 +217,7 @@ pub enum Status {  /// The appearance of an [`Svg`].  #[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct Appearance { +pub struct Style {      /// The [`Color`] filter of an [`Svg`].      ///      /// Useful for coloring a symbolic icon. @@ -217,23 +226,37 @@ pub struct Appearance {      pub color: Option<Color>,  } -/// The style of an [`Svg`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of an [`Svg`]. +pub trait Catalog { +    /// The item class of the [`Catalog`]. +    type Class<'a>; + +    /// The default class produced by the [`Catalog`]. +    fn default<'a>() -> Self::Class<'a>; -/// The default style of an [`Svg`]. -pub trait DefaultStyle { -    /// Returns the default style of an [`Svg`]. -    fn default_style(&self, status: Status) -> Appearance; +    /// 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 { -        Appearance::default() +impl Catalog for Theme { +    type Class<'a> = StyleFn<'a, Self>; + +    fn default<'a>() -> Self::Class<'a> { +        Box::new(|_theme, _status| Style::default()) +    } + +    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { +        class(self, status)      }  } -impl DefaultStyle for Appearance { -    fn default_style(&self, _status: Status) -> Appearance { -        *self +/// A styling function for an [`Svg`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>; + +impl<'a, Theme> From<Style> for StyleFn<'a, Theme> { +    fn from(style: Style) -> Self { +        Box::new(move |_theme, _status| style)      }  } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 5b8f6a1b..a00df3c7 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -32,6 +32,7 @@ pub struct TextEditor<      Renderer = crate::Renderer,  > where      Highlighter: text::Highlighter, +    Theme: Catalog,      Renderer: text::Renderer,  {      content: &'a Content<Renderer>, @@ -41,7 +42,7 @@ pub struct TextEditor<      width: Length,      height: Length,      padding: Padding, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,      on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>,      highlighter_settings: Highlighter::Settings,      highlighter_format: fn( @@ -53,13 +54,11 @@ pub struct TextEditor<  impl<'a, Message, Theme, Renderer>      TextEditor<'a, highlighter::PlainText, Message, Theme, Renderer>  where +    Theme: Catalog,      Renderer: text::Renderer,  {      /// Creates new [`TextEditor`] with the given [`Content`]. -    pub fn new(content: &'a Content<Renderer>) -> Self -    where -        Theme: DefaultStyle + 'a, -    { +    pub fn new(content: &'a Content<Renderer>) -> Self {          Self {              content,              font: None, @@ -68,7 +67,7 @@ where              width: Length::Fill,              height: Length::Shrink,              padding: Padding::new(5.0), -            style: Box::new(Theme::default_style), +            class: Theme::default(),              on_edit: None,              highlighter_settings: (),              highlighter_format: |_highlight, _theme| { @@ -82,6 +81,7 @@ impl<'a, Highlighter, Message, Theme, Renderer>      TextEditor<'a, Highlighter, Message, Theme, Renderer>  where      Highlighter: text::Highlighter, +    Theme: Catalog,      Renderer: text::Renderer,  {      /// Sets the height of the [`TextEditor`]. @@ -134,7 +134,7 @@ where              width: self.width,              height: self.height,              padding: self.padding, -            style: self.style, +            class: self.class,              on_edit: self.on_edit,              highlighter_settings: settings,              highlighter_format: to_format, @@ -142,11 +142,20 @@ where      }      /// Sets the style of the [`TextEditor`]. -    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 [`TextEditor`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into();          self      }  } @@ -309,6 +318,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for TextEditor<'a, Highlighter, Message, Theme, Renderer>  where      Highlighter: text::Highlighter, +    Theme: Catalog,      Renderer: text::Renderer,  {      fn tag(&self) -> widget::tree::Tag { @@ -479,7 +489,7 @@ where          tree: &widget::Tree,          renderer: &mut Renderer,          theme: &Theme, -        style: &renderer::Style, +        defaults: &renderer::Style,          layout: Layout<'_>,          cursor: mouse::Cursor,          viewport: &Rectangle, @@ -508,22 +518,22 @@ 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,          );          renderer.fill_editor(              &internal.editor,              bounds.position()                  + Vector::new(self.padding.left, self.padding.top), -            style.text_color, +            defaults.text_color,              *viewport,          ); @@ -555,7 +565,7 @@ where                                  },                                  ..renderer::Quad::default()                              }, -                            appearance.value, +                            style.value,                          );                      }                  } @@ -568,7 +578,7 @@ where                                  bounds: range,                                  ..renderer::Quad::default()                              }, -                            appearance.selection, +                            style.selection,                          );                      }                  } @@ -604,7 +614,7 @@ impl<'a, Highlighter, Message, Theme, Renderer>  where      Highlighter: text::Highlighter,      Message: 'a, -    Theme: 'a, +    Theme: Catalog + 'a,      Renderer: text::Renderer,  {      fn from( @@ -796,7 +806,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. @@ -811,32 +821,38 @@ pub struct Appearance {      pub selection: Color,  } -/// The style of a [`TextEditor`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of a [`TextEditor`]. +pub trait Catalog { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -/// The default style of a [`TextEditor`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`TextEditor`]. -    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 [`TextEditor`]. +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 [`TextEditor`]. -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(), @@ -851,21 +867,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 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 diff --git a/widget/src/themer.rs b/widget/src/themer.rs index a7eabd2c..f4597458 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -155,9 +155,9 @@ where          if let Some(background) = self.background {              container::draw_background(                  renderer, -                &container::Appearance { +                &container::Style {                      background: Some(background(&theme)), -                    ..container::Appearance::default() +                    ..container::Style::default()                  },                  layout.bounds(),              ); diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index fc9e06e1..ca6e37b0 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -35,6 +35,7 @@ pub struct Toggler<      Theme = crate::Theme,      Renderer = crate::Renderer,  > where +    Theme: Catalog,      Renderer: text::Renderer,  {      is_toggled: bool, @@ -48,11 +49,12 @@ pub struct Toggler<      text_shaping: text::Shaping,      spacing: f32,      font: Option<Renderer::Font>, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer>  where +    Theme: Catalog,      Renderer: text::Renderer,  {      /// The default size of a [`Toggler`]. @@ -72,7 +74,6 @@ where          f: F,      ) -> Self      where -        Theme: 'a + DefaultStyle,          F: 'a + Fn(bool) -> Message,      {          Toggler { @@ -87,7 +88,7 @@ where              text_shaping: text::Shaping::Basic,              spacing: Self::DEFAULT_SIZE / 2.0,              font: None, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -145,11 +146,20 @@ where      }      /// Sets the style of the [`Toggler`]. -    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 [`Toggler`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into();          self      }  } @@ -157,6 +167,7 @@ where  impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for Toggler<'a, Message, Theme, Renderer>  where +    Theme: Catalog,      Renderer: text::Renderer,  {      fn tag(&self) -> tree::Tag { @@ -284,7 +295,7 @@ where                  style,                  label_layout,                  tree.state.downcast_ref(), -                crate::text::Appearance::default(), +                crate::text::Style::default(),                  viewport,              );          } @@ -302,7 +313,7 @@ where              }          }; -        let appearance = (self.style)(theme, status); +        let style = theme.style(&self.class, status);          let border_radius = bounds.height / BORDER_RADIUS_RATIO;          let space = SPACE_RATIO * bounds.height; @@ -319,12 +330,12 @@ where                  bounds: toggler_background_bounds,                  border: Border {                      radius: border_radius.into(), -                    width: appearance.background_border_width, -                    color: appearance.background_border_color, +                    width: style.background_border_width, +                    color: style.background_border_color,                  },                  ..renderer::Quad::default()              }, -            appearance.background, +            style.background,          );          let toggler_foreground_bounds = Rectangle { @@ -344,12 +355,12 @@ where                  bounds: toggler_foreground_bounds,                  border: Border {                      radius: border_radius.into(), -                    width: appearance.foreground_border_width, -                    color: appearance.foreground_border_color, +                    width: style.foreground_border_width, +                    color: style.foreground_border_color,                  },                  ..renderer::Quad::default()              }, -            appearance.foreground, +            style.foreground,          );      }  } @@ -358,7 +369,7 @@ impl<'a, Message, Theme, Renderer> From<Toggler<'a, Message, Theme, Renderer>>      for Element<'a, Message, Theme, Renderer>  where      Message: 'a, -    Theme: 'a, +    Theme: Catalog + 'a,      Renderer: text::Renderer + 'a,  {      fn from( @@ -385,7 +396,7 @@ pub enum Status {  /// The appearance of a toggler.  #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style {      /// The background [`Color`] of the toggler.      pub background: Color,      /// The width of the background border of the toggler. @@ -400,29 +411,37 @@ pub struct Appearance {      pub foreground_border_color: Color,  } -/// The style of a [`Toggler`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of a [`Toggler`]. +pub trait Catalog: Sized { +    /// The item class of the [`Catalog`]. +    type Class<'a>; -/// The default style of a [`Toggler`]. -pub trait DefaultStyle { -    /// Returns the default style of a [`Toggler`]. -    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 [`Toggler`]. +/// +/// 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 [`Toggler`]. -pub fn default(theme: &Theme, status: Status) -> Appearance { +pub fn default(theme: &Theme, status: Status) -> Style {      let palette = theme.extended_palette();      let background = match status { @@ -455,7 +474,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {          }      }; -    Appearance { +    Style {          background,          foreground,          foreground_border_width: 0.0, diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 32c962fc..39f2e07d 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -20,6 +20,7 @@ pub struct Tooltip<      Theme = crate::Theme,      Renderer = crate::Renderer,  > where +    Theme: container::Catalog,      Renderer: text::Renderer,  {      content: Element<'a, Message, Theme, Renderer>, @@ -28,11 +29,12 @@ pub struct Tooltip<      gap: f32,      padding: f32,      snap_within_viewport: bool, -    style: container::Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, Message, Theme, Renderer> Tooltip<'a, Message, Theme, Renderer>  where +    Theme: container::Catalog,      Renderer: text::Renderer,  {      /// The default padding of a [`Tooltip`] drawn by this renderer. @@ -45,10 +47,7 @@ where          content: impl Into<Element<'a, Message, Theme, Renderer>>,          tooltip: impl Into<Element<'a, Message, Theme, Renderer>>,          position: Position, -    ) -> Self -    where -        Theme: container::DefaultStyle + 'a, -    { +    ) -> Self {          Tooltip {              content: content.into(),              tooltip: tooltip.into(), @@ -56,7 +55,7 @@ where              gap: 0.0,              padding: Self::DEFAULT_PADDING,              snap_within_viewport: true, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -79,11 +78,23 @@ where      }      /// Sets the style of the [`Tooltip`]. +    #[must_use]      pub fn style(          mut self, -        style: impl Fn(&Theme, container::Status) -> container::Appearance + 'a, -    ) -> Self { -        self.style = Box::new(style); +        style: impl Fn(&Theme) -> container::Style + 'a, +    ) -> Self +    where +        Theme::Class<'a>: From<container::StyleFn<'a, Theme>>, +    { +        self.class = (Box::new(style) as container::StyleFn<'a, Theme>).into(); +        self +    } + +    /// Sets the style class of the [`Tooltip`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into();          self      }  } @@ -91,6 +102,7 @@ where  impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for Tooltip<'a, Message, Theme, Renderer>  where +    Theme: container::Catalog,      Renderer: text::Renderer,  {      fn children(&self) -> Vec<widget::Tree> { @@ -239,7 +251,7 @@ where                  positioning: self.position,                  gap: self.gap,                  padding: self.padding, -                style: &self.style, +                class: &self.class,              })))          } else {              None @@ -262,7 +274,7 @@ impl<'a, Message, Theme, Renderer> From<Tooltip<'a, Message, Theme, Renderer>>      for Element<'a, Message, Theme, Renderer>  where      Message: 'a, -    Theme: 'a, +    Theme: container::Catalog + 'a,      Renderer: text::Renderer + 'a,  {      fn from( @@ -299,6 +311,7 @@ enum State {  struct Overlay<'a, 'b, Message, Theme, Renderer>  where +    Theme: container::Catalog,      Renderer: text::Renderer,  {      position: Point, @@ -310,14 +323,14 @@ where      positioning: Position,      gap: f32,      padding: f32, -    style: -        &'b (dyn Fn(&Theme, container::Status) -> container::Appearance + 'a), +    class: &'b Theme::Class<'a>,  }  impl<'a, 'b, Message, Theme, Renderer>      overlay::Overlay<Message, Theme, Renderer>      for Overlay<'a, 'b, Message, Theme, Renderer>  where +    Theme: container::Catalog,      Renderer: text::Renderer,  {      fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { @@ -426,7 +439,7 @@ where          layout: Layout<'_>,          cursor_position: mouse::Cursor,      ) { -        let style = (self.style)(theme, container::Status::Idle); +        let style = theme.style(self.class);          container::draw_background(renderer, &style, layout.bounds()); diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 2aa8f4d1..defb442f 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -2,10 +2,9 @@  use std::ops::RangeInclusive;  pub use crate::slider::{ -    default, Appearance, DefaultStyle, Handle, HandleShape, Status, Style, +    default, Catalog, Handle, HandleShape, Status, Style, StyleFn,  }; -use crate::core;  use crate::core::event::{self, Event};  use crate::core::keyboard;  use crate::core::keyboard::key::{self, Key}; @@ -15,8 +14,8 @@ use crate::core::renderer;  use crate::core::touch;  use crate::core::widget::tree::{self, Tree};  use crate::core::{ -    Border, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell, Size, -    Widget, +    self, Border, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell, +    Size, Widget,  };  /// An vertical bar and a handle that selects a single value from a range of @@ -41,7 +40,10 @@ use crate::core::{  /// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged);  /// ```  #[allow(missing_debug_implementations)] -pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> { +pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> +where +    Theme: Catalog, +{      range: RangeInclusive<T>,      step: T,      shift_step: Option<T>, @@ -51,13 +53,14 @@ pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> {      on_release: Option<Message>,      width: f32,      height: Length, -    style: Style<'a, Theme>, +    class: Theme::Class<'a>,  }  impl<'a, T, Message, Theme> VerticalSlider<'a, T, Message, Theme>  where      T: Copy + From<u8> + std::cmp::PartialOrd,      Message: Clone, +    Theme: Catalog,  {      /// The default width of a [`VerticalSlider`].      pub const DEFAULT_WIDTH: f32 = 16.0; @@ -72,7 +75,6 @@ where      ///   `Message`.      pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self      where -        Theme: DefaultStyle + 'a,          F: 'a + Fn(T) -> Message,      {          let value = if value >= *range.start() { @@ -97,7 +99,7 @@ where              on_release: None,              width: Self::DEFAULT_WIDTH,              height: Length::Fill, -            style: Box::new(Theme::default_style), +            class: Theme::default(),          }      } @@ -132,15 +134,6 @@ where          self      } -    /// Sets the style of the [`VerticalSlider`]. -    pub fn style( -        mut self, -        style: impl Fn(&Theme, Status) -> Appearance + 'a, -    ) -> Self { -        self.style = Box::new(style); -        self -    } -      /// Sets the step size of the [`VerticalSlider`].      pub fn step(mut self, step: T) -> Self {          self.step = step; @@ -154,6 +147,24 @@ where          self.shift_step = Some(shift_step.into());          self      } + +    /// Sets the style of the [`VerticalSlider`]. +    #[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 [`VerticalSlider`]. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into(); +        self +    }  }  impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> @@ -161,6 +172,7 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>  where      T: Copy + Into<f64> + num_traits::FromPrimitive,      Message: Clone, +    Theme: Catalog,      Renderer: core::Renderer,  {      fn tag(&self) -> tree::Tag { @@ -354,8 +366,8 @@ where          let bounds = layout.bounds();          let is_mouse_over = cursor.is_over(bounds); -        let style = (self.style)( -            theme, +        let style = theme.style( +            &self.class,              if state.is_dragging {                  Status::Dragged              } else if is_mouse_over { @@ -467,7 +479,7 @@ impl<'a, T, Message, Theme, Renderer>  where      T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,      Message: Clone + 'a, -    Theme: 'a, +    Theme: Catalog + 'a,      Renderer: core::Renderer + 'a,  {      fn from(  | 
