From 68f4ed46b1846e27c03f23d0b98e3ce89dc497b8 Mon Sep 17 00:00:00 2001 From: pml68 Date: Sun, 13 Apr 2025 03:31:36 +0200 Subject: feat(material_theme): implement `pick_list::Catalog` --- material_theme/src/lib.rs | 49 +++++++++-------------------------------- material_theme/src/menu.rs | 16 +++++++++----- material_theme/src/pick_list.rs | 40 +++++++++++++++++++++++++++++++++ material_theme/src/utils.rs | 38 ++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 45 deletions(-) create mode 100644 material_theme/src/pick_list.rs (limited to 'material_theme/src') diff --git a/material_theme/src/lib.rs b/material_theme/src/lib.rs index ba9d478..521af2c 100644 --- a/material_theme/src/lib.rs +++ b/material_theme/src/lib.rs @@ -9,6 +9,7 @@ pub mod container; #[cfg(feature = "dialog")] pub mod dialog; pub mod menu; +pub mod pick_list; pub mod scrollable; pub mod text; pub mod utils; @@ -62,6 +63,12 @@ impl Default for Theme { } } +impl std::fmt::Display for Theme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + impl Base for Theme { fn base(&self) -> Style { Style { @@ -121,6 +128,8 @@ pub struct ColorScheme { pub outline: Outline, #[serde(with = "color_serde")] pub shadow: Color, + #[serde(with = "color_serde")] + pub scrim: Color, } #[derive(Debug, Clone, Copy, PartialEq, Deserialize)] @@ -222,49 +231,11 @@ pub struct Outline { pub variant: Color, } -pub fn parse_argb(s: &str) -> Option { - let hex = s.strip_prefix('#').unwrap_or(s); - - let parse_channel = |from: usize, to: usize| { - let num = - usize::from_str_radix(&hex[from..=to], 16).ok()? as f32 / 255.0; - - // If we only got half a byte (one letter), expand it into a full byte (two letters) - Some(if from == to { num + num * 16.0 } else { num }) - }; - - Some(match hex.len() { - 3 => Color::from_rgb( - parse_channel(0, 0)?, - parse_channel(1, 1)?, - parse_channel(2, 2)?, - ), - 4 => Color::from_rgba( - parse_channel(1, 1)?, - parse_channel(2, 2)?, - parse_channel(3, 3)?, - parse_channel(0, 0)?, - ), - 6 => Color::from_rgb( - parse_channel(0, 1)?, - parse_channel(2, 3)?, - parse_channel(4, 5)?, - ), - 8 => Color::from_rgba( - parse_channel(2, 3)?, - parse_channel(4, 5)?, - parse_channel(6, 7)?, - parse_channel(0, 1)?, - ), - _ => None?, - }) -} - mod color_serde { use iced_widget::core::Color; use serde::{Deserialize, Deserializer}; - use super::parse_argb; + use super::utils::parse_argb; pub fn deserialize<'de, D>(deserializer: D) -> Result where diff --git a/material_theme/src/menu.rs b/material_theme/src/menu.rs index d1bebec..9f43c72 100644 --- a/material_theme/src/menu.rs +++ b/material_theme/src/menu.rs @@ -2,6 +2,7 @@ use iced_widget::core::{Background, border}; use iced_widget::overlay::menu::{Catalog, Style, StyleFn}; use super::Theme; +use crate::utils::{HOVERED_LAYER_OPACITY, mix}; impl Catalog for Theme { type Class<'a> = StyleFn<'a, Self>; @@ -16,14 +17,17 @@ impl Catalog for Theme { } pub fn default(theme: &Theme) -> Style { - let surface = theme.colorscheme.surface; - let secondary = theme.colorscheme.secondary; + let colors = theme.colorscheme.surface; Style { border: border::rounded(4), - background: Background::Color(surface.surface_container.base), - text_color: surface.on_surface, - selected_background: Background::Color(secondary.secondary_container), - selected_text_color: secondary.on_secondary_container, + background: Background::Color(colors.surface_container.base), + text_color: colors.on_surface, + selected_background: Background::Color(mix( + colors.surface_container.base, + colors.on_surface, + HOVERED_LAYER_OPACITY, + )), + selected_text_color: colors.on_surface, } } diff --git a/material_theme/src/pick_list.rs b/material_theme/src/pick_list.rs new file mode 100644 index 0000000..c589100 --- /dev/null +++ b/material_theme/src/pick_list.rs @@ -0,0 +1,40 @@ +use iced_widget::core::{Background, border}; +use iced_widget::pick_list::{Catalog, Status, Style, StyleFn}; + +use super::Theme; + +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> ::Class<'a> { + Box::new(default) + } + + fn style( + &self, + class: &::Class<'_>, + status: Status, + ) -> Style { + class(self, status) + } +} + +pub fn default(theme: &Theme, status: Status) -> Style { + let surface = theme.colorscheme.surface; + + let active = Style { + text_color: surface.on_surface, + placeholder_color: surface.on_surface_variant, + handle_color: surface.on_surface_variant, + background: Background::Color(surface.surface_container.highest), + border: border::rounded(4), + }; + + match status { + Status::Active => active, + Status::Hovered | Status::Opened { .. } => Style { + background: Background::Color(surface.surface_container.highest), + ..active + }, + } +} diff --git a/material_theme/src/utils.rs b/material_theme/src/utils.rs index 7efec9b..a05bc62 100644 --- a/material_theme/src/utils.rs +++ b/material_theme/src/utils.rs @@ -28,6 +28,44 @@ pub fn shadow_from_elevation(elevation: f32, color: Color) -> Shadow { } } +pub fn parse_argb(s: &str) -> Option { + let hex = s.strip_prefix('#').unwrap_or(s); + + let parse_channel = |from: usize, to: usize| { + let num = + usize::from_str_radix(&hex[from..=to], 16).ok()? as f32 / 255.0; + + // If we only got half a byte (one letter), expand it into a full byte (two letters) + Some(if from == to { num + num * 16.0 } else { num }) + }; + + Some(match hex.len() { + 3 => Color::from_rgb( + parse_channel(0, 0)?, + parse_channel(1, 1)?, + parse_channel(2, 2)?, + ), + 4 => Color::from_rgba( + parse_channel(1, 1)?, + parse_channel(2, 2)?, + parse_channel(3, 3)?, + parse_channel(0, 0)?, + ), + 6 => Color::from_rgb( + parse_channel(0, 1)?, + parse_channel(2, 3)?, + parse_channel(4, 5)?, + ), + 8 => Color::from_rgba( + parse_channel(2, 3)?, + parse_channel(4, 5)?, + parse_channel(6, 7)?, + parse_channel(0, 1)?, + ), + _ => None?, + }) +} + pub fn mix(color1: Color, color2: Color, p2: f32) -> Color { if p2 <= 0.0 { return color1; -- cgit v1.2.3