refactor(widget): improvements to button and icon widgets

This commit is contained in:
Michael Aaron Murphy 2023-09-13 15:47:32 +02:00 committed by Michael Murphy
parent 7f0943924a
commit 9dbc1be269
20 changed files with 399 additions and 558 deletions

View file

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 408 B

Before After
Before After

View file

@ -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)
}

View file

@ -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
View 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()
}
}
}

View file

@ -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
}
}

View file

@ -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;
}

View file

@ -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()
}
}
}

View file

@ -165,7 +165,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
/// Creates the widget for window controls.
fn window_controls(&mut self) -> Element<'a, Message> {
let icon = |name, size, on_press| {
widget::icon::handle::from_name(name)
widget::icon::from_name(name)
.size(size)
.handle()
.apply(widget::button::icon)

View file

@ -1,7 +1,7 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{Builder, Icon};
use super::{Icon, Named};
use crate::widget::{image, svg};
use std::borrow::Cow;
use std::ffi::OsStr;
@ -13,7 +13,7 @@ use std::path::PathBuf;
pub struct Handle {
pub symbolic: bool,
#[setters(skip)]
pub variant: Variant,
pub data: Data,
}
impl Handle {
@ -24,16 +24,12 @@ impl Handle {
#[must_use]
#[derive(Clone, Debug, Hash)]
pub enum Variant {
pub enum Data {
Name(Named),
Image(image::Handle),
Svg(svg::Handle),
}
/// Create an icon handle from its XDG icon name.
pub fn from_name(name: &str) -> Builder {
Builder::new(name)
}
/// Create an icon handle from its path.
pub fn from_path(path: PathBuf) -> Handle {
Handle {
@ -41,10 +37,10 @@ pub fn from_path(path: PathBuf) -> Handle {
.file_stem()
.and_then(OsStr::to_str)
.is_some_and(|name| name.ends_with("-symbolic")),
variant: if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
Variant::Svg(svg::Handle::from_path(path))
data: if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
Data::Svg(svg::Handle::from_path(path))
} else {
Variant::Image(image::Handle::from_path(path))
Data::Image(image::Handle::from_path(path))
},
}
}
@ -59,7 +55,7 @@ pub fn from_raster_bytes(
) -> Handle {
Handle {
symbolic: false,
variant: Variant::Image(image::Handle::from_memory(bytes)),
data: Data::Image(image::Handle::from_memory(bytes)),
}
}
@ -75,7 +71,7 @@ pub fn from_raster_pixels(
) -> Handle {
Handle {
symbolic: false,
variant: Variant::Image(image::Handle::from_pixels(width, height, pixels)),
data: Data::Image(image::Handle::from_pixels(width, height, pixels)),
}
}
@ -83,6 +79,6 @@ pub fn from_raster_pixels(
pub fn from_svg_bytes(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
Handle {
symbolic: false,
variant: Variant::Svg(svg::Handle::from_memory(bytes)),
data: Data::Svg(svg::Handle::from_memory(bytes)),
}
}

View file

@ -3,18 +3,19 @@
//! Lazily-generated SVG icon widget for Iced.
mod builder;
pub use builder::Builder;
mod named;
use std::ffi::OsStr;
use std::sync::Arc;
pub mod handle;
pub use handle::Handle;
pub use named::Named;
mod handle;
pub use handle::{from_path, from_raster_bytes, from_raster_pixels, from_svg_bytes, Data, Handle};
use crate::{Element, Renderer};
use derive_setters::Setters;
use iced::widget::{Image, Svg};
use iced::{ContentFit, Length};
use std::borrow::Cow;
use std::path::PathBuf;
/// Create an [`Icon`] from a pre-existing [`Handle`]
pub fn icon(handle: Handle) -> Icon {
@ -28,38 +29,9 @@ pub fn icon(handle: Handle) -> Icon {
}
}
/// Create an [`Icon`] from its path.
pub fn from_path(path: PathBuf) -> Icon {
icon(handle::from_path(path))
}
/// Create an image [`Icon`] from memory.
pub fn from_raster_bytes(
bytes: impl Into<Cow<'static, [u8]>>
+ std::convert::AsRef<[u8]>
+ std::marker::Send
+ std::marker::Sync
+ 'static,
) -> Icon {
icon(handle::from_raster_bytes(bytes))
}
/// Create an image [`Icon`] from RGBA data, where you must define the width and height.
pub fn from_raster_pixels(
width: u32,
height: u32,
pixels: impl Into<Cow<'static, [u8]>>
+ std::convert::AsRef<[u8]>
+ std::marker::Send
+ std::marker::Sync
+ 'static,
) -> Icon {
icon(handle::from_raster_pixels(width, height, pixels))
}
/// Create a SVG [`Icon`] from memory.
pub fn from_svg_bytes(bytes: impl Into<Cow<'static, [u8]>>) -> Icon {
icon(handle::from_svg_bytes(bytes))
/// Create an icon handle from its XDG icon name.
pub fn from_name(name: impl Into<Arc<str>>) -> Named {
Named::new(name)
}
/// An image which may be an SVG or PNG.
@ -80,19 +52,40 @@ pub struct Icon {
impl Icon {
#[must_use]
fn into_element<Message: 'static>(self) -> Element<'static, Message> {
match self.handle.variant {
handle::Variant::Image(handle) => Image::new(handle)
let from_image = |handle| {
Image::new(handle)
.width(self.width.unwrap_or(Length::Fixed(f32::from(self.size))))
.height(self.height.unwrap_or(Length::Fixed(f32::from(self.size))))
.content_fit(self.content_fit)
.into(),
handle::Variant::Svg(handle) => Svg::<Renderer>::new(handle)
.into()
};
let from_svg = |handle| {
Svg::<Renderer>::new(handle)
.style(self.style.clone())
.width(self.width.unwrap_or(Length::Fixed(f32::from(self.size))))
.height(self.height.unwrap_or(Length::Fixed(f32::from(self.size))))
.content_fit(self.content_fit)
.symbolic(self.handle.symbolic)
.into(),
.into()
};
match self.handle.data {
Data::Name(named) => {
if let Some(path) = named.path() {
if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
from_svg(iced_core::svg::Handle::from_path(path))
} else {
from_image(iced_core::image::Handle::from_path(path))
}
} else {
let bytes: &'static [u8] = &[];
from_svg(iced_core::svg::Handle::from_memory(bytes))
}
}
Data::Image(handle) => from_image(handle),
Data::Svg(handle) => from_svg(handle),
}
}
}

View file

@ -1,33 +1,38 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{handle, Handle, Icon};
use std::path::PathBuf;
use super::{Handle, Icon};
use std::{path::PathBuf, sync::Arc};
#[must_use]
#[derive(derive_setters::Setters)]
pub struct Builder<'a> {
#[derive(derive_setters::Setters, Clone, Debug, Hash)]
pub struct Named {
/// Name of icon to locate in an XDG icon path.
name: &'a str,
pub(super) name: Arc<str>,
/// Checks for a fallback if the icon was not found.
fallback: bool,
pub fallback: bool,
/// Restrict the lookup to a given scale.
#[setters(strip_option)]
scale: Option<u16>,
pub scale: Option<u16>,
/// Restrict the lookup to a given size.
#[setters(strip_option)]
size: Option<u16>,
pub size: Option<u16>,
/// Whether the icon is symbolic or not.
pub symbolic: bool,
/// Prioritizes SVG over PNG
prefer_svg: bool,
pub prefer_svg: bool,
}
impl<'a> Builder<'a> {
pub const fn new(name: &'a str) -> Self {
impl Named {
pub fn new(name: impl Into<Arc<str>>) -> Self {
let name = name.into();
Self {
symbolic: name.ends_with("-symbolic"),
name,
fallback: true,
size: None,
@ -37,12 +42,13 @@ impl<'a> Builder<'a> {
}
#[must_use]
pub fn path(mut self) -> Option<PathBuf> {
pub fn path(self) -> Option<PathBuf> {
let mut name = &*self.name;
crate::icon_theme::DEFAULT.with(|theme| {
let theme = theme.borrow();
let locate = || {
let mut lookup = freedesktop_icons::lookup(self.name)
let mut lookup = freedesktop_icons::lookup(name)
.with_theme(theme.as_ref())
.with_cache();
@ -65,10 +71,8 @@ impl<'a> Builder<'a> {
// On failure, attempt to locate fallback icon.
if result.is_none() && self.fallback {
let name = std::mem::take(&mut self.name);
for name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) {
self.name = name;
for new_name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) {
name = new_name;
result = locate();
if result.is_some() {
break;
@ -81,11 +85,9 @@ impl<'a> Builder<'a> {
}
pub fn handle(self) -> Handle {
if let Some(path) = self.path() {
handle::from_path(path)
} else {
let bytes: &'static [u8] = &[];
handle::from_svg_bytes(bytes)
Handle {
symbolic: self.symbolic,
data: super::Data::Name(self),
}
}
@ -101,14 +103,20 @@ impl<'a> Builder<'a> {
}
}
impl<'a> From<Builder<'a>> for Handle {
fn from(builder: Builder<'a>) -> Self {
impl From<Named> for Handle {
fn from(builder: Named) -> Self {
builder.handle()
}
}
impl<'a> From<Builder<'a>> for Icon {
fn from(builder: Builder<'a>) -> Self {
impl From<Named> for Icon {
fn from(builder: Named) -> Self {
builder.icon()
}
}
impl<'a, Message: 'static> From<Named> for crate::Element<'a, Message> {
fn from(builder: Named) -> Self {
builder.icon().into()
}
}

View file

@ -26,12 +26,10 @@ pub fn nav_bar_toggle<Message>() -> NavBarToggle<Message> {
impl<'a, Message: 'static + Clone> From<NavBarToggle<Message>> for Element<'a, Message> {
fn from(nav_bar_toggle: NavBarToggle<Message>) -> Self {
let icon = if nav_bar_toggle.active {
widget::icon::handle::from_svg_bytes(
&include_bytes!("../../res/sidebar-active.svg")[..],
)
.symbolic(true)
widget::icon::from_svg_bytes(&include_bytes!("../../res/sidebar-active.svg")[..])
.symbolic(true)
} else {
widget::icon::handle::from_name("open-menu-symbolic")
widget::icon::from_name("open-menu-symbolic")
.size(16)
.handle()
};

View file

@ -42,7 +42,7 @@ impl<'a, Message: 'static + Clone> Field<'a, Message> {
row::with_capacity(3)
.push(
icon::handle::from_svg_bytes(&include_bytes!("search.svg")[..])
icon::from_svg_bytes(&include_bytes!("search.svg")[..])
.symbolic(true)
.icon()
.size(16),
@ -67,7 +67,7 @@ impl<'a, Message: 'static + Clone> From<Field<'a, Message>> for crate::Element<'
}
fn clear_button<Message: 'static>() -> crate::widget::IconButton<'static, Message> {
icon::handle::from_name("edit-clear-symbolic")
icon::from_name("edit-clear-symbolic")
.size(16)
.apply(crate::widget::button::icon)
}

View file

@ -49,7 +49,7 @@ mod button {
/// A search button which converts to a search [`field`] on click.
#[must_use]
pub fn button<Message: 'static + Clone>(on_press: Message) -> crate::Element<'static, Message> {
icon::handle::from_svg_bytes(&include_bytes!("search.svg")[..])
icon::from_svg_bytes(&include_bytes!("search.svg")[..])
.symbolic(true)
.apply(crate::widget::button::icon)
.on_press(on_press)

View file

@ -122,9 +122,7 @@ where
Self {
model,
id: None,
close_icon: icon::handle::from_name("window-close-symbolic")
.size(16)
.icon(),
close_icon: icon::from_name("window-close-symbolic").size(16).icon(),
show_close_icon_on_hover: false,
button_padding: [4, 4, 4, 4],
button_height: 32,

View file

@ -42,9 +42,8 @@ impl<'a, Message: 'static> SpinButton<'a, Message> {
let Self { on_change, label } = self;
container(
row::with_children(vec![
icon::handle::from_name("list-remove-symbolic")
icon::from_name("list-remove-symbolic")
.size(24)
.icon()
.apply(container)
.width(Length::Fixed(32.0))
.height(Length::Fixed(32.0))
@ -62,9 +61,8 @@ impl<'a, Message: 'static> SpinButton<'a, Message> {
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.into(),
icon::handle::from_name("list-add-symbolic")
icon::from_name("list-add-symbolic")
.size(24)
.icon()
.apply(container)
.width(Length::Fixed(32.0))
.height(Length::Fixed(32.0))

View file

@ -68,23 +68,23 @@ where
let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
let input = TextInput::new(placeholder, value)
.padding([0, spacing, 0, spacing])
.style(super::style::TextInput::Search)
.start_icon(
iced_widget::container(
crate::widget::icon::handle::from_name("system-search-symbolic")
.size(16)
.icon(),
)
.padding([spacing, spacing, spacing, spacing])
.into(),
.style(crate::theme::TextInput::Search)
.leading_icon(
crate::widget::icon::from_name("system-search-symbolic")
.size(16)
.apply(crate::widget::container)
.padding([spacing, spacing, spacing, spacing])
.into(),
);
if let Some(msg) = on_clear {
input.end_icon(
crate::widget::icon::handle::from_name("edit-clear-symbolic")
input.trailing_icon(
crate::widget::icon::from_name("edit-clear-symbolic")
.size(16)
.handle()
.apply(crate::widget::button::icon)
.apply(crate::widget::button)
.style(crate::theme::Button::Icon)
.width(32)
.height(32)
.on_press(msg)
.padding([spacing, spacing, spacing, spacing])
.into(),
@ -108,12 +108,11 @@ where
let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
let mut input = TextInput::new(placeholder, value)
.padding([0, spacing, 0, spacing])
.style(super::style::TextInput::Default)
.start_icon(
crate::widget::icon::handle::from_name("system-lock-screen-symbolic")
.style(crate::theme::TextInput::Default)
.leading_icon(
crate::widget::icon::from_name("system-lock-screen-symbolic")
.size(16)
.icon()
.apply(iced_widget::container)
.apply(crate::widget::container)
.padding([spacing, spacing, spacing, spacing])
.into(),
);
@ -121,11 +120,11 @@ where
input = input.password();
}
if let Some(msg) = on_visible_toggle {
input.end_icon(
crate::widget::icon::handle::from_name("document-properties-symbolic")
input.trailing_icon(
crate::widget::icon::from_name("document-properties-symbolic")
.size(16)
.handle()
.apply(crate::widget::button::icon)
.apply(crate::widget::button)
.style(crate::theme::Button::Icon)
.on_press(msg)
.padding([spacing, spacing, spacing, spacing])
.into(),
@ -145,7 +144,7 @@ where
let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
TextInput::new("", value)
.style(super::style::TextInput::Inline)
.style(crate::theme::TextInput::Inline)
.padding([spacing, spacing, spacing, spacing])
}
@ -204,8 +203,8 @@ pub struct TextInput<'a, Message> {
on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_submit: Option<Message>,
start_icon: Option<Element<'a, Message, crate::Renderer>>,
end_element: Option<Element<'a, Message, crate::Renderer>>,
leading_icon: Option<Element<'a, Message, crate::Renderer>>,
trailing_icon: Option<Element<'a, Message, crate::Renderer>>,
style: <<crate::Renderer as iced_core::Renderer>::Theme as StyleSheet>::Style,
// (text_input::State, mime_type, dnd_action) -> Message
on_create_dnd_source: Option<Box<dyn Fn(State) -> Message + 'a>>,
@ -242,10 +241,10 @@ where
on_input: None,
on_paste: None,
on_submit: None,
start_icon: None,
end_element: None,
leading_icon: None,
trailing_icon: None,
error: None,
style: super::style::TextInput::default(),
style: crate::theme::TextInput::default(),
on_dnd_command_produced: None,
on_create_dnd_source: None,
surface_ids: None,
@ -333,14 +332,14 @@ where
}
/// Sets the start [`Icon`] of the [`TextInput`].
pub fn start_icon(mut self, icon: Element<'a, Message, crate::Renderer>) -> Self {
self.start_icon = Some(icon);
pub fn leading_icon(mut self, icon: Element<'a, Message, crate::Renderer>) -> Self {
self.leading_icon = Some(icon);
self
}
/// Sets the end [`Icon`] of the [`TextInput`].
pub fn end_icon(mut self, icon: Element<'a, Message, crate::Renderer>) -> Self {
self.end_element = Some(icon);
pub fn trailing_icon(mut self, icon: Element<'a, Message, crate::Renderer>) -> Self {
self.trailing_icon = Some(icon);
self
}
@ -398,8 +397,8 @@ where
self.font,
self.on_input.is_none(),
self.is_secure,
self.start_icon.as_ref(),
self.end_element.as_ref(),
self.leading_icon.as_ref(),
self.trailing_icon.as_ref(),
&self.style,
self.dnd_icon,
self.line_height,
@ -485,18 +484,18 @@ where
state.dragging_state = None;
}
let mut children: Vec<_> = self
.start_icon
.leading_icon
.iter_mut()
.chain(self.end_element.iter_mut())
.chain(self.trailing_icon.iter_mut())
.map(iced_core::Element::as_widget_mut)
.collect();
tree.diff_children(children.as_mut_slice());
}
fn children(&self) -> Vec<Tree> {
self.start_icon
self.leading_icon
.iter()
.chain(self.end_element.iter())
.chain(self.trailing_icon.iter())
.map(|icon| Tree::new(icon))
.collect()
}
@ -536,8 +535,8 @@ where
self.width,
self.padding,
self.size,
self.start_icon.as_ref(),
self.end_element.as_ref(),
self.leading_icon.as_ref(),
self.trailing_icon.as_ref(),
self.line_height,
self.label,
self.helper_text,
@ -573,13 +572,13 @@ where
) -> event::Status {
let text_layout = self.text_layout(layout);
let mut child_state = tree.children.iter_mut();
if let (Some(start_icon), Some(tree)) = (self.start_icon.as_mut(), child_state.next()) {
if let (Some(leading_icon), Some(tree)) = (self.leading_icon.as_mut(), child_state.next()) {
let mut children = text_layout.children();
children.next();
let start_icon_layout = children.next().unwrap();
let leading_icon_layout = children.next().unwrap();
if cursor_position.is_over(start_icon_layout.bounds()) {
return start_icon.as_widget_mut().on_event(
if cursor_position.is_over(leading_icon_layout.bounds()) {
return leading_icon.as_widget_mut().on_event(
tree,
event.clone(),
layout,
@ -591,14 +590,15 @@ where
);
}
}
if let (Some(end_icon), Some(tree)) = (self.end_element.as_mut(), child_state.next()) {
if let (Some(trailing_icon), Some(tree)) = (self.trailing_icon.as_mut(), child_state.next())
{
let mut children = text_layout.children();
children.next();
children.next();
let end_icon_layout = children.next().unwrap();
let trailing_icon_layout = children.next().unwrap();
if cursor_position.is_over(end_icon_layout.bounds()) {
return end_icon.as_widget_mut().on_event(
if cursor_position.is_over(trailing_icon_layout.bounds()) {
return trailing_icon.as_widget_mut().on_event(
tree,
event.clone(),
layout,
@ -656,8 +656,8 @@ where
self.font,
self.on_input.is_none(),
self.is_secure,
self.start_icon.as_ref(),
self.end_element.as_ref(),
self.leading_icon.as_ref(),
self.trailing_icon.as_ref(),
&self.style,
self.dnd_icon,
self.line_height,
@ -681,15 +681,15 @@ where
) -> mouse::Interaction {
let layout = self.text_layout(layout);
let mut index = 0;
if let (Some(start_icon), Some(tree)) =
(self.start_icon.as_ref(), state.children.get(index))
if let (Some(leading_icon), Some(tree)) =
(self.leading_icon.as_ref(), state.children.get(index))
{
let mut children = layout.children();
children.next();
let start_icon_layout = children.next().unwrap();
let leading_icon_layout = children.next().unwrap();
if cursor_position.is_over(start_icon_layout.bounds()) {
return start_icon.mouse_interaction(
if cursor_position.is_over(leading_icon_layout.bounds()) {
return leading_icon.mouse_interaction(
tree,
layout,
cursor_position,
@ -700,15 +700,16 @@ where
index += 1;
}
if let (Some(end_icon), Some(tree)) = (self.end_element.as_ref(), state.children.get(index))
if let (Some(trailing_icon), Some(tree)) =
(self.trailing_icon.as_ref(), state.children.get(index))
{
let mut children = layout.children();
children.next();
children.next();
let end_icon_layout = children.next().unwrap();
let trailing_icon_layout = children.next().unwrap();
if cursor_position.is_over(end_icon_layout.bounds()) {
return end_icon.mouse_interaction(
if cursor_position.is_over(trailing_icon_layout.bounds()) {
return trailing_icon.mouse_interaction(
tree,
layout,
cursor_position,
@ -770,8 +771,8 @@ pub fn layout<Message>(
width: Length,
padding: Padding,
size: Option<f32>,
start_icon: Option<&Element<'_, Message, crate::Renderer>>,
end_icon: Option<&Element<'_, Message, crate::Renderer>>,
leading_icon: Option<&Element<'_, Message, crate::Renderer>>,
trailing_icon: Option<&Element<'_, Message, crate::Renderer>>,
line_height: text::LineHeight,
label: Option<&str>,
helper_text: Option<&str>,
@ -804,13 +805,13 @@ pub fn layout<Message>(
let mut text_input_height = text_size * 1.2;
let padding = padding.fit(Size::ZERO, limits.max());
let helper_pos = if start_icon.is_some() || end_icon.is_some() {
let helper_pos = if leading_icon.is_some() || trailing_icon.is_some() {
// TODO configurable icon spacing, maybe via appearance
let limits_copy = limits;
let limits = limits.pad(padding);
let icon_spacing = 8.0;
let (start_icon_width, mut start_icon) = if let Some(icon) = start_icon.as_ref() {
let (leading_icon_width, mut leading_icon) = if let Some(icon) = leading_icon.as_ref() {
let icon_node = icon.layout(
renderer,
&Limits::NONE
@ -823,7 +824,7 @@ pub fn layout<Message>(
(0.0, None)
};
let (end_icon_width, mut end_icon) = if let Some(icon) = end_icon.as_ref() {
let (trailing_icon_width, mut trailing_icon) = if let Some(icon) = trailing_icon.as_ref() {
let icon_node = icon.layout(
renderer,
&Limits::NONE
@ -839,11 +840,12 @@ pub fn layout<Message>(
let text_bounds = text_limits.resolve(Size::ZERO);
let mut text_node =
layout::Node::new(text_bounds - Size::new(start_icon_width + end_icon_width, 0.0));
let mut text_node = layout::Node::new(
text_bounds - Size::new(leading_icon_width + trailing_icon_width, 0.0),
);
text_node.move_to(Point::new(
padding.left + start_icon_width,
padding.left + leading_icon_width,
padding.top + ((text_input_height - text_size * 1.2) / 2.0).max(0.0),
));
let mut node_list: Vec<_> = Vec::with_capacity(3);
@ -851,23 +853,23 @@ pub fn layout<Message>(
let text_node_bounds = text_node.bounds();
node_list.push(text_node);
if let Some(mut start_icon) = start_icon.take() {
start_icon.move_to(Point::new(
if let Some(mut leading_icon) = leading_icon.take() {
leading_icon.move_to(Point::new(
padding.left,
padding.top + ((text_size * 1.2 - start_icon.bounds().height) / 2.0).max(0.0),
padding.top + ((text_size * 1.2 - leading_icon.bounds().height) / 2.0).max(0.0),
));
node_list.push(start_icon);
node_list.push(leading_icon);
}
if let Some(mut end_icon) = end_icon.take() {
end_icon.move_to(Point::new(
if let Some(mut trailing_icon) = trailing_icon.take() {
trailing_icon.move_to(Point::new(
text_node_bounds.x + text_node_bounds.width + f32::from(spacing),
padding.top + ((text_size * 1.2 - end_icon.bounds().height) / 2.0).max(0.0),
padding.top + ((text_size * 1.2 - trailing_icon.bounds().height) / 2.0).max(0.0),
));
node_list.push(end_icon);
node_list.push(trailing_icon);
}
let text_input_size = Size::new(
text_node_bounds.x + text_node_bounds.width + end_icon_width,
text_node_bounds.x + text_node_bounds.width + trailing_icon_width,
text_input_height,
)
.pad(padding);
@ -987,6 +989,7 @@ where
let font: <Renderer as text::Renderer>::Font =
font.unwrap_or_else(|| renderer.default_font());
if is_clicked {
let Some(pos) = cursor_position.position() else {
return event::Status::Ignored;
@ -1737,7 +1740,7 @@ pub fn draw<'a, Message>(
is_disabled: bool,
is_secure: bool,
icon: Option<&Element<'a, Message, crate::Renderer>>,
end_element: Option<&Element<'a, Message, crate::Renderer>>,
trailing_icon: Option<&Element<'a, Message, crate::Renderer>>,
style: &<crate::Theme as StyleSheet>::Style,
dnd_icon: bool,
line_height: text::LineHeight,
@ -1848,9 +1851,9 @@ pub fn draw<'a, Message>(
});
}
let mut child_index = 0;
let start_icon_tree = children.get(child_index);
let leading_icon_tree = children.get(child_index);
// draw the start icon in the text input
if let (Some(icon), Some(tree)) = (icon, start_icon_tree) {
if let (Some(icon), Some(tree)) = (icon, leading_icon_tree) {
let icon_layout = children_layout.next().unwrap();
icon.as_widget().draw(
@ -1858,7 +1861,7 @@ pub fn draw<'a, Message>(
renderer,
theme,
&renderer::Style {
icon_color: renderer_style.icon_color,
icon_color: appearance.icon_color,
text_color: appearance.text_color,
scale_factor: renderer_style.scale_factor,
},
@ -1977,12 +1980,6 @@ pub fn draw<'a, Message>(
text::Shaping::Advanced,
);
let color = if text.is_empty() {
theme.placeholder_color(style)
} else {
appearance.text_color
};
let render = |renderer: &mut crate::Renderer| {
if let Some((cursor, color)) = cursor {
renderer.fill_quad(cursor, color);
@ -1992,7 +1989,11 @@ pub fn draw<'a, Message>(
renderer.fill_text(Text {
content: if text.is_empty() { placeholder } else { &text },
color,
color: if text.is_empty() {
appearance.placeholder_color
} else {
appearance.text_color
},
font,
bounds: Rectangle {
y: text_bounds.center_y(),
@ -2015,10 +2016,10 @@ pub fn draw<'a, Message>(
render(renderer);
}
let end_icon_tree = children.get(child_index);
let trailing_icon_tree = children.get(child_index);
// draw the end icon in the text input
if let (Some(icon), Some(tree)) = (end_element, end_icon_tree) {
if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) {
let icon_layout = children_layout.next().unwrap();
icon.as_widget().draw(
@ -2042,7 +2043,7 @@ pub fn draw<'a, Message>(
content: helper_text,
size: helper_text_size,
font,
color,
color: appearance.text_color,
bounds: helper_text_layout.bounds(),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,

View file

@ -10,5 +10,6 @@ mod input;
mod style;
pub mod value;
pub use crate::theme::TextInput as Style;
pub use input::*;
pub use style::{Appearance as TextInputAppearance, StyleSheet as TextInputStyleSheet};
pub use style::{Appearance, StyleSheet};

View file

@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
//! Change the appearance of a text input.
use iced_core::{Background, BorderRadius, Color};
/// The appearance of a text input.
@ -18,8 +19,12 @@ pub struct Appearance {
pub border_width: f32,
/// The border [`Color`] of the text input.
pub border_color: Color,
/// The [`Color`] of symbolic icons.
pub icon_color: Color,
/// The label [`Color`] of the text input.
pub label_color: Color,
/// The placeholder text [`Color`].
pub placeholder_color: Color,
/// The text [`Color`] of the text input.
pub selected_text_color: Color,
/// The text [`Color`] of the text input.
@ -42,9 +47,6 @@ pub trait StyleSheet {
/// Produces the style of a focused text input.
fn focused(&self, style: &Self::Style) -> Appearance;
/// Produces the [`Color`] of the placeholder of a text input.
fn placeholder_color(&self, style: &Self::Style) -> Color;
/// Produces the style of an hovered text input.
fn hovered(&self, style: &Self::Style) -> Appearance {
self.focused(style)
@ -53,247 +55,3 @@ pub trait StyleSheet {
/// Produces the style of a disabled text input.
fn disabled(&self, style: &Self::Style) -> Appearance;
}
#[derive(Default)]
pub enum TextInput {
#[default]
Default,
ExpandableSearch,
Search,
Inline,
Custom {
active: Box<dyn Fn(&crate::Theme) -> Appearance>,
error: Box<dyn Fn(&crate::Theme) -> Appearance>,
hovered: Box<dyn Fn(&crate::Theme) -> Appearance>,
focused: Box<dyn Fn(&crate::Theme) -> Appearance>,
disabled: Box<dyn Fn(&crate::Theme) -> Appearance>,
placeholder_color: Box<dyn Fn(&crate::Theme) -> Color>,
},
}
impl StyleSheet for crate::Theme {
type Style = TextInput;
fn active(&self, style: &Self::Style) -> Appearance {
let palette = self.cosmic();
let mut bg = palette.palette.neutral_7;
bg.alpha = 0.25;
let corner = palette.corner_radii;
let label_color = palette.palette.neutral_9;
match style {
TextInput::Default => Appearance {
background: Color::from(bg).into(),
border_radius: corner.radius_s.into(),
border_width: 1.0,
border_offset: None,
border_color: self.current_container().component.divider.into(),
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::ExpandableSearch => Appearance {
background: Color::TRANSPARENT.into(),
border_radius: corner.radius_xl.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Search => Appearance {
background: Color::from(bg).into(),
border_radius: corner.radius_xl.into(),
border_width: 1.0,
border_offset: None,
border_color: self.current_container().component.divider.into(),
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Inline => Appearance {
background: Color::TRANSPARENT.into(),
border_radius: corner.radius_0.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Custom { active, .. } => active(self),
}
}
fn error(&self, style: &Self::Style) -> Appearance {
let palette = self.cosmic();
let mut bg = palette.palette.neutral_7;
bg.alpha = 0.25;
let corner = palette.corner_radii;
let label_color = palette.palette.neutral_9;
match style {
TextInput::Default => Appearance {
background: Color::from(bg).into(),
border_radius: corner.radius_s.into(),
border_width: 1.0,
border_offset: Some(2.0),
border_color: Color::from(palette.destructive_color()),
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Search | TextInput::ExpandableSearch => Appearance {
background: Color::from(bg).into(),
border_radius: corner.radius_xl.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Inline => Appearance {
background: Color::TRANSPARENT.into(),
border_radius: corner.radius_0.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Custom { error, .. } => error(self),
}
}
fn hovered(&self, style: &Self::Style) -> Appearance {
let palette = self.cosmic();
let mut bg = palette.palette.neutral_7;
bg.alpha = 0.25;
let corner = palette.corner_radii;
let label_color = palette.palette.neutral_9;
match style {
TextInput::Default => Appearance {
background: Color::from(bg).into(),
border_radius: corner.radius_s.into(),
border_width: 1.0,
border_offset: None,
border_color: palette.accent.base.into(),
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Search => Appearance {
background: Color::from(bg).into(),
border_radius: corner.radius_xl.into(),
border_offset: None,
border_width: 1.0,
border_color: palette.accent.base.into(),
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::ExpandableSearch => Appearance {
background: Color::from(bg).into(),
border_radius: corner.radius_xl.into(),
border_offset: None,
border_width: 0.0,
border_color: Color::TRANSPARENT,
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Inline => Appearance {
background: Color::from(self.current_container().component.hover).into(),
border_radius: corner.radius_0.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Custom { hovered, .. } => hovered(self),
}
}
fn focused(&self, style: &Self::Style) -> Appearance {
let palette = self.cosmic();
let mut bg = palette.palette.neutral_7;
bg.alpha = 0.25;
let corner = palette.corner_radii;
let label_color = palette.palette.neutral_9;
match style {
TextInput::Default => Appearance {
background: Color::from(bg).into(),
border_radius: corner.radius_s.into(),
border_width: 1.0,
border_offset: Some(2.0),
border_color: palette.accent.base.into(),
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Search | TextInput::ExpandableSearch => Appearance {
background: Color::from(bg).into(),
border_radius: corner.radius_xl.into(),
border_width: 1.0,
border_offset: Some(2.0),
border_color: palette.accent.base.into(),
text_color: self.current_container().on.into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Inline => Appearance {
background: Color::from(palette.accent.base).into(),
border_radius: corner.radius_0.into(),
border_width: 0.0,
border_offset: None,
border_color: Color::TRANSPARENT,
// TODO use regular text color here after text rendering handles multiple colors
// in this case, for selected and unselected text
text_color: palette.on_accent_color().into(),
selected_text_color: palette.on_accent_color().into(),
selected_fill: palette.accent_color().into(),
label_color: label_color.into(),
},
TextInput::Custom { focused, .. } => focused(self),
}
}
fn placeholder_color(&self, style: &Self::Style) -> Color {
if let TextInput::Custom {
placeholder_color, ..
} = style
{
return placeholder_color(self);
}
let palette = self.cosmic();
let mut neutral_9 = palette.palette.neutral_9;
neutral_9.alpha = 0.7;
neutral_9.into()
}
fn disabled(&self, style: &Self::Style) -> Appearance {
if let TextInput::Custom { disabled, .. } = style {
return disabled(self);
}
self.active(style)
}
}

View file

@ -32,7 +32,7 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> {
pub fn into_widget(self) -> widget::Container<'a, Message, Renderer> {
let label = widget::container(crate::widget::text(self.message)).width(Length::Fill);
let close_button = icon::handle::from_name("window-close-symbolic")
let close_button = icon::from_name("window-close-symbolic")
.size(16)
.apply(widget::button::icon)
.on_press_maybe(self.on_close);