From 947532413ab4530d4088010243b5da5cdcdee8c0 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 28 Oct 2022 21:59:41 -0700 Subject: [PATCH] Include Cosmic theme in libcosmic, instead of in Iced fork I think it's best to only include things in the Iced fork that can't be done without forking Iced, and/or are expected to be merged upstream. The theme doesn't seem to be either, so it fits more appropriately in libcosmic. That should make it easier to keep up with upstream, and it may help to have all the Cosmic parts in one place. Based on https://github.com/pop-os/iced commit ad9026e. --- Cargo.toml | 19 +- examples/cosmic/src/window.rs | 6 +- src/lib.rs | 5 + src/theme/cosmic.rs | 0 src/theme/expander.rs | 56 +++ src/theme/mod.rs | 758 ++++++++++++++++++++++++++++++++ src/theme/palette.rs | 290 ++++++++++++ src/widget/expander.rs | 11 +- src/widget/header_bar.rs | 5 +- src/widget/list/list_box.rs | 3 +- src/widget/list/macros.rs | 5 +- src/widget/navigation/macros.rs | 3 +- src/widget/navigation/navbar.rs | 3 +- 13 files changed, 1145 insertions(+), 19 deletions(-) create mode 100644 src/theme/cosmic.rs create mode 100644 src/theme/expander.rs create mode 100644 src/theme/mod.rs create mode 100644 src/theme/palette.rs diff --git a/Cargo.toml b/Cargo.toml index 147c939..6f89ed9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,22 @@ debug = ["iced/debug"] freedesktop-icons = "0.2.1" apply = "0.3.0" derive_setters = "0.1.5" +lazy_static = "1.4.0" +palette = "0.6.1" + +[dependencies.cosmic-theme] +git = "https://github.com/pop-os/cosmic-theme.git" [dependencies.iced] git = "https://github.com/pop-os/iced.git" branch = "cosmic-design-system" # path = "../iced" -features = ["cosmic-theme", "image", "svg"] +features = ["image", "svg"] + +[dependencies.iced_core] +git = "https://github.com/pop-os/iced.git" +branch = "cosmic-design-system" +# path = "../iced/core" [dependencies.iced_lazy] git = "https://github.com/pop-os/iced.git" @@ -30,19 +40,22 @@ branch = "cosmic-design-system" git = "https://github.com/pop-os/iced.git" branch = "cosmic-design-system" # path = "../iced/native" -features = ["cosmic-theme"] [dependencies.iced_style] git = "https://github.com/pop-os/iced.git" branch = "cosmic-design-system" # path = "../iced/style" -features = ["cosmic-theme"] [dependencies.iced_winit] git = "https://github.com/pop-os/iced.git" branch = "cosmic-design-system" # path = "../iced/winit" +[dependencies.iced_wgpu] +git = "https://github.com/pop-os/iced.git" +branch = "cosmic-design-system" +# path = "../iced/wgpu" + [workspace] members = [ "examples/*", diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index ecf070b..1a077de 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -4,11 +4,13 @@ use cosmic::{ checkbox, column, container, horizontal_space, pick_list, progress_bar, radio, row, slider, text, }, - iced::{self, theme, Alignment, Application, Color, Command, Element, Length, Theme}, + iced::{self, Alignment, Application, Color, Command, Length}, iced_lazy::responsive, iced_winit::window::{drag, maximize, minimize}, list_view, list_view_item, list_view_row, list_view_section, scrollable, + theme::{self, Theme}, widget::{button, header_bar, list_box, list_row, list_view::*, toggler}, + Element, }; use std::collections::BTreeMap; @@ -106,7 +108,7 @@ impl Application for Window { } fn view(&self) -> Element { - let mut header: Element = header_bar() + let mut header: Element = header_bar() .title(self.title()) .nav_title(String::from("Settings")) .sidebar_active(self.sidebar_toggled) diff --git a/src/lib.rs b/src/lib.rs index ffcf14a..c6dd88f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,13 @@ pub use iced_style; pub use iced_winit; pub mod font; +pub mod theme; pub mod widget; +pub use theme::Theme; +pub type Renderer = iced::Renderer; +pub type Element<'a, Message> = iced::Element<'a, Message, Renderer>; + #[derive(Clone, Copy, Debug)] pub enum WindowMsg { Close, diff --git a/src/theme/cosmic.rs b/src/theme/cosmic.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/theme/expander.rs b/src/theme/expander.rs new file mode 100644 index 0000000..d43dea9 --- /dev/null +++ b/src/theme/expander.rs @@ -0,0 +1,56 @@ +use iced_core::{Background, Color}; + +/// The appearance of a [`Expander`](crate::native::expander::Expander). +#[derive(Clone, Copy, Debug)] +pub struct Appearance { + /// The background of the [`Expander`](crate::native::expander::Expander). + pub background: Background, + + /// The border radius of the [`Expander`](crate::native::expander::Expander). + pub border_radius: f32, + + /// The border width of the [`Expander`](crate::native::expander::Expander). + pub border_width: f32, + + /// The border color of the [`Expander`](crate::native::expander::Expander). + pub border_color: Color, + + /// The background of the head of the [`Expander`](crate::native::expander::Expander). + pub head_background: Background, + + /// The text color of the head of the [`Expander`](crate::native::expander::Expander). + pub head_text_color: Color, + + /// The background of the body of the [`Expander`](crate::native::expander::Expander). + pub body_background: Background, + + /// The text color of the body of the [`Expander`](crate::native::expander::Expander). + pub body_text_color: Color, + + /// The color of the close icon of the [`Expander`](crate::native::expander::Expander). + pub toggle_color: Color, +} + +impl std::default::Default for Appearance { + fn default() -> Self { + Self { + background: Color::WHITE.into(), + border_radius: 10.0, //32.0, + border_width: 1.0, + border_color: [0.87, 0.87, 0.87].into(), //Color::BLACK.into(), + head_background: Background::Color([0.87, 0.87, 0.87].into()), + head_text_color: Color::BLACK, + body_background: Color::TRANSPARENT.into(), + body_text_color: Color::BLACK, + toggle_color: Color::BLACK, + } + } +} + +/// A set of rules that dictate the [`Appearance`] of a container. +pub trait StyleSheet { + type Style: Default + Copy; + + /// Produces the [`Appearance`] of a container. + fn appearance(&self, style: Self::Style) -> Appearance; +} diff --git a/src/theme/mod.rs b/src/theme/mod.rs new file mode 100644 index 0000000..5b4e572 --- /dev/null +++ b/src/theme/mod.rs @@ -0,0 +1,758 @@ +pub mod expander; +pub mod palette; + +pub use self::palette::Palette; + +use iced_style::application; +use iced_style::button; +use iced_style::checkbox; +use iced_style::container; +use iced_style::menu; +use iced_style::pane_grid; +use iced_style::pick_list; +use iced_style::progress_bar; +use iced_style::radio; +use iced_style::rule; +use iced_style::scrollable; +use iced_style::slider; +use iced_style::text; +use iced_style::text_input; +use iced_style::toggler; + +use iced_core::{Background, Color}; + +type CosmicColor = ::palette::rgb::Srgba; +type CosmicComponent = cosmic_theme::Component; +type CosmicTheme = cosmic_theme::Theme; +type CosmicThemeCss = cosmic_theme::Theme; + +lazy_static::lazy_static! { + pub static ref COSMIC_DARK: CosmicTheme = CosmicThemeCss::dark_default().into_srgba(); + pub static ref COSMIC_LIGHT: CosmicTheme = CosmicThemeCss::light_default().into_srgba(); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Theme { + Light, + Dark, +} + +impl Theme { + pub fn cosmic(self) -> &'static CosmicTheme { + match self { + Self::Dark => &COSMIC_DARK, + Self::Light => &COSMIC_LIGHT, + } + } + + pub fn palette(self) -> Palette { + match self { + Self::Dark => Palette::DARK, + Self::Light => Palette::LIGHT, + } + } + + pub fn extended_palette(&self) -> &self::palette::Extended { + match self { + Self::Dark => &self::palette::EXTENDED_DARK, + Self::Light => &self::palette::EXTENDED_LIGHT, + } + } +} + +impl Default for Theme { + fn default() -> Self { + Self::Dark + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Application { + Default, + Custom(fn(Theme) -> application::Appearance), +} + +impl Default for Application { + fn default() -> Self { + Self::Default + } +} + +impl application::StyleSheet for Theme { + type Style = Application; + + fn appearance(&self, style: Self::Style) -> application::Appearance { + let cosmic = self.cosmic(); + + match style { + Application::Default => application::Appearance { + background_color: cosmic.bg_color().into(), + text_color: cosmic.on_bg_color().into(), + }, + Application::Custom(f) => f(*self), + } + } +} + +/* + * TODO: Button + */ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Button { + Primary, + Secondary, + Positive, + Destructive, + Text, +} + +impl Default for Button { + fn default() -> Self { + Self::Primary + } +} + +impl Button { + fn cosmic(&self, theme: &Theme) -> &'static CosmicComponent { + let cosmic = theme.cosmic(); + match self { + Button::Primary => &cosmic.accent, + Button::Secondary => &cosmic.primary.component, + Button::Positive => &cosmic.success, + Button::Destructive => &cosmic.destructive, + Button::Text => &cosmic.secondary.component, + } + } +} + +impl button::StyleSheet for Theme { + type Style = Button; + + fn active(&self, style: Self::Style) -> button::Appearance { + let cosmic = style.cosmic(self); + + button::Appearance { + border_radius: 24.0, + background: match style { + Button::Text => None, + _ => Some(Background::Color(cosmic.base.into())), + }, + text_color: cosmic.on.into(), + ..button::Appearance::default() + } + } + + fn hovered(&self, style: Self::Style) -> button::Appearance { + let active = self.active(style); + let cosmic = style.cosmic(self); + + button::Appearance { + background: Some(Background::Color(cosmic.hover.into())), + ..active + } + } +} + +/* + * TODO: Checkbox + */ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Checkbox { + Primary, + Secondary, + Success, + Danger, +} + +impl Default for Checkbox { + fn default() -> Self { + Self::Primary + } +} + +impl checkbox::StyleSheet for Theme { + type Style = Checkbox; + + fn active( + &self, + style: Self::Style, + is_checked: bool, + ) -> checkbox::Appearance { + let palette = self.extended_palette(); + + match style { + Checkbox::Primary => checkbox_appearance( + palette.primary.strong.text, + palette.background.base, + palette.primary.strong, + is_checked, + ), + Checkbox::Secondary => checkbox_appearance( + palette.background.base.text, + palette.background.base, + palette.background.base, + is_checked, + ), + Checkbox::Success => checkbox_appearance( + palette.success.base.text, + palette.background.base, + palette.success.base, + is_checked, + ), + Checkbox::Danger => checkbox_appearance( + palette.danger.base.text, + palette.background.base, + palette.danger.base, + is_checked, + ), + } + } + + fn hovered( + &self, + style: Self::Style, + is_checked: bool, + ) -> checkbox::Appearance { + let palette = self.extended_palette(); + + match style { + Checkbox::Primary => checkbox_appearance( + palette.primary.strong.text, + palette.background.weak, + palette.primary.base, + is_checked, + ), + Checkbox::Secondary => checkbox_appearance( + palette.background.base.text, + palette.background.weak, + palette.background.base, + is_checked, + ), + Checkbox::Success => checkbox_appearance( + palette.success.base.text, + palette.background.weak, + palette.success.base, + is_checked, + ), + Checkbox::Danger => checkbox_appearance( + palette.danger.base.text, + palette.background.weak, + palette.danger.base, + is_checked, + ), + } + } +} + +fn checkbox_appearance( + checkmark_color: Color, + base: palette::Pair, + accent: palette::Pair, + is_checked: bool, +) -> checkbox::Appearance { + checkbox::Appearance { + background: Background::Color(if is_checked { + accent.color + } else { + base.color + }), + checkmark_color, + border_radius: 4.0, + border_width: if is_checked { 0.0 } else { 1.0 }, + border_color: accent.color, + text_color: None, + } +} + +#[derive(Clone, Copy)] +pub enum Expander { + Default, + Custom(fn(&Theme) -> expander::Appearance), +} + +impl Default for Expander { + fn default() -> Self { + Self::Default + } +} + +impl From expander::Appearance> for Expander { + fn from(f: fn(&Theme) -> expander::Appearance) -> Self { + Self::Custom(f) + } +} + +impl expander::StyleSheet for Theme { + type Style = Expander; + + fn appearance(&self, style: Self::Style) -> expander::Appearance { + match style { + Expander::Default => Default::default(), + Expander::Custom(f) => f(self), + } + } +} + +/* + * TODO: Container + */ +#[derive(Clone, Copy)] +pub enum Container { + Transparent, + Box, + Custom(fn(&Theme) -> container::Appearance), +} + +impl Default for Container { + fn default() -> Self { + Self::Transparent + } +} + +impl From container::Appearance> for Container { + fn from(f: fn(&Theme) -> container::Appearance) -> Self { + Self::Custom(f) + } +} + +impl container::StyleSheet for Theme { + type Style = Container; + + fn appearance(&self, style: Self::Style) -> container::Appearance { + match style { + Container::Transparent => Default::default(), + Container::Box => { + let palette = self.extended_palette(); + + container::Appearance { + text_color: None, + background: palette.background.weak.color.into(), + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + } + } + Container::Custom(f) => f(self), + } + } +} + +/* + * Slider + */ +impl slider::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: Self::Style) -> slider::Appearance { + let cosmic = self.cosmic(); + + //TODO: no way to set rail thickness + slider::Appearance { + rail_colors: ( + cosmic.accent.base.into(), + //TODO: no way to set color before/after slider + Color::TRANSPARENT, + ), + handle: slider::Handle { + shape: slider::HandleShape::Circle { + radius: 10.0, + }, + color: cosmic.accent.base.into(), + border_color: Color::TRANSPARENT, + border_width: 0.0, + } + } + } + + fn hovered(&self, style: Self::Style) -> slider::Appearance { + let mut style = self.active(style); + style.handle.shape = slider::HandleShape::Circle { + radius: 16.0 + }; + style.handle.border_width = 6.0; + style.handle.border_color = match self { + Theme::Dark => Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.1), + Theme::Light => Color::from_rgba8(0, 0, 0, 0.1), + }; + style + } + + fn dragging(&self, style: Self::Style) -> slider::Appearance { + let mut style = self.hovered(style); + style.handle.border_color = match self { + Theme::Dark => Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.2), + Theme::Light => Color::from_rgba8(0, 0, 0, 0.2), + }; + style + } +} + +/* + * TODO: Menu + */ +impl menu::StyleSheet for Theme { + type Style = (); + + fn appearance(&self, _style: Self::Style) -> menu::Appearance { + let cosmic = self.cosmic(); + + menu::Appearance { + text_color: cosmic.primary.component.on.into(), + background: Background::Color(cosmic.background.base.into()), + border_width: 0.0, + border_radius: 16.0, + border_color: Color::TRANSPARENT, + selected_text_color: cosmic.primary.component.on.into(), + selected_background: Background::Color(cosmic.primary.component.hover.into()), + } + } +} + +/* + * TODO: Pick List + */ +impl pick_list::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: ()) -> pick_list::Appearance { + let cosmic = &self.cosmic().primary.component; + + pick_list::Appearance { + text_color: cosmic.on.into(), + background: Color::TRANSPARENT.into(), + placeholder_color: cosmic.on.into(), + border_radius: 24.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + icon_size: 0.7, + } + } + + fn hovered(&self, style: ()) -> pick_list::Appearance { + let cosmic = &self.cosmic().primary.component; + + pick_list::Appearance { + background: Background::Color(cosmic.hover.into()), + ..self.active(style) + } + } +} + +/* + * TODO: Radio + */ +impl radio::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: Self::Style) -> radio::Appearance { + let palette = self.extended_palette(); + + radio::Appearance { + background: Color::TRANSPARENT.into(), + dot_color: palette.primary.strong.color, + border_width: 1.0, + border_color: palette.primary.strong.color, + text_color: None, + } + } + + fn hovered(&self, style: Self::Style) -> radio::Appearance { + let active = self.active(style); + let palette = self.extended_palette(); + + radio::Appearance { + dot_color: palette.primary.strong.color, + background: palette.primary.weak.color.into(), + ..active + } + } +} + +/* + * Toggler + */ +impl toggler::StyleSheet for Theme { + type Style = (); + + fn active( + &self, + _style: Self::Style, + is_active: bool, + ) -> toggler::Appearance { + let palette = self.palette(); + + toggler::Appearance { + background: if is_active { + palette.primary + } else { + //TODO: Grab neutral from palette + match self { + Theme::Dark => Color::from_rgb8(0x78, 0x78, 0x78), + Theme::Light => Color::from_rgb8(0x93, 0x93, 0x93), + } + }, + background_border: None, + //TODO: Grab neutral from palette + foreground: match self { + Theme::Dark => Color::from_rgb8(0x27, 0x27, 0x27), + Theme::Light => Color::from_rgb8(0xe4, 0xe4, 0xe4), + }, + foreground_border: None, + } + } + + fn hovered( + &self, + style: Self::Style, + is_active: bool, + ) -> toggler::Appearance { + //TODO: grab colors from palette + match self { + Theme::Dark => toggler::Appearance { + background: if is_active { + Color::from_rgb8(0x9f, 0xed, 0xed) + } else { + Color::from_rgb8(0xb6, 0xb6, 0xb6) + }, + ..self.active(style, is_active) + }, + Theme::Light => toggler::Appearance { + background: if is_active { + Color::from_rgb8(0x00, 0x42, 0x62) + } else { + Color::from_rgb8(0x54, 0x54, 0x54) + }, + ..self.active(style, is_active) + } + } + } +} + +/* + * TODO: Pane Grid + */ +impl pane_grid::StyleSheet for Theme { + type Style = (); + + fn picked_split(&self, _style: Self::Style) -> Option { + let palette = self.extended_palette(); + + Some(pane_grid::Line { + color: palette.primary.strong.color, + width: 2.0, + }) + } + + fn hovered_split(&self, _style: Self::Style) -> Option { + let palette = self.extended_palette(); + + Some(pane_grid::Line { + color: palette.primary.base.color, + width: 2.0, + }) + } +} + +/* + * TODO: Progress Bar + */ +#[derive(Clone, Copy)] +pub enum ProgressBar { + Primary, + Success, + Danger, + Custom(fn(&Theme) -> progress_bar::Appearance), +} + +impl Default for ProgressBar { + fn default() -> Self { + Self::Primary + } +} + +impl progress_bar::StyleSheet for Theme { + type Style = ProgressBar; + + fn appearance(&self, style: Self::Style) -> progress_bar::Appearance { + let palette = self.extended_palette(); + + let from_palette = |bar: Color| progress_bar::Appearance { + background: palette.background.strong.color.into(), + bar: bar.into(), + border_radius: 2.0, + }; + + match style { + ProgressBar::Primary => from_palette(palette.primary.base.color), + ProgressBar::Success => from_palette(palette.success.base.color), + ProgressBar::Danger => from_palette(palette.danger.base.color), + ProgressBar::Custom(f) => f(self), + } + } +} + +/* + * TODO: Rule + */ +#[derive(Clone, Copy)] +pub enum Rule { + Default, + Custom(fn(&Theme) -> rule::Appearance), +} + +impl Default for Rule { + fn default() -> Self { + Self::Default + } +} + +impl rule::StyleSheet for Theme { + type Style = Rule; + + fn style(&self, style: Self::Style) -> rule::Appearance { + let palette = self.extended_palette(); + + match style { + Rule::Default => rule::Appearance { + color: palette.background.strong.color, + width: 1, + radius: 0.0, + fill_mode: rule::FillMode::Full, + }, + Rule::Custom(f) => f(self), + } + } +} + +/* + * TODO: Scrollable + */ +impl scrollable::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: Self::Style) -> scrollable::Scrollbar { + let palette = self.extended_palette(); + + scrollable::Scrollbar { + background: palette.background.weak.color.into(), + border_radius: 4.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + scroller: scrollable::Scroller { + color: palette.background.strong.color, + border_radius: 4.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + } + } + + fn hovered(&self, _style: Self::Style) -> scrollable::Scrollbar { + let palette = self.extended_palette(); + + scrollable::Scrollbar { + background: palette.background.weak.color.into(), + border_radius: 4.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + scroller: scrollable::Scroller { + color: palette.primary.strong.color, + border_radius: 4.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + } + } +} + +/* + * TODO: Text + */ +#[derive(Clone, Copy)] +pub enum Text { + Default, + Color(Color), + Custom(fn(&Theme) -> text::Appearance), +} + +impl Default for Text { + fn default() -> Self { + Self::Default + } +} + +impl From for Text { + fn from(color: Color) -> Self { + Text::Color(color) + } +} + +impl text::StyleSheet for Theme { + type Style = Text; + + fn appearance(&self, style: Self::Style) -> text::Appearance { + match style { + Text::Default => Default::default(), + Text::Color(c) => text::Appearance { color: Some(c) }, + Text::Custom(f) => f(self), + } + } +} + +/* + * TODO: Text Input + */ +impl text_input::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: Self::Style) -> text_input::Appearance { + let palette = self.extended_palette(); + + text_input::Appearance { + background: palette.background.base.color.into(), + border_radius: 2.0, + border_width: 1.0, + border_color: palette.background.strong.color, + } + } + + fn hovered(&self, _style: Self::Style) -> text_input::Appearance { + let palette = self.extended_palette(); + + text_input::Appearance { + background: palette.background.base.color.into(), + border_radius: 2.0, + border_width: 1.0, + border_color: palette.background.base.text, + } + } + + fn focused(&self, _style: Self::Style) -> text_input::Appearance { + let palette = self.extended_palette(); + + text_input::Appearance { + background: palette.background.base.color.into(), + border_radius: 2.0, + border_width: 1.0, + border_color: palette.primary.strong.color, + } + } + + fn placeholder_color(&self, _style: Self::Style) -> Color { + let palette = self.extended_palette(); + + palette.background.strong.color + } + + fn value_color(&self, _style: Self::Style) -> Color { + let palette = self.extended_palette(); + + palette.background.base.text + } + + fn selection_color(&self, _style: Self::Style) -> Color { + let palette = self.extended_palette(); + + palette.primary.weak.color + } +} diff --git a/src/theme/palette.rs b/src/theme/palette.rs new file mode 100644 index 0000000..1f8a660 --- /dev/null +++ b/src/theme/palette.rs @@ -0,0 +1,290 @@ +//TODO: GET CORRECT PALETTE FROM COSMIC-THEME +use iced_core::Color; + +use lazy_static::lazy_static; +use palette::{FromColor, Hsl, Mix, RelativeContrast, Srgb}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Palette { + pub background: Color, + pub text: Color, + pub primary: Color, + pub success: Color, + pub danger: Color, +} + +impl Palette { + pub const LIGHT: Self = Self { + background: Color::from_rgb( + 0xee as f32 / 255.0, + 0xee as f32 / 255.0, + 0xee as f32 / 255.0, + ), + text: Color::from_rgb( + 0x00 as f32 / 255.0, + 0x00 as f32 / 255.0, + 0x00 as f32 / 255.0, + ), + primary: Color::from_rgb( + 0x00 as f32 / 255.0, + 0x49 as f32 / 255.0, + 0x6d as f32 / 255.0, + ), + success: Color::from_rgb( + 0x3b as f32 / 255.0, + 0x6e as f32 / 255.0, + 0x43 as f32 / 255.0, + ), + danger: Color::from_rgb( + 0xa0 as f32 / 255.0, + 0x25 as f32 / 255.0, + 0x2b as f32 / 255.0, + ), + }; + + pub const DARK: Self = Self { + background: Color::from_rgb( + 0x1e as f32 / 255.0, + 0x1e as f32 / 255.0, + 0x1e as f32 / 255.0 + ), + text: Color::from_rgb( + 0xe4 as f32 / 255.0, + 0xe4 as f32 / 255.0, + 0xe4 as f32 / 255.0, + ), + primary: Color::from_rgb( + 0x94 as f32 / 255.0, + 0xeb as f32 / 255.0, + 0xeb as f32 / 255.0, + ), + success: Color::from_rgb( + 0xac as f32 / 255.0, + 0xf7 as f32 / 255.0, + 0xd2 as f32 / 255.0, + ), + danger: Color::from_rgb( + 0xff as f32 / 255.0, + 0xb5 as f32 / 255.0, + 0xb5 as f32 / 255.0, + ), + }; +} + +pub struct Extended { + pub background: Background, + pub primary: Primary, + pub secondary: Secondary, + pub success: Success, + pub danger: Danger, +} + +lazy_static! { + pub static ref EXTENDED_LIGHT: Extended = + Extended::generate(Palette::LIGHT); + pub static ref EXTENDED_DARK: Extended = Extended::generate(Palette::DARK); +} + +impl Extended { + pub fn generate(palette: Palette) -> Self { + Self { + background: Background::new(palette.background, palette.text), + primary: Primary::generate( + palette.primary, + palette.background, + palette.text, + ), + secondary: Secondary::generate(palette.background, palette.text), + success: Success::generate( + palette.success, + palette.background, + palette.text, + ), + danger: Danger::generate( + palette.danger, + palette.background, + palette.text, + ), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Pair { + pub color: Color, + pub text: Color, +} + +impl Pair { + pub fn new(color: Color, text: Color) -> Self { + Self { + color, + text: readable(color, text), + } + } +} + +pub struct Background { + pub base: Pair, + pub weak: Pair, + pub strong: Pair, +} + +impl Background { + pub fn new(base: Color, text: Color) -> Self { + let weak = mix(base, text, 0.15); + let strong = mix(base, text, 0.40); + + Self { + base: Pair::new(base, text), + weak: Pair::new(weak, text), + strong: Pair::new(strong, text), + } + } +} + +pub struct Primary { + pub base: Pair, + pub weak: Pair, + pub strong: Pair, +} + +impl Primary { + pub fn generate(base: Color, background: Color, text: Color) -> Self { + let weak = mix(base, background, 0.4); + let strong = deviate(base, 0.1); + + Self { + base: Pair::new(base, text), + weak: Pair::new(weak, text), + strong: Pair::new(strong, text), + } + } +} + +pub struct Secondary { + pub base: Pair, + pub weak: Pair, + pub strong: Pair, +} + +impl Secondary { + pub fn generate(base: Color, text: Color) -> Self { + let base = mix(base, text, 0.2); + let weak = mix(base, text, 0.1); + let strong = mix(base, text, 0.3); + + Self { + base: Pair::new(base, text), + weak: Pair::new(weak, text), + strong: Pair::new(strong, text), + } + } +} + +pub struct Success { + pub base: Pair, + pub weak: Pair, + pub strong: Pair, +} + +impl Success { + pub fn generate(base: Color, background: Color, text: Color) -> Self { + let weak = mix(base, background, 0.4); + let strong = deviate(base, 0.1); + + Self { + base: Pair::new(base, text), + weak: Pair::new(weak, text), + strong: Pair::new(strong, text), + } + } +} + +pub struct Danger { + pub base: Pair, + pub weak: Pair, + pub strong: Pair, +} + +impl Danger { + pub fn generate(base: Color, background: Color, text: Color) -> Self { + let weak = mix(base, background, 0.4); + let strong = deviate(base, 0.1); + + Self { + base: Pair::new(base, text), + weak: Pair::new(weak, text), + strong: Pair::new(strong, text), + } + } +} + +fn darken(color: Color, amount: f32) -> Color { + let mut hsl = to_hsl(color); + + hsl.lightness = if hsl.lightness - amount < 0.0 { + 0.0 + } else { + hsl.lightness - amount + }; + + from_hsl(hsl) +} + +fn lighten(color: Color, amount: f32) -> Color { + let mut hsl = to_hsl(color); + + hsl.lightness = if hsl.lightness + amount > 1.0 { + 1.0 + } else { + hsl.lightness + amount + }; + + from_hsl(hsl) +} + +fn deviate(color: Color, amount: f32) -> Color { + if is_dark(color) { + lighten(color, amount) + } else { + darken(color, amount) + } +} + +fn mix(a: Color, b: Color, factor: f32) -> Color { + let a_lin = Srgb::from(a).into_linear(); + let b_lin = Srgb::from(b).into_linear(); + + let mixed = a_lin.mix(&b_lin, factor); + Srgb::from_linear(mixed).into() +} + +fn readable(background: Color, text: Color) -> Color { + if is_readable(background, text) { + text + } else if is_dark(background) { + Color::WHITE + } else { + Color::BLACK + } +} + +fn is_dark(color: Color) -> bool { + to_hsl(color).lightness < 0.6 +} + +fn is_readable(a: Color, b: Color) -> bool { + let a_srgb = Srgb::from(a); + let b_srgb = Srgb::from(b); + + a_srgb.has_enhanced_contrast_text(&b_srgb) +} + +fn to_hsl(color: Color) -> Hsl { + Hsl::from_color(Srgb::from(color)) +} + +fn from_hsl(hsl: Hsl) -> Color { + Srgb::from_color(hsl).into() +} diff --git a/src/widget/expander.rs b/src/widget/expander.rs index 484e361..5a9cfac 100644 --- a/src/widget/expander.rs +++ b/src/widget/expander.rs @@ -1,12 +1,11 @@ use std::vec; -use crate::{list_box_row, separator, widget::ListRow}; +use crate::{list_box_row, separator, theme, widget::ListRow, Element, Renderer, Theme}; use apply::Apply; use derive_setters::Setters; use iced::{ - theme, widget::{self, button, container, horizontal_space, row, text, Column}, - Alignment, Background, Element, Length, Renderer, Theme, + Alignment, Background, Length, }; use iced_lazy::Component; use iced_native::widget::{column, event_container}; @@ -86,14 +85,14 @@ impl<'a, Message: Clone + 'a> Component for Expander<'a, Mess } fn view(&self, state: &Self::State) -> Element { - let heading: Element = { + let heading: Element = { let mut captions = vec![text(&self.title).size(18).into()]; if let Some(subtitle) = &self.subtitle { captions.push(text(subtitle).size(16).into()); } let text = column(captions); - let space: Element = horizontal_space(Length::Fill).into(); - let toggler: Element = { + let space: Element = horizontal_space(Length::Fill).into(); + let toggler: Element = { let mut icon = super::icon( if state.expanded { "go-down-symbolic" diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 3b324e1..80d089f 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -1,7 +1,8 @@ use apply::Apply; use derive_setters::*; -use iced::{self, alignment::Vertical, theme, widget, Element, Length, Renderer}; +use iced::{self, alignment::Vertical, widget, Length}; use iced_lazy::Component; +use crate::{theme, Element, Renderer}; #[derive(Setters)] pub struct HeaderBar { @@ -103,7 +104,7 @@ impl Component for HeaderBar { .into(); let window_controls = { - let mut widgets: Vec> = Vec::with_capacity(3); + let mut widgets: Vec> = Vec::with_capacity(3); let icon = |name, size, on_press| { super::icon(name, size) diff --git a/src/widget/list/list_box.rs b/src/widget/list/list_box.rs index f8ce7db..d689a7b 100644 --- a/src/widget/list/list_box.rs +++ b/src/widget/list/list_box.rs @@ -1,4 +1,5 @@ use crate::separator; +use crate::theme::{self, Container}; use derive_setters::Setters; use iced::mouse::Interaction; use iced::{overlay, Alignment, Length, Padding, Point, Rectangle}; @@ -12,8 +13,6 @@ use iced_native::{ renderer, row, Background, Clipboard, Color, Element, Event, Layout, Shell, Widget, }; use iced_style::container::{Appearance, StyleSheet}; -use iced_style::theme; -use iced_style::theme::Container; #[derive(Setters)] #[allow(dead_code)] diff --git a/src/widget/list/macros.rs b/src/widget/list/macros.rs index 67ec3c1..f2f3649 100644 --- a/src/widget/list/macros.rs +++ b/src/widget/list/macros.rs @@ -1,4 +1,5 @@ -pub use iced::{widget, Background, Color, Theme}; +pub use iced::{widget, Background, Color}; +pub use crate::Theme; pub mod list_view { #[macro_export] @@ -82,7 +83,7 @@ pub mod list_view { use crate::widget::{Background, Color}; use iced::widget; - use iced_style::Theme; + use crate::Theme; pub use list_view; pub use list_view_item; diff --git a/src/widget/navigation/macros.rs b/src/widget/navigation/macros.rs index e00f878..0cd7085 100644 --- a/src/widget/navigation/macros.rs +++ b/src/widget/navigation/macros.rs @@ -1,5 +1,6 @@ pub mod nav_bar { - use iced::{widget, Background, Color, Theme}; + use iced::{widget, Background, Color}; + use crate::Theme; #[macro_export] macro_rules! nav_button { diff --git a/src/widget/navigation/navbar.rs b/src/widget/navigation/navbar.rs index 67a9981..9daf8f1 100644 --- a/src/widget/navigation/navbar.rs +++ b/src/widget/navigation/navbar.rs @@ -1,13 +1,14 @@ use crate::scrollable; use crate::widget::nav_bar::{nav_bar_pages_style, nav_bar_sections_style}; use crate::widget::{icon, Background}; +use crate::{theme, Theme}; use derive_setters::Setters; use iced::Length; use iced_lazy::Component; use iced_native::widget::{button, column, container, text}; use iced_native::{row, Alignment, Element}; use iced_style::button::Appearance; -use iced_style::{scrollable, theme, Theme}; +use iced_style::scrollable; use std::collections::BTreeMap; #[derive(Setters, Default)]