feat(button): add ImageButton widget variant
This commit is contained in:
parent
470b966e8d
commit
34386561b3
6 changed files with 191 additions and 20 deletions
|
|
@ -24,6 +24,7 @@ pub enum Button {
|
|||
Link,
|
||||
Icon,
|
||||
IconVertical,
|
||||
Image,
|
||||
#[default]
|
||||
Standard,
|
||||
Suggested,
|
||||
|
|
@ -80,6 +81,22 @@ pub fn appearance(
|
|||
}
|
||||
}
|
||||
|
||||
Button::Image => {
|
||||
appearance.background = Some(Background::Color(cosmic.bg_color().into()));
|
||||
appearance.text_color = Some(cosmic.accent.base.into());
|
||||
appearance.icon_color = Some(cosmic.accent.base.into());
|
||||
|
||||
corner_radii = &cosmic.corner_radii.radius_s;
|
||||
appearance.border_radius = (*corner_radii).into();
|
||||
|
||||
if focused {
|
||||
appearance.border_width = 3.0;
|
||||
appearance.border_color = cosmic.accent.base.into();
|
||||
}
|
||||
|
||||
return appearance;
|
||||
}
|
||||
|
||||
Button::Link => {
|
||||
appearance.background = None;
|
||||
appearance.icon_color = Some(cosmic.accent.base.into());
|
||||
|
|
|
|||
72
src/widget/button/image.rs
Normal file
72
src/widget/button/image.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::{Builder, Style};
|
||||
use crate::{
|
||||
widget::{self, image::Handle},
|
||||
Element,
|
||||
};
|
||||
use apply::Apply;
|
||||
use iced_core::{font::Weight, widget::Id, Length, Padding};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle>>;
|
||||
|
||||
pub fn image<'a, Message>(handle: impl Into<Handle> + 'a) -> Button<'a, Message> {
|
||||
Button::new(Image {
|
||||
image: widget::image(handle).border_radius([9.0; 4]),
|
||||
selected: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub struct Image<'a, Handle> {
|
||||
image: widget::Image<'a, Handle>,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl<'a, Message> Button<'a, Message> {
|
||||
pub fn new(variant: Image<'a, Handle>) -> Self {
|
||||
Self {
|
||||
id: Id::unique(),
|
||||
label: Cow::Borrowed(""),
|
||||
tooltip: Cow::Borrowed(""),
|
||||
on_press: None,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
padding: Padding::from(0),
|
||||
spacing: 0,
|
||||
icon_size: 16,
|
||||
line_height: 20,
|
||||
font_size: 14,
|
||||
font_weight: Weight::Normal,
|
||||
style: Style::Image,
|
||||
variant,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.variant.selected = selected;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> From<Button<'a, Message>> for Element<'a, Message>
|
||||
where
|
||||
Handle: Clone + std::hash::Hash,
|
||||
Message: Clone + 'static,
|
||||
{
|
||||
fn from(builder: Button<'a, Message>) -> Element<'a, Message> {
|
||||
builder
|
||||
.variant
|
||||
.image
|
||||
.width(builder.width)
|
||||
.height(builder.height)
|
||||
.apply(widget::button)
|
||||
.selected(builder.variant.selected)
|
||||
.id(builder.id)
|
||||
.padding(0)
|
||||
.on_press_maybe(builder.on_press)
|
||||
.style(builder.style)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,10 @@ mod icon;
|
|||
pub use icon::icon;
|
||||
pub use icon::Button as IconButton;
|
||||
|
||||
mod image;
|
||||
pub use image::image;
|
||||
pub use image::Button as ImageButton;
|
||||
|
||||
mod style;
|
||||
pub use style::{Appearance, StyleSheet};
|
||||
|
||||
|
|
@ -81,7 +85,7 @@ pub struct Builder<'a, Message, Variant> {
|
|||
/// Sets the preferred font weight.
|
||||
font_weight: Weight,
|
||||
|
||||
// The preferred style of the button.
|
||||
/// The preferred style of the button.
|
||||
style: Style,
|
||||
|
||||
#[setters(skip)]
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ use apply::Apply;
|
|||
use iced_core::{font::Weight, text::LineHeight, widget::Id, Alignment, Length, Padding};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A [`Button`] with the highest level of attention.
|
||||
///
|
||||
/// There should only be one primary button used per page.
|
||||
pub type Button<'a, Message> = Builder<'a, Message, Text>;
|
||||
|
||||
pub fn destructive<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ use iced_runtime::core::widget::Id;
|
|||
use iced_runtime::{keyboard, Command};
|
||||
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::layout;
|
||||
use iced_core::mouse;
|
||||
use iced_core::overlay;
|
||||
use iced_core::renderer;
|
||||
use iced_core::renderer::{self, Quad};
|
||||
use iced_core::touch;
|
||||
use iced_core::widget::tree::{self, Tree};
|
||||
use iced_core::widget::Operation;
|
||||
use iced_core::{layout, svg};
|
||||
use iced_core::{
|
||||
Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell,
|
||||
Vector, Widget,
|
||||
|
|
@ -24,6 +24,10 @@ use iced_renderer::core::widget::{operation, OperationOutputWrapper};
|
|||
|
||||
pub use super::style::{Appearance, StyleSheet};
|
||||
|
||||
struct Selected {
|
||||
icon: svg::Handle,
|
||||
}
|
||||
|
||||
/// A generic widget that produces a message when pressed.
|
||||
///
|
||||
/// ```no_run
|
||||
|
|
@ -77,6 +81,7 @@ where
|
|||
width: Length,
|
||||
height: Length,
|
||||
padding: Padding,
|
||||
selected: Option<Selected>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
|
|
@ -100,10 +105,17 @@ where
|
|||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
padding: Padding::new(5.0),
|
||||
selected: None,
|
||||
style: <Renderer::Theme as StyleSheet>::Style::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the [`Id`] of the [`Button`].
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Button`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
|
|
@ -139,15 +151,27 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the style variant of this [`Button`].
|
||||
pub fn style(mut self, style: <Renderer::Theme as StyleSheet>::Style) -> Self {
|
||||
self.style = style;
|
||||
/// Sets the widget to a selected state.
|
||||
///
|
||||
/// Displays a selection indicator on image buttons.
|
||||
pub(super) fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected.then(|| Selected {
|
||||
icon: crate::widget::icon::from_name("object-select-symbolic")
|
||||
.size(16)
|
||||
.icon()
|
||||
.into_svg_handle()
|
||||
.unwrap_or_else(|| {
|
||||
let bytes: &'static [u8] = &[];
|
||||
iced_core::svg::Handle::from_memory(bytes)
|
||||
}),
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Id`] of the [`Button`].
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = id;
|
||||
/// Sets the style variant of this [`Button`].
|
||||
pub fn style(mut self, style: <Renderer::Theme as StyleSheet>::Style) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +209,7 @@ where
|
|||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Button<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + iced_core::Renderer,
|
||||
Renderer: 'a + iced_core::Renderer + svg::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
|
|
@ -295,6 +319,7 @@ where
|
|||
bounds,
|
||||
cursor,
|
||||
self.on_press.is_some(),
|
||||
self.selected.is_some(),
|
||||
theme,
|
||||
&self.style,
|
||||
|| tree.state.downcast_ref::<State>(),
|
||||
|
|
@ -313,6 +338,37 @@ where
|
|||
cursor,
|
||||
&bounds,
|
||||
);
|
||||
|
||||
if let Some(ref selected) = self.selected {
|
||||
renderer.fill_quad(
|
||||
Quad {
|
||||
bounds: Rectangle {
|
||||
width: 24.0,
|
||||
height: 20.0,
|
||||
x: bounds.x,
|
||||
y: bounds.y + (bounds.height - 20.0),
|
||||
},
|
||||
border_radius: [0.0, 8.0, 0.0, 8.0].into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
styling
|
||||
.background
|
||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
||||
);
|
||||
|
||||
iced_core::svg::Renderer::draw(
|
||||
renderer,
|
||||
selected.icon.clone(),
|
||||
styling.icon_color,
|
||||
Rectangle {
|
||||
width: 16.0,
|
||||
height: 16.0,
|
||||
x: bounds.x + 4.0,
|
||||
y: bounds.y + (bounds.height - 16.0),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -417,7 +473,7 @@ where
|
|||
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone + 'a,
|
||||
Renderer: iced_core::Renderer + 'a,
|
||||
Renderer: iced_core::Renderer + svg::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(button: Button<'a, Message, Renderer>) -> Self {
|
||||
|
|
@ -538,12 +594,13 @@ pub fn update<'a, Message: Clone>(
|
|||
event::Status::Ignored
|
||||
}
|
||||
|
||||
/// Draws a [`Button`].
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn draw<'a, Renderer: iced_core::Renderer>(
|
||||
renderer: &mut Renderer,
|
||||
bounds: Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
is_enabled: bool,
|
||||
is_selected: bool,
|
||||
style_sheet: &dyn StyleSheet<Style = <Renderer::Theme as StyleSheet>::Style>,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
state: impl FnOnce() -> &'a State,
|
||||
|
|
@ -559,12 +616,12 @@ where
|
|||
style_sheet.disabled(style)
|
||||
} else if is_mouse_over {
|
||||
if state.is_pressed {
|
||||
style_sheet.pressed(state.is_focused, style)
|
||||
style_sheet.pressed(state.is_focused || is_selected, style)
|
||||
} else {
|
||||
style_sheet.hovered(state.is_focused, style)
|
||||
style_sheet.hovered(state.is_focused || is_selected, style)
|
||||
}
|
||||
} else {
|
||||
style_sheet.active(state.is_focused, style)
|
||||
style_sheet.active(state.is_focused || is_selected, style)
|
||||
};
|
||||
|
||||
let doubled_border_width = styling.border_width * 2.0;
|
||||
|
|
@ -595,7 +652,8 @@ where
|
|||
bounds: Rectangle {
|
||||
x: bounds.x + styling.shadow_offset.x,
|
||||
y: bounds.y + styling.shadow_offset.y,
|
||||
..bounds
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
border_radius: styling.border_radius,
|
||||
border_width: 0.0,
|
||||
|
|
@ -607,7 +665,12 @@ where
|
|||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + if is_selected { -1.0 } else { 0.0 },
|
||||
y: bounds.y + if is_selected { -1.0 } else { 0.0 },
|
||||
width: bounds.width + if is_selected { 2.0 } else { 0.0 },
|
||||
height: bounds.height + if is_selected { 2.0 } else { 0.0 },
|
||||
},
|
||||
border_radius: styling.border_radius,
|
||||
border_width: styling.border_width,
|
||||
border_color: styling.border_color,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,24 @@ pub struct Icon {
|
|||
}
|
||||
|
||||
impl Icon {
|
||||
#[must_use]
|
||||
pub fn into_svg_handle(self) -> Option<crate::widget::svg::Handle> {
|
||||
match self.handle.data {
|
||||
Data::Name(named) => {
|
||||
if let Some(path) = named.path() {
|
||||
if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
|
||||
return Some(iced_core::svg::Handle::from_path(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Data::Image(_) => (),
|
||||
Data::Svg(handle) => return Some(handle),
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn into_element<Message: 'static>(self) -> Element<'static, Message> {
|
||||
let from_image = |handle| {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue