Simplify theming for Checkbox widget

This commit is contained in:
Héctor Ramón Jiménez 2024-03-05 02:08:19 +01:00
parent f4a4845ddb
commit 1f0a0c235a
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
7 changed files with 196 additions and 218 deletions

View file

@ -1,6 +1,5 @@
use iced::executor; use iced::executor;
use iced::font::{self, Font}; use iced::font::{self, Font};
use iced::theme;
use iced::widget::{checkbox, column, container, row, text}; use iced::widget::{checkbox, column, container, row, text};
use iced::{Application, Command, Element, Length, Settings, Theme}; use iced::{Application, Command, Element, Length, Settings, Theme};
@ -71,10 +70,10 @@ impl Application for Example {
}; };
let checkboxes = row![ let checkboxes = row![
styled_checkbox("Primary", theme::Checkbox::Primary), styled_checkbox("Primary", checkbox::primary),
styled_checkbox("Secondary", theme::Checkbox::Secondary), styled_checkbox("Secondary", checkbox::secondary),
styled_checkbox("Success", theme::Checkbox::Success), styled_checkbox("Success", checkbox::success),
styled_checkbox("Danger", theme::Checkbox::Danger), styled_checkbox("Danger", checkbox::danger),
] ]
.spacing(20); .spacing(20);

View file

@ -1 +0,0 @@

View file

@ -1,37 +0,0 @@
//! Change the appearance of a checkbox.
use iced_core::{Background, Border, Color};
/// The appearance of a checkbox.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the checkbox.
pub background: Background,
/// The icon [`Color`] of the checkbox.
pub icon_color: Color,
/// The [`Border`] of hte checkbox.
pub border: Border,
/// The text [`Color`] of the checkbox.
pub text_color: Option<Color>,
}
/// A set of rules that dictate the style of a checkbox.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the active [`Appearance`] of a checkbox.
fn active(&self, style: &Self::Style, is_checked: bool) -> Appearance;
/// Produces the hovered [`Appearance`] of a checkbox.
fn hovered(&self, style: &Self::Style, is_checked: bool) -> Appearance;
/// Produces the disabled [`Appearance`] of a checkbox.
fn disabled(&self, style: &Self::Style, is_checked: bool) -> Appearance {
let active = self.active(style, is_checked);
Appearance {
background: active.background.transparentize(0.5),
..active
}
}
}

View file

@ -17,8 +17,6 @@
pub use iced_core as core; pub use iced_core as core;
pub mod application; pub mod application;
pub mod button;
pub mod checkbox;
pub mod container; pub mod container;
pub mod menu; pub mod menu;
pub mod pane_grid; pub mod pane_grid;

View file

