Merge pull request #2350 from iced-rs/theming-revolutions

Theming Revolutions
This commit is contained in:
Héctor Ramón 2024-03-25 22:21:22 +01:00 committed by GitHub
commit eae4065300
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1340 additions and 1053 deletions

View file

@ -52,7 +52,7 @@ highlighter = ["iced_highlighter"]
# Enables experimental multi-window support. # Enables experimental multi-window support.
multi-window = ["iced_winit/multi-window"] multi-window = ["iced_winit/multi-window"]
# Enables the advanced module # Enables the advanced module
advanced = [] advanced = ["iced_widget/advanced"]
# Enables embedding Fira Sans as the default font on Wasm builds # Enables embedding Fira Sans as the default font on Wasm builds
fira-sans = ["iced_renderer/fira-sans"] fira-sans = ["iced_renderer/fira-sans"]
# Enables auto-detecting light/dark mode for the built-in theme # Enables auto-detecting light/dark mode for the built-in theme

View file

@ -18,6 +18,7 @@ pub use text::{LineHeight, Shaping};
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Text<'a, Theme, Renderer> pub struct Text<'a, Theme, Renderer>
where where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
content: Cow<'a, str>, content: Cow<'a, str>,
@ -29,18 +30,16 @@ where
vertical_alignment: alignment::Vertical, vertical_alignment: alignment::Vertical,
font: Option<Renderer::Font>, font: Option<Renderer::Font>,
shaping: Shaping, shaping: Shaping,
style: Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer> impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
where where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
/// Create a new fragment of [`Text`] with the given contents. /// Create a new fragment of [`Text`] with the given contents.
pub fn new(content: impl Into<Cow<'a, str>>) -> Self pub fn new(content: impl Into<Cow<'a, str>>) -> Self {
where
Theme: DefaultStyle + 'a,
{
Text { Text {
content: content.into(), content: content.into(),
size: None, size: None,
@ -51,7 +50,7 @@ where
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
shaping: Shaping::Basic, shaping: Shaping::Basic,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -75,25 +74,6 @@ where
self 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<Color>) -> Self {
self.color_maybe(Some(color))
}
/// Sets the [`Color`] of the [`Text`], if `Some`.
pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
let color = color.map(Into::into);
self.style = Box::new(move |_theme| Appearance { color });
self
}
/// Sets the width of the [`Text`] boundaries. /// Sets the width of the [`Text`] boundaries.
pub fn width(mut self, width: impl Into<Length>) -> Self { pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into(); self.width = width.into();
@ -129,6 +109,42 @@ where
self.shaping = shaping; self.shaping = shaping;
self 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<StyleFn<'a, Theme>>,
{
self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
self
}
/// Sets the [`Color`] of the [`Text`].
pub fn color(self, color: impl Into<Color>) -> Self
where
Theme::Class<'a>: From<StyleFn<'a, Theme>>,
{
self.color_maybe(Some(color))
}
/// Sets the [`Color`] of the [`Text`], if `Some`.
pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
where
Theme::Class<'a>: From<StyleFn<'a, Theme>>,
{
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self
}
} }
/// The internal state of a [`Text`] widget. /// The internal state of a [`Text`] widget.
@ -138,6 +154,7 @@ pub struct State<P: Paragraph>(P);
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Text<'a, Theme, Renderer> for Text<'a, Theme, Renderer>
where where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -182,15 +199,15 @@ where
tree: &Tree, tree: &Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &Theme, theme: &Theme,
style: &renderer::Style, defaults: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor_position: mouse::Cursor, _cursor_position: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>(); let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
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<Renderer>(
style: &renderer::Style, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
state: &State<Renderer::Paragraph>, state: &State<Renderer::Paragraph>,
appearance: Appearance, appearance: Style,
viewport: &Rectangle, viewport: &Rectangle,
) where ) where
Renderer: text::Renderer, Renderer: text::Renderer,
@ -281,7 +298,7 @@ pub fn draw<Renderer>(
impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>> impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Theme: 'a, Theme: Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn from( fn from(
@ -293,7 +310,7 @@ where
impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer> impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
where where
Theme: DefaultStyle + 'a, Theme: Catalog + 'a,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn from(content: &'a str) -> Self { fn from(content: &'a str) -> Self {
@ -304,7 +321,7 @@ where
impl<'a, Message, Theme, Renderer> From<&'a str> impl<'a, Message, Theme, Renderer> From<&'a str>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Theme: DefaultStyle + 'a, Theme: Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn from(content: &'a str) -> Self { fn from(content: &'a str) -> Self {
@ -314,30 +331,38 @@ where
/// The appearance of some text. /// The appearance of some text.
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct Appearance { pub struct Style {
/// The [`Color`] of the text. /// The [`Color`] of the text.
/// ///
/// The default, `None`, means using the inherited color. /// The default, `None`, means using the inherited color.
pub color: Option<Color>, pub color: Option<Color>,
} }
/// The style of some [`Text`]. /// The theme catalog of a [`Text`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>; pub trait Catalog: Sized {
/// The item class of this [`Catalog`].
type Class<'a>;
/// The default style of some [`Text`]. /// The default class produced by this [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of some [`Text`].
fn default_style(&self) -> Appearance; /// The [`Style`] of a class with the given status.
fn style(&self, item: &Self::Class<'_>) -> Style;
} }
impl DefaultStyle for Theme { /// A styling function for a [`Text`].
fn default_style(&self) -> Appearance { ///
Appearance::default() /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
} pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
}
impl Catalog for Theme {
impl DefaultStyle for Color { type Class<'a> = StyleFn<'a, Self>;
fn default_style(&self) -> Appearance {
Appearance { color: Some(*self) } fn default<'a>() -> Self::Class<'a> {
Box::new(|_theme| Style::default())
}
fn style(&self, class: &Self::Class<'_>) -> Style {
class(self)
} }
} }

View file

@ -73,10 +73,7 @@ mod numeric_input {
impl<Message, Theme> Component<Message, Theme> for NumericInput<Message> impl<Message, Theme> Component<Message, Theme> for NumericInput<Message>
where where
Theme: text::DefaultStyle Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static,
+ button::DefaultStyle
+ text_input::DefaultStyle
+ 'static,
{ {
type State = (); type State = ();
type Event = Event; type Event = Event;
@ -151,10 +148,7 @@ mod numeric_input {
impl<'a, Message, Theme> From<NumericInput<Message>> impl<'a, Message, Theme> From<NumericInput<Message>>
for Element<'a, Message, Theme> for Element<'a, Message, Theme>
where where
Theme: text::DefaultStyle Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static,
+ button::DefaultStyle
+ text_input::DefaultStyle
+ 'static,
Message: 'a, Message: 'a,
{ {
fn from(numeric_input: NumericInput<Message>) -> Self { fn from(numeric_input: NumericInput<Message>) -> Self {

View file

@ -60,7 +60,7 @@ impl Gradient {
} = *self; } = *self;
let gradient_box = container(horizontal_space()) let gradient_box = container(horizontal_space())
.style(move |_theme, _status| { .style(move |_theme| {
let gradient = gradient::Linear::new(angle) let gradient = gradient::Linear::new(angle)
.add_stop(0.0, start) .add_stop(0.0, start)
.add_stop(1.0, end); .add_stop(1.0, end);

View file

@ -81,10 +81,10 @@ impl Layout {
} else { } else {
self.example.view() self.example.view()
}) })
.style(|theme, _status| { .style(|theme| {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
container::Appearance::default() container::Style::default()
.with_border(palette.background.strong.color, 4.0) .with_border(palette.background.strong.color, 4.0)
}) })
.padding(4) .padding(4)
@ -245,10 +245,10 @@ fn application<'a>() -> Element<'a, Message> {
.padding(10) .padding(10)
.align_items(Alignment::Center), .align_items(Alignment::Center),
) )
.style(|theme, _status| { .style(|theme| {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
container::Appearance::default() container::Style::default()
.with_border(palette.background.strong.color, 1) .with_border(palette.background.strong.color, 1)
}); });

View file

@ -338,39 +338,30 @@ mod style {
use iced::widget::container; use iced::widget::container;
use iced::{Border, Theme}; use iced::{Border, Theme};
pub fn title_bar_active( pub fn title_bar_active(theme: &Theme) -> container::Style {
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
container::Appearance { container::Style {
text_color: Some(palette.background.strong.text), text_color: Some(palette.background.strong.text),
background: Some(palette.background.strong.color.into()), background: Some(palette.background.strong.color.into()),
..Default::default() ..Default::default()
} }
} }
pub fn title_bar_focused( pub fn title_bar_focused(theme: &Theme) -> container::Style {
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
container::Appearance { container::Style {
text_color: Some(palette.primary.strong.text), text_color: Some(palette.primary.strong.text),
background: Some(palette.primary.strong.color.into()), background: Some(palette.primary.strong.color.into()),
..Default::default() ..Default::default()
} }
} }
pub fn pane_active( pub fn pane_active(theme: &Theme) -> container::Style {
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
container::Appearance { container::Style {
background: Some(palette.background.weak.color.into()), background: Some(palette.background.weak.color.into()),
border: Border { border: Border {
width: 2.0, width: 2.0,
@ -381,13 +372,10 @@ mod style {
} }
} }
pub fn pane_focused( pub fn pane_focused(theme: &Theme) -> container::Style {
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
container::Appearance { container::Style {
background: Some(palette.background.weak.color.into()), background: Some(palette.background.weak.color.into()),
border: Border { border: Border {
width: 2.0, width: 2.0,

View file

@ -341,8 +341,8 @@ impl Default for ScrollableDemo {
} }
} }
fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance { fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Style {
progress_bar::Appearance { progress_bar::Style {
background: theme.extended_palette().background.strong.color.into(), background: theme.extended_palette().background.strong.color.into(),
bar: Color::from_rgb8(250, 85, 134).into(), bar: Color::from_rgb8(250, 85, 134).into(),
border: Border::default(), border: Border::default(),

View file

@ -31,7 +31,7 @@ impl Tiger {
)); ));
let svg = svg(handle).width(Length::Fill).height(Length::Fill).style( 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 { color: if self.apply_color_filter {
Some(color!(0x0000ff)) Some(color!(0x0000ff))
} else { } else {

View file

@ -651,45 +651,33 @@ mod toast {
} }
} }
fn styled(pair: theme::palette::Pair) -> container::Appearance { fn styled(pair: theme::palette::Pair) -> container::Style {
container::Appearance { container::Style {
background: Some(pair.color.into()), background: Some(pair.color.into()),
text_color: pair.text.into(), text_color: pair.text.into(),
..Default::default() ..Default::default()
} }
} }
fn primary( fn primary(theme: &Theme) -> container::Style {
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
styled(palette.primary.weak) styled(palette.primary.weak)
} }
fn secondary( fn secondary(theme: &Theme) -> container::Style {
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
styled(palette.secondary.weak) styled(palette.secondary.weak)
} }
fn success( fn success(theme: &Theme) -> container::Style {
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
styled(palette.success.weak) styled(palette.success.weak)
} }
fn danger( fn danger(theme: &Theme) -> container::Style {
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
styled(palette.danger.weak) styled(palette.danger.weak)

View file

@ -21,6 +21,7 @@ svg = ["iced_renderer/svg"]
canvas = ["iced_renderer/geometry"] canvas = ["iced_renderer/geometry"]
qr_code = ["canvas", "qrcode"] qr_code = ["canvas", "qrcode"]
wgpu = ["iced_renderer/wgpu"] wgpu = ["iced_renderer/wgpu"]
advanced = []
[dependencies] [dependencies]
iced_renderer.workspace = true iced_renderer.workspace = true

View file

@ -49,6 +49,7 @@ use crate::core::{
pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
where where
Renderer: crate::core::Renderer, Renderer: crate::core::Renderer,
Theme: Catalog,
{ {
content: Element<'a, Message, Theme, Renderer>, content: Element<'a, Message, Theme, Renderer>,
on_press: Option<Message>, on_press: Option<Message>,
@ -56,20 +57,18 @@ where
height: Length, height: Length,
padding: Padding, padding: Padding,
clip: bool, clip: bool,
style: Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Renderer: crate::core::Renderer,
Theme: Catalog,
{ {
/// Creates a new [`Button`] with the given content. /// Creates a new [`Button`] with the given content.
pub fn new( pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self ) -> Self {
where
Theme: DefaultStyle + 'a,
{
let content = content.into(); let content = content.into();
let size = content.as_widget().size_hint(); let size = content.as_widget().size_hint();
@ -80,7 +79,7 @@ where
height: size.height.fluid(), height: size.height.fluid(),
padding: DEFAULT_PADDING, padding: DEFAULT_PADDING,
clip: false, clip: false,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -119,21 +118,30 @@ where
self self
} }
/// Sets the style variant of this [`Button`].
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 [`Button`] should be clipped on /// Sets whether the contents of the [`Button`] should be clipped on
/// overflow. /// overflow.
pub fn clip(mut self, clip: bool) -> Self { pub fn clip(mut self, clip: bool) -> Self {
self.clip = clip; self.clip = clip;
self self
} }
/// Sets the style of the [`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 the [`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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@ -146,6 +154,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where where
Message: 'a + Clone, Message: 'a + Clone,
Renderer: 'a + crate::core::Renderer, Renderer: 'a + crate::core::Renderer,
Theme: Catalog,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>() tree::Tag::of::<State>()
@ -304,19 +313,19 @@ where
Status::Active Status::Active
}; };
let styling = (self.style)(theme, status); let style = theme.style(&self.class, status);
if styling.background.is_some() if style.background.is_some()
|| styling.border.width > 0.0 || style.border.width > 0.0
|| styling.shadow.color.a > 0.0 || style.shadow.color.a > 0.0
{ {
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: styling.border, border: style.border,
shadow: styling.shadow, shadow: style.shadow,
}, },
styling style
.background .background
.unwrap_or(Background::Color(Color::TRANSPARENT)), .unwrap_or(Background::Color(Color::TRANSPARENT)),
); );
@ -333,7 +342,7 @@ where
renderer, renderer,
theme, theme,
&renderer::Style { &renderer::Style {
text_color: styling.text_color, text_color: style.text_color,
}, },
content_layout, content_layout,
cursor, cursor,
@ -378,7 +387,7 @@ impl<'a, Message, Theme, Renderer> From<Button<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: Clone + 'a, Message: Clone + 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: crate::core::Renderer + 'a, Renderer: crate::core::Renderer + 'a,
{ {
fn from(button: Button<'a, Message, Theme, Renderer>) -> Self { fn from(button: Button<'a, Message, Theme, Renderer>) -> Self {
@ -407,9 +416,9 @@ pub enum Status {
Disabled, Disabled,
} }
/// The appearance of a button. /// The style of a button.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance { pub struct Style {
/// The [`Background`] of the button. /// The [`Background`] of the button.
pub background: Option<Background>, pub background: Option<Background>,
/// The text [`Color`] of the button. /// The text [`Color`] of the button.
@ -420,8 +429,8 @@ pub struct Appearance {
pub shadow: Shadow, pub shadow: Shadow,
} }
impl Appearance { 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 { pub fn with_background(self, background: impl Into<Background>) -> Self {
Self { Self {
background: Some(background.into()), background: Some(background.into()),
@ -430,7 +439,7 @@ impl Appearance {
} }
} }
impl std::default::Default for Appearance { impl Default for Style {
fn default() -> Self { fn default() -> Self {
Self { Self {
background: None, background: None,
@ -441,41 +450,41 @@ impl std::default::Default for Appearance {
} }
} }
/// The style of a [`Button`]. /// The theme catalog of a [`Button`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub trait Catalog {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`Button`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`Button`].
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 { /// A styling function for a [`Button`].
fn default_style(&self, status: Status) -> Appearance { pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
primary(self, status)
impl Catalog for Theme {
type Class<'a> = StyleFn<'a, Self>;
fn default<'a>() -> Self::Class<'a> {
Box::new(primary)
} }
}
impl DefaultStyle for Appearance { fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
fn default_style(&self, _status: Status) -> Appearance { class(self, status)
*self
}
}
impl DefaultStyle for Color {
fn default_style(&self, _status: Status) -> Appearance {
Appearance::default().with_background(*self)
} }
} }
/// A primary button; denoting a main action. /// A primary button; denoting a main action.
pub fn primary(theme: &Theme, status: Status) -> Appearance { pub fn primary(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
let base = styled(palette.primary.strong); let base = styled(palette.primary.strong);
match status { match status {
Status::Active | Status::Pressed => base, Status::Active | Status::Pressed => base,
Status::Hovered => Appearance { Status::Hovered => Style {
background: Some(Background::Color(palette.primary.base.color)), background: Some(Background::Color(palette.primary.base.color)),
..base ..base
}, },
@ -484,13 +493,13 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance {
} }
/// A secondary button; denoting a complementary action. /// A secondary button; denoting a complementary action.
pub fn secondary(theme: &Theme, status: Status) -> Appearance { pub fn secondary(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
let base = styled(palette.secondary.base); let base = styled(palette.secondary.base);
match status { match status {
Status::Active | Status::Pressed => base, Status::Active | Status::Pressed => base,
Status::Hovered => Appearance { Status::Hovered => Style {
background: Some(Background::Color(palette.secondary.strong.color)), background: Some(Background::Color(palette.secondary.strong.color)),
..base ..base
}, },
@ -499,13 +508,13 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance {
} }
/// A success button; denoting a good outcome. /// A success button; denoting a good outcome.
pub fn success(theme: &Theme, status: Status) -> Appearance { pub fn success(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
let base = styled(palette.success.base); let base = styled(palette.success.base);
match status { match status {
Status::Active | Status::Pressed => base, Status::Active | Status::Pressed => base,
Status::Hovered => Appearance { Status::Hovered => Style {
background: Some(Background::Color(palette.success.strong.color)), background: Some(Background::Color(palette.success.strong.color)),
..base ..base
}, },
@ -514,13 +523,13 @@ pub fn success(theme: &Theme, status: Status) -> Appearance {
} }
/// A danger button; denoting a destructive action. /// A danger button; denoting a destructive action.
pub fn danger(theme: &Theme, status: Status) -> Appearance { pub fn danger(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
let base = styled(palette.danger.base); let base = styled(palette.danger.base);
match status { match status {
Status::Active | Status::Pressed => base, Status::Active | Status::Pressed => base,
Status::Hovered => Appearance { Status::Hovered => Style {
background: Some(Background::Color(palette.danger.strong.color)), background: Some(Background::Color(palette.danger.strong.color)),
..base ..base
}, },
@ -529,17 +538,17 @@ pub fn danger(theme: &Theme, status: Status) -> Appearance {
} }
/// A text button; useful for links. /// A text button; useful for links.
pub fn text(theme: &Theme, status: Status) -> Appearance { pub fn text(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
let base = Appearance { let base = Style {
text_color: palette.background.base.text, text_color: palette.background.base.text,
..Appearance::default() ..Style::default()
}; };
match status { match status {
Status::Active | Status::Pressed => base, Status::Active | Status::Pressed => base,
Status::Hovered => Appearance { Status::Hovered => Style {
text_color: palette.background.base.text.scale_alpha(0.8), text_color: palette.background.base.text.scale_alpha(0.8),
..base ..base
}, },
@ -547,21 +556,21 @@ pub fn text(theme: &Theme, status: Status) -> Appearance {
} }
} }
fn styled(pair: palette::Pair) -> Appearance { fn styled(pair: palette::Pair) -> Style {
Appearance { Style {
background: Some(Background::Color(pair.color)), background: Some(Background::Color(pair.color)),
text_color: pair.text, text_color: pair.text,
border: Border::rounded(2), border: Border::rounded(2),
..Appearance::default() ..Style::default()
} }
} }
fn disabled(appearance: Appearance) -> Appearance { fn disabled(style: Style) -> Style {
Appearance { Style {
background: appearance background: style
.background .background
.map(|background| background.scale_alpha(0.5)), .map(|background| background.scale_alpha(0.5)),
text_color: appearance.text_color.scale_alpha(0.5), text_color: style.text_color.scale_alpha(0.5),
..appearance ..style
} }
} }

View file

@ -39,6 +39,7 @@ pub struct Checkbox<
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Renderer: text::Renderer, Renderer: text::Renderer,
Theme: Catalog,
{ {
is_checked: bool, is_checked: bool,
on_toggle: Option<Box<dyn Fn(bool) -> Message + 'a>>, on_toggle: Option<Box<dyn Fn(bool) -> Message + 'a>>,
@ -51,12 +52,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: Style<'a, Theme>, class: Theme::Class<'a>,
} }
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: Catalog,
{ {
/// The default size of a [`Checkbox`]. /// The default size of a [`Checkbox`].
const DEFAULT_SIZE: f32 = 16.0; const DEFAULT_SIZE: f32 = 16.0;
@ -69,10 +71,7 @@ where
/// It expects: /// It expects:
/// * the label of the [`Checkbox`] /// * the label of the [`Checkbox`]
/// * a boolean describing whether the [`Checkbox`] is checked or not /// * a boolean describing whether the [`Checkbox`] is checked or not
pub fn new(label: impl Into<String>, is_checked: bool) -> Self pub fn new(label: impl Into<String>, is_checked: bool) -> Self {
where
Theme: DefaultStyle + 'a,
{
Checkbox { Checkbox {
is_checked, is_checked,
on_toggle: None, on_toggle: None,
@ -91,7 +90,7 @@ where
line_height: text::LineHeight::default(), line_height: text::LineHeight::default(),
shaping: text::Shaping::Basic, shaping: text::Shaping::Basic,
}, },
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -174,11 +173,20 @@ where
} }
/// Sets the style of the [`Checkbox`]. /// Sets the style of the [`Checkbox`].
pub fn style( #[must_use]
mut self, pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
style: impl Fn(&Theme, Status) -> Appearance + 'a, where
) -> Self { Theme::Class<'a>: From<StyleFn<'a, Theme>>,
self.style = Box::new(style); {
self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
self
}
/// Sets the style class of the [`Checkbox`].
#[cfg(feature = "advanced")]
#[must_use]
pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
@ -187,6 +195,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Checkbox<'a, Message, Theme, Renderer> for Checkbox<'a, Message, Theme, Renderer>
where where
Renderer: text::Renderer, Renderer: text::Renderer,
Theme: Catalog,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
tree::Tag::of::<widget::text::State<Renderer::Paragraph>>() tree::Tag::of::<widget::text::State<Renderer::Paragraph>>()
@ -285,7 +294,7 @@ where
tree: &Tree, tree: &Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &Theme, theme: &Theme,
style: &renderer::Style, defaults: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
@ -304,7 +313,7 @@ where
Status::Active { is_checked } Status::Active { is_checked }
}; };
let appearance = (self.style)(theme, status); let style = theme.style(&self.class, status);
{ {
let layout = children.next().unwrap(); let layout = children.next().unwrap();
@ -313,10 +322,10 @@ where
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: appearance.border, border: style.border,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.background, style.background,
); );
let Icon { let Icon {
@ -341,7 +350,7 @@ where
shaping: *shaping, shaping: *shaping,
}, },
bounds.center(), bounds.center(),
appearance.icon_color, style.icon_color,
*viewport, *viewport,
); );
} }
@ -352,11 +361,11 @@ where
crate::text::draw( crate::text::draw(
renderer, renderer,
style, defaults,
label_layout, label_layout,
tree.state.downcast_ref(), tree.state.downcast_ref(),
crate::text::Appearance { crate::text::Style {
color: appearance.text_color, color: style.text_color,
}, },
viewport, viewport,
); );
@ -368,7 +377,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, Theme: 'a + Catalog,
Renderer: 'a + text::Renderer, Renderer: 'a + text::Renderer,
{ {
fn from( fn from(
@ -413,9 +422,9 @@ pub enum Status {
}, },
} }
/// The appearance of a checkbox. /// The style of a checkbox.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The [`Background`] of the checkbox. /// The [`Background`] of the checkbox.
pub background: Background, pub background: Background,
/// The icon [`Color`] of the checkbox. /// The icon [`Color`] of the checkbox.
@ -426,29 +435,37 @@ pub struct Appearance {
pub text_color: Option<Color>, pub text_color: Option<Color>,
} }
/// The style of a [`Checkbox`]. /// The theme catalog of a [`Checkbox`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub trait Catalog: Sized {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`Checkbox`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`Checkbox`].
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 { /// A styling function for a [`Checkbox`].
fn default_style(&self, status: Status) -> Appearance { ///
primary(self, status) /// 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 style(&self, class: &Self::Class<'_>, status: Status) -> Style {
fn default_style(&self, _status: Status) -> Appearance { class(self, status)
*self
} }
} }
/// A primary checkbox; denoting a main toggle. /// 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(); let palette = theme.extended_palette();
match status { match status {
@ -474,7 +491,7 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance {
} }
/// A secondary checkbox; denoting a complementary toggle. /// 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(); let palette = theme.extended_palette();
match status { match status {
@ -500,7 +517,7 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance {
} }
/// A success checkbox; denoting a positive toggle. /// 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(); let palette = theme.extended_palette();
match status { match status {
@ -526,7 +543,7 @@ pub fn success(theme: &Theme, status: Status) -> Appearance {
} }
/// A danger checkbox; denoting a negaive toggle. /// 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(); let palette = theme.extended_palette();
match status { match status {
@ -556,8 +573,8 @@ fn styled(
base: palette::Pair, base: palette::Pair,
accent: palette::Pair, accent: palette::Pair,
is_checked: bool, is_checked: bool,
) -> Appearance { ) -> Style {
Appearance { Style {
background: Background::Color(if is_checked { background: Background::Color(if is_checked {
accent.color accent.color
} else { } else {

View file

@ -32,6 +32,7 @@ pub struct ComboBox<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
state: &'a State<T>, state: &'a State<T>,
@ -42,7 +43,7 @@ pub struct ComboBox<
on_option_hovered: Option<Box<dyn Fn(T) -> Message>>, on_option_hovered: Option<Box<dyn Fn(T) -> Message>>,
on_close: Option<Message>, on_close: Option<Message>,
on_input: Option<Box<dyn Fn(String) -> Message>>, on_input: Option<Box<dyn Fn(String) -> Message>>,
menu_style: menu::Style<'a, Theme>, menu_class: <Theme as menu::Catalog>::Class<'a>,
padding: Padding, padding: Padding,
size: Option<f32>, size: Option<f32>,
} }
@ -50,6 +51,7 @@ pub struct ComboBox<
impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer> impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer>
where where
T: std::fmt::Display + Clone, T: std::fmt::Display + Clone,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
/// Creates a new [`ComboBox`] with the given list of options, a placeholder, /// Creates a new [`ComboBox`] with the given list of options, a placeholder,
@ -60,18 +62,10 @@ where
placeholder: &str, placeholder: &str,
selection: Option<&T>, selection: Option<&T>,
on_selected: impl Fn(T) -> Message + 'static, on_selected: impl Fn(T) -> Message + 'static,
) -> Self ) -> Self {
where let text_input = TextInput::new(placeholder, &state.value())
Theme: DefaultStyle + 'a, .on_input(TextInputEvent::TextChanged)
{ .class(Theme::default_input());
let style = Theme::default_style();
let text_input = TextInput::with_style(
placeholder,
&state.value(),
style.text_input,
)
.on_input(TextInputEvent::TextChanged);
let selection = selection.map(T::to_string).unwrap_or_default(); let selection = selection.map(T::to_string).unwrap_or_default();
@ -84,7 +78,7 @@ where
on_option_hovered: None, on_option_hovered: None,
on_input: None, on_input: None,
on_close: None, on_close: None,
menu_style: style.menu, menu_class: <Theme as Catalog>::default_menu(),
padding: text_input::DEFAULT_PADDING, padding: text_input::DEFAULT_PADDING,
size: None, size: None,
} }
@ -124,18 +118,6 @@ where
self self
} }
/// Sets the style of the [`ComboBox`].
pub fn style(mut self, style: impl Into<Style<'a, Theme>>) -> 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`]. /// Sets the [`Renderer::Font`] of the [`ComboBox`].
/// ///
/// [`Renderer::Font`]: text::Renderer /// [`Renderer::Font`]: text::Renderer
@ -173,6 +155,55 @@ where
..self ..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
<Theme as text_input::Catalog>::Class<'a>:
From<text_input::StyleFn<'a, Theme>>,
{
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
<Theme as menu::Catalog>::Class<'a>: From<menu::StyleFn<'a, Theme>>,
{
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<<Theme as text_input::Catalog>::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<<Theme as menu::Catalog>::Class<'a>>,
) -> Self {
self.menu_class = class.into();
self
}
} }
/// The local state of a [`ComboBox`]. /// The local state of a [`ComboBox`].
@ -296,6 +327,7 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where where
T: Display + Clone + 'static, T: Display + Clone + 'static,
Message: Clone, Message: Clone,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn size(&self) -> Size<Length> { fn size(&self) -> Size<Length> {
@ -686,7 +718,7 @@ where
(self.on_selected)(x) (self.on_selected)(x)
}, },
self.on_option_hovered.as_deref(), self.on_option_hovered.as_deref(),
&self.menu_style, &self.menu_class,
) )
.width(bounds.width) .width(bounds.width)
.padding(self.padding); .padding(self.padding);
@ -712,7 +744,7 @@ impl<'a, T, Message, Theme, Renderer>
where where
T: Display + Clone + 'static, T: Display + Clone + 'static,
Message: Clone + 'a, Message: Clone + 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn from(combo_box: ComboBox<'a, T, Message, Theme, Renderer>) -> Self { fn from(combo_box: ComboBox<'a, T, Message, Theme, Renderer>) -> Self {
@ -720,6 +752,21 @@ where
} }
} }
/// The theme catalog of a [`ComboBox`].
pub trait Catalog: text_input::Catalog + menu::Catalog {
/// The default class for the text input of the [`ComboBox`].
fn default_input<'a>() -> <Self as text_input::Catalog>::Class<'a> {
<Self as text_input::Catalog>::default()
}
/// The default class for the menu of the [`ComboBox`].
fn default_menu<'a>() -> <Self as menu::Catalog>::Class<'a> {
<Self as menu::Catalog>::default()
}
}
impl Catalog for Theme {}
fn search<'a, T, A>( fn search<'a, T, A>(
options: impl IntoIterator<Item = T> + 'a, options: impl IntoIterator<Item = T> + 'a,
option_matchers: impl IntoIterator<Item = &'a A> + 'a, option_matchers: impl IntoIterator<Item = &'a A> + 'a,
@ -762,30 +809,3 @@ where
}) })
.collect() .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(),
}
}
}

View file

@ -9,8 +9,9 @@ use crate::core::renderer;
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::widget::{self, Operation}; use crate::core::widget::{self, Operation};
use crate::core::{ use crate::core::{
Background, Border, Clipboard, Color, Element, Layout, Length, Padding, self, Background, Border, Clipboard, Color, Element, Layout, Length,
Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget, Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,
Widget,
}; };
use crate::runtime::Command; use crate::runtime::Command;
@ -24,7 +25,8 @@ pub struct Container<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
id: Option<Id>, id: Option<Id>,
padding: Padding, padding: Padding,
@ -36,27 +38,17 @@ pub struct Container<
vertical_alignment: alignment::Vertical, vertical_alignment: alignment::Vertical,
clip: bool, clip: bool,
content: Element<'a, Message, Theme, Renderer>, content: Element<'a, Message, Theme, Renderer>,
style: Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
/// Creates a [`Container`] with the given content. /// Creates a [`Container`] with the given content.
pub fn new( pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> 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<Element<'a, Message, Theme, Renderer>>,
style: impl Fn(&Theme, Status) -> Appearance + 'a,
) -> Self { ) -> Self {
let content = content.into(); let content = content.into();
let size = content.as_widget().size_hint(); let size = content.as_widget().size_hint();
@ -71,7 +63,7 @@ where
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
clip: false, clip: false,
style: Box::new(style), class: Theme::default(),
content, content,
} }
} }
@ -136,27 +128,37 @@ where
self 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 /// Sets whether the contents of the [`Container`] should be clipped on
/// overflow. /// overflow.
pub fn clip(mut self, clip: bool) -> Self { pub fn clip(mut self, clip: bool) -> Self {
self.clip = clip; self.clip = clip;
self 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<StyleFn<'a, Theme>>,
{
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self
}
} }
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Container<'a, Message, Theme, Renderer> for Container<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
self.content.as_widget().tag() self.content.as_widget().tag()
@ -272,14 +274,7 @@ where
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let bounds = layout.bounds(); let bounds = layout.bounds();
let style = theme.style(&self.class);
let status = if cursor.is_over(bounds) {
Status::Hovered
} else {
Status::Idle
};
let style = (self.style)(theme, status);
if let Some(clipped_viewport) = bounds.intersection(viewport) { if let Some(clipped_viewport) = bounds.intersection(viewport) {
draw_background(renderer, &style, bounds); draw_background(renderer, &style, bounds);
@ -324,8 +319,8 @@ impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: 'a + crate::core::Renderer, Renderer: core::Renderer + 'a,
{ {
fn from( fn from(
column: Container<'a, Message, Theme, Renderer>, 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>( pub fn draw_background<Renderer>(
renderer: &mut Renderer, renderer: &mut Renderer,
appearance: &Appearance, style: &Style,
bounds: Rectangle, bounds: Rectangle,
) where ) where
Renderer: crate::core::Renderer, Renderer: core::Renderer,
{ {
if appearance.background.is_some() if style.background.is_some()
|| appearance.border.width > 0.0 || style.border.width > 0.0
|| appearance.shadow.color.a > 0.0 || style.shadow.color.a > 0.0
{ {
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: appearance.border, border: style.border,
shadow: appearance.shadow, shadow: style.shadow,
}, },
appearance style
.background .background
.unwrap_or(Background::Color(Color::TRANSPARENT)), .unwrap_or(Background::Color(Color::TRANSPARENT)),
); );
@ -502,7 +497,7 @@ pub fn visible_bounds(id: Id) -> Command<Option<Rectangle>> {
/// The appearance of a container. /// The appearance of a container.
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct Appearance { pub struct Style {
/// The text [`Color`] of the container. /// The text [`Color`] of the container.
pub text_color: Option<Color>, pub text_color: Option<Color>,
/// The [`Background`] of the container. /// The [`Background`] of the container.
@ -513,8 +508,8 @@ pub struct Appearance {
pub shadow: Shadow, pub shadow: Shadow,
} }
impl Appearance { impl Style {
/// Updates the border of the [`Appearance`] with the given [`Color`] and `width`. /// Updates the border of the [`Style`] with the given [`Color`] and `width`.
pub fn with_border( pub fn with_border(
self, self,
color: impl Into<Color>, color: impl Into<Color>,
@ -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<Background>) -> Self { pub fn with_background(self, background: impl Into<Background>) -> Self {
Self { Self {
background: Some(background.into()), background: Some(background.into()),
@ -539,99 +534,78 @@ impl Appearance {
} }
} }
impl From<Color> for Appearance { impl From<Color> for Style {
fn from(color: Color) -> Self { fn from(color: Color) -> Self {
Self::default().with_background(color) Self::default().with_background(color)
} }
} }
impl From<Gradient> for Appearance { impl From<Gradient> for Style {
fn from(gradient: Gradient) -> Self { fn from(gradient: Gradient) -> Self {
Self::default().with_background(gradient) Self::default().with_background(gradient)
} }
} }
impl From<gradient::Linear> for Appearance { impl From<gradient::Linear> for Style {
fn from(gradient: gradient::Linear) -> Self { fn from(gradient: gradient::Linear) -> Self {
Self::default().with_background(gradient) Self::default().with_background(gradient)
} }
} }
/// The possible status of a [`Container`]. /// The theme catalog of a [`Container`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub trait Catalog {
pub enum Status { /// The item class of the [`Catalog`].
/// The [`Container`] is idle. type Class<'a>;
Idle,
/// The [`Container`] is being hovered. /// The default class produced by the [`Catalog`].
Hovered, fn default<'a>() -> Self::Class<'a>;
/// The [`Style`] of a class with the given status.
fn style(&self, class: &Self::Class<'_>) -> Style;
} }
/// The style of a [`Container`]. /// A styling function for a [`Container`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
/// The default style of a [`Container`]. impl Catalog for Theme {
pub trait DefaultStyle { type Class<'a> = StyleFn<'a, Self>;
/// Returns the default style of a [`Container`].
fn default_style(&self, status: Status) -> Appearance;
}
impl DefaultStyle for Theme { fn default<'a>() -> Self::Class<'a> {
fn default_style(&self, status: Status) -> Appearance { Box::new(transparent)
transparent(self, status)
} }
}
impl DefaultStyle for Appearance { fn style(&self, class: &Self::Class<'_>) -> Style {
fn default_style(&self, _status: Status) -> Appearance { class(self)
*self
}
}
impl DefaultStyle for Color {
fn default_style(&self, _status: Status) -> Appearance {
Appearance::from(*self)
}
}
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)
} }
} }
/// A transparent [`Container`]. /// A transparent [`Container`].
pub fn transparent<Theme>(_theme: &Theme, _status: Status) -> Appearance { pub fn transparent<Theme>(_theme: &Theme) -> Style {
Appearance::default() Style::default()
} }
/// A rounded [`Container`] with a background. /// 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(); let palette = theme.extended_palette();
Appearance { Style {
background: Some(palette.background.weak.color.into()), background: Some(palette.background.weak.color.into()),
border: Border::rounded(2), border: Border::rounded(2),
..Appearance::default() ..Style::default()
} }
} }
/// A bordered [`Container`] with a background. /// 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(); let palette = theme.extended_palette();
Appearance { Style {
background: Some(palette.background.weak.color.into()), background: Some(palette.background.weak.color.into()),
border: Border { border: Border {
width: 1.0, width: 1.0,
radius: 0.0.into(), radius: 0.0.into(),
color: palette.background.strong.color, color: palette.background.strong.color,
}, },
..Appearance::default() ..Style::default()
} }
} }

View file

@ -7,6 +7,7 @@ use crate::core;
use crate::core::widget::operation; use crate::core::widget::operation;
use crate::core::{Element, Length, Pixels}; use crate::core::{Element, Length, Pixels};
use crate::keyed; use crate::keyed;
use crate::overlay;
use crate::pick_list::{self, PickList}; use crate::pick_list::{self, PickList};
use crate::progress_bar::{self, ProgressBar}; use crate::progress_bar::{self, ProgressBar};
use crate::radio::{self, Radio}; use crate::radio::{self, Radio};
@ -58,7 +59,7 @@ pub fn container<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Container<'a, Message, Theme, Renderer> ) -> Container<'a, Message, Theme, Renderer>
where where
Theme: container::DefaultStyle + 'a, Theme: container::Catalog + 'a,
Renderer: core::Renderer, Renderer: core::Renderer,
{ {
Container::new(content) Container::new(content)
@ -104,7 +105,7 @@ pub fn scrollable<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Scrollable<'a, Message, Theme, Renderer> ) -> Scrollable<'a, Message, Theme, Renderer>
where where
Theme: scrollable::DefaultStyle + 'a, Theme: scrollable::Catalog + 'a,
Renderer: core::Renderer, Renderer: core::Renderer,
{ {
Scrollable::new(content) Scrollable::new(content)
@ -117,7 +118,7 @@ pub fn button<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Button<'a, Message, Theme, Renderer> ) -> Button<'a, Message, Theme, Renderer>
where where
Theme: button::DefaultStyle + 'a, Theme: button::Catalog + 'a,
Renderer: core::Renderer, Renderer: core::Renderer,
{ {
Button::new(content) Button::new(content)
@ -134,7 +135,7 @@ pub fn tooltip<'a, Message, Theme, Renderer>(
position: tooltip::Position, position: tooltip::Position,
) -> crate::Tooltip<'a, Message, Theme, Renderer> ) -> crate::Tooltip<'a, Message, Theme, Renderer>
where where
Theme: container::DefaultStyle + 'a, Theme: container::Catalog + 'a,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
{ {
Tooltip::new(content, tooltip, position) Tooltip::new(content, tooltip, position)
@ -147,7 +148,7 @@ pub fn text<'a, Theme, Renderer>(
text: impl ToString, text: impl ToString,
) -> Text<'a, Theme, Renderer> ) -> Text<'a, Theme, Renderer>
where where
Theme: text::DefaultStyle + 'a, Theme: text::Catalog + 'a,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
{ {
Text::new(text.to_string()) Text::new(text.to_string())
@ -161,7 +162,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::DefaultStyle + 'a, Theme: checkbox::Catalog + 'a,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
{ {
Checkbox::new(label, is_checked) Checkbox::new(label, is_checked)
@ -178,7 +179,7 @@ pub fn radio<'a, Message, Theme, Renderer, V>(
) -> Radio<'a, Message, Theme, Renderer> ) -> Radio<'a, Message, Theme, Renderer>
where where
Message: Clone, Message: Clone,
Theme: radio::DefaultStyle + 'a, Theme: radio::Catalog + 'a,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
V: Copy + Eq, V: Copy + Eq,
{ {
@ -194,7 +195,7 @@ pub fn toggler<'a, Message, Theme, Renderer>(
f: impl Fn(bool) -> Message + 'a, f: impl Fn(bool) -> Message + 'a,
) -> Toggler<'a, Message, Theme, Renderer> ) -> Toggler<'a, Message, Theme, Renderer>
where where
Theme: toggler::DefaultStyle + 'a, Theme: toggler::Catalog + 'a,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
{ {
Toggler::new(label, is_checked, f) Toggler::new(label, is_checked, f)
@ -209,7 +210,7 @@ pub fn text_input<'a, Message, Theme, Renderer>(
) -> TextInput<'a, Message, Theme, Renderer> ) -> TextInput<'a, Message, Theme, Renderer>
where where
Message: Clone, Message: Clone,
Theme: text_input::DefaultStyle + 'a, Theme: text_input::Catalog + 'a,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
{ {
TextInput::new(placeholder, value) 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> ) -> TextEditor<'a, core::text::highlighter::PlainText, Message, Theme, Renderer>
where where
Message: Clone, Message: Clone,
Theme: text_editor::DefaultStyle + 'a, Theme: text_editor::Catalog + 'a,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
{ {
TextEditor::new(content) TextEditor::new(content)
@ -240,7 +241,7 @@ pub fn slider<'a, T, Message, Theme>(
where where
T: Copy + From<u8> + std::cmp::PartialOrd, T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone, Message: Clone,
Theme: slider::DefaultStyle + 'a, Theme: slider::Catalog + 'a,
{ {
Slider::new(range, value, on_change) Slider::new(range, value, on_change)
} }
@ -256,7 +257,7 @@ pub fn vertical_slider<'a, T, Message, Theme>(
where where
T: Copy + From<u8> + std::cmp::PartialOrd, T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone, Message: Clone,
Theme: vertical_slider::DefaultStyle + 'a, Theme: vertical_slider::Catalog + 'a,
{ {
VerticalSlider::new(range, value, on_change) VerticalSlider::new(range, value, on_change)
} }
@ -274,7 +275,7 @@ where
L: Borrow<[T]> + 'a, L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a, V: Borrow<T> + 'a,
Message: Clone, Message: Clone,
Theme: pick_list::DefaultStyle, Theme: pick_list::Catalog + overlay::menu::Catalog,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
{ {
PickList::new(options, selected, on_selected) 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> ) -> ComboBox<'a, T, Message, Theme, Renderer>
where where
T: std::fmt::Display + Clone, T: std::fmt::Display + Clone,
Theme: combo_box::DefaultStyle + 'a, Theme: combo_box::Catalog + 'a,
Renderer: core::text::Renderer, Renderer: core::text::Renderer,
{ {
ComboBox::new(state, placeholder, selection, on_selected) ComboBox::new(state, placeholder, selection, on_selected)
@ -318,7 +319,7 @@ pub fn vertical_space() -> Space {
/// [`Rule`]: crate::Rule /// [`Rule`]: crate::Rule
pub fn horizontal_rule<'a, Theme>(height: impl Into<Pixels>) -> Rule<'a, Theme> pub fn horizontal_rule<'a, Theme>(height: impl Into<Pixels>) -> Rule<'a, Theme>
where where
Theme: rule::DefaultStyle + 'a, Theme: rule::Catalog + 'a,
{ {
Rule::horizontal(height) Rule::horizontal(height)
} }
@ -328,7 +329,7 @@ where
/// [`Rule`]: crate::Rule /// [`Rule`]: crate::Rule
pub fn vertical_rule<'a, Theme>(width: impl Into<Pixels>) -> Rule<'a, Theme> pub fn vertical_rule<'a, Theme>(width: impl Into<Pixels>) -> Rule<'a, Theme>
where where
Theme: rule::DefaultStyle + 'a, Theme: rule::Catalog + 'a,
{ {
Rule::vertical(width) Rule::vertical(width)
} }
@ -345,7 +346,7 @@ pub fn progress_bar<'a, Theme>(
value: f32, value: f32,
) -> ProgressBar<'a, Theme> ) -> ProgressBar<'a, Theme>
where where
Theme: progress_bar::DefaultStyle + 'a, Theme: progress_bar::Catalog + 'a,
{ {
ProgressBar::new(range, value) ProgressBar::new(range, value)
} }
@ -367,7 +368,7 @@ pub fn svg<'a, Theme>(
handle: impl Into<core::svg::Handle>, handle: impl Into<core::svg::Handle>,
) -> crate::Svg<'a, Theme> ) -> crate::Svg<'a, Theme>
where where
Theme: crate::svg::DefaultStyle + 'a, Theme: crate::svg::Catalog,
{ {
crate::Svg::new(handle) crate::Svg::new(handle)
} }
@ -395,7 +396,7 @@ pub fn qr_code<'a, Theme>(
data: &'a crate::qr_code::Data, data: &'a crate::qr_code::Data,
) -> crate::QRCode<'a, Theme> ) -> crate::QRCode<'a, Theme>
where where
Theme: crate::qr_code::DefaultStyle + 'a, Theme: crate::qr_code::Catalog + 'a,
{ {
crate::QRCode::new(data) crate::QRCode::new(data)
} }

View file

@ -1,5 +1,4 @@
//! Build and show dropdown menus. //! Build and show dropdown menus.
use crate::container::{self, Container};
use crate::core::alignment; use crate::core::alignment;
use crate::core::event::{self, Event}; use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout}; use crate::core::layout::{self, Layout};
@ -20,12 +19,15 @@ use crate::scrollable::{self, Scrollable};
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Menu< pub struct Menu<
'a, 'a,
'b,
T, T,
Message, Message,
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
'b: 'a,
{ {
state: &'a mut State, state: &'a mut State,
options: &'a [T], options: &'a [T],
@ -38,15 +40,17 @@ pub struct Menu<
text_line_height: text::LineHeight, text_line_height: text::LineHeight,
text_shaping: text::Shaping, text_shaping: text::Shaping,
font: Option<Renderer::Font>, font: Option<Renderer::Font>,
style: &'a Style<'a, Theme>, class: &'a <Theme as Catalog>::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 where
T: ToString + Clone, T: ToString + Clone,
Message: 'a, Message: 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
'b: 'a,
{ {
/// Creates a new [`Menu`] with the given [`State`], a list of options, /// Creates a new [`Menu`] with the given [`State`], a list of options,
/// the message to produced when an option is selected, and its [`Style`]. /// the message to produced when an option is selected, and its [`Style`].
@ -56,7 +60,7 @@ where
hovered_option: &'a mut Option<usize>, hovered_option: &'a mut Option<usize>,
on_selected: impl FnMut(T) -> Message + 'a, on_selected: impl FnMut(T) -> Message + 'a,
on_option_hovered: Option<&'a dyn Fn(T) -> Message>, on_option_hovered: Option<&'a dyn Fn(T) -> Message>,
style: &'a Style<'a, Theme>, class: &'a <Theme as Catalog>::Class<'b>,
) -> Self { ) -> Self {
Menu { Menu {
state, state,
@ -70,7 +74,7 @@ where
text_line_height: text::LineHeight::default(), text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic, text_shaping: text::Shaping::Basic,
font: None, font: None,
style, class,
} }
} }
@ -153,27 +157,29 @@ impl Default for State {
} }
} }
struct Overlay<'a, Message, Theme, Renderer> struct Overlay<'a, 'b, Message, Theme, Renderer>
where where
Theme: Catalog,
Renderer: crate::core::Renderer, Renderer: crate::core::Renderer,
{ {
position: Point, position: Point,
state: &'a mut Tree, state: &'a mut Tree,
container: Container<'a, Message, Theme, Renderer>, list: Scrollable<'a, Message, Theme, Renderer>,
width: f32, width: f32,
target_height: f32, target_height: f32,
style: &'a Style<'a, Theme>, class: &'a <Theme as Catalog>::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 where
Message: 'a, Message: 'a,
Theme: 'a, Theme: Catalog + scrollable::Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
'b: 'a,
{ {
pub fn new<T>( pub fn new<T>(
position: Point, position: Point,
menu: Menu<'a, T, Message, Theme, Renderer>, menu: Menu<'a, 'b, T, Message, Theme, Renderer>,
target_height: f32, target_height: f32,
) -> Self ) -> Self
where where
@ -191,46 +197,43 @@ where
text_size, text_size,
text_line_height, text_line_height,
text_shaping, text_shaping,
style, class,
} = menu; } = menu;
let container = Container::with_style( let list = Scrollable::with_direction(
Scrollable::with_direction_and_style( List {
List { options,
options, hovered_option,
hovered_option, on_selected,
on_selected, on_option_hovered,
on_option_hovered, font,
font, text_size,
text_size, text_line_height,
text_line_height, text_shaping,
text_shaping, padding,
padding, class,
style: &style.list, },
}, scrollable::Direction::default(),
scrollable::Direction::default(),
&style.scrollable,
),
container::transparent,
); );
state.tree.diff(&container as &dyn Widget<_, _, _>); state.tree.diff(&list as &dyn Widget<_, _, _>);
Self { Self {
position, position,
state: &mut state.tree, state: &mut state.tree,
container, list,
width, width,
target_height, target_height,
style, class,
} }
} }
} }
impl<'a, Message, Theme, Renderer> impl<'a, 'b, Message, Theme, Renderer>
crate::core::Overlay<Message, Theme, Renderer> crate::core::Overlay<Message, Theme, Renderer>
for Overlay<'a, Message, Theme, Renderer> for Overlay<'a, 'b, Message, Theme, Renderer>
where where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
@ -251,7 +254,7 @@ where
) )
.width(self.width); .width(self.width);
let node = self.container.layout(self.state, renderer, &limits); let node = self.list.layout(self.state, renderer, &limits);
let size = node.size(); let size = node.size();
node.move_to(if space_below > space_above { node.move_to(if space_below > space_above {
@ -272,7 +275,7 @@ where
) -> event::Status { ) -> event::Status {
let bounds = layout.bounds(); let bounds = layout.bounds();
self.container.on_event( self.list.on_event(
self.state, event, layout, cursor, renderer, clipboard, shell, self.state, event, layout, cursor, renderer, clipboard, shell,
&bounds, &bounds,
) )
@ -285,7 +288,7 @@ where
viewport: &Rectangle, viewport: &Rectangle,
renderer: &Renderer, renderer: &Renderer,
) -> mouse::Interaction { ) -> mouse::Interaction {
self.container self.list
.mouse_interaction(self.state, layout, cursor, viewport, renderer) .mouse_interaction(self.state, layout, cursor, viewport, renderer)
} }
@ -293,30 +296,32 @@ where
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &Theme, theme: &Theme,
style: &renderer::Style, defaults: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
) { ) {
let bounds = layout.bounds(); let bounds = layout.bounds();
let appearance = (self.style.list)(theme); let style = Catalog::style(theme, self.class);
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: appearance.border, border: style.border,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.background, style.background,
); );
self.container self.list.draw(
.draw(self.state, renderer, theme, style, layout, cursor, &bounds); self.state, renderer, theme, defaults, layout, cursor, &bounds,
);
} }
} }
struct List<'a, T, Message, Theme, Renderer> struct List<'a, 'b, T, Message, Theme, Renderer>
where where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
options: &'a [T], options: &'a [T],
@ -328,13 +333,14 @@ where
text_line_height: text::LineHeight, text_line_height: text::LineHeight,
text_shaping: text::Shaping, text_shaping: text::Shaping,
font: Option<Renderer::Font>, font: Option<Renderer::Font>,
style: &'a dyn Fn(&Theme) -> Appearance, class: &'a <Theme as Catalog>::Class<'b>,
} }
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for List<'a, T, Message, Theme, Renderer> for List<'a, 'b, T, Message, Theme, Renderer>
where where
T: Clone + ToString, T: Clone + ToString,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn size(&self) -> Size<Length> { fn size(&self) -> Size<Length> {
@ -477,7 +483,7 @@ where
_cursor: mouse::Cursor, _cursor: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let appearance = (self.style)(theme); let style = Catalog::style(theme, self.class);
let bounds = layout.bounds(); let bounds = layout.bounds();
let text_size = let text_size =
@ -507,14 +513,14 @@ where
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: Rectangle { bounds: Rectangle {
x: bounds.x + appearance.border.width, x: bounds.x + style.border.width,
width: bounds.width - appearance.border.width * 2.0, width: bounds.width - style.border.width * 2.0,
..bounds ..bounds
}, },
border: Border::rounded(appearance.border.radius), border: Border::rounded(style.border.radius),
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.selected_background, style.selected_background,
); );
} }
@ -531,9 +537,9 @@ where
}, },
Point::new(bounds.x + self.padding.left, bounds.center_y()), Point::new(bounds.x + self.padding.left, bounds.center_y()),
if is_selected { if is_selected {
appearance.selected_text_color style.selected_text_color
} else { } else {
appearance.text_color style.text_color
}, },
*viewport, *viewport,
); );
@ -541,23 +547,24 @@ where
} }
} }
impl<'a, T, Message, Theme, Renderer> impl<'a, 'b, T, Message, Theme, Renderer>
From<List<'a, T, Message, Theme, Renderer>> From<List<'a, 'b, T, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
T: ToString + Clone, T: ToString + Clone,
Message: 'a, Message: 'a,
Theme: 'a, Theme: 'a + Catalog,
Renderer: 'a + text::Renderer, 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) Element::new(list)
} }
} }
/// The appearance of a [`Menu`]. /// The appearance of a [`Menu`].
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The [`Background`] of the menu. /// The [`Background`] of the menu.
pub background: Background, pub background: Background,
/// The [`Border`] of the menu. /// The [`Border`] of the menu.
@ -570,35 +577,43 @@ pub struct Appearance {
pub selected_background: Background, pub selected_background: Background,
} }
/// The style of the different parts of a [`Menu`]. /// The theme catalog of a [`Menu`].
#[allow(missing_debug_implementations)] pub trait Catalog: scrollable::Catalog {
pub struct Style<'a, Theme> { /// The item class of the [`Catalog`].
/// The style of the list of the [`Menu`]. type Class<'a>;
pub list: Box<dyn Fn(&Theme) -> Appearance + 'a>,
/// The style of the [`Scrollable`] of the [`Menu`]. /// The default class produced by the [`Catalog`].
pub scrollable: scrollable::Style<'a, Theme>, fn default<'a>() -> <Self as Catalog>::Class<'a>;
/// The default class for the scrollable of the [`Menu`].
fn default_scrollable<'a>() -> <Self as scrollable::Catalog>::Class<'a> {
<Self as scrollable::Catalog>::default()
}
/// The [`Style`] of a class with the given status.
fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style;
} }
/// The default style of a [`Menu`]. /// A styling function for a [`Menu`].
pub trait DefaultStyle: Sized { pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
/// Returns the default style of a [`Menu`].
fn default_style() -> Style<'static, Self>;
}
impl DefaultStyle for Theme { impl Catalog for Theme {
fn default_style() -> Style<'static, Self> { type Class<'a> = StyleFn<'a, Self>;
Style {
list: Box::new(default), fn default<'a>() -> StyleFn<'a, Self> {
scrollable: Box::new(scrollable::default), Box::new(default)
} }
fn style(&self, class: &StyleFn<'_, Self>) -> Style {
class(self)
} }
} }
/// The default style of the list of a [`Menu`]. /// 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(); let palette = theme.extended_palette();
Appearance { Style {
background: palette.background.weak.color.into(), background: palette.background.weak.color.into(),
border: Border { border: Border {
width: 1.0, width: 1.0,

View file

@ -30,6 +30,7 @@ pub use split::Split;
pub use state::State; pub use state::State;
pub use title_bar::TitleBar; pub use title_bar::TitleBar;
use crate::container;
use crate::core::event::{self, Event}; use crate::core::event::{self, Event};
use crate::core::layout; use crate::core::layout;
use crate::core::mouse; use crate::core::mouse;
@ -39,8 +40,8 @@ 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::{
Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, self, Background, Border, Clipboard, Color, Element, Layout, Length,
Point, Rectangle, Shell, Size, Theme, Vector, Widget, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
}; };
const DRAG_DEADBAND_DISTANCE: f32 = 10.0; const DRAG_DEADBAND_DISTANCE: f32 = 10.0;
@ -101,7 +102,8 @@ pub struct PaneGrid<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
contents: Contents<'a, Content<'a, Message, Theme, Renderer>>, contents: Contents<'a, Content<'a, Message, Theme, Renderer>>,
width: Length, width: Length,
@ -110,12 +112,13 @@ pub struct PaneGrid<
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>, on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
style: Style<'a, Theme>, class: <Theme as Catalog>::Class<'a>,
} }
impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
/// Creates a [`PaneGrid`] with the given [`State`] and view function. /// Creates a [`PaneGrid`] with the given [`State`] and view function.
/// ///
@ -124,10 +127,7 @@ where
pub fn new<T>( pub fn new<T>(
state: &'a State<T>, state: &'a State<T>,
view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>, view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>,
) -> Self ) -> Self {
where
Theme: DefaultStyle + 'a,
{
let contents = if let Some((pane, pane_state)) = let contents = if let Some((pane, pane_state)) =
state.maximized.and_then(|pane| { state.maximized.and_then(|pane| {
state.panes.get(&pane).map(|pane_state| (pane, pane_state)) state.panes.get(&pane).map(|pane_state| (pane, pane_state))
@ -158,7 +158,7 @@ where
on_click: None, on_click: None,
on_drag: None, on_drag: None,
on_resize: None, on_resize: None,
style: Box::new(Theme::default_style), class: <Theme as Catalog>::default(),
} }
} }
@ -218,8 +218,23 @@ where
} }
/// Sets the style of the [`PaneGrid`]. /// Sets the style of the [`PaneGrid`].
pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self { #[must_use]
self.style = Box::new(style); pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
where
<Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme>>,
{
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<<Theme as Catalog>::Class<'a>>,
) -> Self {
self.class = class.into();
self self
} }
@ -233,7 +248,8 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for PaneGrid<'a, Message, Theme, Renderer> for PaneGrid<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
tree::Tag::of::<state::Action>() tree::Tag::of::<state::Action>()
@ -596,7 +612,7 @@ where
tree: &Tree, tree: &Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &Theme, theme: &Theme,
style: &renderer::Style, defaults: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
@ -677,7 +693,7 @@ where
None None
}; };
let appearance = (self.style)(theme); let style = Catalog::style(theme, &self.class);
for ((id, (content, tree)), pane_layout) in for ((id, (content, tree)), pane_layout) in
contents.zip(layout.children()) contents.zip(layout.children())
@ -692,7 +708,7 @@ where
tree, tree,
renderer, renderer,
theme, theme,
style, defaults,
pane_layout, pane_layout,
pane_cursor, pane_cursor,
viewport, viewport,
@ -710,10 +726,10 @@ where
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: appearance.hovered_region.border, border: style.hovered_region.border,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.hovered_region.background, style.hovered_region.background,
); );
} }
} }
@ -723,7 +739,7 @@ where
tree, tree,
renderer, renderer,
theme, theme,
style, defaults,
pane_layout, pane_layout,
pane_cursor, pane_cursor,
viewport, viewport,
@ -738,10 +754,10 @@ where
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: appearance.hovered_region.border, border: style.hovered_region.border,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.hovered_region.background, style.hovered_region.background,
); );
} }
@ -759,7 +775,7 @@ where
tree, tree,
renderer, renderer,
theme, theme,
style, defaults,
layout, layout,
pane_cursor, pane_cursor,
viewport, viewport,
@ -772,9 +788,9 @@ where
if picked_pane.is_none() { if picked_pane.is_none() {
if let Some((axis, split_region, is_picked)) = picked_split { if let Some((axis, split_region, is_picked)) = picked_split {
let highlight = if is_picked { let highlight = if is_picked {
appearance.picked_split style.picked_split
} else { } else {
appearance.hovered_split style.hovered_split
}; };
renderer.fill_quad( renderer.fill_quad(
@ -832,8 +848,8 @@ impl<'a, Message, Theme, Renderer> From<PaneGrid<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: crate::core::Renderer + 'a, Renderer: core::Renderer + 'a,
{ {
fn from( fn from(
pane_grid: PaneGrid<'a, Message, Theme, Renderer>, pane_grid: PaneGrid<'a, Message, Theme, Renderer>,
@ -1116,7 +1132,7 @@ impl<'a, T> Contents<'a, T> {
/// The appearance of a [`PaneGrid`]. /// The appearance of a [`PaneGrid`].
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance { pub struct Style {
/// The appearance of a hovered region highlight. /// The appearance of a hovered region highlight.
pub hovered_region: Highlight, pub hovered_region: Highlight,
/// The appearance of a picked split. /// The appearance of a picked split.
@ -1145,32 +1161,40 @@ pub struct Line {
pub width: f32, pub width: f32,
} }
/// The style of a [`PaneGrid`]. /// The theme catalog of a [`PaneGrid`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>; pub trait Catalog: container::Catalog {
/// The item class of this [`Catalog`].
type Class<'a>;
/// The default style of a [`PaneGrid`]. /// The default class produced by this [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> <Self as Catalog>::Class<'a>;
/// Returns the default style of a [`PaneGrid`].
fn default_style(&self) -> Appearance; /// The [`Style`] of a class with the given status.
fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style;
} }
impl DefaultStyle for Theme { /// A styling function for a [`PaneGrid`].
fn default_style(&self) -> Appearance { ///
default(self) /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> 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 style(&self, class: &StyleFn<'_, Self>) -> Style {
fn default_style(&self) -> Appearance { class(self)
*self
} }
} }
/// The default style of a [`PaneGrid`]. /// The default style of a [`PaneGrid`].
pub fn default(theme: &Theme) -> Appearance { pub fn default(theme: &Theme) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
Appearance { Style {
hovered_region: Highlight { hovered_region: Highlight {
background: Background::Color(Color { background: Background::Color(Color {
a: 0.5, a: 0.5,

View file

@ -6,7 +6,7 @@ use crate::core::overlay;
use crate::core::renderer; use crate::core::renderer;
use crate::core::widget::{self, Tree}; use crate::core::widget::{self, Tree};
use crate::core::{ 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}; use crate::pane_grid::{Draggable, TitleBar};
@ -20,30 +20,29 @@ pub struct Content<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Renderer: crate::core::Renderer, Theme: container::Catalog,
Renderer: core::Renderer,
{ {
title_bar: Option<TitleBar<'a, Message, Theme, Renderer>>, title_bar: Option<TitleBar<'a, Message, Theme, Renderer>>,
body: Element<'a, Message, Theme, Renderer>, 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> impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: container::Catalog,
Renderer: core::Renderer,
{ {
/// Creates a new [`Content`] with the provided body. /// Creates a new [`Content`] with the provided body.
pub fn new(body: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self pub fn new(body: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
where
Theme: container::DefaultStyle + 'a,
{
Self { Self {
title_bar: None, title_bar: None,
body: body.into(), 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( pub fn title_bar(
mut self, mut self,
title_bar: TitleBar<'a, Message, Theme, Renderer>, title_bar: TitleBar<'a, Message, Theme, Renderer>,
@ -53,18 +52,31 @@ where
} }
/// Sets the style of the [`Content`]. /// Sets the style of the [`Content`].
#[must_use]
pub fn style( pub fn style(
mut self, mut self,
style: impl Fn(&Theme, container::Status) -> container::Appearance + 'a, style: impl Fn(&Theme) -> container::Style + 'a,
) -> Self { ) -> Self
self.style = Box::new(style); where
Theme::Class<'a>: From<container::StyleFn<'a, Theme>>,
{
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: container::Catalog,
Renderer: core::Renderer,
{ {
pub(super) fn state(&self) -> Tree { pub(super) fn state(&self) -> Tree {
let children = if let Some(title_bar) = self.title_bar.as_ref() { 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`]. /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
/// ///
/// [`Renderer`]: crate::core::Renderer /// [`Renderer`]: core::Renderer
pub fn draw( pub fn draw(
&self, &self,
tree: &Tree, tree: &Tree,
@ -107,15 +119,7 @@ where
let bounds = layout.bounds(); let bounds = layout.bounds();
{ {
let style = { let style = theme.style(&self.class);
let status = if cursor.is_over(bounds) {
container::Status::Hovered
} else {
container::Status::Idle
};
(self.style)(theme, status)
};
container::draw_background(renderer, &style, bounds); container::draw_background(renderer, &style, bounds);
} }
@ -381,7 +385,8 @@ where
impl<'a, Message, Theme, Renderer> Draggable impl<'a, Message, Theme, Renderer> Draggable
for &Content<'a, Message, Theme, Renderer> for &Content<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: container::Catalog,
Renderer: core::Renderer,
{ {
fn can_be_dragged_at( fn can_be_dragged_at(
&self, &self,
@ -403,8 +408,8 @@ impl<'a, T, Message, Theme, Renderer> From<T>
for Content<'a, Message, Theme, Renderer> for Content<'a, Message, Theme, Renderer>
where where
T: Into<Element<'a, Message, Theme, Renderer>>, T: Into<Element<'a, Message, Theme, Renderer>>,
Theme: container::DefaultStyle + 'a, Theme: container::Catalog + 'a,
Renderer: crate::core::Renderer, Renderer: core::Renderer,
{ {
fn from(element: T) -> Self { fn from(element: T) -> Self {
Self::new(element) Self::new(element)

View file

@ -6,7 +6,8 @@ use crate::core::overlay;
use crate::core::renderer; use crate::core::renderer;
use crate::core::widget::{self, Tree}; use crate::core::widget::{self, Tree};
use crate::core::{ 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`]. /// The title bar of a [`Pane`].
@ -19,32 +20,31 @@ pub struct TitleBar<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Renderer: crate::core::Renderer, Theme: container::Catalog,
Renderer: core::Renderer,
{ {
content: Element<'a, Message, Theme, Renderer>, content: Element<'a, Message, Theme, Renderer>,
controls: Option<Element<'a, Message, Theme, Renderer>>, controls: Option<Element<'a, Message, Theme, Renderer>>,
padding: Padding, padding: Padding,
always_show_controls: bool, always_show_controls: bool,
style: container::Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: container::Catalog,
Renderer: core::Renderer,
{ {
/// Creates a new [`TitleBar`] with the given content. /// Creates a new [`TitleBar`] with the given content.
pub fn new( pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self ) -> Self {
where
Theme: container::DefaultStyle + 'a,
{
Self { Self {
content: content.into(), content: content.into(),
controls: None, controls: None,
padding: Padding::ZERO, padding: Padding::ZERO,
always_show_controls: false, always_show_controls: false,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -63,15 +63,6 @@ where
self 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 /// Sets whether or not the [`controls`] attached to this [`TitleBar`] are
/// always visible. /// always visible.
/// ///
@ -84,11 +75,33 @@ where
self.always_show_controls = true; self.always_show_controls = true;
self 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<container::StyleFn<'a, Theme>>,
{
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self
}
} }
impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: container::Catalog,
Renderer: core::Renderer,
{ {
pub(super) fn state(&self) -> Tree { pub(super) fn state(&self) -> Tree {
let children = if let Some(controls) = self.controls.as_ref() { let children = if let Some(controls) = self.controls.as_ref() {
@ -117,7 +130,7 @@ where
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`]. /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
/// ///
/// [`Renderer`]: crate::core::Renderer /// [`Renderer`]: core::Renderer
pub fn draw( pub fn draw(
&self, &self,
tree: &Tree, tree: &Tree,
@ -130,16 +143,7 @@ where
show_controls: bool, show_controls: bool,
) { ) {
let bounds = layout.bounds(); let bounds = layout.bounds();
let style = theme.style(&self.class);
let style = {
let status = if cursor.is_over(bounds) {
container::Status::Hovered
} else {
container::Status::Idle
};
(self.style)(theme, status)
};
let inherited_style = renderer::Style { let inherited_style = renderer::Style {
text_color: style.text_color.unwrap_or(inherited_style.text_color), text_color: style.text_color.unwrap_or(inherited_style.text_color),

View file

@ -32,6 +32,7 @@ pub struct PickList<
T: ToString + PartialEq + Clone, T: ToString + PartialEq + Clone,
L: Borrow<[T]> + 'a, L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a, V: Borrow<T> + 'a,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
on_select: Box<dyn Fn(T) -> Message + 'a>, on_select: Box<dyn Fn(T) -> Message + 'a>,
@ -47,7 +48,8 @@ pub struct PickList<
text_shaping: text::Shaping, text_shaping: text::Shaping,
font: Option<Renderer::Font>, font: Option<Renderer::Font>,
handle: Handle<Renderer::Font>, handle: Handle<Renderer::Font>,
style: Style<'a, Theme>, class: <Theme as Catalog>::Class<'a>,
menu_class: <Theme as menu::Catalog>::Class<'a>,
} }
impl<'a, T, L, V, Message, Theme, Renderer> impl<'a, T, L, V, Message, Theme, Renderer>
@ -57,6 +59,7 @@ where
L: Borrow<[T]> + 'a, L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a, V: Borrow<T> + 'a,
Message: Clone, Message: Clone,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
/// Creates a new [`PickList`] with the given list of options, the current /// Creates a new [`PickList`] with the given list of options, the current
@ -65,10 +68,7 @@ where
options: L, options: L,
selected: Option<V>, selected: Option<V>,
on_select: impl Fn(T) -> Message + 'a, on_select: impl Fn(T) -> Message + 'a,
) -> Self ) -> Self {
where
Theme: DefaultStyle,
{
Self { Self {
on_select: Box::new(on_select), on_select: Box::new(on_select),
on_open: None, on_open: None,
@ -83,7 +83,8 @@ where
text_shaping: text::Shaping::Basic, text_shaping: text::Shaping::Basic,
font: None, font: None,
handle: Handle::default(), handle: Handle::default(),
style: Theme::default_style(), class: <Theme as Catalog>::default(),
menu_class: <Theme as Catalog>::default_menu(),
} }
} }
@ -151,8 +152,23 @@ where
} }
/// Sets the style of the [`PickList`]. /// Sets the style of the [`PickList`].
pub fn style(mut self, style: impl Into<Style<'a, Theme>>) -> Self { #[must_use]
self.style = style.into(); pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
where
<Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme>>,
{
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<<Theme as Catalog>::Class<'a>>,
) -> Self {
self.class = class.into();
self self
} }
} }
@ -164,6 +180,7 @@ where
L: Borrow<[T]>, L: Borrow<[T]>,
V: Borrow<T>, V: Borrow<T>,
Message: Clone + 'a, Message: Clone + 'a,
Theme: Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -409,15 +426,15 @@ where
Status::Active Status::Active
}; };
let appearance = (self.style.field)(theme, status); let style = Catalog::style(theme, &self.class, status);
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: appearance.border, border: style.border,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.background, style.background,
); );
let handle = match &self.handle { let handle = match &self.handle {
@ -478,7 +495,7 @@ where
bounds.x + bounds.width - self.padding.right, bounds.x + bounds.width - self.padding.right,
bounds.center_y(), bounds.center_y(),
), ),
appearance.handle_color, style.handle_color,
*viewport, *viewport,
); );
} }
@ -505,9 +522,9 @@ where
}, },
Point::new(bounds.x + self.padding.left, bounds.center_y()), Point::new(bounds.x + self.padding.left, bounds.center_y()),
if is_selected { if is_selected {
appearance.text_color style.text_color
} else { } else {
appearance.placeholder_color style.placeholder_color
}, },
*viewport, *viewport,
); );
@ -539,7 +556,7 @@ where
(on_select)(option) (on_select)(option)
}, },
None, None,
&self.style.menu, &self.menu_class,
) )
.width(bounds.width) .width(bounds.width)
.padding(self.padding) .padding(self.padding)
@ -565,7 +582,7 @@ where
L: Borrow<[T]> + 'a, L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a, V: Borrow<T> + 'a,
Message: Clone + 'a, Message: Clone + 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn from( fn from(
@ -662,7 +679,7 @@ pub enum Status {
/// The appearance of a pick list. /// The appearance of a pick list.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The text [`Color`] of the pick list. /// The text [`Color`] of the pick list.
pub text_color: Color, pub text_color: Color,
/// The placeholder [`Color`] of the pick list. /// The placeholder [`Color`] of the pick list.
@ -675,36 +692,49 @@ pub struct Appearance {
pub border: Border, pub border: Border,
} }
/// The styles of the different parts of a [`PickList`]. /// The theme catalog of a [`PickList`].
#[allow(missing_debug_implementations)] pub trait Catalog: menu::Catalog {
pub struct Style<'a, Theme> { /// The item class of the [`Catalog`].
/// The style of the [`PickList`] itself. type Class<'a>;
pub field: Box<dyn Fn(&Theme, Status) -> Appearance + 'a>,
/// The style of the [`Menu`] of the pick list. /// The default class produced by the [`Catalog`].
pub menu: menu::Style<'a, Theme>, fn default<'a>() -> <Self as Catalog>::Class<'a>;
/// The default class for the menu of the [`PickList`].
fn default_menu<'a>() -> <Self as menu::Catalog>::Class<'a> {
<Self as menu::Catalog>::default()
}
/// The [`Style`] of a class with the given status.
fn style(
&self,
class: &<Self as Catalog>::Class<'_>,
status: Status,
) -> Style;
} }
/// The default style of a [`PickList`]. /// A styling function for a [`PickList`].
pub trait DefaultStyle: Sized { ///
/// Returns the default style of a [`PickList`]. /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
fn default_style() -> Style<'static, Self>; pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
}
impl DefaultStyle for Theme { impl Catalog for Theme {
fn default_style() -> Style<'static, Self> { type Class<'a> = StyleFn<'a, Self>;
Style {
field: Box::new(default), fn default<'a>() -> StyleFn<'a, Self> {
menu: menu::DefaultStyle::default_style(), Box::new(default)
} }
fn style(&self, class: &StyleFn<'_, Self>, status: Status) -> Style {
class(self, status)
} }
} }
/// The default style of the field of a [`PickList`]. /// 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 palette = theme.extended_palette();
let active = Appearance { let active = Style {
text_color: palette.background.weak.text, text_color: palette.background.weak.text,
background: palette.background.weak.color.into(), background: palette.background.weak.color.into(),
placeholder_color: palette.background.strong.color, placeholder_color: palette.background.strong.color,
@ -718,7 +748,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
match status { match status {
Status::Active => active, Status::Active => active,
Status::Hovered | Status::Opened => Appearance { Status::Hovered | Status::Opened => Style {
border: Border { border: Border {
color: palette.primary.strong.color, color: palette.primary.strong.color,
..active.border ..active.border

View file

@ -4,7 +4,8 @@ use crate::core::mouse;
use crate::core::renderer; use crate::core::renderer;
use crate::core::widget::Tree; use crate::core::widget::Tree;
use crate::core::{ 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; 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) /// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct ProgressBar<'a, Theme = crate::Theme> { pub struct ProgressBar<'a, Theme = crate::Theme>
where
Theme: Catalog,
{
range: RangeInclusive<f32>, range: RangeInclusive<f32>,
value: f32, value: f32,
width: Length, width: Length,
height: Option<Length>, height: Option<Length>,
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`]. /// The default height of a [`ProgressBar`].
pub const DEFAULT_HEIGHT: f32 = 30.0; pub const DEFAULT_HEIGHT: f32 = 30.0;
@ -39,16 +46,13 @@ impl<'a, Theme> ProgressBar<'a, Theme> {
/// It expects: /// It expects:
/// * an inclusive range of possible values /// * an inclusive range of possible values
/// * the current value of the [`ProgressBar`] /// * the current value of the [`ProgressBar`]
pub fn new(range: RangeInclusive<f32>, value: f32) -> Self pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
where
Theme: DefaultStyle + 'a,
{
ProgressBar { ProgressBar {
value: value.clamp(*range.start(), *range.end()), value: value.clamp(*range.start(), *range.end()),
range, range,
width: Length::Fill, width: Length::Fill,
height: None, 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`]. /// Sets the style of the [`ProgressBar`].
pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self { #[must_use]
self.style = Box::new(style); pub fn style(mut self, style: impl Fn(&Theme) -> 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 the [`ProgressBar`].
#[cfg(feature = "advanced")]
#[must_use]
pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
@ -74,7 +90,8 @@ impl<'a, Theme> ProgressBar<'a, Theme> {
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for ProgressBar<'a, Theme> for ProgressBar<'a, Theme>
where where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
fn size(&self) -> Size<Length> { fn size(&self) -> Size<Length> {
Size { Size {
@ -116,15 +133,15 @@ where
/ (range_end - range_start) / (range_end - range_start)
}; };
let appearance = (self.style)(theme); let style = theme.style(&self.class);
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: Rectangle { ..bounds }, bounds: Rectangle { ..bounds },
border: appearance.border, border: style.border,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.background, style.background,
); );
if active_progress_width > 0.0 { if active_progress_width > 0.0 {
@ -134,10 +151,10 @@ where
width: active_progress_width, width: active_progress_width,
..bounds ..bounds
}, },
border: Border::rounded(appearance.border.radius), border: Border::rounded(style.border.radius),
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.bar, style.bar,
); );
} }
} }
@ -147,8 +164,8 @@ impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Theme: 'a, Theme: 'a + Catalog,
Renderer: 'a + crate::core::Renderer, Renderer: 'a + core::Renderer,
{ {
fn from( fn from(
progress_bar: ProgressBar<'a, Theme>, progress_bar: ProgressBar<'a, Theme>,
@ -159,7 +176,7 @@ where
/// The appearance of a progress bar. /// The appearance of a progress bar.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The [`Background`] of the progress bar. /// The [`Background`] of the progress bar.
pub background: Background, pub background: Background,
/// The [`Background`] of the bar of the progress bar. /// The [`Background`] of the bar of the progress bar.
@ -168,29 +185,37 @@ pub struct Appearance {
pub border: Border, pub border: Border,
} }
/// The style of a [`ProgressBar`]. /// The theme catalog of a [`ProgressBar`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>; pub trait Catalog: Sized {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`ProgressBar`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// 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 { /// A styling function for a [`ProgressBar`].
fn default_style(&self) -> Appearance { ///
primary(self) /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> 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 style(&self, class: &Self::Class<'_>) -> Style {
fn default_style(&self) -> Appearance { class(self)
*self
} }
} }
/// The primary style of a [`ProgressBar`]. /// The primary style of a [`ProgressBar`].
pub fn primary(theme: &Theme) -> Appearance { pub fn primary(theme: &Theme) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
styled( styled(
@ -200,7 +225,7 @@ pub fn primary(theme: &Theme) -> Appearance {
} }
/// The secondary style of a [`ProgressBar`]. /// The secondary style of a [`ProgressBar`].
pub fn secondary(theme: &Theme) -> Appearance { pub fn secondary(theme: &Theme) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
styled( styled(
@ -210,14 +235,14 @@ pub fn secondary(theme: &Theme) -> Appearance {
} }
/// The success style of a [`ProgressBar`]. /// The success style of a [`ProgressBar`].
pub fn success(theme: &Theme) -> Appearance { pub fn success(theme: &Theme) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
styled(palette.background.strong.color, palette.success.base.color) styled(palette.background.strong.color, palette.success.base.color)
} }
/// The danger style of a [`ProgressBar`]. /// The danger style of a [`ProgressBar`].
pub fn danger(theme: &Theme) -> Appearance { pub fn danger(theme: &Theme) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
styled(palette.background.strong.color, palette.danger.base.color) styled(palette.background.strong.color, palette.danger.base.color)
@ -226,8 +251,8 @@ pub fn danger(theme: &Theme) -> Appearance {
fn styled( fn styled(
background: impl Into<Background>, background: impl Into<Background>,
bar: impl Into<Background>, bar: impl Into<Background>,
) -> Appearance { ) -> Style {
Appearance { Style {
background: background.into(), background: background.into(),
bar: bar.into(), bar: bar.into(),
border: Border::rounded(2), border: Border::rounded(2),

View file

@ -19,22 +19,25 @@ const QUIET_ZONE: usize = 2;
/// A type of matrix barcode consisting of squares arranged in a grid which /// A type of matrix barcode consisting of squares arranged in a grid which
/// can be read by an imaging device, such as a camera. /// can be read by an imaging device, such as a camera.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct QRCode<'a, Theme = crate::Theme> { pub struct QRCode<'a, Theme = crate::Theme>
where
Theme: Catalog,
{
data: &'a Data, data: &'a Data,
cell_size: u16, 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`]. /// Creates a new [`QRCode`] with the provided [`Data`].
pub fn new(data: &'a Data) -> Self pub fn new(data: &'a Data) -> Self {
where
Theme: DefaultStyle + 'a,
{
Self { Self {
data, data,
cell_size: DEFAULT_CELL_SIZE, cell_size: DEFAULT_CELL_SIZE,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -45,14 +48,27 @@ impl<'a, Theme> QRCode<'a, Theme> {
} }
/// Sets the style of the [`QRCode`]. /// Sets the style of the [`QRCode`].
pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self { #[must_use]
self.style = Box::new(style); pub fn style(mut self, style: impl Fn(&Theme) -> 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 the [`QRCode`].
#[cfg(feature = "advanced")]
#[must_use]
pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
impl<'a, Message, Theme> Widget<Message, Theme, Renderer> impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a, Theme>
for QRCode<'a, Theme> where
Theme: Catalog,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>() tree::Tag::of::<State>()
@ -96,13 +112,13 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
let bounds = layout.bounds(); let bounds = layout.bounds();
let side_length = self.data.width + 2 * QUIET_ZONE; let side_length = self.data.width + 2 * QUIET_ZONE;
let appearance = (self.style)(theme); let style = theme.style(&self.class);
let mut last_appearance = state.last_appearance.borrow_mut(); let mut last_style = state.last_style.borrow_mut();
if Some(appearance) != *last_appearance { if Some(style) != *last_style {
self.data.cache.clear(); self.data.cache.clear();
*last_appearance = Some(appearance); *last_style = Some(style);
} }
// Reuse cache if possible // Reuse cache if possible
@ -114,7 +130,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
frame.fill_rectangle( frame.fill_rectangle(
Point::ORIGIN, Point::ORIGIN,
Size::new(side_length as f32, side_length as f32), Size::new(side_length as f32, side_length as f32),
appearance.background, style.background,
); );
// Avoid drawing on the quiet zone // Avoid drawing on the quiet zone
@ -133,7 +149,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
frame.fill_rectangle( frame.fill_rectangle(
Point::new(column as f32, row as f32), Point::new(column as f32, row as f32),
Size::UNIT, Size::UNIT,
appearance.cell, style.cell,
); );
}); });
}); });
@ -152,7 +168,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
impl<'a, Message, Theme> From<QRCode<'a, Theme>> impl<'a, Message, Theme> From<QRCode<'a, Theme>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Theme: 'a, Theme: Catalog + 'a,
{ {
fn from(qr_code: QRCode<'a, Theme>) -> Self { fn from(qr_code: QRCode<'a, Theme>) -> Self {
Self::new(qr_code) Self::new(qr_code)
@ -324,44 +340,50 @@ impl From<qrcode::types::QrError> for Error {
#[derive(Default)] #[derive(Default)]
struct State { struct State {
last_appearance: RefCell<Option<Appearance>>, last_style: RefCell<Option<Style>>,
} }
/// The appearance of a QR code. /// The appearance of a QR code.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance { pub struct Style {
/// The color of the QR code data cells /// The color of the QR code data cells
pub cell: Color, pub cell: Color,
/// The color of the QR code background /// The color of the QR code background
pub background: Color, pub background: Color,
} }
/// The style of a [`QRCode`]. /// The theme catalog of a [`QRCode`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>; pub trait Catalog {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`QRCode`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`QRCode`].
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 { /// A styling function for a [`QRCode`].
fn default_style(&self) -> Appearance { pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
default(self)
impl Catalog for Theme {
type Class<'a> = StyleFn<'a, Self>;
fn default<'a>() -> Self::Class<'a> {
Box::new(default)
} }
}
impl DefaultStyle for Appearance { fn style(&self, class: &Self::Class<'_>) -> Style {
fn default_style(&self) -> Appearance { class(self)
*self
} }
} }
/// The default style of a [`QRCode`]. /// The default style of a [`QRCode`].
pub fn default(theme: &Theme) -> Appearance { pub fn default(theme: &Theme) -> Style {
let palette = theme.palette(); let palette = theme.palette();
Appearance { Style {
cell: palette.text, cell: palette.text,
background: palette.background, background: palette.background,
} }

View file

@ -69,6 +69,7 @@ use crate::core::{
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Radio<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> pub struct Radio<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
where where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
is_selected: bool, is_selected: bool,
@ -81,12 +82,13 @@ where
text_line_height: text::LineHeight, text_line_height: text::LineHeight,
text_shaping: text::Shaping, text_shaping: text::Shaping,
font: Option<Renderer::Font>, font: Option<Renderer::Font>,
style: Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer>
where where
Message: Clone, Message: Clone,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
/// The default size of a [`Radio`] button. /// The default size of a [`Radio`] button.
@ -110,7 +112,6 @@ where
f: F, f: F,
) -> Self ) -> Self
where where
Theme: DefaultStyle + 'a,
V: Eq + Copy, V: Eq + Copy,
F: FnOnce(V) -> Message, F: FnOnce(V) -> Message,
{ {
@ -125,7 +126,7 @@ where
text_line_height: text::LineHeight::default(), text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic, text_shaping: text::Shaping::Basic,
font: None, font: None,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -175,11 +176,20 @@ where
} }
/// Sets the style of the [`Radio`] button. /// Sets the style of the [`Radio`] button.
pub fn style( #[must_use]
mut self, pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
style: impl Fn(&Theme, Status) -> Appearance + 'a, where
) -> Self { Theme::Class<'a>: From<StyleFn<'a, Theme>>,
self.style = Box::new(style); {
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
@ -188,6 +198,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Radio<'a, Message, Theme, Renderer> for Radio<'a, Message, Theme, Renderer>
where where
Message: Clone, Message: Clone,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -284,7 +295,7 @@ where
tree: &Tree, tree: &Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &Theme, theme: &Theme,
style: &renderer::Style, defaults: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
@ -300,7 +311,7 @@ where
Status::Active { is_selected } Status::Active { is_selected }
}; };
let appearance = (self.style)(theme, status); let style = theme.style(&self.class, status);
{ {
let layout = children.next().unwrap(); let layout = children.next().unwrap();
@ -314,12 +325,12 @@ where
bounds, bounds,
border: Border { border: Border {
radius: (size / 2.0).into(), radius: (size / 2.0).into(),
width: appearance.border_width, width: style.border_width,
color: appearance.border_color, color: style.border_color,
}, },
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.background, style.background,
); );
if self.is_selected { if self.is_selected {
@ -334,7 +345,7 @@ where
border: Border::rounded(dot_size / 2.0), border: Border::rounded(dot_size / 2.0),
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.dot_color, style.dot_color,
); );
} }
} }
@ -344,11 +355,11 @@ where
crate::text::draw( crate::text::draw(
renderer, renderer,
style, defaults,
label_layout, label_layout,
tree.state.downcast_ref(), tree.state.downcast_ref(),
crate::text::Appearance { crate::text::Style {
color: appearance.text_color, color: style.text_color,
}, },
viewport, viewport,
); );
@ -360,7 +371,7 @@ impl<'a, Message, Theme, Renderer> From<Radio<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a + Clone, Message: 'a + Clone,
Theme: 'a, Theme: 'a + Catalog,
Renderer: 'a + text::Renderer, Renderer: 'a + text::Renderer,
{ {
fn from( fn from(
@ -387,7 +398,7 @@ pub enum Status {
/// The appearance of a radio button. /// The appearance of a radio button.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The [`Background`] of the radio button. /// The [`Background`] of the radio button.
pub background: Background, pub background: Background,
/// The [`Color`] of the dot of the radio button. /// The [`Color`] of the dot of the radio button.
@ -400,32 +411,38 @@ pub struct Appearance {
pub text_color: Option<Color>, pub text_color: Option<Color>,
} }
/// The style of a [`Radio`] button. /// The theme catalog of a [`Radio`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub trait Catalog {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`Radio`] button. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`Radio`] button.
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 { /// A styling function for a [`Radio`].
fn default_style(&self, status: Status) -> Appearance { pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
default(self, status)
impl Catalog for Theme {
type Class<'a> = StyleFn<'a, Self>;
fn default<'a>() -> Self::Class<'a> {
Box::new(default)
} }
}
impl DefaultStyle for Appearance { fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
fn default_style(&self, _status: Status) -> Appearance { class(self, status)
*self
} }
} }
/// The default style of a [`Radio`] button. /// 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 palette = theme.extended_palette();
let active = Appearance { let active = Style {
background: Color::TRANSPARENT.into(), background: Color::TRANSPARENT.into(),
dot_color: palette.primary.strong.color, dot_color: palette.primary.strong.color,
border_width: 1.0, border_width: 1.0,
@ -435,7 +452,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
match status { match status {
Status::Active { .. } => active, Status::Active { .. } => active,
Status::Hovered { .. } => Appearance { Status::Hovered { .. } => Style {
dot_color: palette.primary.strong.color, dot_color: palette.primary.strong.color,
background: palette.primary.weak.color.into(), background: palette.primary.weak.color.into(),
..active ..active

View file

@ -1,4 +1,5 @@
//! Display a horizontal or vertical rule for dividing content. //! Display a horizontal or vertical rule for dividing content.
use crate::core;
use crate::core::border::{self, Border}; use crate::core::border::{self, Border};
use crate::core::layout; use crate::core::layout;
use crate::core::mouse; use crate::core::mouse;
@ -10,43 +11,55 @@ use crate::core::{
/// Display a horizontal or vertical rule for dividing content. /// Display a horizontal or vertical rule for dividing content.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Rule<'a, Theme = crate::Theme> { pub struct Rule<'a, Theme = crate::Theme>
where
Theme: Catalog,
{
width: Length, width: Length,
height: Length, height: Length,
is_horizontal: bool, 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. /// Creates a horizontal [`Rule`] with the given height.
pub fn horizontal(height: impl Into<Pixels>) -> Self pub fn horizontal(height: impl Into<Pixels>) -> Self {
where
Theme: DefaultStyle + 'a,
{
Rule { Rule {
width: Length::Fill, width: Length::Fill,
height: Length::Fixed(height.into().0), height: Length::Fixed(height.into().0),
is_horizontal: true, is_horizontal: true,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
/// Creates a vertical [`Rule`] with the given width. /// Creates a vertical [`Rule`] with the given width.
pub fn vertical(width: impl Into<Pixels>) -> Self pub fn vertical(width: impl Into<Pixels>) -> Self {
where
Theme: DefaultStyle + 'a,
{
Rule { Rule {
width: Length::Fixed(width.into().0), width: Length::Fixed(width.into().0),
height: Length::Fill, height: Length::Fill,
is_horizontal: false, is_horizontal: false,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
/// Sets the style of the [`Rule`]. /// Sets the style of the [`Rule`].
pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self { #[must_use]
self.style = Box::new(style); pub fn style(mut self, style: impl Fn(&Theme) -> 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 the [`Rule`].
#[cfg(feature = "advanced")]
#[must_use]
pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
@ -54,7 +67,8 @@ impl<'a, Theme> Rule<'a, Theme> {
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Rule<'a, Theme> for Rule<'a, Theme>
where where
Renderer: crate::core::Renderer, Renderer: core::Renderer,
Theme: Catalog,
{ {
fn size(&self) -> Size<Length> { fn size(&self) -> Size<Length> {
Size { Size {
@ -83,35 +97,34 @@ where
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) {
let bounds = layout.bounds(); let bounds = layout.bounds();
let appearance = (self.style)(theme); let style = theme.style(&self.class);
let bounds = if self.is_horizontal { let bounds = if self.is_horizontal {
let line_y = (bounds.y + (bounds.height / 2.0) let line_y = (bounds.y + (bounds.height / 2.0)
- (appearance.width as f32 / 2.0)) - (style.width as f32 / 2.0))
.round(); .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; let line_x = bounds.x + offset;
Rectangle { Rectangle {
x: line_x, x: line_x,
y: line_y, y: line_y,
width: line_width, width: line_width,
height: appearance.width as f32, height: style.width as f32,
} }
} else { } else {
let line_x = (bounds.x + (bounds.width / 2.0) let line_x = (bounds.x + (bounds.width / 2.0)
- (appearance.width as f32 / 2.0)) - (style.width as f32 / 2.0))
.round(); .round();
let (offset, line_height) = let (offset, line_height) = style.fill_mode.fill(bounds.height);
appearance.fill_mode.fill(bounds.height);
let line_y = bounds.y + offset; let line_y = bounds.y + offset;
Rectangle { Rectangle {
x: line_x, x: line_x,
y: line_y, y: line_y,
width: appearance.width as f32, width: style.width as f32,
height: line_height, height: line_height,
} }
}; };
@ -119,10 +132,10 @@ where
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: Border::rounded(appearance.radius), border: Border::rounded(style.radius),
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.color, style.color,
); );
} }
} }
@ -131,8 +144,8 @@ impl<'a, Message, Theme, Renderer> From<Rule<'a, Theme>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Theme: 'a, Theme: 'a + Catalog,
Renderer: 'a + crate::core::Renderer, Renderer: 'a + core::Renderer,
{ {
fn from(rule: Rule<'a, Theme>) -> Element<'a, Message, Theme, Renderer> { fn from(rule: Rule<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
Element::new(rule) Element::new(rule)
@ -141,7 +154,7 @@ where
/// The appearance of a rule. /// The appearance of a rule.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The color of the rule. /// The color of the rule.
pub color: Color, pub color: Color,
/// The width (thickness) of the rule line. /// The width (thickness) of the rule line.
@ -216,32 +229,40 @@ impl FillMode {
} }
} }
/// The style of a [`Rule`]. /// The theme catalog of a [`Rule`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>; pub trait Catalog: Sized {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`Rule`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`Rule`].
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 { /// A styling function for a [`Rule`].
fn default_style(&self) -> Appearance { ///
default(self) /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> 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 style(&self, class: &Self::Class<'_>) -> Style {
fn default_style(&self) -> Appearance { class(self)
*self
} }
} }
/// The default styling of a [`Rule`]. /// The default styling of a [`Rule`].
pub fn default(theme: &Theme) -> Appearance { pub fn default(theme: &Theme) -> Style {
let palette = theme.extended_palette(); let palette = theme.extended_palette();
Appearance { Style {
color: palette.background.strong.color, color: palette.background.strong.color,
width: 1, width: 1,
radius: 0.0.into(), radius: 0.0.into(),

View file

@ -12,8 +12,8 @@ use crate::core::widget;
use crate::core::widget::operation::{self, Operation}; use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::{ use crate::core::{
Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, self, Background, Border, Clipboard, Color, Element, Layout, Length,
Point, Rectangle, Shell, Size, Theme, Vector, Widget, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
}; };
use crate::runtime::Command; use crate::runtime::Command;
@ -28,7 +28,8 @@ pub struct Scrollable<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
id: Option<Id>, id: Option<Id>,
width: Length, width: Length,
@ -36,20 +37,18 @@ pub struct Scrollable<
direction: Direction, direction: Direction,
content: Element<'a, Message, Theme, Renderer>, content: Element<'a, Message, Theme, Renderer>,
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>, on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
style: Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
/// Creates a new vertical [`Scrollable`]. /// Creates a new vertical [`Scrollable`].
pub fn new( pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self ) -> Self {
where
Theme: DefaultStyle + 'a,
{
Self::with_direction(content, Direction::default()) Self::with_direction(content, Direction::default())
} }
@ -57,18 +56,6 @@ where
pub fn with_direction( pub fn with_direction(
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
direction: Direction, 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<Element<'a, Message, Theme, Renderer>>,
direction: Direction,
style: impl Fn(&Theme, Status) -> Appearance + 'a,
) -> Self { ) -> Self {
let content = content.into(); let content = content.into();
@ -91,7 +78,7 @@ where
direction, direction,
content, content,
on_scroll: None, on_scroll: None,
style: Box::new(style), class: Theme::default(),
} }
} }
@ -121,12 +108,21 @@ where
self self
} }
/// Sets the style of the [`Scrollable`] . /// Sets the style of this [`Scrollable`].
pub fn style( #[must_use]
mut self, pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
style: impl Fn(&Theme, Status) -> Appearance + 'a, where
) -> Self { Theme::Class<'a>: From<StyleFn<'a, Theme>>,
self.style = Box::new(style); {
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
@ -237,7 +233,8 @@ pub enum Alignment {
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Scrollable<'a, Message, Theme, Renderer> for Scrollable<'a, Message, Theme, Renderer>
where where
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>() tree::Tag::of::<State>()
@ -651,7 +648,7 @@ where
tree: &Tree, tree: &Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &Theme, theme: &Theme,
style: &renderer::Style, defaults: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
_viewport: &Rectangle, _viewport: &Rectangle,
@ -701,13 +698,9 @@ where
Status::Active Status::Active
}; };
let appearance = (self.style)(theme, status); let style = theme.style(&self.class, status);
container::draw_background( container::draw_background(renderer, &style.container, layout.bounds());
renderer,
&appearance.container,
layout.bounds(),
);
// Draw inner content // Draw inner content
if scrollbars.active() { if scrollbars.active() {
@ -719,7 +712,7 @@ where
&tree.children[0], &tree.children[0],
renderer, renderer,
theme, theme,
style, defaults,
content_layout, content_layout,
cursor, cursor,
&Rectangle { &Rectangle {
@ -782,7 +775,7 @@ where
if let Some(scrollbar) = scrollbars.y { if let Some(scrollbar) = scrollbars.y {
draw_scrollbar( draw_scrollbar(
renderer, renderer,
appearance.vertical_scrollbar, style.vertical_scrollbar,
&scrollbar, &scrollbar,
); );
} }
@ -790,14 +783,14 @@ where
if let Some(scrollbar) = scrollbars.x { if let Some(scrollbar) = scrollbars.x {
draw_scrollbar( draw_scrollbar(
renderer, renderer,
appearance.horizontal_scrollbar, style.horizontal_scrollbar,
&scrollbar, &scrollbar,
); );
} }
if let (Some(x), Some(y)) = (scrollbars.x, scrollbars.y) { if let (Some(x), Some(y)) = (scrollbars.x, scrollbars.y) {
let background = let background =
appearance.gap.or(appearance.container.background); style.gap.or(style.container.background);
if let Some(background) = background { if let Some(background) = background {
renderer.fill_quad( renderer.fill_quad(
@ -821,7 +814,7 @@ where
&tree.children[0], &tree.children[0],
renderer, renderer,
theme, theme,
style, defaults,
content_layout, content_layout,
cursor, cursor,
&Rectangle { &Rectangle {
@ -916,8 +909,8 @@ impl<'a, Message, Theme, Renderer>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Theme: 'a, Theme: 'a + Catalog,
Renderer: 'a + crate::core::Renderer, Renderer: 'a + core::Renderer,
{ {
fn from( fn from(
text_input: Scrollable<'a, Message, Theme, Renderer>, text_input: Scrollable<'a, Message, Theme, Renderer>,
@ -1570,9 +1563,9 @@ pub enum Status {
/// The appearance of a scrolable. /// The appearance of a scrolable.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The [`container::Appearance`] of a scrollable. /// The [`container::Style`] of a scrollable.
pub container: container::Appearance, pub container: container::Style,
/// The vertical [`Scrollbar`] appearance. /// The vertical [`Scrollbar`] appearance.
pub vertical_scrollbar: Scrollbar, pub vertical_scrollbar: Scrollbar,
/// The horizontal [`Scrollbar`] appearance. /// The horizontal [`Scrollbar`] appearance.
@ -1601,29 +1594,35 @@ pub struct Scroller {
pub border: Border, pub border: Border,
} }
/// The style of a [`Scrollable`]. /// The theme catalog of a [`Scrollable`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub trait Catalog {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`Scrollable`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`Scrollable`].
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 { /// A styling function for a [`Scrollable`].
fn default_style(&self, status: Status) -> Appearance { pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
default(self, status)
impl Catalog for Theme {
type Class<'a> = StyleFn<'a, Self>;
fn default<'a>() -> Self::Class<'a> {
Box::new(default)
} }
}
impl DefaultStyle for Appearance { fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
fn default_style(&self, _status: Status) -> Appearance { class(self, status)
*self
} }
} }
/// The default style of a [`Scrollable`]. /// 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 palette = theme.extended_palette();
let scrollbar = Scrollbar { let scrollbar = Scrollbar {
@ -1636,8 +1635,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
}; };
match status { match status {
Status::Active => Appearance { Status::Active => Style {
container: container::Appearance::default(), container: container::Style::default(),
vertical_scrollbar: scrollbar, vertical_scrollbar: scrollbar,
horizontal_scrollbar: scrollbar, horizontal_scrollbar: scrollbar,
gap: None, gap: None,
@ -1654,8 +1653,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
..scrollbar ..scrollbar
}; };
Appearance { Style {
container: container::Appearance::default(), container: container::Style::default(),
vertical_scrollbar: if is_vertical_scrollbar_hovered { vertical_scrollbar: if is_vertical_scrollbar_hovered {
hovered_scrollbar hovered_scrollbar
} else { } else {
@ -1681,8 +1680,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
..scrollbar ..scrollbar
}; };
Appearance { Style {
container: container::Appearance::default(), container: container::Style::default(),
vertical_scrollbar: if is_vertical_scrollbar_dragged { vertical_scrollbar: if is_vertical_scrollbar_dragged {
dragged_scrollbar dragged_scrollbar
} else { } else {

View file

@ -9,7 +9,7 @@ use crate::core::renderer;
use crate::core::touch; use crate::core::touch;
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::{ 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, 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) /// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)] #[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<T>, range: RangeInclusive<T>,
step: T, step: T,
shift_step: Option<T>, shift_step: Option<T>,
@ -49,13 +52,14 @@ pub struct Slider<'a, T, Message, Theme = crate::Theme> {
on_release: Option<Message>, on_release: Option<Message>,
width: Length, width: Length,
height: f32, height: f32,
style: Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme> impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>
where where
T: Copy + From<u8> + PartialOrd, T: Copy + From<u8> + PartialOrd,
Message: Clone, Message: Clone,
Theme: Catalog,
{ {
/// The default height of a [`Slider`]. /// The default height of a [`Slider`].
pub const DEFAULT_HEIGHT: f32 = 16.0; pub const DEFAULT_HEIGHT: f32 = 16.0;
@ -70,7 +74,6 @@ where
/// `Message`. /// `Message`.
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where where
Theme: DefaultStyle + 'a,
F: 'a + Fn(T) -> Message, F: 'a + Fn(T) -> Message,
{ {
let value = if value >= *range.start() { let value = if value >= *range.start() {
@ -95,7 +98,7 @@ where
on_release: None, on_release: None,
width: Length::Fill, width: Length::Fill,
height: Self::DEFAULT_HEIGHT, height: Self::DEFAULT_HEIGHT,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -130,15 +133,6 @@ where
self 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`]. /// Sets the step size of the [`Slider`].
pub fn step(mut self, step: impl Into<T>) -> Self { pub fn step(mut self, step: impl Into<T>) -> Self {
self.step = step.into(); self.step = step.into();
@ -152,6 +146,24 @@ where
self.shift_step = Some(shift_step.into()); self.shift_step = Some(shift_step.into());
self 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<StyleFn<'a, Theme>>,
{
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self
}
} }
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
@ -159,7 +171,8 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where where
T: Copy + Into<f64> + num_traits::FromPrimitive, T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone, Message: Clone,
Renderer: crate::core::Renderer, Theme: Catalog,
Renderer: core::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>() tree::Tag::of::<State>()
@ -349,8 +362,8 @@ where
let bounds = layout.bounds(); let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds); let is_mouse_over = cursor.is_over(bounds);
let style = (self.style)( let style = theme.style(
theme, &self.class,
if state.is_dragging { if state.is_dragging {
Status::Dragged Status::Dragged
} else if is_mouse_over { } else if is_mouse_over {
@ -461,8 +474,8 @@ impl<'a, T, Message, Theme, Renderer> From<Slider<'a, T, Message, Theme>>
where where
T: Copy + Into<f64> + num_traits::FromPrimitive + 'a, T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,
Message: Clone + 'a, Message: Clone + 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: crate::core::Renderer + 'a, Renderer: core::Renderer + 'a,
{ {
fn from( fn from(
slider: Slider<'a, T, Message, Theme>, slider: Slider<'a, T, Message, Theme>,
@ -490,15 +503,15 @@ pub enum Status {
/// The appearance of a slider. /// The appearance of a slider.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The colors of the rail of the slider. /// The colors of the rail of the slider.
pub rail: Rail, pub rail: Rail,
/// The appearance of the [`Handle`] of the slider. /// The appearance of the [`Handle`] of the slider.
pub handle: Handle, pub handle: Handle,
} }
impl Appearance { impl Style {
/// Changes the [`HandleShape`] of the [`Appearance`] to a circle /// Changes the [`HandleShape`] of the [`Style`] to a circle
/// with the given radius. /// with the given radius.
pub fn with_circular_handle(mut self, radius: impl Into<Pixels>) -> Self { pub fn with_circular_handle(mut self, radius: impl Into<Pixels>) -> Self {
self.handle.shape = HandleShape::Circle { self.handle.shape = HandleShape::Circle {
@ -549,29 +562,35 @@ pub enum HandleShape {
}, },
} }
/// The style of a [`Slider`]. /// The theme catalog of a [`Slider`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub trait Catalog: Sized {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`Slider`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`Slider`].
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 { /// A styling function for a [`Slider`].
fn default_style(&self, status: Status) -> Appearance { pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
default(self, status)
impl Catalog for Theme {
type Class<'a> = StyleFn<'a, Self>;
fn default<'a>() -> Self::Class<'a> {
Box::new(default)
} }
}
impl DefaultStyle for Appearance { fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
fn default_style(&self, _status: Status) -> Appearance { class(self, status)
*self
} }
} }
/// The default style of a [`Slider`]. /// 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 palette = theme.extended_palette();
let color = match status { let color = match status {
@ -580,7 +599,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
Status::Dragged => palette.primary.strong.color, Status::Dragged => palette.primary.strong.color,
}; };
Appearance { Style {
rail: Rail { rail: Rail {
colors: (color, palette.secondary.base.color), colors: (color, palette.secondary.base.color),
width: 4.0, width: 4.0,

View file

@ -20,36 +20,36 @@ pub use crate::core::svg::Handle;
/// [`Svg`] images can have a considerable rendering cost when resized, /// [`Svg`] images can have a considerable rendering cost when resized,
/// specially when they are complex. /// specially when they are complex.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Svg<'a, Theme = crate::Theme> { pub struct Svg<'a, Theme = crate::Theme>
where
Theme: Catalog,
{
handle: Handle, handle: Handle,
width: Length, width: Length,
height: Length, height: Length,
content_fit: ContentFit, 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`]. /// Creates a new [`Svg`] from the given [`Handle`].
pub fn new(handle: impl Into<Handle>) -> Self pub fn new(handle: impl Into<Handle>) -> Self {
where
Theme: DefaultStyle + 'a,
{
Svg { Svg {
handle: handle.into(), handle: handle.into(),
width: Length::Fill, width: Length::Fill,
height: Length::Shrink, height: Length::Shrink,
content_fit: ContentFit::Contain, 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 /// Creates a new [`Svg`] that will display the contents of the file at the
/// provided path. /// provided path.
#[must_use] #[must_use]
pub fn from_path(path: impl Into<PathBuf>) -> Self pub fn from_path(path: impl Into<PathBuf>) -> Self {
where
Theme: DefaultStyle + 'a,
{
Self::new(Handle::from_path(path)) 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 the [`Svg`].
#[must_use] #[must_use]
pub fn style( pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
mut self, where
style: impl Fn(&Theme, Status) -> Appearance + 'a, Theme::Class<'a>: From<StyleFn<'a, Theme>>,
) -> Self { {
self.style = Box::new(style); self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
self
}
/// Sets the style class of the [`Svg`].
#[cfg(feature = "advanced")]
#[must_use]
pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
@ -93,6 +101,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Svg<'a, Theme> for Svg<'a, Theme>
where where
Renderer: svg::Renderer, Renderer: svg::Renderer,
Theme: Catalog,
{ {
fn size(&self) -> Size<Length> { fn size(&self) -> Size<Length> {
Size { Size {
@ -167,11 +176,11 @@ where
Status::Idle Status::Idle
}; };
let appearance = (self.style)(theme, status); let style = theme.style(&self.class, status);
renderer.draw_svg( renderer.draw_svg(
self.handle.clone(), self.handle.clone(),
appearance.color, style.color,
drawing_bounds + offset, drawing_bounds + offset,
); );
}; };
@ -189,7 +198,7 @@ where
impl<'a, Message, Theme, Renderer> From<Svg<'a, Theme>> impl<'a, Message, Theme, Renderer> From<Svg<'a, Theme>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Theme: 'a, Theme: Catalog + 'a,
Renderer: svg::Renderer + 'a, Renderer: svg::Renderer + 'a,
{ {
fn from(icon: Svg<'a, Theme>) -> Element<'a, Message, Theme, Renderer> { fn from(icon: Svg<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
@ -208,7 +217,7 @@ pub enum Status {
/// The appearance of an [`Svg`]. /// The appearance of an [`Svg`].
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Appearance { pub struct Style {
/// The [`Color`] filter of an [`Svg`]. /// The [`Color`] filter of an [`Svg`].
/// ///
/// Useful for coloring a symbolic icon. /// Useful for coloring a symbolic icon.
@ -217,23 +226,37 @@ pub struct Appearance {
pub color: Option<Color>, pub color: Option<Color>,
} }
/// The style of an [`Svg`]. /// The theme catalog of an [`Svg`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub trait Catalog {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of an [`Svg`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// 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 { impl Catalog for Theme {
fn default_style(&self, _status: Status) -> Appearance { type Class<'a> = StyleFn<'a, Self>;
Appearance::default()
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 { /// A styling function for an [`Svg`].
fn default_style(&self, _status: Status) -> Appearance { ///
*self /// 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)
} }
} }

View file

@ -32,6 +32,7 @@ pub struct TextEditor<
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Highlighter: text::Highlighter, Highlighter: text::Highlighter,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
content: &'a Content<Renderer>, content: &'a Content<Renderer>,
@ -41,7 +42,7 @@ pub struct TextEditor<
width: Length, width: Length,
height: Length, height: Length,
padding: Padding, padding: Padding,
style: Style<'a, Theme>, class: Theme::Class<'a>,
on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>, on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>,
highlighter_settings: Highlighter::Settings, highlighter_settings: Highlighter::Settings,
highlighter_format: fn( highlighter_format: fn(
@ -53,13 +54,11 @@ pub struct TextEditor<
impl<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer>
TextEditor<'a, highlighter::PlainText, Message, Theme, Renderer> TextEditor<'a, highlighter::PlainText, Message, Theme, Renderer>
where where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
/// Creates new [`TextEditor`] with the given [`Content`]. /// Creates new [`TextEditor`] with the given [`Content`].
pub fn new(content: &'a Content<Renderer>) -> Self pub fn new(content: &'a Content<Renderer>) -> Self {
where
Theme: DefaultStyle + 'a,
{
Self { Self {
content, content,
font: None, font: None,
@ -68,7 +67,7 @@ where
width: Length::Fill, width: Length::Fill,
height: Length::Shrink, height: Length::Shrink,
padding: Padding::new(5.0), padding: Padding::new(5.0),
style: Box::new(Theme::default_style), class: Theme::default(),
on_edit: None, on_edit: None,
highlighter_settings: (), highlighter_settings: (),
highlighter_format: |_highlight, _theme| { highlighter_format: |_highlight, _theme| {
@ -82,6 +81,7 @@ impl<'a, Highlighter, Message, Theme, Renderer>
TextEditor<'a, Highlighter, Message, Theme, Renderer> TextEditor<'a, Highlighter, Message, Theme, Renderer>
where where
Highlighter: text::Highlighter, Highlighter: text::Highlighter,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
/// Sets the height of the [`TextEditor`]. /// Sets the height of the [`TextEditor`].
@ -134,7 +134,7 @@ where
width: self.width, width: self.width,
height: self.height, height: self.height,
padding: self.padding, padding: self.padding,
style: self.style, class: self.class,
on_edit: self.on_edit, on_edit: self.on_edit,
highlighter_settings: settings, highlighter_settings: settings,
highlighter_format: to_format, highlighter_format: to_format,
@ -142,11 +142,20 @@ where
} }
/// Sets the style of the [`TextEditor`]. /// Sets the style of the [`TextEditor`].
pub fn style( #[must_use]
mut self, pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
style: impl Fn(&Theme, Status) -> Appearance + 'a, where
) -> Self { Theme::Class<'a>: From<StyleFn<'a, Theme>>,
self.style = Box::new(style); {
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
@ -309,6 +318,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for TextEditor<'a, Highlighter, Message, Theme, Renderer> for TextEditor<'a, Highlighter, Message, Theme, Renderer>
where where
Highlighter: text::Highlighter, Highlighter: text::Highlighter,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn tag(&self) -> widget::tree::Tag { fn tag(&self) -> widget::tree::Tag {
@ -479,7 +489,7 @@ where
tree: &widget::Tree, tree: &widget::Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &Theme, theme: &Theme,
style: &renderer::Style, defaults: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
@ -508,22 +518,22 @@ where
Status::Active Status::Active
}; };
let appearance = (self.style)(theme, status); let style = theme.style(&self.class, status);
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: appearance.border, border: style.border,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.background, style.background,
); );
renderer.fill_editor( renderer.fill_editor(
&internal.editor, &internal.editor,
bounds.position() bounds.position()
+ Vector::new(self.padding.left, self.padding.top), + Vector::new(self.padding.left, self.padding.top),
style.text_color, defaults.text_color,
*viewport, *viewport,
); );
@ -555,7 +565,7 @@ where
}, },
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.value, style.value,
); );
} }
} }
@ -568,7 +578,7 @@ where
bounds: range, bounds: range,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.selection, style.selection,
); );
} }
} }
@ -604,7 +614,7 @@ impl<'a, Highlighter, Message, Theme, Renderer>
where where
Highlighter: text::Highlighter, Highlighter: text::Highlighter,
Message: 'a, Message: 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn from( fn from(
@ -796,7 +806,7 @@ pub enum Status {
/// The appearance of a text input. /// The appearance of a text input.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The [`Background`] of the text input. /// The [`Background`] of the text input.
pub background: Background, pub background: Background,
/// The [`Border`] of the text input. /// The [`Border`] of the text input.
@ -811,32 +821,38 @@ pub struct Appearance {
pub selection: Color, pub selection: Color,
} }
/// The style of a [`TextEditor`]. /// The theme catalog of a [`TextEditor`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub trait Catalog {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`TextEditor`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`TextEditor`].
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 { /// A styling function for a [`TextEditor`].
fn default_style(&self, status: Status) -> Appearance { pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
default(self, status)
impl Catalog for Theme {
type Class<'a> = StyleFn<'a, Self>;
fn default<'a>() -> Self::Class<'a> {
Box::new(default)
} }
}
impl DefaultStyle for Appearance { fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
fn default_style(&self, _status: Status) -> Appearance { class(self, status)
*self
} }
} }
/// The default style of a [`TextEditor`]. /// 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 palette = theme.extended_palette();
let active = Appearance { let active = Style {
background: Background::Color(palette.background.base.color), background: Background::Color(palette.background.base.color),
border: Border { border: Border {
radius: 2.0.into(), radius: 2.0.into(),
@ -851,21 +867,21 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
match status { match status {
Status::Active => active, Status::Active => active,
Status::Hovered => Appearance { Status::Hovered => Style {
border: Border { border: Border {
color: palette.background.base.text, color: palette.background.base.text,
..active.border ..active.border
}, },
..active ..active
}, },
Status::Focused => Appearance { Status::Focused => Style {
border: Border { border: Border {
color: palette.primary.strong.color, color: palette.primary.strong.color,
..active.border ..active.border
}, },
..active ..active
}, },
Status::Disabled => Appearance { Status::Disabled => Style {
background: Background::Color(palette.background.weak.color), background: Background::Color(palette.background.weak.color),
value: active.placeholder, value: active.placeholder,
..active ..active

View file

@ -60,6 +60,7 @@ pub struct TextInput<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
id: Option<Id>, id: Option<Id>,
@ -75,7 +76,7 @@ pub struct TextInput<
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>, on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_submit: Option<Message>, on_submit: Option<Message>,
icon: Option<Icon<Renderer::Font>>, icon: Option<Icon<Renderer::Font>>,
style: Style<'a, Theme>, class: Theme::Class<'a>,
} }
/// The default [`Padding`] of a [`TextInput`]. /// 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> impl<'a, Message, Theme, Renderer> TextInput<'a, Message, Theme, Renderer>
where where
Message: Clone, Message: Clone,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
/// Creates a new [`TextInput`] with the given placeholder and /// Creates a new [`TextInput`] with the given placeholder and
/// its current value. /// its current value.
pub fn new(placeholder: &str, value: &str) -> Self 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 {
TextInput { TextInput {
id: None, id: None,
placeholder: String::from(placeholder), placeholder: String::from(placeholder),
@ -116,7 +105,7 @@ where
on_paste: None, on_paste: None,
on_submit: None, on_submit: None,
icon: None, icon: None,
style: Box::new(style), class: Theme::default(),
} }
} }
@ -203,11 +192,19 @@ where
} }
/// Sets the style of the [`TextInput`]. /// Sets the style of the [`TextInput`].
pub fn style( #[must_use]
mut self, pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
style: impl Fn(&Theme, Status) -> Appearance + 'a, where
) -> Self { Theme::Class<'a>: From<StyleFn<'a, Theme>>,
self.style = Box::new(style); {
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
@ -345,15 +342,15 @@ where
Status::Active Status::Active
}; };
let appearance = (self.style)(theme, status); let style = theme.style(&self.class, status);
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds,
border: appearance.border, border: style.border,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.background, style.background,
); );
if self.icon.is_some() { if self.icon.is_some() {
@ -362,7 +359,7 @@ where
renderer.fill_paragraph( renderer.fill_paragraph(
&state.icon, &state.icon,
icon_layout.bounds().center(), icon_layout.bounds().center(),
appearance.icon, style.icon,
*viewport, *viewport,
); );
} }
@ -401,7 +398,7 @@ where
}, },
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.value, style.value,
)) ))
} else { } else {
None None
@ -440,7 +437,7 @@ where
}, },
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.selection, style.selection,
)), )),
if end == right { if end == right {
right_offset right_offset
@ -475,9 +472,9 @@ where
Point::new(text_bounds.x, text_bounds.center_y()) Point::new(text_bounds.x, text_bounds.center_y())
- Vector::new(offset, 0.0), - Vector::new(offset, 0.0),
if text.is_empty() { if text.is_empty() {
appearance.placeholder style.placeholder
} else { } else {
appearance.value style.value
}, },
viewport, viewport,
); );
@ -496,6 +493,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for TextInput<'a, Message, Theme, Renderer> for TextInput<'a, Message, Theme, Renderer>
where where
Message: Clone, Message: Clone,
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -1058,8 +1056,8 @@ where
impl<'a, Message, Theme, Renderer> From<TextInput<'a, Message, Theme, Renderer>> impl<'a, Message, Theme, Renderer> From<TextInput<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a + Clone, Message: Clone + 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn from( fn from(
@ -1400,7 +1398,7 @@ pub enum Status {
/// The appearance of a text input. /// The appearance of a text input.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The [`Background`] of the text input. /// The [`Background`] of the text input.
pub background: Background, pub background: Background,
/// The [`Border`] of the text input. /// The [`Border`] of the text input.
@ -1415,32 +1413,40 @@ pub struct Appearance {
pub selection: Color, pub selection: Color,
} }
/// The style of a [`TextInput`]. /// The theme catalog of a [`TextInput`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub trait Catalog: Sized {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`TextInput`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`TextInput`].
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 { /// A styling function for a [`TextInput`].
fn default_style(&self, status: Status) -> Appearance { ///
default(self, status) /// 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(default)
} }
}
impl DefaultStyle for Appearance { fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
fn default_style(&self, _status: Status) -> Appearance { class(self, status)
*self
} }
} }
/// The default style of a [`TextInput`]. /// 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 palette = theme.extended_palette();
let active = Appearance { let active = Style {
background: Background::Color(palette.background.base.color), background: Background::Color(palette.background.base.color),
border: Border { border: Border {
radius: 2.0.into(), radius: 2.0.into(),
@ -1455,21 +1461,21 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
match status { match status {
Status::Active => active, Status::Active => active,
Status::Hovered => Appearance { Status::Hovered => Style {
border: Border { border: Border {
color: palette.background.base.text, color: palette.background.base.text,
..active.border ..active.border
}, },
..active ..active
}, },
Status::Focused => Appearance { Status::Focused => Style {
border: Border { border: Border {
color: palette.primary.strong.color, color: palette.primary.strong.color,
..active.border ..active.border
}, },
..active ..active
}, },
Status::Disabled => Appearance { Status::Disabled => Style {
background: Background::Color(palette.background.weak.color), background: Background::Color(palette.background.weak.color),
value: active.placeholder, value: active.placeholder,
..active ..active

View file

@ -155,9 +155,9 @@ where
if let Some(background) = self.background { if let Some(background) = self.background {
container::draw_background( container::draw_background(
renderer, renderer,
&container::Appearance { &container::Style {
background: Some(background(&theme)), background: Some(background(&theme)),
..container::Appearance::default() ..container::Style::default()
}, },
layout.bounds(), layout.bounds(),
); );

View file

@ -35,6 +35,7 @@ pub struct Toggler<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
is_toggled: bool, is_toggled: bool,
@ -48,11 +49,12 @@ pub struct Toggler<
text_shaping: text::Shaping, text_shaping: text::Shaping,
spacing: f32, spacing: f32,
font: Option<Renderer::Font>, font: Option<Renderer::Font>,
style: Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer>
where where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
/// The default size of a [`Toggler`]. /// The default size of a [`Toggler`].
@ -72,7 +74,6 @@ where
f: F, f: F,
) -> Self ) -> Self
where where
Theme: 'a + DefaultStyle,
F: 'a + Fn(bool) -> Message, F: 'a + Fn(bool) -> Message,
{ {
Toggler { Toggler {
@ -87,7 +88,7 @@ where
text_shaping: text::Shaping::Basic, text_shaping: text::Shaping::Basic,
spacing: Self::DEFAULT_SIZE / 2.0, spacing: Self::DEFAULT_SIZE / 2.0,
font: None, font: None,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -145,11 +146,20 @@ where
} }
/// Sets the style of the [`Toggler`]. /// Sets the style of the [`Toggler`].
pub fn style( #[must_use]
mut self, pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
style: impl Fn(&Theme, Status) -> Appearance + 'a, where
) -> Self { Theme::Class<'a>: From<StyleFn<'a, Theme>>,
self.style = Box::new(style); {
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
@ -157,6 +167,7 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Toggler<'a, Message, Theme, Renderer> for Toggler<'a, Message, Theme, Renderer>
where where
Theme: Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -284,7 +295,7 @@ where
style, style,
label_layout, label_layout,
tree.state.downcast_ref(), tree.state.downcast_ref(),
crate::text::Appearance::default(), crate::text::Style::default(),
viewport, 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 border_radius = bounds.height / BORDER_RADIUS_RATIO;
let space = SPACE_RATIO * bounds.height; let space = SPACE_RATIO * bounds.height;
@ -319,12 +330,12 @@ where
bounds: toggler_background_bounds, bounds: toggler_background_bounds,
border: Border { border: Border {
radius: border_radius.into(), radius: border_radius.into(),
width: appearance.background_border_width, width: style.background_border_width,
color: appearance.background_border_color, color: style.background_border_color,
}, },
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.background, style.background,
); );
let toggler_foreground_bounds = Rectangle { let toggler_foreground_bounds = Rectangle {
@ -344,12 +355,12 @@ where
bounds: toggler_foreground_bounds, bounds: toggler_foreground_bounds,
border: Border { border: Border {
radius: border_radius.into(), radius: border_radius.into(),
width: appearance.foreground_border_width, width: style.foreground_border_width,
color: appearance.foreground_border_color, color: style.foreground_border_color,
}, },
..renderer::Quad::default() ..renderer::Quad::default()
}, },
appearance.foreground, style.foreground,
); );
} }
} }
@ -358,7 +369,7 @@ impl<'a, Message, Theme, Renderer> From<Toggler<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn from( fn from(
@ -385,7 +396,7 @@ pub enum Status {
/// The appearance of a toggler. /// The appearance of a toggler.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The background [`Color`] of the toggler. /// The background [`Color`] of the toggler.
pub background: Color, pub background: Color,
/// The width of the background border of the toggler. /// The width of the background border of the toggler.
@ -400,29 +411,37 @@ pub struct Appearance {
pub foreground_border_color: Color, pub foreground_border_color: Color,
} }
/// The style of a [`Toggler`]. /// The theme catalog of a [`Toggler`].
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>; pub trait Catalog: Sized {
/// The item class of the [`Catalog`].
type Class<'a>;
/// The default style of a [`Toggler`]. /// The default class produced by the [`Catalog`].
pub trait DefaultStyle { fn default<'a>() -> Self::Class<'a>;
/// Returns the default style of a [`Toggler`].
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 { /// A styling function for a [`Toggler`].
fn default_style(&self, status: Status) -> Appearance { ///
default(self, status) /// 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(default)
} }
}
impl DefaultStyle for Appearance { fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
fn default_style(&self, _status: Status) -> Appearance { class(self, status)
*self
} }
} }
/// The default style of a [`Toggler`]. /// 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 palette = theme.extended_palette();
let background = match status { let background = match status {
@ -455,7 +474,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
} }
}; };
Appearance { Style {
background, background,
foreground, foreground,
foreground_border_width: 0.0, foreground_border_width: 0.0,

View file

@ -20,6 +20,7 @@ pub struct Tooltip<
Theme = crate::Theme, Theme = crate::Theme,
Renderer = crate::Renderer, Renderer = crate::Renderer,
> where > where
Theme: container::Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
content: Element<'a, Message, Theme, Renderer>, content: Element<'a, Message, Theme, Renderer>,
@ -28,11 +29,12 @@ pub struct Tooltip<
gap: f32, gap: f32,
padding: f32, padding: f32,
snap_within_viewport: bool, snap_within_viewport: bool,
style: container::Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, Message, Theme, Renderer> Tooltip<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Tooltip<'a, Message, Theme, Renderer>
where where
Theme: container::Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
/// The default padding of a [`Tooltip`] drawn by this renderer. /// The default padding of a [`Tooltip`] drawn by this renderer.
@ -45,10 +47,7 @@ where
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
tooltip: impl Into<Element<'a, Message, Theme, Renderer>>, tooltip: impl Into<Element<'a, Message, Theme, Renderer>>,
position: Position, position: Position,
) -> Self ) -> Self {
where
Theme: container::DefaultStyle + 'a,
{
Tooltip { Tooltip {
content: content.into(), content: content.into(),
tooltip: tooltip.into(), tooltip: tooltip.into(),
@ -56,7 +55,7 @@ where
gap: 0.0, gap: 0.0,
padding: Self::DEFAULT_PADDING, padding: Self::DEFAULT_PADDING,
snap_within_viewport: true, snap_within_viewport: true,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -79,11 +78,23 @@ where
} }
/// Sets the style of the [`Tooltip`]. /// Sets the style of the [`Tooltip`].
#[must_use]
pub fn style( pub fn style(
mut self, mut self,
style: impl Fn(&Theme, container::Status) -> container::Appearance + 'a, style: impl Fn(&Theme) -> container::Style + 'a,
) -> Self { ) -> Self
self.style = Box::new(style); where
Theme::Class<'a>: From<container::StyleFn<'a, Theme>>,
{
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self self
} }
} }
@ -91,6 +102,7 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Tooltip<'a, Message, Theme, Renderer> for Tooltip<'a, Message, Theme, Renderer>
where where
Theme: container::Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn children(&self) -> Vec<widget::Tree> { fn children(&self) -> Vec<widget::Tree> {
@ -239,7 +251,7 @@ where
positioning: self.position, positioning: self.position,
gap: self.gap, gap: self.gap,
padding: self.padding, padding: self.padding,
style: &self.style, class: &self.class,
}))) })))
} else { } else {
None None
@ -262,7 +274,7 @@ impl<'a, Message, Theme, Renderer> From<Tooltip<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Theme: 'a, Theme: container::Catalog + 'a,
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn from( fn from(
@ -299,6 +311,7 @@ enum State {
struct Overlay<'a, 'b, Message, Theme, Renderer> struct Overlay<'a, 'b, Message, Theme, Renderer>
where where
Theme: container::Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
position: Point, position: Point,
@ -310,14 +323,14 @@ where
positioning: Position, positioning: Position,
gap: f32, gap: f32,
padding: f32, padding: f32,
style: class: &'b Theme::Class<'a>,
&'b (dyn Fn(&Theme, container::Status) -> container::Appearance + 'a),
} }
impl<'a, 'b, Message, Theme, Renderer> impl<'a, 'b, Message, Theme, Renderer>
overlay::Overlay<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
for Overlay<'a, 'b, Message, Theme, Renderer> for Overlay<'a, 'b, Message, Theme, Renderer>
where where
Theme: container::Catalog,
Renderer: text::Renderer, Renderer: text::Renderer,
{ {
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
@ -426,7 +439,7 @@ where
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: mouse::Cursor, 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()); container::draw_background(renderer, &style, layout.bounds());

View file

@ -2,10 +2,9 @@
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
pub use crate::slider::{ 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::event::{self, Event};
use crate::core::keyboard; use crate::core::keyboard;
use crate::core::keyboard::key::{self, Key}; use crate::core::keyboard::key::{self, Key};
@ -15,8 +14,8 @@ use crate::core::renderer;
use crate::core::touch; use crate::core::touch;
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::{ use crate::core::{
Border, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell, Size, self, Border, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell,
Widget, Size, Widget,
}; };
/// An vertical bar and a handle that selects a single value from a range of /// 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); /// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged);
/// ``` /// ```
#[allow(missing_debug_implementations)] #[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<T>, range: RangeInclusive<T>,
step: T, step: T,
shift_step: Option<T>, shift_step: Option<T>,
@ -51,13 +53,14 @@ pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> {
on_release: Option<Message>, on_release: Option<Message>,
width: f32, width: f32,
height: Length, height: Length,
style: Style<'a, Theme>, class: Theme::Class<'a>,
} }
impl<'a, T, Message, Theme> VerticalSlider<'a, T, Message, Theme> impl<'a, T, Message, Theme> VerticalSlider<'a, T, Message, Theme>
where where
T: Copy + From<u8> + std::cmp::PartialOrd, T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone, Message: Clone,
Theme: Catalog,
{ {
/// The default width of a [`VerticalSlider`]. /// The default width of a [`VerticalSlider`].
pub const DEFAULT_WIDTH: f32 = 16.0; pub const DEFAULT_WIDTH: f32 = 16.0;
@ -72,7 +75,6 @@ where
/// `Message`. /// `Message`.
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where where
Theme: DefaultStyle + 'a,
F: 'a + Fn(T) -> Message, F: 'a + Fn(T) -> Message,
{ {
let value = if value >= *range.start() { let value = if value >= *range.start() {
@ -97,7 +99,7 @@ where
on_release: None, on_release: None,
width: Self::DEFAULT_WIDTH, width: Self::DEFAULT_WIDTH,
height: Length::Fill, height: Length::Fill,
style: Box::new(Theme::default_style), class: Theme::default(),
} }
} }
@ -132,15 +134,6 @@ where
self 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`]. /// Sets the step size of the [`VerticalSlider`].
pub fn step(mut self, step: T) -> Self { pub fn step(mut self, step: T) -> Self {
self.step = step; self.step = step;
@ -154,6 +147,24 @@ where
self.shift_step = Some(shift_step.into()); self.shift_step = Some(shift_step.into());
self 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<StyleFn<'a, Theme>>,
{
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<Theme::Class<'a>>) -> Self {
self.class = class.into();
self
}
} }
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
@ -161,6 +172,7 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where where
T: Copy + Into<f64> + num_traits::FromPrimitive, T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone, Message: Clone,
Theme: Catalog,
Renderer: core::Renderer, Renderer: core::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -354,8 +366,8 @@ where
let bounds = layout.bounds(); let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds); let is_mouse_over = cursor.is_over(bounds);
let style = (self.style)( let style = theme.style(
theme, &self.class,
if state.is_dragging { if state.is_dragging {
Status::Dragged Status::Dragged
} else if is_mouse_over { } else if is_mouse_over {
@ -467,7 +479,7 @@ impl<'a, T, Message, Theme, Renderer>
where where
T: Copy + Into<f64> + num_traits::FromPrimitive + 'a, T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,
Message: Clone + 'a, Message: Clone + 'a,
Theme: 'a, Theme: Catalog + 'a,
Renderer: core::Renderer + 'a, Renderer: core::Renderer + 'a,
{ {
fn from( fn from(