refactor: make Theme a generic parameter for the button

This makes the button possible to use with custom themes in cosmic-time
This commit is contained in:
Ashley Wulber 2024-02-02 20:28:05 -05:00 committed by Ashley Wulber
parent 11ac07010b
commit 82dfab633a
5 changed files with 84 additions and 71 deletions

View file

@ -58,32 +58,33 @@ pub fn icon() -> Handle {
impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> { impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {
fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> { fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> {
let button: super::Button<'a, Message, crate::Renderer> = row::with_capacity(2) let button: super::Button<'a, Message, crate::Theme, crate::Renderer> =
.push({ row::with_capacity(2)
let mut font = crate::font::DEFAULT; .push({
font.weight = builder.font_weight; let mut font = crate::font::DEFAULT;
font.weight = builder.font_weight;
// TODO: Avoid allocation // TODO: Avoid allocation
crate::widget::text(builder.label.to_string()) crate::widget::text(builder.label.to_string())
.size(builder.font_size) .size(builder.font_size)
.line_height(LineHeight::Absolute(builder.line_height.into())) .line_height(LineHeight::Absolute(builder.line_height.into()))
.font(font) .font(font)
}) })
.push_maybe(if builder.variant.trailing_icon { .push_maybe(if builder.variant.trailing_icon {
Some(icon().icon().size(builder.icon_size)) Some(icon().icon().size(builder.icon_size))
} else { } else {
None None
}) })
.padding(builder.padding) .padding(builder.padding)
.width(builder.width) .width(builder.width)
.height(builder.height) .height(builder.height)
.spacing(builder.spacing) .spacing(builder.spacing)
.align_items(Alignment::Center) .align_items(Alignment::Center)
.apply(button) .apply(button)
.padding(0) .padding(0)
.id(builder.id) .id(builder.id)
.on_press_maybe(builder.on_press.take()) .on_press_maybe(builder.on_press.take())
.style(builder.style); .style(builder.style);
if builder.tooltip.is_empty() { if builder.tooltip.is_empty() {
button.into() button.into()

View file

@ -26,22 +26,28 @@ pub use text::{destructive, standard, suggested, text};
mod widget; mod widget;
pub use widget::{draw, focus, layout, mouse_interaction, Button}; pub use widget::{draw, focus, layout, mouse_interaction, Button};
use crate::Element; use crate::iced::Element;
use iced_core::font::Weight; use iced_core::font::Weight;
use iced_core::widget::Id; use iced_core::widget::Id;
use iced_core::{Length, Padding}; use iced_core::{Length, Padding};
use std::borrow::Cow; use std::borrow::Cow;
pub fn button<'a, Message>( pub fn button<'a, Message, Theme>(
content: impl Into<Element<'a, Message>>, content: impl Into<Element<'a, Message, Theme, crate::Renderer>>,
) -> Button<'a, Message, crate::Renderer> { ) -> Button<'a, Message, Theme, crate::Renderer>
where
Theme: style::StyleSheet,
{
Button::new(content) Button::new(content)
} }
pub fn custom_image_button<'a, Message>( pub fn custom_image_button<'a, Message, Theme>(
content: impl Into<Element<'a, Message>>, content: impl Into<Element<'a, Message, Theme, crate::Renderer>>,
on_remove: Option<Message>, on_remove: Option<Message>,
) -> Button<'a, Message, crate::Renderer> { ) -> Button<'a, Message, Theme, crate::Renderer>
where
Theme: style::StyleSheet,
{
Button::new_image(content, on_remove) Button::new_image(content, on_remove)
} }

View file

@ -109,23 +109,24 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.into() .into()
}); });
let button: super::Button<'a, Message, crate::Renderer> = row::with_capacity(3) let button: super::Button<'a, Message, crate::Theme, crate::Renderer> =
// Optional icon to place before label. row::with_capacity(3)
.push_maybe(leading_icon) // Optional icon to place before label.
// Optional label between icons. .push_maybe(leading_icon)
.push_maybe(label) // Optional label between icons.
// Optional icon to place behind the label. .push_maybe(label)
.push_maybe(trailing_icon) // Optional icon to place behind the label.
.padding(builder.padding) .push_maybe(trailing_icon)
.width(builder.width) .padding(builder.padding)
.height(builder.height) .width(builder.width)
.spacing(builder.spacing) .height(builder.height)
.align_items(Alignment::Center) .spacing(builder.spacing)
.apply(button) .align_items(Alignment::Center)
.padding(0) .apply(button)
.id(builder.id) .padding(0)
.on_press_maybe(builder.on_press.take()) .id(builder.id)
.style(builder.style); .on_press_maybe(builder.on_press.take())
.style(builder.style);
if builder.tooltip.is_empty() { if builder.tooltip.is_empty() {
button.into() button.into()

View file

@ -40,9 +40,10 @@ enum Variant<Message> {
/// A generic button which emits a message when pressed. /// A generic button which emits a message when pressed.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
#[must_use] #[must_use]
pub struct Button<'a, Message, Renderer> pub struct Button<'a, Message, Theme, Renderer>
where where
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
Theme: super::style::StyleSheet,
{ {
id: Id, id: Id,
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
@ -51,22 +52,23 @@ where
description: Option<iced_accessibility::Description<'a>>, description: Option<iced_accessibility::Description<'a>>,
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
label: Option<Vec<iced_accessibility::accesskit::NodeId>>, label: Option<Vec<iced_accessibility::accesskit::NodeId>>,
content: Element<'a, Message, crate::Theme, Renderer>, content: Element<'a, Message, Theme, Renderer>,
on_press: Option<Message>, on_press: Option<Message>,
width: Length, width: Length,
height: Length, height: Length,
padding: Padding, padding: Padding,
selected: bool, selected: bool,
style: <crate::Theme as StyleSheet>::Style, style: <Theme as StyleSheet>::Style,
variant: Variant<Message>, variant: Variant<Message>,
} }
impl<'a, Message, Renderer> Button<'a, Message, Renderer> impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer>
where where
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
Theme: super::style::StyleSheet,
{ {
/// Creates a new [`Button`] with the given content. /// Creates a new [`Button`] with the given content.
pub fn new(content: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self { pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
Self { Self {
id: Id::unique(), id: Id::unique(),
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
@ -81,13 +83,13 @@ where
height: Length::Shrink, height: Length::Shrink,
padding: Padding::new(5.0), padding: Padding::new(5.0),
selected: false, selected: false,
style: <crate::Theme as StyleSheet>::Style::default(), style: <Theme as StyleSheet>::Style::default(),
variant: Variant::Normal, variant: Variant::Normal,
} }
} }
pub fn new_image( pub fn new_image(
content: impl Into<Element<'a, Message, crate::Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
on_remove: Option<Message>, on_remove: Option<Message>,
) -> Self { ) -> Self {
Self { Self {
@ -104,7 +106,7 @@ where
height: Length::Shrink, height: Length::Shrink,
padding: Padding::new(5.0), padding: Padding::new(5.0),
selected: false, selected: false,
style: <crate::Theme as StyleSheet>::Style::default(), style: <Theme as StyleSheet>::Style::default(),
variant: Variant::Image { variant: Variant::Image {
on_remove, on_remove,
close_icon: crate::widget::icon::from_name("window-close-symbolic") close_icon: crate::widget::icon::from_name("window-close-symbolic")
@ -178,7 +180,7 @@ where
} }
/// Sets the style variant of this [`Button`]. /// Sets the style variant of this [`Button`].
pub fn style(mut self, style: <crate::Theme as StyleSheet>::Style) -> Self { pub fn style(mut self, style: <Theme as StyleSheet>::Style) -> Self {
self.style = style; self.style = style;
self self
} }
@ -214,11 +216,12 @@ where
} }
} }
impl<'a, Message, Renderer> Widget<Message, crate::Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Button<'a, Message, Renderer> for Button<'a, Message, Theme, Renderer>
where where
Message: 'a + Clone, Message: 'a + Clone,
Renderer: 'a + iced_core::Renderer + svg::Renderer, Renderer: 'a + iced_core::Renderer + svg::Renderer,
Theme: super::style::StyleSheet,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>() tree::Tag::of::<State>()
@ -340,7 +343,7 @@ where
&self, &self,
tree: &Tree, tree: &Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &crate::Theme, theme: &Theme,
renderer_style: &renderer::Style, renderer_style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
@ -349,7 +352,7 @@ where
let bounds = layout.bounds(); let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap(); let content_layout = layout.children().next().unwrap();
let styling = draw( let styling = draw::<_, Theme>(
renderer, renderer,
bounds, bounds,
cursor, cursor,
@ -471,7 +474,7 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay( self.content.as_widget_mut().overlay(
&mut tree.children[0], &mut tree.children[0],
layout.children().next().unwrap(), layout.children().next().unwrap(),
@ -554,13 +557,14 @@ where
} }
} }
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>> impl<'a, Message, Theme, Renderer> From<Button<'a, Message, Theme, Renderer>>
for Element<'a, Message, crate::Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: Clone + 'a, Message: Clone + 'a,
Renderer: iced_core::Renderer + svg::Renderer + 'a, Renderer: iced_core::Renderer + svg::Renderer + 'a,
Theme: super::style::StyleSheet + 'a,
{ {
fn from(button: Button<'a, Message, Renderer>) -> Self { fn from(button: Button<'a, Message, Theme, Renderer>) -> Self {
Self::new(button) Self::new(button)
} }
} }
@ -680,18 +684,19 @@ pub fn update<'a, Message: Clone>(
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn draw<'a, Renderer: iced_core::Renderer>( pub fn draw<'a, Renderer: iced_core::Renderer, Theme>(
renderer: &mut Renderer, renderer: &mut Renderer,
bounds: Rectangle, bounds: Rectangle,
cursor: mouse::Cursor, cursor: mouse::Cursor,
is_enabled: bool, is_enabled: bool,
is_selected: bool, is_selected: bool,
style_sheet: &dyn StyleSheet<Style = <crate::Theme as StyleSheet>::Style>, style_sheet: &dyn StyleSheet<Style = <Theme as StyleSheet>::Style>,
style: &<crate::Theme as StyleSheet>::Style, style: &<Theme as StyleSheet>::Style,
state: impl FnOnce() -> &'a State, state: impl FnOnce() -> &'a State,
draw_contents: impl FnOnce(&mut Renderer, Appearance), draw_contents: impl FnOnce(&mut Renderer, Appearance),
) -> Appearance ) -> Appearance
where where
Theme: super::style::StyleSheet,
{ {
let is_mouse_over = cursor.position().is_some_and(|p| bounds.contains(p)); let is_mouse_over = cursor.position().is_some_and(|p| bounds.contains(p));

View file

@ -122,7 +122,7 @@ impl ColorPickerModel {
&self, &self,
f: T, f: T,
icon_portion: Option<u16>, icon_portion: Option<u16>,
) -> crate::widget::Button<'a, Message, crate::Renderer> { ) -> crate::widget::Button<'a, Message, crate::Theme, crate::Renderer> {
color_button( color_button(
Some(f(ColorPickerUpdate::ToggleColorPicker)), Some(f(ColorPickerUpdate::ToggleColorPicker)),
self.applied_color, self.applied_color,
@ -755,7 +755,7 @@ pub fn color_button<'a, Message: 'static>(
on_press: Option<Message>, on_press: Option<Message>,
color: Option<Color>, color: Option<Color>,
icon_portion: Length, icon_portion: Length,
) -> crate::widget::Button<'a, Message, crate::Renderer> { ) -> crate::widget::Button<'a, Message, crate::Theme, crate::Renderer> {
let spacing = THEME.with(|t| t.borrow().cosmic().spacing); let spacing = THEME.with(|t| t.borrow().cosmic().spacing);
button(if color.is_some() { button(if color.is_some() {