feat(progress_bar): custom style
This commit is contained in:
parent
d9431dc367
commit
d921602cde
3 changed files with 128 additions and 78 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
//! Show a circular progress indicator.
|
//! Show a circular progress indicator.
|
||||||
use super::animation::{Animation, Progress};
|
use super::animation::{Animation, Progress};
|
||||||
use super::style::StyleSheet;
|
use super::style::{self, Catalog};
|
||||||
use iced::advanced::widget::tree::{self, Tree};
|
use iced::advanced::widget::tree::{self, Tree};
|
||||||
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
|
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
|
||||||
use iced::widget::canvas;
|
use iced::widget::canvas;
|
||||||
|
|
@ -15,11 +15,11 @@ const MAX_WRAP: f32 = 1.0 - MIN_GAP_ANGLE.0 / (2.0 * PI);
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct Circular<Theme>
|
pub struct Circular<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog,
|
||||||
{
|
{
|
||||||
size: f32,
|
size: f32,
|
||||||
bar_height: Option<f32>,
|
bar_height: Option<f32>,
|
||||||
style: Theme::Style,
|
class: Theme::Class,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
period: Duration,
|
period: Duration,
|
||||||
progress: Option<f32>,
|
progress: Option<f32>,
|
||||||
|
|
@ -27,14 +27,14 @@ where
|
||||||
|
|
||||||
impl<Theme> Circular<Theme>
|
impl<Theme> Circular<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog<Class = style::Class>,
|
||||||
{
|
{
|
||||||
/// Creates a new [`Circular`] with the given content.
|
/// Creates a new [`Circular`] with the given content.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Circular {
|
Circular {
|
||||||
size: 48.0,
|
size: 48.0,
|
||||||
bar_height: None,
|
bar_height: None,
|
||||||
style: Theme::Style::default(),
|
class: Theme::Class::default(),
|
||||||
cycle_duration: Duration::from_millis(1500),
|
cycle_duration: Duration::from_millis(1500),
|
||||||
period: Duration::from_secs(2),
|
period: Duration::from_secs(2),
|
||||||
progress: None,
|
progress: None,
|
||||||
|
|
@ -54,9 +54,9 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style variant of this [`Circular`].
|
/// Sets the style class of this [`Circular`].
|
||||||
pub fn style(mut self, style: Theme::Style) -> Self {
|
pub fn class(mut self, class: Theme::Class) -> Self {
|
||||||
self.style = style;
|
self.class = class;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,11 +78,29 @@ where
|
||||||
self.progress = Some(progress.clamp(0.0, 1.0));
|
self.progress = Some(progress.clamp(0.0, 1.0));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the track color of this [`Circular`].
|
||||||
|
pub fn track_color(mut self, color: impl Into<iced::Color>) -> 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<iced::Color>) -> 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<iced::Color>) -> Self {
|
||||||
|
self.class = self.class.border_color(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Theme> Default for Circular<Theme>
|
impl<Theme> Default for Circular<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog<Class = style::Class>,
|
||||||
{
|
{
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
|
@ -99,7 +117,7 @@ struct State {
|
||||||
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
|
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Theme: StyleSheet,
|
Theme: Catalog,
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
tree::Tag::of::<State>()
|
tree::Tag::of::<State>()
|
||||||
|
|
@ -170,7 +188,7 @@ where
|
||||||
|
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
let bounds = layout.bounds();
|
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 geometry = state.cache.draw(renderer, bounds.size(), |frame| {
|
||||||
let bar_height = self.bar_height.unwrap_or((frame.width() / 12.0).max(2.0));
|
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<Circular<Theme>> for Element<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme> From<Circular<Theme>> for Element<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone + 'a,
|
Message: Clone + 'a,
|
||||||
Theme: StyleSheet + 'a,
|
Theme: Catalog + 'a,
|
||||||
{
|
{
|
||||||
fn from(circular: Circular<Theme>) -> Self {
|
fn from(circular: Circular<Theme>) -> Self {
|
||||||
Self::new(circular)
|
Self::new(circular)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Show a linear progress indicator.
|
//! Show a linear progress indicator.
|
||||||
use super::animation::{Animation, Progress};
|
use super::animation::{Animation, Progress};
|
||||||
use super::style::StyleSheet;
|
use super::style::{self, Catalog};
|
||||||
use iced::advanced::widget::tree::{self, Tree};
|
use iced::advanced::widget::tree::{self, Tree};
|
||||||
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
|
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
|
||||||
use iced::{Border, Color, Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
|
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]
|
#[must_use]
|
||||||
pub struct Linear<Theme>
|
pub struct Linear<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog,
|
||||||
{
|
{
|
||||||
width: Length,
|
width: Length,
|
||||||
girth: Length,
|
girth: Length,
|
||||||
style: Theme::Style,
|
class: Theme::Class,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
period: Duration,
|
period: Duration,
|
||||||
progress: Option<f32>,
|
progress: Option<f32>,
|
||||||
|
|
@ -27,14 +27,14 @@ where
|
||||||
|
|
||||||
impl<Theme> Linear<Theme>
|
impl<Theme> Linear<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog<Class = style::Class>,
|
||||||
{
|
{
|
||||||
/// Creates a new [`Linear`] with the given content.
|
/// Creates a new [`Linear`] with the given content.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Linear {
|
Linear {
|
||||||
width: Length::Fixed(100.0),
|
width: Length::Fixed(100.0),
|
||||||
girth: Length::Fixed(4.0),
|
girth: Length::Fixed(4.0),
|
||||||
style: Theme::Style::default(),
|
class: Theme::Class::default(),
|
||||||
cycle_duration: Duration::from_millis(1500),
|
cycle_duration: Duration::from_millis(1500),
|
||||||
period: Duration::from_secs(2),
|
period: Duration::from_secs(2),
|
||||||
progress: None,
|
progress: None,
|
||||||
|
|
@ -55,9 +55,9 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style variant of this [`Linear`].
|
/// Sets the style class of this [`Linear`].
|
||||||
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
|
pub fn class(mut self, class: Theme::Class) -> Self {
|
||||||
self.style = style.into();
|
self.class = class;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,11 +99,35 @@ where
|
||||||
self.segment_spacing = spacing.into().0.max(1.0);
|
self.segment_spacing = spacing.into().0.max(1.0);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the track color of this [`Linear`].
|
||||||
|
pub fn track_color(mut self, color: impl Into<iced::Color>) -> 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<iced::Color>) -> 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<iced::Color>) -> 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<Theme> Default for Linear<Theme>
|
impl<Theme> Default for Linear<Theme>
|
||||||
where
|
where
|
||||||
Theme: StyleSheet,
|
Theme: Catalog<Class = style::Class>,
|
||||||
{
|
{
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
|
@ -119,7 +143,7 @@ struct State {
|
||||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Theme: StyleSheet,
|
Theme: Catalog,
|
||||||
Renderer: advanced::Renderer,
|
Renderer: advanced::Renderer,
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
|
|
@ -186,7 +210,7 @@ where
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let bounds = layout.bounds();
|
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::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
let border_width = if custom_style.border_color.is_some() {
|
let border_width = if custom_style.border_color.is_some() {
|
||||||
|
|
@ -307,7 +331,7 @@ where
|
||||||
impl<'a, Message, Theme, Renderer> From<Linear<Theme>> for Element<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> From<Linear<Theme>> for Element<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone + 'a,
|
Message: Clone + 'a,
|
||||||
Theme: StyleSheet + 'a,
|
Theme: Catalog + 'a,
|
||||||
Renderer: iced::advanced::Renderer + 'a,
|
Renderer: iced::advanced::Renderer + 'a,
|
||||||
{
|
{
|
||||||
fn from(linear: Linear<Theme>) -> Self {
|
fn from(linear: Linear<Theme>) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use iced::Color;
|
use iced::Color;
|
||||||
|
use palette::WithAlpha;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Appearance {
|
pub struct Style {
|
||||||
/// The track [`Color`] of the progress indicator.
|
/// The track [`Color`] of the progress indicator.
|
||||||
pub track_color: Color,
|
pub track_color: Color,
|
||||||
/// The bar [`Color`] of the progress indicator.
|
/// The bar [`Color`] of the progress indicator.
|
||||||
|
|
@ -12,94 +13,101 @@ pub struct Appearance {
|
||||||
pub border_radius: f32,
|
pub border_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::default::Default for Appearance {
|
/// [`Style`] field overrides
|
||||||
fn default() -> Self {
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
Self {
|
pub struct Class {
|
||||||
track_color: Color::TRANSPARENT,
|
pub track_color: Option<Color>,
|
||||||
bar_color: Color::BLACK,
|
pub bar_color: Option<Color>,
|
||||||
border_color: None,
|
pub border_color: Option<Color>,
|
||||||
border_radius: 0.0,
|
pub border_radius: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Class {
|
||||||
|
pub fn track_color(mut self, color: impl Into<Color>) -> Self {
|
||||||
|
self.track_color = Some(color.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bar_color(mut self, color: impl Into<Color>) -> Self {
|
||||||
|
self.bar_color = Some(color.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn border_color(mut self, color: impl Into<Color>) -> 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.
|
/// A set of rules that dictate the style of an indicator.
|
||||||
pub trait StyleSheet {
|
pub trait Catalog: Sized {
|
||||||
/// The supported style of the [`StyleSheet`].
|
/// The supported class of the [`Catalog`].
|
||||||
type Style: Default;
|
type Class: Default;
|
||||||
|
|
||||||
/// Produces the active [`Appearance`] of a indicator.
|
/// Produces the active [`Style`] of an indicator.
|
||||||
fn appearance(
|
fn style(&self, class: &Self::Class, is_determinate: bool, is_circular: bool) -> Style;
|
||||||
&self,
|
|
||||||
style: &Self::Style,
|
|
||||||
is_determinate: bool,
|
|
||||||
is_circular: bool,
|
|
||||||
) -> Appearance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleSheet for iced::Theme {
|
impl Catalog for iced::Theme {
|
||||||
type Style = ();
|
type Class = Class;
|
||||||
|
|
||||||
fn appearance(
|
fn style(&self, class: &Self::Class, _is_determinate: bool, _is_circular: bool) -> Style {
|
||||||
&self,
|
|
||||||
_style: &Self::Style,
|
|
||||||
_is_determinate: bool,
|
|
||||||
_is_circular: bool,
|
|
||||||
) -> Appearance {
|
|
||||||
let palette = self.extended_palette();
|
let palette = self.extended_palette();
|
||||||
|
class.resolve(Style {
|
||||||
Appearance {
|
|
||||||
track_color: palette.background.weak.color,
|
track_color: palette.background.weak.color,
|
||||||
bar_color: palette.primary.base.color,
|
bar_color: palette.primary.base.color,
|
||||||
border_color: None,
|
border_color: None,
|
||||||
border_radius: 0.0,
|
border_radius: 0.0,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleSheet for crate::Theme {
|
impl Catalog for crate::Theme {
|
||||||
type Style = ();
|
type Class = Class;
|
||||||
|
|
||||||
fn appearance(
|
fn style(&self, class: &Self::Class, is_determinate: bool, is_circular: bool) -> Style {
|
||||||
&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;
|
|
||||||
let theme = self.cosmic();
|
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.palette.neutral_6.into(),
|
||||||
theme.accent_text_color().into(),
|
theme.accent_text_color().into(),
|
||||||
)
|
),
|
||||||
} else if theme.is_dark {
|
(true, false) => (theme.palette.neutral_5.into(), theme.accent_color().into()),
|
||||||
(theme.palette.neutral_5.into(), theme.accent_color().into())
|
(false, true) => (
|
||||||
} else if theme.is_high_contrast {
|
|
||||||
(
|
|
||||||
theme.palette.neutral_4.into(),
|
theme.palette.neutral_4.into(),
|
||||||
theme.accent_text_color().into(),
|
theme.accent_text_color().into(),
|
||||||
)
|
),
|
||||||
} else {
|
(false, false) => (theme.palette.neutral_3.into(), theme.accent_color().into()),
|
||||||
(theme.palette.neutral_3.into(), theme.accent_color().into())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_determinate && is_circular {
|
if !is_determinate && is_circular {
|
||||||
track_color = Color::TRANSPARENT;
|
track_color = Color::TRANSPARENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
Appearance {
|
class.resolve(Style {
|
||||||
track_color,
|
track_color,
|
||||||
bar_color,
|
bar_color,
|
||||||
border_color: if is_determinate && theme.is_high_contrast {
|
border_color: if is_determinate && theme.is_high_contrast {
|
||||||
Some(cur_divider.into())
|
Some(self.current_container().divider.with_alpha(0.5).into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
border_radius: theme.corner_radii.radius_xl[0],
|
border_radius: theme.corner_radii.radius_xl[0],
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue