improv(button): specialize button for cosmic, and apply alpha to header buttons

This commit is contained in:
Michael Aaron Murphy 2024-05-17 19:08:43 +02:00 • committed by Michael Murphy
parent c7dc5ab1ed
commit 1355588723
8 changed files with 138 additions and 164 deletions

View file

@ -152,7 +152,7 @@ impl Context {
pub fn icon_button_from_handle<'a, Message: 'static>( pub fn icon_button_from_handle<'a, Message: 'static>(
&self, &self,
icon: widget::icon::Handle, icon: widget::icon::Handle,
) -> crate::widget::Button<'a, Message, crate::Theme, Renderer> { ) -> crate::widget::Button<'a, Message> {
let symbolic = icon.symbolic; let symbolic = icon.symbolic;
let suggested = self.suggested_size(symbolic); let suggested = self.suggested_size(symbolic);
let applet_padding = self.suggested_padding(symbolic); let applet_padding = self.suggested_padding(symbolic);
@ -176,7 +176,7 @@ impl Context {
pub fn icon_button<'a, Message: 'static>( pub fn icon_button<'a, Message: 'static>(
&self, &self,
icon_name: &'a str, icon_name: &'a str,
) -> crate::widget::Button<'a, Message, crate::Theme, Renderer> { ) -> crate::widget::Button<'a, Message> {
self.icon_button_from_handle( self.icon_button_from_handle(
widget::icon::from_name(icon_name) widget::icon::from_name(icon_name)
.symbolic(true) .symbolic(true)
@ -371,7 +371,7 @@ pub fn style() -> <crate::Theme as iced_style::application::StyleSheet>::Style {
pub fn menu_button<'a, Message>( pub fn menu_button<'a, Message>(
content: impl Into<Element<'a, Message>>, content: impl Into<Element<'a, Message>>,
) -> crate::widget::Button<'a, Message, crate::Theme, crate::Renderer> { ) -> crate::widget::Button<'a, Message> {
crate::widget::Button::new(content) crate::widget::Button::new(content)
.style(Button::AppletMenu) .style(Button::AppletMenu)
.padding(menu_control_padding()) .padding(menu_control_padding())

View file

@ -58,33 +58,32 @@ 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::Theme, crate::Renderer> = let button: super::Button<'a, Message> = row::with_capacity(2)
row::with_capacity(2) .push({
.push({ let mut font = crate::font::DEFAULT;
let mut font = crate::font::DEFAULT; font.weight = builder.font_weight;
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,28 +26,19 @@ 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::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, Theme>( pub fn button<'a, Message>(content: impl Into<crate::Element<'a, Message>>) -> Button<'a, Message> {
content: impl Into<Element<'a, Message, Theme, 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, Theme>( pub fn custom_image_button<'a, Message>(
content: impl Into<Element<'a, Message, Theme, crate::Renderer>>, content: impl Into<crate::Element<'a, Message>>,
on_remove: Option<Message>, on_remove: Option<Message>,
) -> Button<'a, Message, Theme, crate::Renderer> ) -> Button<'a, Message> {
where
Theme: style::StyleSheet,
{
Button::new_image(content, on_remove) Button::new_image(content, on_remove)
} }

View file

@ -109,24 +109,23 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.into() .into()
}); });
let button: super::Button<'a, Message, crate::Theme, crate::Renderer> = let button: super::Button<'a, Message> = row::with_capacity(3)
row::with_capacity(3) // Optional icon to place before label.
// Optional icon to place before label. .push_maybe(leading_icon)
.push_maybe(leading_icon) // Optional label between icons.
// Optional label between icons. .push_maybe(label)
.push_maybe(label) // Optional icon to place behind the label.
// Optional icon to place behind the label. .push_maybe(trailing_icon)
.push_maybe(trailing_icon) .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

@ -10,7 +10,7 @@ use iced_runtime::core::widget::Id;
use iced_runtime::{keyboard, Command}; use iced_runtime::{keyboard, Command};
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::renderer::{self, Quad}; use iced_core::renderer::{self, Quad, Renderer};
use iced_core::touch; use iced_core::touch;
use iced_core::widget::tree::{self, Tree}; use iced_core::widget::tree::{self, Tree};
use iced_core::widget::Operation; use iced_core::widget::Operation;
@ -18,8 +18,7 @@ use iced_core::{layout, svg};
use iced_core::{mouse, Border}; use iced_core::{mouse, Border};
use iced_core::{overlay, Shadow}; use iced_core::{overlay, Shadow};
use iced_core::{ use iced_core::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell, Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget,
Vector, Widget,
}; };
use iced_renderer::core::widget::{operation, OperationOutputWrapper}; use iced_renderer::core::widget::{operation, OperationOutputWrapper};
@ -39,11 +38,7 @@ 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, Theme, Renderer> pub struct Button<'a, Message> {
where
Renderer: iced_core::Renderer,
Theme: super::style::StyleSheet,
{
id: Id, id: Id,
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
name: Option<std::borrow::Cow<'a, str>>, name: Option<std::borrow::Cow<'a, str>>,
@ -51,23 +46,19 @@ 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, Theme, Renderer>, content: crate::Element<'a, Message>,
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: <Theme as StyleSheet>::Style, style: crate::theme::Button,
variant: Variant<Message>, variant: Variant<Message>,
} }
impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> impl<'a, Message> Button<'a, Message> {
where
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, Theme, Renderer>>) -> Self { pub fn new(content: impl Into<crate::Element<'a, Message>>) -> Self {
Self { Self {
id: Id::unique(), id: Id::unique(),
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
@ -82,13 +73,13 @@ where
height: Length::Shrink, height: Length::Shrink,
padding: Padding::new(5.0), padding: Padding::new(5.0),
selected: false, selected: false,
style: <Theme as StyleSheet>::Style::default(), style: crate::theme::Button::default(),
variant: Variant::Normal, variant: Variant::Normal,
} }
} }
pub fn new_image( pub fn new_image(
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<crate::Element<'a, Message>>,
on_remove: Option<Message>, on_remove: Option<Message>,
) -> Self { ) -> Self {
Self { Self {
@ -105,7 +96,7 @@ where
height: Length::Shrink, height: Length::Shrink,
padding: Padding::new(5.0), padding: Padding::new(5.0),
selected: false, selected: false,
style: <Theme as StyleSheet>::Style::default(), style: crate::theme::Button::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")
@ -171,7 +162,7 @@ where
} }
/// Sets the style variant of this [`Button`]. /// Sets the style variant of this [`Button`].
pub fn style(mut self, style: <Theme as StyleSheet>::Style) -> Self { pub fn style(mut self, style: crate::theme::Button) -> Self {
self.style = style; self.style = style;
self self
} }
@ -207,12 +198,8 @@ where
} }
} }
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
for Button<'a, Message, Theme, Renderer> for Button<'a, Message>
where
Message: 'a + Clone,
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>()
@ -237,7 +224,7 @@ where
fn layout( fn layout(
&self, &self,
tree: &mut Tree, tree: &mut Tree,
renderer: &Renderer, renderer: &crate::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
layout( layout(
@ -258,7 +245,7 @@ where
&self, &self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &crate::Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
) { ) {
operation.container(None, layout.bounds(), &mut |operation| { operation.container(None, layout.bounds(), &mut |operation| {
@ -279,7 +266,7 @@ where
event: Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
@ -330,11 +317,12 @@ where
) )
} }
#[allow(clippy::too_many_lines)]
fn draw( fn draw(
&self, &self,
tree: &Tree, tree: &Tree,
renderer: &mut Renderer, renderer: &mut crate::Renderer,
theme: &Theme, theme: &crate::Theme,
renderer_style: &renderer::Style, renderer_style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
@ -343,31 +331,60 @@ 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::<_, Theme>( let mut headerbar_alpha = None;
renderer,
bounds, let is_enabled = self.on_press.is_some();
cursor, let is_mouse_over = cursor.position().is_some_and(|p| bounds.contains(p));
self.on_press.is_some(),
self.selected, let state = tree.state.downcast_ref::<State>();
theme,
&self.style, let styling = if !is_enabled {
|| tree.state.downcast_ref::<State>(), theme.disabled(&self.style)
|renderer, styling| { } else if is_mouse_over {
self.content.as_widget().draw( if state.is_pressed {
&tree.children[0], if !self.selected && matches!(self.style, crate::theme::Button::HeaderBar) {
renderer, headerbar_alpha = Some(0.8);
theme, }
&renderer::Style {
icon_color: styling.icon_color.unwrap_or(renderer_style.icon_color), theme.pressed(state.is_focused, self.selected, &self.style)
text_color: styling.text_color.unwrap_or(renderer_style.icon_color), } else {
scale_factor: renderer_style.scale_factor, if !self.selected && matches!(self.style, crate::theme::Button::HeaderBar) {
}, headerbar_alpha = Some(0.8);
content_layout, }
cursor,
&bounds, theme.hovered(state.is_focused, self.selected, &self.style)
); }
}, } else {
); if !self.selected && matches!(self.style, crate::theme::Button::HeaderBar) {
headerbar_alpha = Some(0.75);
}
theme.active(state.is_focused, self.selected, &self.style)
};
let mut icon_color = styling.icon_color.unwrap_or(renderer_style.icon_color);
let mut text_color = styling.text_color.unwrap_or(renderer_style.text_color);
if let Some(alpha) = headerbar_alpha {
icon_color.a = alpha;
text_color.a = alpha;
}
draw::<_, crate::Theme>(renderer, bounds, &styling, |renderer, _styling| {
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
&renderer::Style {
icon_color,
text_color,
scale_factor: renderer_style.scale_factor,
},
content_layout,
cursor,
&bounds,
);
});
if let Variant::Image { if let Variant::Image {
close_icon, close_icon,
@ -405,7 +422,7 @@ where
iced_core::svg::Renderer::draw( iced_core::svg::Renderer::draw(
renderer, renderer,
crate::widget::common::object_select().clone(), crate::widget::common::object_select().clone(),
styling.icon_color, Some(icon_color),
Rectangle { Rectangle {
width: 16.0, width: 16.0,
height: 16.0, height: 16.0,
@ -434,7 +451,7 @@ where
iced_core::svg::Renderer::draw( iced_core::svg::Renderer::draw(
renderer, renderer,
close_icon.clone(), close_icon.clone(),
styling.icon_color, Some(icon_color),
Rectangle { Rectangle {
width: 16.0, width: 16.0,
height: 16.0, height: 16.0,
@ -454,7 +471,7 @@ where
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
_viewport: &Rectangle, _viewport: &Rectangle,
_renderer: &Renderer, _renderer: &crate::Renderer,
) -> mouse::Interaction { ) -> mouse::Interaction {
mouse_interaction(layout, cursor, self.on_press.is_some()) mouse_interaction(layout, cursor, self.on_press.is_some())
} }
@ -463,8 +480,8 @@ where
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &crate::Renderer,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::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(),
@ -547,14 +564,8 @@ where
} }
} }
impl<'a, Message, Theme, Renderer> From<Button<'a, Message, Theme, Renderer>> impl<'a, Message: Clone + 'a> From<Button<'a, Message>> for crate::Element<'a, Message> {
for Element<'a, Message, Theme, Renderer> fn from(button: Button<'a, Message>) -> Self {
where
Message: Clone + 'a,
Renderer: iced_core::Renderer + svg::Renderer + 'a,
Theme: super::style::StyleSheet + 'a,
{
fn from(button: Button<'a, Message, Theme, Renderer>) -> Self {
Self::new(button) Self::new(button)
} }
} }
@ -674,36 +685,14 @@ pub fn update<'a, Message: Clone>(
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn draw<'a, Renderer: iced_core::Renderer, Theme>( pub fn draw<Renderer: iced_core::Renderer, Theme>(
renderer: &mut Renderer, renderer: &mut Renderer,
bounds: Rectangle, bounds: Rectangle,
cursor: mouse::Cursor, styling: &super::style::Appearance,
is_enabled: bool, draw_contents: impl FnOnce(&mut Renderer, &Appearance),
is_selected: bool, ) where
style_sheet: &dyn StyleSheet<Style = <Theme as StyleSheet>::Style>,
style: &<Theme as StyleSheet>::Style,
state: impl FnOnce() -> &'a State,
draw_contents: impl FnOnce(&mut Renderer, Appearance),
) -> Appearance
where
Theme: super::style::StyleSheet, Theme: super::style::StyleSheet,
{ {
let is_mouse_over = cursor.position().is_some_and(|p| bounds.contains(p));
let state: &State = state();
let styling = if !is_enabled {
style_sheet.disabled(style)
} else if is_mouse_over {
if state.is_pressed {
style_sheet.pressed(state.is_focused, is_selected, style)
} else {
style_sheet.hovered(state.is_focused, is_selected, style)
}
} else {
style_sheet.active(state.is_focused, is_selected, style)
};
let doubled_border_width = styling.border_width * 2.0; let doubled_border_width = styling.border_width * 2.0;
let doubled_outline_width = styling.outline_width * 2.0; let doubled_outline_width = styling.outline_width * 2.0;
@ -782,8 +771,6 @@ where
} else { } else {
draw_contents(renderer, styling); draw_contents(renderer, styling);
} }
styling
} }
/// Computes the layout of a [`Button`]. /// Computes the layout of a [`Button`].

View file

@ -133,7 +133,7 @@ fn date_button<Message>(
is_month: bool, is_month: bool,
is_day: bool, is_day: bool,
on_select: &dyn Fn(NaiveDate) -> Message, on_select: &dyn Fn(NaiveDate) -> Message,
) -> crate::widget::Button<'static, Message, crate::Theme, crate::Renderer> { ) -> crate::widget::Button<'static, Message> {
let style = if is_day { let style = if is_day {
button::Style::Suggested button::Style::Suggested
} else { } else {

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::Theme, crate::Renderer> { ) -> crate::widget::Button<'a, Message> {
color_button( color_button(
Some(f(ColorPickerUpdate::ToggleColorPicker)), Some(f(ColorPickerUpdate::ToggleColorPicker)),
self.applied_color, self.applied_color,
@ -761,7 +761,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::Theme, crate::Renderer> { ) -> crate::widget::Button<'a, Message> {
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() {

View file

@ -193,8 +193,7 @@ pub fn menu_root<'a, Message, Renderer: renderer::Renderer>(
label: impl Into<Cow<'a, str>> + 'a, label: impl Into<Cow<'a, str>> + 'a,
) -> iced::Element<'a, Message, crate::Theme, Renderer> ) -> iced::Element<'a, Message, crate::Theme, Renderer>
where where
Element<'a, Message, crate::Theme, Renderer>: Element<'a, Message, crate::Theme, Renderer>: From<widget::Button<'a, Message>>,
From<widget::button::Button<'a, Message, crate::Theme, iced::Renderer>>,
{ {
widget::button(widget::text(label)) widget::button(widget::text(label))
.padding([4, 12]) .padding([4, 12])
@ -223,8 +222,7 @@ pub fn menu_items<
children: Vec<MenuItem<A, L>>, children: Vec<MenuItem<A, L>>,
) -> Vec<MenuTree<'a, Message, Renderer>> ) -> Vec<MenuTree<'a, Message, Renderer>>
where where
Element<'a, Message, crate::Theme, Renderer>: Element<'a, Message, crate::Theme, Renderer>: From<widget::button::Button<'a, Message>>,
From<widget::button::Button<'a, Message, crate::Theme, iced::Renderer>>,
{ {
fn find_key<A: MenuAction>(action: &A, key_binds: &HashMap<KeyBind, A>) -> String { fn find_key<A: MenuAction>(action: &A, key_binds: &HashMap<KeyBind, A>) -> String {
for (key_bind, key_action) in key_binds.iter() { for (key_bind, key_action) in key_binds.iter() {