@ -4,7 +4,6 @@ pub mod palette;
pub use palette::Palette; pub use palette::Palette;
use crate::application; use crate::application;
use crate::checkbox;
use crate::container; use crate::container;
use crate::core::widget::text; use crate::core::widget::text;
use crate::menu; use crate::menu;
@ -284,156 +283,6 @@ impl<T: Fn(&Theme) -> application::Appearance> application::StyleSheet for T {
} }
} }
/// The style of a checkbox.
#[derive(Default)]
pub enum Checkbox {
/// The primary style.
#[default]
Primary,
/// The secondary style.
Secondary,
/// The success style.
Success,
/// The danger style.
Danger,
/// A custom style.
Custom(Box<dyn checkbox::StyleSheet<Style = Theme>>),
}
impl checkbox::StyleSheet for Theme {
type Style = Checkbox;
fn active(
&self,
style: &Self::Style,
is_checked: bool,
) -> checkbox::Appearance {
let palette = self.extended_palette();
match style {
Checkbox::Primary => checkbox_appearance(
palette.primary.strong.text,
palette.background.base,
palette.primary.strong,
is_checked,
),
Checkbox::Secondary => checkbox_appearance(
palette.background.base.text,
palette.background.base,
palette.background.strong,
is_checked,
),
Checkbox::Success => checkbox_appearance(
palette.success.base.text,
palette.background.base,
palette.success.base,
is_checked,
),
Checkbox::Danger => checkbox_appearance(
palette.danger.base.text,
palette.background.base,
palette.danger.base,
is_checked,
),
Checkbox::Custom(custom) => custom.active(self, is_checked),
}
}
fn hovered(
&self,
style: &Self::Style,
is_checked: bool,
) -> checkbox::Appearance {
let palette = self.extended_palette();
match style {
Checkbox::Primary => checkbox_appearance(
palette.primary.strong.text,
palette.background.weak,
palette.primary.base,
is_checked,
),
Checkbox::Secondary => checkbox_appearance(
palette.background.base.text,
palette.background.weak,
palette.background.strong,
is_checked,
),
Checkbox::Success => checkbox_appearance(
palette.success.base.text,
palette.background.weak,
palette.success.base,
is_checked,
),
Checkbox::Danger => checkbox_appearance(
palette.danger.base.text,
palette.background.weak,
palette.danger.base,
is_checked,
),
Checkbox::Custom(custom) => custom.hovered(self, is_checked),
}
}
fn disabled(
&self,
style: &Self::Style,
is_checked: bool,
) -> checkbox::Appearance {
let palette = self.extended_palette();
match style {
Checkbox::Primary => checkbox_appearance(
palette.primary.strong.text,
palette.background.weak,
palette.background.strong,
is_checked,
),
Checkbox::Secondary => checkbox_appearance(
palette.background.strong.color,
palette.background.weak,
palette.background.weak,
is_checked,
),
Checkbox::Success => checkbox_appearance(
palette.success.base.text,
palette.background.weak,
palette.success.weak,
is_checked,
),
Checkbox::Danger => checkbox_appearance(
palette.danger.base.text,
palette.background.weak,
palette.danger.weak,
is_checked,
),
Checkbox::Custom(custom) => custom.active(self, is_checked),
}
}
}
fn checkbox_appearance(
icon_color: Color,
base: palette::Pair,
accent: palette::Pair,
is_checked: bool,
) -> checkbox::Appearance {
checkbox::Appearance {
background: Background::Color(if is_checked {
accent.color
} else {
base.color
}),
icon_color,
border: Border {
radius: 2.0.into(),
width: 1.0,
color: accent.color,
},
text_color: None,
}
}
/// The style of a container. /// The style of a container.
#[derive(Default)] #[derive(Default)]
pub enum Container { pub enum Container {

View file

@ -9,10 +9,11 @@ use crate::core::touch;
use crate::core::widget; use crate::core::widget;
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::{ use crate::core::{
Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, Background, Border, Clipboard, Color, Element, Layout, Length, Pixels,
Rectangle, Shell, Size, Widget,
}; };
use crate::style::theme::palette;
pub use crate::style::checkbox::{Appearance, StyleSheet}; use crate::style::Theme;
/// A box that can be checked. /// A box that can be checked.
/// ///
@ -39,7 +40,6 @@ pub struct Checkbox<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Theme: StyleSheet,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
is_checked: bool, is_checked: bool,
@ -53,13 +53,13 @@ pub struct Checkbox<
text_shaping: text::Shaping, text_shaping: text::Shaping,
font: Option<Renderer::Font>, font: Option<Renderer::Font>,
icon: Icon<Renderer::Font>, icon: Icon<Renderer::Font>,
style: <Theme as StyleSheet>::Style, style: fn(&Theme, Status) -> Appearance,
} }
impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer>
where where
Renderer: text::Renderer, Renderer: text::Renderer,
Theme: StyleSheet + crate::text::StyleSheet, Theme: Style,
{ {
/// The default size of a [`Checkbox`]. /// The default size of a [`Checkbox`].
const DEFAULT_SIZE: f32 = 20.0; const DEFAULT_SIZE: f32 = 20.0;
@ -91,7 +91,7 @@ where
line_height: text::LineHeight::default(), line_height: text::LineHeight::default(),
shaping: text::Shaping::Basic, shaping: text::Shaping::Basic,
}, },
style: Default::default(), style: Theme::default(),
} }
} }
@ -174,10 +174,7 @@ where
} }
/// Sets the style of the [`Checkbox`]. /// Sets the style of the [`Checkbox`].
pub fn style( pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
mut self,
style: impl Into<<Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into(); self.style = style.into();
self self
} }
@ -186,7 +183,6 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Checkbox<'a, Message, Theme, Renderer> for Checkbox<'a, Message, Theme, Renderer>
where where
Theme: StyleSheet + crate::text::StyleSheet,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -293,17 +289,20 @@ where
) { ) {
let is_mouse_over = cursor.is_over(layout.bounds()); let is_mouse_over = cursor.is_over(layout.bounds());
let is_disabled = self.on_toggle.is_none(); let is_disabled = self.on_toggle.is_none();
let is_checked = self.is_checked;
let mut children = layout.children(); let mut children = layout.children();
let custom_style = if is_disabled { let status = if is_disabled {
theme.disabled(&self.style, self.is_checked) Status::Disabled { is_checked }
} else if is_mouse_over { } else if is_mouse_over {
theme.hovered(&self.style, self.is_checked) Status::Hovered { is_checked }
} else { } else {
theme.active(&self.style, self.is_checked) Status::Active { is_checked }
}; };
let appearance = (self.style)(theme, status);
{ {
let layout = children.next().unwrap(); let layout = children.next().unwrap();
let bounds = layout.bounds(); let bounds = layout.bounds();
@ -311,10 +310,10 @@ where
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: custom_style.border, border: appearance.border,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
custom_style.background, appearance.background,
); );
let Icon { let Icon {
@ -339,7 +338,7 @@ where
shaping: *shaping, shaping: *shaping,
}, },
bounds.center(), bounds.center(),
custom_style.icon_color, appearance.icon_color,
*viewport, *viewport,
); );
} }
@ -354,7 +353,7 @@ where
label_layout, label_layout,
tree.state.downcast_ref(), tree.state.downcast_ref(),
crate::text::Appearance { crate::text::Appearance {
color: custom_style.text_color, color: appearance.text_color,
}, },
viewport, viewport,
); );
@ -366,7 +365,7 @@ impl<'a, Message, Theme, Renderer> From<Checkbox<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Theme: 'a + StyleSheet + crate::text::StyleSheet, Theme: 'a,
Renderer: 'a + text::Renderer, Renderer: 'a + text::Renderer,
{ {
fn from( fn from(
@ -390,3 +389,174 @@ pub struct Icon<Font> {
/// The shaping strategy of the icon. /// The shaping strategy of the icon.
pub shaping: text::Shaping, pub shaping: text::Shaping,
} }
/// The possible status of a [`Checkbox`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Checkbox`] can be interacted with.
Active {
/// Indicates if the [`Checkbox`] is currently checked.
is_checked: bool,
},
/// The [`Checkbox`] can be interacted with and it is being hovered.
Hovered {
/// Indicates if the [`Checkbox`] is currently checked.
is_checked: bool,
},
/// The [`Checkbox`] cannot be interacted with.
Disabled {
/// Indicates if the [`Checkbox`] is currently checked.
is_checked: bool,
},
}
/// The appearance of a checkbox.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the checkbox.
pub background: Background,
/// The icon [`Color`] of the checkbox.
pub icon_color: Color,
/// The [`Border`] of hte checkbox.
pub border: Border,
/// The text [`Color`] of the checkbox.
pub text_color: Option<Color>,
}
/// A set of rules that dictate the style of a checkbox.
pub trait Style {
/// The supported style of the [`StyleSheet`].
fn default() -> fn(&Self, Status) -> Appearance;
}
impl Style for Theme {
fn default() -> fn(&Self, Status) -> Appearance {
primary
}
}
/// A primary checkbox; denoting a main toggle.
pub fn primary(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
match status {
Status::Active { is_checked } => styled(
palette.primary.strong.text,
palette.background.base,
palette.primary.strong,
is_checked,
),
Status::Hovered { is_checked } => styled(
palette.primary.strong.text,
palette.background.weak,
palette.primary.base,
is_checked,
),
Status::Disabled { is_checked } => styled(
palette.primary.strong.text,
palette.background.weak,
palette.background.strong,
is_checked,
),
}
}
/// A secondary checkbox; denoting a complementary toggle.
pub fn secondary(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
match status {
Status::Active { is_checked } => styled(
palette.background.base.text,
palette.background.base,
palette.background.strong,
is_checked,
),
Status::Hovered { is_checked } => styled(
palette.background.base.text,
palette.background.weak,
palette.background.strong,
is_checked,
),
Status::Disabled { is_checked } => styled(
palette.background.strong.color,
palette.background.weak,
palette.background.weak,
is_checked,
),
}
}
/// A success checkbox; denoting a positive toggle.
pub fn success(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
match status {
Status::Active { is_checked } => styled(
palette.success.base.text,
palette.background.base,
palette.success.base,
is_checked,
),
Status::Hovered { is_checked } => styled(
palette.success.base.text,
palette.background.weak,
palette.success.base,
is_checked,
),
Status::Disabled { is_checked } => styled(
palette.success.base.text,
palette.background.weak,
palette.success.weak,
is_checked,
),
}
}
/// A danger checkbox; denoting a negaive toggle.
pub fn danger(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
match status {
Status::Active { is_checked } => styled(
palette.danger.base.text,
palette.background.base,
palette.danger.base,
is_checked,
),
Status::Hovered { is_checked } => styled(
palette.danger.base.text,
palette.background.weak,
palette.danger.base,
is_checked,
),
Status::Disabled { is_checked } => styled(
palette.danger.base.text,
palette.background.weak,
palette.danger.weak,
is_checked,
),
}
}
fn styled(
icon_color: Color,
base: palette::Pair,
accent: palette::Pair,
is_checked: bool,
) -> Appearance {
Appearance {
background: Background::Color(if is_checked {
accent.color
} else {
base.color
}),
icon_color,
border: Border {
radius: 2.0.into(),
width: 1.0,
color: accent.color,
},
text_color: None,
}
}

View file

@ -161,7 +161,7 @@ pub fn checkbox<'a, Message, Theme, Renderer>(
is_checked: bool, is_checked: bool,
) -> Checkbox<'a, Message, Theme, Renderer> ) -> Checkbox<'a, Message, Theme, Renderer>
where where
Theme: checkbox::StyleSheet + text::StyleSheet, Theme: checkbox::Style,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
{ {
Checkbox::new(label, is_checked) Checkbox::new(label, is_checked)