From d921602cde8248c070b493ce43b0edd388e19b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:37:20 +0200 Subject: [PATCH] feat(progress_bar): custom style --- src/widget/progress_bar/circular.rs | 42 +++++++--- src/widget/progress_bar/linear.rs | 48 +++++++++--- src/widget/progress_bar/style.rs | 116 +++++++++++++++------------- 3 files changed, 128 insertions(+), 78 deletions(-) diff --git a/src/widget/progress_bar/circular.rs b/src/widget/progress_bar/circular.rs index 8da4be14..b1fd8836 100644 --- a/src/widget/progress_bar/circular.rs +++ b/src/widget/progress_bar/circular.rs @@ -1,6 +1,6 @@ //! Show a circular progress indicator. use super::animation::{Animation, Progress}; -use super::style::StyleSheet; +use super::style::{self, Catalog}; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer}; use iced::widget::canvas; @@ -15,11 +15,11 @@ const MAX_WRAP: f32 = 1.0 - MIN_GAP_ANGLE.0 / (2.0 * PI); #[must_use] pub struct Circular where - Theme: StyleSheet, + Theme: Catalog, { size: f32, bar_height: Option, - style: Theme::Style, + class: Theme::Class, cycle_duration: Duration, period: Duration, progress: Option, @@ -27,14 +27,14 @@ where impl Circular where - Theme: StyleSheet, + Theme: Catalog, { /// Creates a new [`Circular`] with the given content. pub fn new() -> Self { Circular { size: 48.0, bar_height: None, - style: Theme::Style::default(), + class: Theme::Class::default(), cycle_duration: Duration::from_millis(1500), period: Duration::from_secs(2), progress: None, @@ -54,9 +54,9 @@ where self } - /// Sets the style variant of this [`Circular`]. - pub fn style(mut self, style: Theme::Style) -> Self { - self.style = style; + /// Sets the style class of this [`Circular`]. + pub fn class(mut self, class: Theme::Class) -> Self { + self.class = class; self } @@ -78,11 +78,29 @@ where self.progress = Some(progress.clamp(0.0, 1.0)); self } + + /// Sets the track color of this [`Circular`]. + pub fn track_color(mut self, color: impl Into) -> Self { + self.class = self.class.track_color(color); + self + } + + /// Sets the bar color of this [`Circular`]. + pub fn bar_color(mut self, color: impl Into) -> Self { + self.class = self.class.bar_color(color); + self + } + + /// Sets the border color of this [`Circular`]. + pub fn border_color(mut self, color: impl Into) -> Self { + self.class = self.class.border_color(color); + self + } } impl Default for Circular where - Theme: StyleSheet, + Theme: Catalog, { fn default() -> Self { Self::new() @@ -99,7 +117,7 @@ struct State { impl Widget for Circular where Message: Clone, - Theme: StyleSheet, + Theme: Catalog, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -170,7 +188,7 @@ where let state = tree.state.downcast_ref::(); let bounds = layout.bounds(); - let custom_style = Theme::appearance(theme, &self.style, self.progress.is_some(), true); + let custom_style = theme.style(&self.class, self.progress.is_some(), true); let geometry = state.cache.draw(renderer, bounds.size(), |frame| { let bar_height = self.bar_height.unwrap_or((frame.width() / 12.0).max(2.0)); @@ -240,7 +258,7 @@ where impl<'a, Message, Theme> From> for Element<'a, Message, Theme, Renderer> where Message: Clone + 'a, - Theme: StyleSheet + 'a, + Theme: Catalog + 'a, { fn from(circular: Circular) -> Self { Self::new(circular) diff --git a/src/widget/progress_bar/linear.rs b/src/widget/progress_bar/linear.rs index 6c06e2f7..c5072b4d 100644 --- a/src/widget/progress_bar/linear.rs +++ b/src/widget/progress_bar/linear.rs @@ -1,6 +1,6 @@ //! Show a linear progress indicator. use super::animation::{Animation, Progress}; -use super::style::StyleSheet; +use super::style::{self, Catalog}; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer}; use iced::{Border, Color, Element, Event, Length, Pixels, Rectangle, Size, mouse, window}; @@ -13,11 +13,11 @@ const WRAP_LENGTH: f32 = 0.618; // avoids animation repetition #[must_use] pub struct Linear where - Theme: StyleSheet, + Theme: Catalog, { width: Length, girth: Length, - style: Theme::Style, + class: Theme::Class, cycle_duration: Duration, period: Duration, progress: Option, @@ -27,14 +27,14 @@ where impl Linear where - Theme: StyleSheet, + Theme: Catalog, { /// Creates a new [`Linear`] with the given content. pub fn new() -> Self { Linear { width: Length::Fixed(100.0), girth: Length::Fixed(4.0), - style: Theme::Style::default(), + class: Theme::Class::default(), cycle_duration: Duration::from_millis(1500), period: Duration::from_secs(2), progress: None, @@ -55,9 +55,9 @@ where self } - /// Sets the style variant of this [`Linear`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + /// Sets the style class of this [`Linear`]. + pub fn class(mut self, class: Theme::Class) -> Self { + self.class = class; self } @@ -99,11 +99,35 @@ where self.segment_spacing = spacing.into().0.max(1.0); self } + + /// Sets the track color of this [`Linear`]. + pub fn track_color(mut self, color: impl Into) -> Self { + self.class = self.class.track_color(color); + self + } + + /// Sets the bar color of this [`Linear`]. + pub fn bar_color(mut self, color: impl Into) -> Self { + self.class = self.class.bar_color(color); + self + } + + /// Sets the border color of this [`Linear`]. + pub fn border_color(mut self, color: impl Into) -> Self { + self.class = self.class.border_color(color); + self + } + + /// Sets the border radius of this [`Linear`]. + pub fn border_radius(mut self, radius: f32) -> Self { + self.class = self.class.border_radius(radius); + self + } } impl Default for Linear where - Theme: StyleSheet, + Theme: Catalog, { fn default() -> Self { Self::new() @@ -119,7 +143,7 @@ struct State { impl Widget for Linear where Message: Clone, - Theme: StyleSheet, + Theme: Catalog, Renderer: advanced::Renderer, { fn tag(&self) -> tree::Tag { @@ -186,7 +210,7 @@ where _viewport: &Rectangle, ) { let bounds = layout.bounds(); - let custom_style = theme.appearance(&self.style, self.progress.is_some(), false); + let custom_style = theme.style(&self.class, self.progress.is_some(), false); let state = tree.state.downcast_ref::(); let border_width = if custom_style.border_color.is_some() { @@ -307,7 +331,7 @@ where impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: Clone + 'a, - Theme: StyleSheet + 'a, + Theme: Catalog + 'a, Renderer: iced::advanced::Renderer + 'a, { fn from(linear: Linear) -> Self { diff --git a/src/widget/progress_bar/style.rs b/src/widget/progress_bar/style.rs index db2fe64d..5447fdec 100644 --- a/src/widget/progress_bar/style.rs +++ b/src/widget/progress_bar/style.rs @@ -1,7 +1,8 @@ use iced::Color; +use palette::WithAlpha; -#[derive(Debug, Clone, Copy)] -pub struct Appearance { +#[derive(Clone, Copy, Debug)] +pub struct Style { /// The track [`Color`] of the progress indicator. pub track_color: Color, /// The bar [`Color`] of the progress indicator. @@ -12,94 +13,101 @@ pub struct Appearance { pub border_radius: f32, } -impl std::default::Default for Appearance { - fn default() -> Self { - Self { - track_color: Color::TRANSPARENT, - bar_color: Color::BLACK, - border_color: None, - border_radius: 0.0, +/// [`Style`] field overrides +#[derive(Clone, Copy, Debug, Default)] +pub struct Class { + pub track_color: Option, + pub bar_color: Option, + pub border_color: Option, + pub border_radius: Option, +} + +impl Class { + pub fn track_color(mut self, color: impl Into) -> Self { + self.track_color = Some(color.into()); + self + } + + pub fn bar_color(mut self, color: impl Into) -> Self { + self.bar_color = Some(color.into()); + self + } + + pub fn border_color(mut self, color: impl Into) -> Self { + self.border_color = Some(color.into()); + self + } + + pub fn border_radius(mut self, radius: f32) -> Self { + self.border_radius = Some(radius); + self + } + + fn resolve(&self, base: Style) -> Style { + Style { + track_color: self.track_color.unwrap_or(base.track_color), + bar_color: self.bar_color.unwrap_or(base.bar_color), + border_color: self.border_color.or(base.border_color), + border_radius: self.border_radius.unwrap_or(base.border_radius), } } } /// A set of rules that dictate the style of an indicator. -pub trait StyleSheet { - /// The supported style of the [`StyleSheet`]. - type Style: Default; +pub trait Catalog: Sized { + /// The supported class of the [`Catalog`]. + type Class: Default; - /// Produces the active [`Appearance`] of a indicator. - fn appearance( - &self, - style: &Self::Style, - is_determinate: bool, - is_circular: bool, - ) -> Appearance; + /// Produces the active [`Style`] of an indicator. + fn style(&self, class: &Self::Class, is_determinate: bool, is_circular: bool) -> Style; } -impl StyleSheet for iced::Theme { - type Style = (); +impl Catalog for iced::Theme { + type Class = Class; - fn appearance( - &self, - _style: &Self::Style, - _is_determinate: bool, - _is_circular: bool, - ) -> Appearance { + fn style(&self, class: &Self::Class, _is_determinate: bool, _is_circular: bool) -> Style { let palette = self.extended_palette(); - - Appearance { + class.resolve(Style { track_color: palette.background.weak.color, bar_color: palette.primary.base.color, border_color: None, border_radius: 0.0, - } + }) } } -impl StyleSheet for crate::Theme { - type Style = (); +impl Catalog for crate::Theme { + type Class = Class; - fn appearance( - &self, - _style: &Self::Style, - is_determinate: bool, - is_circular: bool, - ) -> Appearance { - let cur = self.current_container(); - let mut cur_divider = cur.divider; - cur_divider.alpha = 0.5; + fn style(&self, class: &Self::Class, is_determinate: bool, is_circular: bool) -> Style { let theme = self.cosmic(); - let (mut track_color, bar_color) = if theme.is_dark && theme.is_high_contrast { - ( + let (mut track_color, bar_color) = match (theme.is_dark, theme.is_high_contrast) { + (true, true) => ( theme.palette.neutral_6.into(), theme.accent_text_color().into(), - ) - } else if theme.is_dark { - (theme.palette.neutral_5.into(), theme.accent_color().into()) - } else if theme.is_high_contrast { - ( + ), + (true, false) => (theme.palette.neutral_5.into(), theme.accent_color().into()), + (false, true) => ( theme.palette.neutral_4.into(), theme.accent_text_color().into(), - ) - } else { - (theme.palette.neutral_3.into(), theme.accent_color().into()) + ), + (false, false) => (theme.palette.neutral_3.into(), theme.accent_color().into()), }; if !is_determinate && is_circular { track_color = Color::TRANSPARENT; } - Appearance { + class.resolve(Style { track_color, bar_color, border_color: if is_determinate && theme.is_high_contrast { - Some(cur_divider.into()) + Some(self.current_container().divider.with_alpha(0.5).into()) } else { None }, border_radius: theme.corner_radii.radius_xl[0], - } + }) } }