refactor(widget): improvements to button and icon widgets
This commit is contained in:
parent
7f0943924a
commit
9dbc1be269
20 changed files with 399 additions and 558 deletions
6
src/widget/button/external-link.svg
Normal file
6
src/widget/button/external-link.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon Symbolic">
|
||||
<path id="Vector" d="M8 1V3H11.586L7.385 7.201C7.146 7.386 7.023 7.692 7.018 8H7V9H8V8.99C8.308 8.987 8.614 8.86 8.799 8.615L12.999 4.414V8L14.999 8.006L15 1H8Z" fill="#232323"/>
|
||||
<path id="Vector 42" d="M12 9V13H3V4H7" stroke="black" stroke-width="2" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::Builder;
|
||||
use super::Style;
|
||||
use crate::widget::icon::{self, Handle};
|
||||
use iced_core::{font::Weight, widget::Id, Length, Padding};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub type Button<'a, Message> = Builder<'a, Message, Hyperlink>;
|
||||
|
||||
pub struct Hyperlink {
|
||||
trailing_icon: bool,
|
||||
}
|
||||
|
||||
pub fn hyperlink<'a, Message>() -> Button<'a, Message> {
|
||||
Button::new(Hyperlink {
|
||||
trailing_icon: false,
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a, Message> Button<'a, Message> {
|
||||
pub fn new(link: Hyperlink) -> Self {
|
||||
Self {
|
||||
id: Id::unique(),
|
||||
label: 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::Link,
|
||||
variant: link,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn with_icon(mut self) -> Self {
|
||||
self.variant.trailing_icon = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon() -> Handle {
|
||||
icon::handle::from_svg_bytes(&include_bytes!("../../../res/external-link.svg")[..])
|
||||
.symbolic(true)
|
||||
}
|
||||
|
|
@ -2,7 +2,11 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::{button, Builder, Style};
|
||||
use crate::{widget::icon::Handle, Element};
|
||||
use crate::widget::{
|
||||
icon::{self, Handle},
|
||||
tooltip,
|
||||
};
|
||||
use crate::Element;
|
||||
use apply::Apply;
|
||||
use iced_core::{font::Weight, text::LineHeight, widget::Id, Alignment, Length, Padding};
|
||||
use std::borrow::Cow;
|
||||
|
|
@ -11,14 +15,12 @@ pub type Button<'a, Message> = Builder<'a, Message, Icon>;
|
|||
|
||||
pub struct Icon {
|
||||
handle: Handle,
|
||||
selected: bool,
|
||||
vertical: bool,
|
||||
}
|
||||
|
||||
pub fn icon<Message>(handle: impl Into<Handle>) -> Button<'static, Message> {
|
||||
pub fn icon<'a, Message>(handle: impl Into<Handle>) -> Button<'a, Message> {
|
||||
Button::new(Icon {
|
||||
handle: handle.into(),
|
||||
selected: false,
|
||||
vertical: false,
|
||||
})
|
||||
}
|
||||
|
|
@ -33,12 +35,13 @@ impl<'a, Message> Button<'a, Message> {
|
|||
Self {
|
||||
id: Id::unique(),
|
||||
label: Cow::Borrowed(""),
|
||||
tooltip: Cow::Borrowed(""),
|
||||
on_press: None,
|
||||
width: Length::Shrink,
|
||||
height: Length::Fixed(46.0),
|
||||
padding: Padding::from(padding),
|
||||
spacing: theme.space_xxxs(),
|
||||
icon_size: 16,
|
||||
icon_size: if icon.handle.symbolic { 16 } else { 24 },
|
||||
line_height: 20,
|
||||
font_size: 14,
|
||||
font_weight: Weight::Normal,
|
||||
|
|
@ -91,7 +94,7 @@ impl<'a, Message> Button<'a, Message> {
|
|||
let theme = theme.cosmic();
|
||||
|
||||
self.font_size = 28;
|
||||
self.font_weight = Weight::Light;
|
||||
self.font_weight = Weight::Normal;
|
||||
self.icon_size = 40;
|
||||
self.line_height = 36;
|
||||
self.height = Length::Fixed(64.0);
|
||||
|
|
@ -121,16 +124,21 @@ impl<'a, Message> Button<'a, Message> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.variant.selected = selected;
|
||||
pub fn vertical(mut self, vertical: bool) -> Self {
|
||||
self.variant.vertical = vertical;
|
||||
self.style = Style::IconVertical;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {
|
||||
fn from(builder: Button<'a, Message>) -> Element<'a, Message> {
|
||||
fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> {
|
||||
let mut content = Vec::with_capacity(2);
|
||||
|
||||
if let icon::Data::Name(ref mut named) = builder.variant.handle.data {
|
||||
named.size = Some(builder.icon_size);
|
||||
}
|
||||
|
||||
content.push(
|
||||
crate::widget::icon(builder.variant.handle.clone())
|
||||
.size(builder.icon_size)
|
||||
|
|
@ -154,8 +162,8 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
|
|||
let button = if builder.variant.vertical {
|
||||
crate::widget::column::with_children(content)
|
||||
.padding(builder.padding)
|
||||
.width(builder.width)
|
||||
.height(builder.height)
|
||||
// .width(builder.width)
|
||||
// .height(builder.height)
|
||||
.spacing(builder.spacing)
|
||||
.align_items(Alignment::Center)
|
||||
.apply(button)
|
||||
|
|
@ -169,11 +177,23 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
|
|||
.apply(button)
|
||||
};
|
||||
|
||||
button
|
||||
let button = button
|
||||
.padding(0)
|
||||
.id(builder.id)
|
||||
.on_press_maybe(builder.on_press)
|
||||
.style(builder.style)
|
||||
.into()
|
||||
.style(builder.style);
|
||||
|
||||
if builder.tooltip.is_empty() {
|
||||
button.into()
|
||||
} else {
|
||||
tooltip(button, builder.tooltip, tooltip::Position::Top)
|
||||
.size(builder.font_size)
|
||||
.font({
|
||||
let mut font = crate::font::DEFAULT;
|
||||
font.weight = builder.font_weight;
|
||||
font
|
||||
})
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
101
src/widget/button/link.rs
Normal file
101
src/widget/button/link.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::Builder;
|
||||
use super::Style;
|
||||
use crate::prelude::*;
|
||||
use crate::widget::icon::{self, Handle};
|
||||
use crate::widget::{button, row, tooltip};
|
||||
use crate::Element;
|
||||
use iced_core::text::LineHeight;
|
||||
use iced_core::{font::Weight, widget::Id, Alignment, Length, Padding};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub type Button<'a, Message> = Builder<'a, Message, Hyperlink>;
|
||||
|
||||
pub struct Hyperlink {
|
||||
trailing_icon: bool,
|
||||
}
|
||||
|
||||
pub fn link<'a, Message>(label: impl Into<Cow<'a, str>> + 'static) -> Button<'a, Message> {
|
||||
Button::new(
|
||||
label,
|
||||
Hyperlink {
|
||||
trailing_icon: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
impl<'a, Message> Button<'a, Message> {
|
||||
pub fn new(label: impl Into<Cow<'a, str>> + 'static, link: Hyperlink) -> Self {
|
||||
Self {
|
||||
id: Id::unique(),
|
||||
label: label.into(),
|
||||
tooltip: Cow::Borrowed(""),
|
||||
on_press: None,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
padding: Padding::from(4),
|
||||
spacing: 0,
|
||||
icon_size: 16,
|
||||
line_height: 20,
|
||||
font_size: 14,
|
||||
font_weight: Weight::Normal,
|
||||
style: Style::Link,
|
||||
variant: link,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn trailing_icon(mut self, set: bool) -> Self {
|
||||
self.variant.trailing_icon = set;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon() -> Handle {
|
||||
icon::from_svg_bytes(&include_bytes!("external-link.svg")[..]).symbolic(true)
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for 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)
|
||||
.push({
|
||||
let mut font = crate::font::DEFAULT;
|
||||
font.weight = builder.font_weight;
|
||||
|
||||
// TODO: Avoid allocation
|
||||
crate::widget::text(builder.label.to_string())
|
||||
.size(builder.font_size)
|
||||
.line_height(LineHeight::Absolute(builder.line_height.into()))
|
||||
.font(font)
|
||||
})
|
||||
.push_maybe(if builder.variant.trailing_icon {
|
||||
Some(icon().icon().size(builder.icon_size))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.padding(builder.padding)
|
||||
.width(builder.width)
|
||||
.height(builder.height)
|
||||
.spacing(builder.spacing)
|
||||
.align_items(Alignment::Center)
|
||||
.apply(button)
|
||||
.padding(0)
|
||||
.id(builder.id)
|
||||
.on_press_maybe(builder.on_press.take())
|
||||
.style(builder.style);
|
||||
|
||||
if builder.tooltip.is_empty() {
|
||||
button.into()
|
||||
} else {
|
||||
tooltip(button, builder.tooltip, tooltip::Position::Top)
|
||||
.size(builder.font_size)
|
||||
.font({
|
||||
let mut font = crate::font::DEFAULT;
|
||||
font.weight = builder.font_weight;
|
||||
font
|
||||
})
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod hyperlink;
|
||||
pub use hyperlink::Button as LinkButton;
|
||||
pub use crate::theme::Button as Style;
|
||||
|
||||
pub mod link;
|
||||
pub use link::link;
|
||||
pub use link::Button as LinkButton;
|
||||
|
||||
mod icon;
|
||||
pub use icon::icon;
|
||||
|
|
@ -18,7 +21,6 @@ pub use text::{destructive, standard, suggested, text};
|
|||
mod widget;
|
||||
pub use widget::{draw, focus, layout, mouse_interaction, Button};
|
||||
|
||||
pub use crate::theme::Button as Style;
|
||||
use crate::Element;
|
||||
use iced_core::font::Weight;
|
||||
use iced_core::widget::Id;
|
||||
|
|
@ -35,7 +37,7 @@ pub fn button<'a, Message>(
|
|||
pub struct Builder<'a, Message, Variant> {
|
||||
id: Id,
|
||||
label: Cow<'a, str>,
|
||||
// tooltip: Cow<'a, str>,
|
||||
tooltip: Cow<'a, str>,
|
||||
on_press: Option<Message>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
|
|
@ -49,13 +51,8 @@ pub struct Builder<'a, Message, Variant> {
|
|||
variant: Variant,
|
||||
}
|
||||
|
||||
// /// A [`Button`] with an icon, which may be used in place of text.
|
||||
// pub const fn icon<'a>(selected: bool) -> Button<'a> {
|
||||
// Builder::new(Standard::Icon { selected })
|
||||
// }
|
||||
|
||||
impl<'a, Message, Variant> Builder<'a, Message, Variant> {
|
||||
/// Sets the [`Id`] of the [`Button`].
|
||||
/// Sets the [`Id`] of the button.
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
|
|
@ -66,48 +63,50 @@ impl<'a, Message, Variant> Builder<'a, Message, Variant> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Button`].
|
||||
/// Sets the width of the button.
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Button`].
|
||||
/// Sets the height of the button.
|
||||
pub fn height(mut self, height: impl Into<Length>) -> Self {
|
||||
self.height = height.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Padding`] of the [`Button`].
|
||||
/// Sets the [`Padding`] of the button.
|
||||
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
|
||||
self.padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the message that will be produced when the [`Button`] is pressed.
|
||||
/// Sets the message that will be produced when the button is pressed.
|
||||
///
|
||||
/// Unless `on_press` is called, the [`Button`] will be disabled.
|
||||
/// Unless `on_press` is called, the button will be disabled.
|
||||
pub fn on_press(mut self, on_press: Message) -> Self {
|
||||
self.on_press = Some(on_press);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the message that will be produced when the [`Button`] is pressed,
|
||||
/// Sets the message that will be produced when the button is pressed,
|
||||
/// if `Some`.
|
||||
///
|
||||
/// If `None`, the [`Button`] will be disabled.
|
||||
/// If `None`, the button will be disabled.
|
||||
pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
|
||||
self.on_press = on_press;
|
||||
self
|
||||
}
|
||||
|
||||
/// Overrides the preferred style of the button.
|
||||
pub fn style(mut self, style: Style) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tooltip(mut self, label: impl Into<Cow<'a, str>>) -> Self {
|
||||
self.label = label.into();
|
||||
/// Adds a tooltip to the button.
|
||||
pub fn tooltip(mut self, tooltip: impl Into<Cow<'a, str>>) -> Self {
|
||||
self.tooltip = tooltip.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,4 @@ pub trait StyleSheet {
|
|||
|
||||
/// Produces the pressed [`Appearance`] of a button.
|
||||
fn pressed(&self, focused: bool, style: &Self::Style) -> Appearance;
|
||||
|
||||
/// [`Appearance`] when a button is selected.
|
||||
fn selected(&self, focused: bool, style: &Self::Style) -> Appearance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::{button, Builder, Style};
|
||||
use crate::widget::{self, icon::Handle, row};
|
||||
use crate::widget::{icon, row, tooltip};
|
||||
use crate::{ext::CollectionWidget, Element};
|
||||
use apply::Apply;
|
||||
use iced_core::{font::Weight, text::LineHeight, widget::Id, Alignment, Length, Padding};
|
||||
|
|
@ -34,8 +34,8 @@ pub fn text<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message>
|
|||
}
|
||||
|
||||
pub struct Text {
|
||||
pub(super) leading_icon: Option<crate::widget::icon::Handle>,
|
||||
pub(super) trailing_icon: Option<crate::widget::icon::Handle>,
|
||||
pub(super) leading_icon: Option<icon::Handle>,
|
||||
pub(super) trailing_icon: Option<icon::Handle>,
|
||||
}
|
||||
|
||||
impl Text {
|
||||
|
|
@ -55,6 +55,7 @@ impl<'a, Message> Button<'a, Message> {
|
|||
Self {
|
||||
id: Id::unique(),
|
||||
label: Cow::Borrowed(""),
|
||||
tooltip: Cow::Borrowed(""),
|
||||
on_press: None,
|
||||
width: Length::Shrink,
|
||||
height: Length::Fixed(theme.space_l().into()),
|
||||
|
|
@ -70,54 +71,76 @@ impl<'a, Message> Button<'a, Message> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn leading_icon(mut self, icon: impl Into<Handle>) -> Self {
|
||||
pub fn leading_icon(mut self, icon: impl Into<icon::Handle>) -> Self {
|
||||
self.variant.leading_icon = Some(icon.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn trailing_icon(mut self, icon: impl Into<Handle>) -> Self {
|
||||
pub fn trailing_icon(mut self, icon: impl Into<icon::Handle>) -> Self {
|
||||
self.variant.trailing_icon = Some(icon.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {
|
||||
fn from(mut b: Button<'a, Message>) -> Element<'a, Message> {
|
||||
// TODO: Determine why this needs to be set before the label to prevent lifetime conflict.
|
||||
let trailing_icon = b
|
||||
.variant
|
||||
.trailing_icon
|
||||
.map(|i| Element::from(widget::icon(i).size(b.icon_size)));
|
||||
fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> {
|
||||
let trailing_icon = builder.variant.trailing_icon.map(|mut i| {
|
||||
if let icon::Data::Name(ref mut named) = i.data {
|
||||
named.size = Some(builder.icon_size);
|
||||
}
|
||||
|
||||
row::with_capacity(3)
|
||||
i.icon()
|
||||
});
|
||||
|
||||
let leading_icon = builder.variant.leading_icon.map(|mut i| {
|
||||
if let icon::Data::Name(ref mut named) = i.data {
|
||||
named.size = Some(builder.icon_size);
|
||||
}
|
||||
|
||||
i.icon()
|
||||
});
|
||||
|
||||
let label: Option<Element<'_, _>> = (!builder.label.is_empty()).then(|| {
|
||||
let mut font = crate::font::DEFAULT;
|
||||
font.weight = builder.font_weight;
|
||||
|
||||
// TODO: Avoid allocation
|
||||
crate::widget::text(builder.label.to_string())
|
||||
.size(builder.font_size)
|
||||
.line_height(LineHeight::Absolute(builder.line_height.into()))
|
||||
.font(font)
|
||||
.into()
|
||||
});
|
||||
|
||||
let button: super::Button<'a, Message, crate::Renderer> = row::with_capacity(3)
|
||||
// Optional icon to place before label.
|
||||
.push_maybe(
|
||||
b.variant
|
||||
.leading_icon
|
||||
.map(|i| widget::icon(i).size(b.icon_size)),
|
||||
)
|
||||
.push_maybe(leading_icon)
|
||||
// Optional label between icons.
|
||||
.push_maybe((!b.label.is_empty()).then(|| {
|
||||
let mut font = crate::font::DEFAULT;
|
||||
font.weight = b.font_weight;
|
||||
|
||||
crate::widget::text(b.label)
|
||||
.size(b.font_size)
|
||||
.line_height(LineHeight::Absolute(b.line_height.into()))
|
||||
.font(font)
|
||||
}))
|
||||
.push_maybe(label)
|
||||
// Optional icon to place behind the label.
|
||||
.push_maybe(trailing_icon)
|
||||
.padding(b.padding)
|
||||
.width(b.width)
|
||||
.height(b.height)
|
||||
.spacing(b.spacing)
|
||||
.padding(builder.padding)
|
||||
.width(builder.width)
|
||||
.height(builder.height)
|
||||
.spacing(builder.spacing)
|
||||
.align_items(Alignment::Center)
|
||||
.apply(button)
|
||||
.padding(0)
|
||||
.id(b.id)
|
||||
.on_press_maybe(b.on_press.take())
|
||||
.style(b.style)
|
||||
.into()
|
||||
.id(builder.id)
|
||||
.on_press_maybe(builder.on_press.take())
|
||||
.style(builder.style);
|
||||
|
||||
if builder.tooltip.is_empty() {
|
||||
button.into()
|
||||
} else {
|
||||
tooltip(button, builder.tooltip, tooltip::Position::Top)
|
||||
.size(builder.font_size)
|
||||
.font({
|
||||
let mut font = crate::font::DEFAULT;
|
||||
font.weight = builder.font_weight;
|
||||
font
|
||||
})
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue