From 9dbc1be269238d1aaa3a55f15b4f83ec42ed32c1 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 13 Sep 2023 15:47:32 +0200 Subject: [PATCH] refactor(widget): improvements to button and icon widgets --- {res => src/widget/button}/external-link.svg | 0 src/widget/button/hyperlink.rs | 50 ---- src/widget/button/icon.rs | 48 ++-- src/widget/button/link.rs | 101 ++++++++ src/widget/button/mod.rs | 37 ++- src/widget/button/style.rs | 3 - src/widget/button/text.rs | 91 ++++--- src/widget/header_bar.rs | 2 +- src/widget/icon/handle.rs | 24 +- src/widget/icon/mod.rs | 79 +++--- src/widget/icon/{builder.rs => named.rs} | 60 +++-- src/widget/nav_bar_toggle.rs | 8 +- src/widget/search/field.rs | 4 +- src/widget/search/mod.rs | 2 +- src/widget/segmented_button/widget.rs | 4 +- src/widget/spin_button/mod.rs | 6 +- src/widget/text_input/input.rs | 181 ++++++------- src/widget/text_input/mod.rs | 3 +- src/widget/text_input/style.rs | 252 +------------------ src/widget/warning.rs | 2 +- 20 files changed, 399 insertions(+), 558 deletions(-) rename {res => src/widget/button}/external-link.svg (100%) delete mode 100644 src/widget/button/hyperlink.rs create mode 100644 src/widget/button/link.rs rename src/widget/icon/{builder.rs => named.rs} (62%) diff --git a/res/external-link.svg b/src/widget/button/external-link.svg similarity index 100% rename from res/external-link.svg rename to src/widget/button/external-link.svg diff --git a/src/widget/button/hyperlink.rs b/src/widget/button/hyperlink.rs deleted file mode 100644 index 472d3162..00000000 --- a/src/widget/button/hyperlink.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2023 System76 -// 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) -} diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index c03aaba9..1fcd6455 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -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(handle: impl Into) -> Button<'static, Message> { +pub fn icon<'a, Message>(handle: impl Into) -> 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> 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> 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> 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() + } } } diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs new file mode 100644 index 00000000..960d70a8 --- /dev/null +++ b/src/widget/button/link.rs @@ -0,0 +1,101 @@ +// Copyright 2023 System76 +// 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> + 'static) -> Button<'a, Message> { + Button::new( + label, + Hyperlink { + trailing_icon: false, + }, + ) +} + +impl<'a, Message> Button<'a, Message> { + pub fn new(label: impl Into> + '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> 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() + } + } +} diff --git a/src/widget/button/mod.rs b/src/widget/button/mod.rs index 853d6c67..283a80bc 100644 --- a/src/widget/button/mod.rs +++ b/src/widget/button/mod.rs @@ -1,8 +1,11 @@ // Copyright 2023 System76 // 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, 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) -> 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) -> Self { self.height = height.into(); self } - /// Sets the [`Padding`] of the [`Button`]. + /// Sets the [`Padding`] of the button. pub fn 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) -> 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>) -> Self { - self.label = label.into(); + /// Adds a tooltip to the button. + pub fn tooltip(mut self, tooltip: impl Into>) -> Self { + self.tooltip = tooltip.into(); self } } diff --git a/src/widget/button/style.rs b/src/widget/button/style.rs index 2304ff15..5d105f31 100644 --- a/src/widget/button/style.rs +++ b/src/widget/button/style.rs @@ -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; } diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 5d6a9fc9..375033f2 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -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>) -> Button<'a, Message> } pub struct Text { - pub(super) leading_icon: Option, - pub(super) trailing_icon: Option, + pub(super) leading_icon: Option, + pub(super) trailing_icon: Option, } 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) -> Self { + pub fn leading_icon(mut self, icon: impl Into) -> Self { self.variant.leading_icon = Some(icon.into()); self } - pub fn trailing_icon(mut self, icon: impl Into) -> Self { + pub fn trailing_icon(mut self, icon: impl Into) -> Self { self.variant.trailing_icon = Some(icon.into()); self } } impl<'a, Message: Clone + 'static> From> 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> = (!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() + } } } diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 7309845f..5c4f4627 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -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) diff --git a/src/widget/icon/handle.rs b/src/widget/icon/handle.rs index 62a0a17c..1a138847 100644 --- a/src/widget/icon/handle.rs +++ b/src/widget/icon/handle.rs @@ -1,7 +1,7 @@ // Copyright 2023 System76 // 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>) -> Handle { Handle { symbolic: false, - variant: Variant::Svg(svg::Handle::from_memory(bytes)), + data: Data::Svg(svg::Handle::from_memory(bytes)), } } diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 30ff9a68..d754ef0e 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -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> - + 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> - + 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>) -> Icon { - icon(handle::from_svg_bytes(bytes)) +/// Create an icon handle from its XDG icon name. +pub fn from_name(name: impl Into>) -> 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(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::::new(handle) + .into() + }; + + let from_svg = |handle| { + Svg::::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), } } } diff --git a/src/widget/icon/builder.rs b/src/widget/icon/named.rs similarity index 62% rename from src/widget/icon/builder.rs rename to src/widget/icon/named.rs index 791e66e2..724eec79 100644 --- a/src/widget/icon/builder.rs +++ b/src/widget/icon/named.rs @@ -1,33 +1,38 @@ // Copyright 2023 System76 // 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, /// 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, + pub scale: Option, /// Restrict the lookup to a given size. #[setters(strip_option)] - size: Option, + pub size: Option, + + /// 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>) -> 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 { + pub fn path(self) -> Option { + 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> for Handle { - fn from(builder: Builder<'a>) -> Self { +impl From for Handle { + fn from(builder: Named) -> Self { builder.handle() } } -impl<'a> From> for Icon { - fn from(builder: Builder<'a>) -> Self { +impl From for Icon { + fn from(builder: Named) -> Self { builder.icon() } } + +impl<'a, Message: 'static> From for crate::Element<'a, Message> { + fn from(builder: Named) -> Self { + builder.icon().into() + } +} diff --git a/src/widget/nav_bar_toggle.rs b/src/widget/nav_bar_toggle.rs index d8146f77..54883e19 100644 --- a/src/widget/nav_bar_toggle.rs +++ b/src/widget/nav_bar_toggle.rs @@ -26,12 +26,10 @@ pub fn nav_bar_toggle() -> NavBarToggle { impl<'a, Message: 'static + Clone> From> for Element<'a, Message> { fn from(nav_bar_toggle: NavBarToggle) -> 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() }; diff --git a/src/widget/search/field.rs b/src/widget/search/field.rs index 2bd62857..0ca5af1d 100644 --- a/src/widget/search/field.rs +++ b/src/widget/search/field.rs @@ -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> for crate::Element<' } fn clear_button() -> 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) } diff --git a/src/widget/search/mod.rs b/src/widget/search/mod.rs index 257238c0..4a1f374e 100644 --- a/src/widget/search/mod.rs +++ b/src/widget/search/mod.rs @@ -49,7 +49,7 @@ mod button { /// A search button which converts to a search [`field`] on click. #[must_use] pub fn button(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) diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index b0340be6..37e6a6e7 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -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, diff --git a/src/widget/spin_button/mod.rs b/src/widget/spin_button/mod.rs index ee9f7d29..090be581 100644 --- a/src/widget/spin_button/mod.rs +++ b/src/widget/spin_button/mod.rs @@ -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)) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 13751903..7225d862 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -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 Message + 'a>>, on_paste: Option Message + 'a>>, on_submit: Option, - start_icon: Option>, - end_element: Option>, + leading_icon: Option>, + trailing_icon: Option>, style: <::Theme as StyleSheet>::Style, // (text_input::State, mime_type, dnd_action) -> Message on_create_dnd_source: Option 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 { - 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( width: Length, padding: Padding, size: Option, - 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( 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( (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( 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( 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: ::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: &::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, diff --git a/src/widget/text_input/mod.rs b/src/widget/text_input/mod.rs index 1ac42ff4..46c309fd 100644 --- a/src/widget/text_input/mod.rs +++ b/src/widget/text_input/mod.rs @@ -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}; diff --git a/src/widget/text_input/style.rs b/src/widget/text_input/style.rs index 472cec73..026902bc 100644 --- a/src/widget/text_input/style.rs +++ b/src/widget/text_input/style.rs @@ -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 Appearance>, - error: Box Appearance>, - hovered: Box Appearance>, - focused: Box Appearance>, - disabled: Box Appearance>, - placeholder_color: Box 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) - } -} diff --git a/src/widget/warning.rs b/src/widget/warning.rs index ea470a75..38c364ee 100644 --- a/src/widget/warning.rs +++ b/src/widget/warning.rs @@ -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);