diff options
author | 2024-03-24 02:08:20 +0100 | |
---|---|---|
committer | 2024-03-24 02:08:20 +0100 | |
commit | e657dc2ecd2ffa72c0abd87f9a59dcc1acbc7083 (patch) | |
tree | 1868220077e1b3f9ea57f601be2bd3aaf7def483 | |
parent | 999ad2d288a9354f045bb2e1b838014b3d302779 (diff) | |
download | iced-e657dc2ecd2ffa72c0abd87f9a59dcc1acbc7083.tar.gz iced-e657dc2ecd2ffa72c0abd87f9a59dcc1acbc7083.tar.bz2 iced-e657dc2ecd2ffa72c0abd87f9a59dcc1acbc7083.zip |
Fine-tune `Catalog` approach for `button`, `checkbox`, and `svg`
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | core/src/closure.rs | 33 | ||||
-rw-r--r-- | core/src/lib.rs | 1 | ||||
-rw-r--r-- | examples/svg/src/main.rs | 2 | ||||
-rw-r--r-- | widget/Cargo.toml | 1 | ||||
-rw-r--r-- | widget/src/button.rs | 85 | ||||
-rw-r--r-- | widget/src/checkbox.rs | 99 | ||||
-rw-r--r-- | widget/src/helpers.rs | 4 | ||||
-rw-r--r-- | widget/src/svg.rs | 89 |
9 files changed, 167 insertions, 149 deletions
@@ -50,7 +50,7 @@ highlighter = ["iced_highlighter"] # Enables experimental multi-window support. multi-window = ["iced_winit/multi-window"] # Enables the advanced module -advanced = [] +advanced = ["iced_widget/advanced"] # Enables embedding Fira Sans as the default font on Wasm builds fira-sans = ["iced_renderer/fira-sans"] # Enables auto-detecting light/dark mode for the built-in theme diff --git a/core/src/closure.rs b/core/src/closure.rs deleted file mode 100644 index dc7b4fee..00000000 --- a/core/src/closure.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Box closures with ease. -//! -//! These are just a bunch of types that wrap boxed closures with -//! blanket [`From`] implementations for easy conversions. -//! -//! Mainly, it allows functions to take `Into<T>` where `T` may end -//! up being a boxed closure. - -/// A boxed closure that takes `A` by reference and produces `O`. -#[allow(missing_debug_implementations)] -pub struct Unary<'a, A, O>(pub Box<dyn Fn(&A) -> O + 'a>); - -impl<'a, A, O, T> From<T> for Unary<'a, A, O> -where - T: Fn(&A) -> O + 'a, -{ - fn from(f: T) -> Self { - Self(Box::new(f)) - } -} - -/// A boxed closure that takes `A` by reference and `B` by value and produces `O`. -#[allow(missing_debug_implementations)] -pub struct Binary<'a, A, B, O>(pub Box<dyn Fn(&A, B) -> O + 'a>); - -impl<'a, A, B, O, T> From<T> for Binary<'a, A, B, O> -where - T: Fn(&A, B) -> O + 'a, -{ - fn from(f: T) -> Self { - Self(Box::new(f)) - } -} diff --git a/core/src/lib.rs b/core/src/lib.rs index 36b47d80..d076413e 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,7 +19,6 @@ pub mod alignment; pub mod border; pub mod clipboard; -pub mod closure; pub mod event; pub mod font; pub mod gradient; diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index cc686dca..0dcf9bc1 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -31,7 +31,7 @@ impl Tiger { )); let svg = svg(handle).width(Length::Fill).height(Length::Fill).style( - |_theme, _status| svg::Appearance { + |_theme, _status| svg::Style { color: if self.apply_color_filter { Some(color!(0x0000ff)) } else { diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 3c9ffddb..a45f47ef 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -21,6 +21,7 @@ svg = ["iced_renderer/svg"] canvas = ["iced_renderer/geometry"] qr_code = ["canvas", "qrcode"] wgpu = ["iced_renderer/wgpu"] +advanced = [] [dependencies] iced_renderer.workspace = true diff --git a/widget/src/button.rs b/widget/src/button.rs index 8754622f..80a18e82 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -1,5 +1,4 @@ //! Allow your users to perform actions by pressing a button. -use crate::core::closure; use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -58,7 +57,7 @@ where height: Length, padding: Padding, clip: bool, - style: Theme::Item<'a>, + class: Theme::Class<'a>, } impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> @@ -80,7 +79,7 @@ where height: size.height.fluid(), padding: DEFAULT_PADDING, clip: false, - style: Theme::default(), + class: Theme::default(), } } @@ -119,18 +118,30 @@ where self } - /// Sets the style variant of this [`Button`]. - pub fn style(mut self, style: impl Into<Theme::Item<'a>>) -> Self { - self.style = style.into(); - self - } - /// Sets whether the contents of the [`Button`] should be clipped on /// overflow. pub fn clip(mut self, clip: bool) -> Self { self.clip = clip; self } + + /// Sets the style of this [`Button`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + Theme::Class<'a>: From<StyleFn<'a, Theme>>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of this [`Button`]. + #[cfg(feature = "advanced")] + #[must_use] + pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { + self.class = class.into(); + self + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -302,19 +313,19 @@ where Status::Active }; - let styling = theme.style(&self.style, status); + let style = theme.style(&self.class, status); - if styling.background.is_some() - || styling.border.width > 0.0 - || styling.shadow.color.a > 0.0 + if style.background.is_some() + || style.border.width > 0.0 + || style.shadow.color.a > 0.0 { renderer.fill_quad( renderer::Quad { bounds, - border: styling.border, - shadow: styling.shadow, + border: style.border, + shadow: style.shadow, }, - styling + style .background .unwrap_or(Background::Color(Color::TRANSPARENT)), ); @@ -331,7 +342,7 @@ where renderer, theme, &renderer::Style { - text_color: styling.text_color, + text_color: style.text_color, }, content_layout, cursor, @@ -405,7 +416,7 @@ pub enum Status { Disabled, } -/// The appearance of a button. +/// The style of a button. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { /// The [`Background`] of the button. @@ -419,7 +430,7 @@ pub struct Style { } impl Style { - /// Updates the [`Appearance`] with the given [`Background`]. + /// Updates the [`Style`] with the given [`Background`]. pub fn with_background(self, background: impl Into<Background>) -> Self { Self { background: Some(background.into()), @@ -428,7 +439,7 @@ impl Style { } } -impl std::default::Default for Style { +impl Default for Style { fn default() -> Self { Self { background: None, @@ -441,30 +452,30 @@ impl std::default::Default for Style { /// The theme catalog of a [`Button`]. pub trait Catalog: Sized { - /// The item type of this [`Catalog`]. - type Item<'a>; + /// The item class of this [`Catalog`]. + type Class<'a>; - /// The default item produced by this [`Catalog`]. - fn default<'a>() -> Self::Item<'a>; + /// The default class produced by this [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; - /// The [`Style`] of an item with the given status. - fn style(&self, item: &Self::Item<'_>, status: Status) -> Style; + /// The [`Style`] of a class with the given status. + fn style(&self, item: &Self::Class<'_>, status: Status) -> Style; } -/// The item of a button [`Catalog`] for the built-in [`Theme`]. +/// A styling function for a [`Button`]. /// /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. -pub type Item<'a, Theme> = closure::Binary<'a, Theme, Status, Style>; +pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>; impl Catalog for Theme { - type Item<'a> = Item<'a, Self>; + type Class<'a> = StyleFn<'a, Self>; - fn default<'a>() -> Self::Item<'a> { - closure::Binary::from(primary) + fn default<'a>() -> Self::Class<'a> { + Box::new(primary) } - fn style(&self, item: &Self::Item<'_>, status: Status) -> Style { - (item.0)(self, status) + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + class(self, status) } } @@ -556,12 +567,12 @@ fn styled(pair: palette::Pair) -> Style { } } -fn disabled(appearance: Style) -> Style { +fn disabled(style: Style) -> Style { Style { - background: appearance + background: style .background .map(|background| background.scale_alpha(0.5)), - text_color: appearance.text_color.scale_alpha(0.5), - ..appearance + text_color: style.text_color.scale_alpha(0.5), + ..style } } diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 15fb8f58..a90914b8 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -39,6 +39,7 @@ pub struct Checkbox< Renderer = crate::Renderer, > where Renderer: text::Renderer, + Theme: Catalog, { is_checked: bool, on_toggle: Option<Box<dyn Fn(bool) -> Message + 'a>>, @@ -51,12 +52,13 @@ pub struct Checkbox< text_shaping: text::Shaping, font: Option<Renderer::Font>, icon: Icon<Renderer::Font>, - style: Style<'a, Theme>, + class: Theme::Class<'a>, } impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer> where Renderer: text::Renderer, + Theme: Catalog, { /// The default size of a [`Checkbox`]. const DEFAULT_SIZE: f32 = 16.0; @@ -69,10 +71,7 @@ where /// It expects: /// * the label of the [`Checkbox`] /// * a boolean describing whether the [`Checkbox`] is checked or not - pub fn new(label: impl Into<String>, is_checked: bool) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn new(label: impl Into<String>, is_checked: bool) -> Self { Checkbox { is_checked, on_toggle: None, @@ -91,7 +90,7 @@ where line_height: text::LineHeight::default(), shaping: text::Shaping::Basic, }, - style: Box::new(Theme::default_style), + class: Theme::default(), } } @@ -173,12 +172,21 @@ where self } - /// Sets the style of the [`Checkbox`]. - pub fn style( - mut self, - style: impl Fn(&Theme, Status) -> Appearance + 'a, - ) -> Self { - self.style = Box::new(style); + /// Sets the style of this [`Checkbox`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + Theme::Class<'a>: From<StyleFn<'a, Theme>>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of this [`Checkbox`]. + #[cfg(feature = "advanced")] + #[must_use] + pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { + self.class = class.into(); self } } @@ -187,6 +195,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Checkbox<'a, Message, Theme, Renderer> where Renderer: text::Renderer, + Theme: Catalog, { fn tag(&self) -> tree::Tag { tree::Tag::of::<widget::text::State<Renderer::Paragraph>>() @@ -285,7 +294,7 @@ where tree: &Tree, renderer: &mut Renderer, theme: &Theme, - style: &renderer::Style, + defaults: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, @@ -304,7 +313,7 @@ where Status::Active { is_checked } }; - let appearance = (self.style)(theme, status); + let style = theme.style(&self.class, status); { let layout = children.next().unwrap(); @@ -313,10 +322,10 @@ where renderer.fill_quad( renderer::Quad { bounds, - border: appearance.border, + border: style.border, ..renderer::Quad::default() }, - appearance.background, + style.background, ); let Icon { @@ -341,7 +350,7 @@ where shaping: *shaping, }, bounds.center(), - appearance.icon_color, + style.icon_color, *viewport, ); } @@ -352,11 +361,11 @@ where crate::text::draw( renderer, - style, + defaults, label_layout, tree.state.downcast_ref(), crate::text::Appearance { - color: appearance.text_color, + color: style.text_color, }, viewport, ); @@ -368,7 +377,7 @@ impl<'a, Message, Theme, Renderer> From<Checkbox<'a, Message, Theme, Renderer>> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: 'a, + Theme: 'a + Catalog, Renderer: 'a + text::Renderer, { fn from( @@ -413,9 +422,9 @@ pub enum Status { }, } -/// The appearance of a checkbox. +/// The style of a checkbox. #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style { /// The [`Background`] of the checkbox. pub background: Background, /// The icon [`Color`] of the checkbox. @@ -426,29 +435,37 @@ pub struct Appearance { pub text_color: Option<Color>, } -/// The style of a [`Checkbox`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of a [`Checkbox`]. +pub trait Catalog: Sized { + /// The item class of this [`Catalog`]. + type Class<'a>; -/// The default style of a [`Checkbox`]. -pub trait DefaultStyle { - /// Returns the default style of a [`Checkbox`]. - fn default_style(&self, status: Status) -> Appearance; + /// The default class produced by this [`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; } -impl DefaultStyle for Theme { - fn default_style(&self, status: Status) -> Appearance { - primary(self, status) +/// A styling function for a [`Checkbox`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>; + +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(primary) } -} -impl DefaultStyle for Appearance { - fn default_style(&self, _status: Status) -> Appearance { - *self + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + class(self, status) } } /// A primary checkbox; denoting a main toggle. -pub fn primary(theme: &Theme, status: Status) -> Appearance { +pub fn primary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -474,7 +491,7 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance { } /// A secondary checkbox; denoting a complementary toggle. -pub fn secondary(theme: &Theme, status: Status) -> Appearance { +pub fn secondary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -500,7 +517,7 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance { } /// A success checkbox; denoting a positive toggle. -pub fn success(theme: &Theme, status: Status) -> Appearance { +pub fn success(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -526,7 +543,7 @@ pub fn success(theme: &Theme, status: Status) -> Appearance { } /// A danger checkbox; denoting a negaive toggle. -pub fn danger(theme: &Theme, status: Status) -> Appearance { +pub fn danger(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -556,8 +573,8 @@ fn styled( base: palette::Pair, accent: palette::Pair, is_checked: bool, -) -> Appearance { - Appearance { +) -> Style { + Style { background: Background::Color(if is_checked { accent.color } else { diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e60096d1..4c4d1012 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -161,7 +161,7 @@ pub fn checkbox<'a, Message, Theme, Renderer>( is_checked: bool, ) -> Checkbox<'a, Message, Theme, Renderer> where - Theme: checkbox::DefaultStyle + 'a, + Theme: checkbox::Catalog + 'a, Renderer: core::text::Renderer, { Checkbox::new(label, is_checked) @@ -367,7 +367,7 @@ pub fn svg<'a, Theme>( handle: impl Into<core::svg::Handle>, ) -> crate::Svg<'a, Theme> where - Theme: crate::svg::DefaultStyle + 'a, + Theme: crate::svg::Catalog, { crate::Svg::new(handle) } diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 1ac07ade..d2fd0aee 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -20,36 +20,36 @@ pub use crate::core::svg::Handle; /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. #[allow(missing_debug_implementations)] -pub struct Svg<'a, Theme = crate::Theme> { +pub struct Svg<'a, Theme = crate::Theme> +where + Theme: Catalog, +{ handle: Handle, width: Length, height: Length, content_fit: ContentFit, - style: Style<'a, Theme>, + class: Theme::Class<'a>, } -impl<'a, Theme> Svg<'a, Theme> { +impl<'a, Theme> Svg<'a, Theme> +where + Theme: Catalog, +{ /// Creates a new [`Svg`] from the given [`Handle`]. - pub fn new(handle: impl Into<Handle>) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn new(handle: impl Into<Handle>) -> Self { Svg { handle: handle.into(), width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, - style: Box::new(Theme::default_style), + class: Theme::default(), } } /// Creates a new [`Svg`] that will display the contents of the file at the /// provided path. #[must_use] - pub fn from_path(path: impl Into<PathBuf>) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn from_path(path: impl Into<PathBuf>) -> Self { Self::new(Handle::from_path(path)) } @@ -78,13 +78,21 @@ impl<'a, Theme> Svg<'a, Theme> { } } - /// Sets the style variant of this [`Svg`]. + /// Sets the style of this [`Svg`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + Theme::Class<'a>: From<StyleFn<'a, Theme>>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of this [`Svg`]. + #[cfg(feature = "advanced")] #[must_use] - pub fn style( - mut self, - style: impl Fn(&Theme, Status) -> Appearance + 'a, - ) -> Self { - self.style = Box::new(style); + pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { + self.class = class.into(); self } } @@ -93,6 +101,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Svg<'a, Theme> where Renderer: svg::Renderer, + Theme: Catalog, { fn size(&self) -> Size<Length> { Size { @@ -167,7 +176,7 @@ where Status::Idle }; - let appearance = (self.style)(theme, status); + let appearance = theme.style(&self.class, status); renderer.draw( self.handle.clone(), @@ -189,7 +198,7 @@ where impl<'a, Message, Theme, Renderer> From<Svg<'a, Theme>> for Element<'a, Message, Theme, Renderer> where - Theme: 'a, + Theme: Catalog + 'a, Renderer: svg::Renderer + 'a, { fn from(icon: Svg<'a, Theme>) -> Element<'a, Message, Theme, Renderer> { @@ -208,7 +217,7 @@ pub enum Status { /// The appearance of an [`Svg`]. #[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct Appearance { +pub struct Style { /// The [`Color`] filter of an [`Svg`]. /// /// Useful for coloring a symbolic icon. @@ -217,23 +226,37 @@ pub struct Appearance { pub color: Option<Color>, } -/// The style of an [`Svg`]. -pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; +/// The theme catalog of an [`Svg`]. +pub trait Catalog { + /// The item class of this [`Catalog`]. + type Class<'a>; + + /// The default class produced by this [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; -/// The default style of an [`Svg`]. -pub trait DefaultStyle { - /// Returns the default style of an [`Svg`]. - fn default_style(&self, status: Status) -> Appearance; + /// The [`Style`] of a class with the given status. + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style; } -impl DefaultStyle for Theme { - fn default_style(&self, _status: Status) -> Appearance { - Appearance::default() +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(|_theme, _status| Style::default()) + } + + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + class(self, status) } } -impl DefaultStyle for Appearance { - fn default_style(&self, _status: Status) -> Appearance { - *self +/// A styling function for an [`Svg`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>; + +impl<'a, Theme> From<Style> for StyleFn<'a, Theme> { + fn from(style: Style) -> Self { + Box::new(move |_theme, _status| style) } } |