diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 66e2d066c6..12f6956a39 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -18,6 +18,7 @@ pub use text::{LineHeight, Shaping}; #[allow(missing_debug_implementations)] pub struct Text<'a, Theme, Renderer> where + Theme: Catalog, Renderer: text::Renderer, { content: Cow<'a, str>, @@ -29,18 +30,16 @@ where vertical_alignment: alignment::Vertical, font: Option, shaping: Shaping, - style: Style<'a, Theme>, + class: Theme::Class<'a>, } impl<'a, Theme, Renderer> Text<'a, Theme, Renderer> where + Theme: Catalog, Renderer: text::Renderer, { /// Create a new fragment of [`Text`] with the given contents. - pub fn new(content: impl Into>) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn new(content: impl Into>) -> Self { Text { content: content.into(), size: None, @@ -51,7 +50,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: Shaping::Basic, - style: Box::new(Theme::default_style), + class: Theme::default(), } } @@ -75,25 +74,6 @@ where self } - /// Sets the style of the [`Text`]. - pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self { - self.style = Box::new(style); - self - } - - /// Sets the [`Color`] of the [`Text`]. - pub fn color(self, color: impl Into) -> Self { - self.color_maybe(Some(color)) - } - - /// Sets the [`Color`] of the [`Text`], if `Some`. - pub fn color_maybe(mut self, color: Option>) -> Self { - let color = color.map(Into::into); - - self.style = Box::new(move |_theme| Appearance { color }); - self - } - /// Sets the width of the [`Text`] boundaries. pub fn width(mut self, width: impl Into) -> Self { self.width = width.into(); @@ -129,6 +109,42 @@ where self.shaping = shaping; self } + + /// Sets the style of the [`Text`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the [`Color`] of the [`Text`]. + pub fn color(self, color: impl Into) -> Self + where + Theme::Class<'a>: From>, + { + self.color_maybe(Some(color)) + } + + /// Sets the [`Color`] of the [`Text`], if `Some`. + pub fn color_maybe(self, color: Option>) -> Self + where + Theme::Class<'a>: From>, + { + let color = color.map(Into::into); + + self.style(move |_theme| Style { color }) + } + + /// Sets the style class of the [`Text`]. + #[cfg(feature = "advanced")] + #[must_use] + pub fn class(mut self, class: impl Into>) -> Self { + self.class = class.into(); + self + } } /// The internal state of a [`Text`] widget. @@ -138,6 +154,7 @@ pub struct State(P); impl<'a, Message, Theme, Renderer> Widget for Text<'a, Theme, Renderer> where + Theme: Catalog, Renderer: text::Renderer, { fn tag(&self) -> tree::Tag { @@ -182,15 +199,15 @@ where tree: &Tree, renderer: &mut Renderer, theme: &Theme, - style: &renderer::Style, + defaults: &renderer::Style, layout: Layout<'_>, _cursor_position: mouse::Cursor, viewport: &Rectangle, ) { let state = tree.state.downcast_ref::>(); - let appearance = (self.style)(theme); + let style = theme.style(&self.class); - draw(renderer, style, layout, state, appearance, viewport); + draw(renderer, defaults, layout, state, style, viewport); } } @@ -250,7 +267,7 @@ pub fn draw( style: &renderer::Style, layout: Layout<'_>, state: &State, - appearance: Appearance, + appearance: Style, viewport: &Rectangle, ) where Renderer: text::Renderer, @@ -281,7 +298,7 @@ pub fn draw( impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where - Theme: 'a, + Theme: Catalog + 'a, Renderer: text::Renderer + 'a, { fn from( @@ -293,7 +310,7 @@ where impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer> where - Theme: DefaultStyle + 'a, + Theme: Catalog + 'a, Renderer: text::Renderer, { fn from(content: &'a str) -> Self { @@ -304,7 +321,7 @@ where impl<'a, Message, Theme, Renderer> From<&'a str> for Element<'a, Message, Theme, Renderer> where - Theme: DefaultStyle + 'a, + Theme: Catalog + 'a, Renderer: text::Renderer + 'a, { fn from(content: &'a str) -> Self { @@ -314,30 +331,38 @@ where /// The appearance of some text. #[derive(Debug, Clone, Copy, Default)] -pub struct Appearance { +pub struct Style { /// The [`Color`] of the text. /// /// The default, `None`, means using the inherited color. pub color: Option, } -/// The style of some [`Text`]. -pub type Style<'a, Theme> = Box Appearance + 'a>; +/// The theme catalog of a [`Text`]. +pub trait Catalog: Sized { + /// The item class of this [`Catalog`]. + type Class<'a>; -/// The default style of some [`Text`]. -pub trait DefaultStyle { - /// Returns the default style of some [`Text`]. - fn default_style(&self) -> Appearance; + /// The default class produced by this [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; + + /// The [`Style`] of a class with the given status. + fn style(&self, item: &Self::Class<'_>) -> Style; } -impl DefaultStyle for Theme { - fn default_style(&self) -> Appearance { - Appearance::default() +/// A styling function for a [`Text`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box Style + 'a>; + +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(|_theme| Style::default()) } -} -impl DefaultStyle for Color { - fn default_style(&self) -> Appearance { - Appearance { color: Some(*self) } + fn style(&self, class: &Self::Class<'_>) -> Style { + class(self) } } diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs index d562608742..b2c71b3f74 100644 --- a/examples/component/src/main.rs +++ b/examples/component/src/main.rs @@ -73,10 +73,7 @@ mod numeric_input { impl Component for NumericInput where - Theme: text::DefaultStyle - + button::Catalog - + text_input::DefaultStyle - + 'static, + Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static, { type State = (); type Event = Event; @@ -151,10 +148,7 @@ mod numeric_input { impl<'a, Message, Theme> From> for Element<'a, Message, Theme> where - Theme: text::DefaultStyle - + button::Catalog - + text_input::DefaultStyle - + 'static, + Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static, Message: 'a, { fn from(numeric_input: NumericInput) -> Self { diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index 22c21cdd66..2b906c3260 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -60,7 +60,7 @@ impl Gradient { } = *self; let gradient_box = container(horizontal_space()) - .style(move |_theme, _status| { + .style(move |_theme| { let gradient = gradient::Linear::new(angle) .add_stop(0.0, start) .add_stop(1.0, end); diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 713e2b70e0..66d7909118 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -81,10 +81,10 @@ impl Layout { } else { self.example.view() }) - .style(|theme, _status| { + .style(|theme| { let palette = theme.extended_palette(); - container::Appearance::default() + container::Style::default() .with_border(palette.background.strong.color, 4.0) }) .padding(4) @@ -245,10 +245,10 @@ fn application<'a>() -> Element<'a, Message> { .padding(10) .align_items(Alignment::Center), ) - .style(|theme, _status| { + .style(|theme| { let palette = theme.extended_palette(); - container::Appearance::default() + container::Style::default() .with_border(palette.background.strong.color, 1) }); diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 829996d87e..1711278553 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -338,39 +338,30 @@ mod style { use iced::widget::container; use iced::{Border, Theme}; - pub fn title_bar_active( - theme: &Theme, - _status: container::Status, - ) -> container::Appearance { + pub fn title_bar_active(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); - container::Appearance { + container::Style { text_color: Some(palette.background.strong.text), background: Some(palette.background.strong.color.into()), ..Default::default() } } - pub fn title_bar_focused( - theme: &Theme, - _status: container::Status, - ) -> container::Appearance { + pub fn title_bar_focused(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); - container::Appearance { + container::Style { text_color: Some(palette.primary.strong.text), background: Some(palette.primary.strong.color.into()), ..Default::default() } } - pub fn pane_active( - theme: &Theme, - _status: container::Status, - ) -> container::Appearance { + pub fn pane_active(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); - container::Appearance { + container::Style { background: Some(palette.background.weak.color.into()), border: Border { width: 2.0, @@ -381,13 +372,10 @@ mod style { } } - pub fn pane_focused( - theme: &Theme, - _status: container::Status, - ) -> container::Appearance { + pub fn pane_focused(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); - container::Appearance { + container::Style { background: Some(palette.background.weak.color.into()), border: Border { width: 2.0, diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 240ae908c1..c02e754bed 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -341,8 +341,8 @@ impl Default for ScrollableDemo { } } -fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance { - progress_bar::Appearance { +fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Style { + progress_bar::Style { background: theme.extended_palette().background.strong.color.into(), bar: Color::from_rgb8(250, 85, 134).into(), border: Border::default(), diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index fdae1dc177..9968962c43 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -651,45 +651,33 @@ mod toast { } } - fn styled(pair: theme::palette::Pair) -> container::Appearance { - container::Appearance { + fn styled(pair: theme::palette::Pair) -> container::Style { + container::Style { background: Some(pair.color.into()), text_color: pair.text.into(), ..Default::default() } } - fn primary( - theme: &Theme, - _status: container::Status, - ) -> container::Appearance { + fn primary(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); styled(palette.primary.weak) } - fn secondary( - theme: &Theme, - _status: container::Status, - ) -> container::Appearance { + fn secondary(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); styled(palette.secondary.weak) } - fn success( - theme: &Theme, - _status: container::Status, - ) -> container::Appearance { + fn success(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); styled(palette.success.weak) } - fn danger( - theme: &Theme, - _status: container::Status, - ) -> container::Appearance { + fn danger(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); styled(palette.danger.weak) diff --git a/widget/src/button.rs b/widget/src/button.rs index 80a18e8214..dc9496713c 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -125,7 +125,7 @@ where self } - /// Sets the style of this [`Button`]. + /// Sets the style of the [`Button`]. #[must_use] pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self where @@ -135,7 +135,7 @@ where self } - /// Sets the style class of this [`Button`]. + /// Sets the style class of the [`Button`]. #[cfg(feature = "advanced")] #[must_use] pub fn class(mut self, class: impl Into>) -> Self { @@ -451,20 +451,18 @@ impl Default for Style { } /// The theme catalog of a [`Button`]. -pub trait Catalog: Sized { - /// The item class of this [`Catalog`]. +pub trait Catalog { + /// The item class of the [`Catalog`]. type Class<'a>; - /// The default class produced by this [`Catalog`]. + /// 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, item: &Self::Class<'_>, status: Status) -> Style; + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style; } /// A styling function for a [`Button`]. -/// -/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. pub type StyleFn<'a, Theme> = Box Style + 'a>; impl Catalog for Theme { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index a90914b8e3..48f6abf663 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -172,7 +172,7 @@ where self } - /// Sets the style of this [`Checkbox`]. + /// Sets the style of the [`Checkbox`]. #[must_use] pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self where @@ -182,7 +182,7 @@ where self } - /// Sets the style class of this [`Checkbox`]. + /// Sets the style class of the [`Checkbox`]. #[cfg(feature = "advanced")] #[must_use] pub fn class(mut self, class: impl Into>) -> Self { @@ -364,7 +364,7 @@ where defaults, label_layout, tree.state.downcast_ref(), - crate::text::Appearance { + crate::text::Style { color: style.text_color, }, viewport, @@ -437,14 +437,14 @@ pub struct Style { /// The theme catalog of a [`Checkbox`]. pub trait Catalog: Sized { - /// The item class of this [`Catalog`]. + /// The item class of the [`Catalog`]. type Class<'a>; - /// The default class produced by this [`Catalog`]. + /// 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, item: &Self::Class<'_>, status: Status) -> Style; + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style; } /// A styling function for a [`Checkbox`]. diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index ee24d7429a..df5358d6f9 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, @@ -42,7 +43,7 @@ pub struct ComboBox< on_option_hovered: Option Message>>, on_close: Option, on_input: Option Message>>, - menu_style: menu::Style<'a, Theme>, + menu_class: ::Class<'a>, padding: Padding, size: Option, } @@ -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,9 @@ 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); let selection = selection.map(T::to_string).unwrap_or_default(); @@ -84,7 +77,7 @@ where on_option_hovered: None, on_input: None, on_close: None, - menu_style: style.menu, + menu_class: ::default(), padding: text_input::DEFAULT_PADDING, size: None, } @@ -124,18 +117,6 @@ where self } - /// Sets the style of the [`ComboBox`]. - pub fn style(mut self, style: impl Into>) -> 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 +154,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 + ::Class<'a>: + From>, + { + 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 + ::Class<'a>: From>, + { + 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<::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<::Class<'a>>, + ) -> Self { + self.menu_class = class.into(); + self + } } /// The local state of a [`ComboBox`]. @@ -296,6 +326,7 @@ impl<'a, T, Message, Theme, Renderer> Widget where T: Display + Clone + 'static, Message: Clone, + Theme: Catalog, Renderer: text::Renderer, { fn size(&self) -> Size { @@ -686,7 +717,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 +743,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 +751,11 @@ where } } +/// The theme catalog of a [`ComboBox`]. +pub trait Catalog: text_input::Catalog + menu::Catalog {} + +impl Catalog for Theme {} + fn search<'a, T, A>( options: impl IntoIterator + 'a, option_matchers: impl IntoIterator + 'a, @@ -762,30 +798,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 7c133588ed..21405722ef 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, 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>, - ) -> 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>, - 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>, + { + 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>) -> Self { + self.class = class.into(); + self + } } impl<'a, Message, Theme, Renderer> Widget 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> 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: &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> { /// 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, /// 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, @@ -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) -> Self { Self { background: Some(background.into()), @@ -539,99 +534,78 @@ impl Appearance { } } -impl From for Appearance { +impl From for Style { fn from(color: Color) -> Self { Self::default().with_background(color) } } -impl From for Appearance { +impl From for Style { fn from(gradient: Gradient) -> Self { Self::default().with_background(gradient) } } -impl From for Appearance { +impl From 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 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 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, _status: Status) -> Appearance { - Appearance::default() +pub fn transparent(_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 4c4d101216..77b308821b 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>, ) -> 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>, ) -> Scrollable<'a, Message, Theme, Renderer> where - Theme: scrollable::DefaultStyle + 'a, + Theme: scrollable::Catalog + 'a, Renderer: core::Renderer, { Scrollable::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()) @@ -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 + 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 + 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 + '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) -> 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) -> 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) } @@ -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 0364f980d4..3d9d6910ef 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -20,12 +20,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 +41,17 @@ pub struct Menu< text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, - style: &'a Style<'a, Theme>, + class: &'a ::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 +61,7 @@ where hovered_option: &'a mut Option, on_selected: impl FnMut(T) -> Message + 'a, on_option_hovered: Option<&'a dyn Fn(T) -> Message>, - style: &'a Style<'a, Theme>, + class: &'a ::Class<'b>, ) -> Self { Menu { state, @@ -70,7 +75,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, font: None, - style, + class, } } @@ -153,8 +158,9 @@ 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, @@ -162,18 +168,19 @@ where container: Container<'a, Message, Theme, Renderer>, width: f32, target_height: f32, - style: &'a Style<'a, Theme>, + class: &'a ::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( position: Point, - menu: Menu<'a, T, Message, Theme, Renderer>, + menu: Menu<'a, 'b, T, Message, Theme, Renderer>, target_height: f32, ) -> Self where @@ -191,28 +198,24 @@ 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 container = Container::new(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<_, _, _>); @@ -222,15 +225,16 @@ where container, width, target_height, - style, + class, } } } -impl<'a, Message, Theme, Renderer> +impl<'a, 'b, Message, Theme, Renderer> crate::core::Overlay - 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 { @@ -293,30 +297,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.container.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 +334,14 @@ where text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, - style: &'a dyn Fn(&Theme) -> Appearance, + class: &'a ::Class<'b>, } -impl<'a, T, Message, Theme, Renderer> Widget - for List<'a, T, Message, Theme, Renderer> +impl<'a, 'b, T, Message, Theme, Renderer> Widget + for List<'a, 'b, T, Message, Theme, Renderer> where T: Clone + ToString, + Theme: Catalog, Renderer: text::Renderer, { fn size(&self) -> Size { @@ -477,7 +484,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 +514,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 +538,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 +548,24 @@ where } } -impl<'a, T, Message, Theme, Renderer> - From> +impl<'a, 'b, T, Message, Theme, Renderer> + From> 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 +578,38 @@ 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 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 + container::Catalog { + /// The item class of the [`Catalog`]. + type Class<'a>; + + /// The default class produced by the [`Catalog`]. + fn default<'a>() -> ::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 [`Style`] of a class with the given status. + fn style(&self, class: &::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 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 beac0bd8a2..acfa9d44d6 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 Message + 'a>>, on_drag: Option Message + 'a>>, on_resize: Option<(f32, Box Message + 'a>)>, - style: Style<'a, Theme>, + class: ::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( state: &'a State, 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: ::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 + ::Class<'a>: From>, + { + 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<::Class<'a>>, + ) -> Self { + self.class = class.into(); self } @@ -233,7 +248,8 @@ where impl<'a, Message, Theme, Renderer> Widget for PaneGrid<'a, Message, Theme, Renderer> where - Renderer: crate::core::Renderer, + Theme: Catalog, + Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -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> 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 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>() -> ::Class<'a>; + + /// The [`Style`] of a class with the given status. + fn style(&self, class: &::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 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 98f4f99af3..30ad52ca41 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>, 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>) -> Self - where - Theme: container::DefaultStyle + 'a, - { + pub fn new(body: impl Into>) -> 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>, + { + 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>) -> 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 for Content<'a, Message, Theme, Renderer> where T: Into>, - 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 8dfea6e319..c2eeebb76d 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>, 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>, - ) -> 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>, + { + 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>) -> 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 52d5439705..e9c33d3d6d 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 + 'a, + Theme: Catalog, Renderer: text::Renderer, { on_select: Box Message + 'a>, @@ -47,7 +48,8 @@ pub struct PickList< text_shaping: text::Shaping, font: Option, handle: Handle, - style: Style<'a, Theme>, + class: ::Class<'a>, + menu_class: ::Class<'a>, } impl<'a, T, L, V, Message, Theme, Renderer> @@ -57,6 +59,7 @@ where L: Borrow<[T]> + 'a, V: Borrow + '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, 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: ::default(), + menu_class: ::default(), } } @@ -151,8 +152,23 @@ where } /// Sets the style of the [`PickList`]. - pub fn style(mut self, style: impl Into>) -> Self { - self.style = style.into(); + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + ::Class<'a>: From>, + { + 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<::Class<'a>>, + ) -> Self { + self.class = class.into(); self } } @@ -164,6 +180,7 @@ where L: Borrow<[T]>, V: Borrow, 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 + '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,44 @@ 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 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>() -> ::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 [`Style`] of a class with the given status. + fn style( + &self, + class: &::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 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 +743,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 38d8da85b7..e7821b431b 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; /// /// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png) #[allow(missing_debug_implementations)] -pub struct ProgressBar<'a, Theme = crate::Theme> { +pub struct ProgressBar<'a, Theme = crate::Theme> +where + Theme: Catalog, +{ range: RangeInclusive, value: f32, width: Length, height: Option, - 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, value: f32) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn new(range: RangeInclusive, 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>, + { + 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>) -> Self { + self.class = class.into(); self } } @@ -74,7 +90,8 @@ impl<'a, Theme> ProgressBar<'a, Theme> { impl<'a, Message, Theme, Renderer> Widget for ProgressBar<'a, Theme> where - Renderer: crate::core::Renderer, + Theme: Catalog, + Renderer: core::Renderer, { fn size(&self) -> Size { 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> 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 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 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, bar: impl Into, -) -> 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 90c0c97044..3cc2c2948c 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -20,22 +20,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(), } } @@ -46,14 +49,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>, + { + 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>) -> Self { + self.class = class.into(); self } } -impl<'a, Message, Theme> Widget - for QRCode<'a, Theme> +impl<'a, Message, Theme> Widget for QRCode<'a, Theme> +where + Theme: Catalog, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -97,13 +113,13 @@ impl<'a, Message, Theme> Widget 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 @@ -115,7 +131,7 @@ impl<'a, Message, Theme> Widget 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 @@ -134,7 +150,7 @@ impl<'a, Message, Theme> Widget frame.fill_rectangle( Point::new(column as f32, row as f32), Size::UNIT, - appearance.cell, + style.cell, ); }); }); @@ -151,7 +167,7 @@ impl<'a, Message, Theme> Widget impl<'a, Message, Theme> From> for Element<'a, Message, Theme, Renderer> where - Theme: 'a, + Theme: Catalog + 'a, { fn from(qr_code: QRCode<'a, Theme>) -> Self { Self::new(qr_code) @@ -323,44 +339,50 @@ impl From for Error { #[derive(Default)] struct State { - last_appearance: RefCell>, + last_style: RefCell>, } /// 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 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 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 a7b7dd036a..6b22961db4 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, - 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>, + { + 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>) -> Self { + self.class = class.into(); self } } @@ -188,6 +198,7 @@ impl<'a, Message, Theme, Renderer> Widget 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> 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, } -/// The style of a [`Radio`] button. -pub type Style<'a, Theme> = Box 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 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 9fa5f74fb1..1a536d2fac 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) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn horizontal(height: impl Into) -> 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) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn vertical(width: impl Into) -> 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>, + { + 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>) -> Self { + self.class = class.into(); self } } @@ -54,7 +67,8 @@ impl<'a, Theme> Rule<'a, Theme> { impl<'a, Message, Theme, Renderer> Widget for Rule<'a, Theme> where - Renderer: crate::core::Renderer, + Renderer: core::Renderer, + Theme: Catalog, { fn size(&self) -> Size { 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> 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 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 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 c03bbb7db1..84e9ac1551 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, width: Length, @@ -36,20 +37,18 @@ pub struct Scrollable< direction: Direction, content: Element<'a, Message, Theme, Renderer>, on_scroll: Option 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>, - ) -> Self - where - Theme: DefaultStyle + 'a, - { + ) -> Self { Self::with_direction(content, Direction::default()) } @@ -57,18 +56,6 @@ where pub fn with_direction( content: impl Into>, 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>, - 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>, + { + 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>) -> Self { + self.class = class.into(); self } } @@ -237,7 +233,8 @@ pub enum Alignment { impl<'a, Message, Theme, Renderer> Widget for Scrollable<'a, Message, Theme, Renderer> where - Renderer: crate::core::Renderer, + Theme: Catalog, + Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -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 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 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 d3b46a98c1..a8f1d19250 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; /// /// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true) #[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, step: T, shift_step: Option, @@ -49,13 +52,14 @@ pub struct Slider<'a, T, Message, Theme = crate::Theme> { on_release: Option, 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 + 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(range: RangeInclusive, 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) -> 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>, + { + 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>) -> Self { + self.class = class.into(); + self + } } impl<'a, T, Message, Theme, Renderer> Widget @@ -159,7 +171,8 @@ impl<'a, T, Message, Theme, Renderer> Widget where T: Copy + Into + num_traits::FromPrimitive, Message: Clone, - Renderer: crate::core::Renderer, + Theme: Catalog, + Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -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> where T: Copy + Into + 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) -> Self { self.handle.shape = HandleShape::Circle { @@ -549,29 +562,35 @@ pub enum HandleShape { }, } -/// The style of a [`Slider`]. -pub type Style<'a, Theme> = Box 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 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 d2fd0aee10..6698809710 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -78,7 +78,7 @@ where } } - /// Sets the style 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 @@ -88,7 +88,7 @@ where self } - /// Sets the style class of this [`Svg`]. + /// Sets the style class of the [`Svg`]. #[cfg(feature = "advanced")] #[must_use] pub fn class(mut self, class: impl Into>) -> Self { @@ -176,11 +176,11 @@ where Status::Idle }; - let appearance = theme.style(&self.class, status); + let style = theme.style(&self.class, status); renderer.draw( self.handle.clone(), - appearance.color, + style.color, drawing_bounds + offset, ); }; @@ -228,10 +228,10 @@ pub struct Style { /// The theme catalog of an [`Svg`]. pub trait Catalog { - /// The item class of this [`Catalog`]. + /// The item class of the [`Catalog`]. type Class<'a>; - /// The default class produced by this [`Catalog`]. + /// The default class produced by the [`Catalog`]. fn default<'a>() -> Self::Class<'a>; /// The [`Style`] of a class with the given status. diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 5b8f6a1b72..a00df3c749 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, @@ -41,7 +42,7 @@ pub struct TextEditor< width: Length, height: Length, padding: Padding, - style: Style<'a, Theme>, + class: Theme::Class<'a>, on_edit: Option 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) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn new(content: &'a Content) -> 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>, + { + 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>) -> Self { + self.class = class.into(); self } } @@ -309,6 +318,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> Widget 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 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 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 b161ec74f9..dafe2fca83 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, @@ -75,7 +76,7 @@ pub struct TextInput< on_paste: Option Message + 'a>>, on_submit: Option, icon: Option>, - 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>, + { + 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>) -> 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 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> 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 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 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 a7eabd2c46..f4597458e7 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 fc9e06e1d4..ca6e37b0c0 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, - 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>, + { + 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>) -> Self { + self.class = class.into(); self } } @@ -157,6 +167,7 @@ where impl<'a, Message, Theme, Renderer> Widget 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> 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 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 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 32c962fc48..39f2e07de0 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>, tooltip: impl Into>, 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>, + { + 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>) -> Self { + self.class = class.into(); self } } @@ -91,6 +102,7 @@ where impl<'a, Message, Theme, Renderer> Widget for Tooltip<'a, Message, Theme, Renderer> where + Theme: container::Catalog, Renderer: text::Renderer, { fn children(&self) -> Vec { @@ -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> 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 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 2aa8f4d1f0..defb442ff5 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, step: T, shift_step: Option, @@ -51,13 +53,14 @@ pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> { on_release: Option, 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 + 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(range: RangeInclusive, 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>, + { + 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>) -> Self { + self.class = class.into(); + self + } } impl<'a, T, Message, Theme, Renderer> Widget @@ -161,6 +172,7 @@ impl<'a, T, Message, Theme, Renderer> Widget where T: Copy + Into + 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 + num_traits::FromPrimitive + 'a, Message: Clone + 'a, - Theme: 'a, + Theme: Catalog + 'a, Renderer: core::Renderer + 'a, { fn from(