diff --git a/Cargo.toml b/Cargo.toml index bf8d452..e0e7038 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ fraction = "0.13.0" [dependencies.cosmic-theme] git = "https://github.com/pop-os/cosmic-theme.git" -branch = "overlays" +branch = "layer" [dependencies.iced] path = "iced" diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index 8bba3f1..672f8e6 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -6,7 +6,7 @@ use cosmic::{ iced_native::{subscription, window}, iced_winit::window::{close, drag, minimize, toggle_maximize}, keyboard_nav, - theme::{self, Theme}, + theme::{self, Theme, COSMIC_DARK, COSMIC_LIGHT}, widget::{ header_bar, icon, list, nav_bar, nav_bar_toggle, scrollable, segmented_button, settings, warning, IconSource, @@ -25,7 +25,7 @@ mod bluetooth; mod demo; -use self::desktop::DesktopPage; +use self::{demo::ThemeMode, desktop::DesktopPage}; mod desktop; mod editor; @@ -176,7 +176,7 @@ impl Window { } #[allow(dead_code)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum Message { Bluetooth(bluetooth::Message), Close, @@ -390,7 +390,10 @@ impl Application for Window { Message::Demo(message) => match self.demo.update(message) { Some(demo::Output::Debug(debug)) => self.debug = debug, Some(demo::Output::ScalingFactor(factor)) => self.set_scale_factor(factor), - Some(demo::Output::ThemeChanged(theme)) => self.theme = theme, + Some(demo::Output::ThemeChanged(theme)) => match theme { + ThemeMode::Light => self.theme = Theme::light(), + ThemeMode::Dark => self.theme = Theme::dark(), + }, Some(demo::Output::ToggleWarning) => self.toggle_warning(), None => (), }, @@ -565,6 +568,6 @@ impl Application for Window { } fn theme(&self) -> Theme { - self.theme + self.theme.clone() } } diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index 4f4ca42..1c6fe54 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -28,7 +28,13 @@ pub enum MultiOption { OptionE, } -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ThemeMode { + Light, + Dark, +} + +#[derive(Clone, Debug)] pub enum Message { ButtonPressed, CheckboxToggled(bool), @@ -41,7 +47,7 @@ pub enum Message { Selection(segmented_button::Entity), SliderChanged(f32), SpinButton(spin_button::Message), - ThemeChanged(Theme), + ThemeChanged(ThemeMode), ToggleWarning, TogglerToggled(bool), ViewSwitcher(segmented_button::Entity), @@ -50,7 +56,7 @@ pub enum Message { pub enum Output { Debug(bool), ScalingFactor(f32), - ThemeChanged(Theme), + ThemeChanged(ThemeMode), ToggleWarning, } @@ -141,13 +147,19 @@ impl State { } pub(super) fn view<'a>(&'a self, window: &'a Window) -> Element<'a, Message> { - let choose_theme = [Theme::Light, Theme::Dark].iter().fold( + let choose_theme = [ThemeMode::Light, ThemeMode::Dark].iter().fold( row![].spacing(10).align_items(Alignment::Center), |row, theme| { row.push(radio( format!("{:?}", theme), *theme, - Some(window.theme), + if window.theme.cosmic().is_dark && matches!(theme, ThemeMode::Dark) + || !window.theme.cosmic().is_dark && matches!(theme, ThemeMode::Light) + { + Some(*theme) + } else { + None + }, Message::ThemeChanged, )) }, diff --git a/src/theme/mod.rs b/src/theme/mod.rs index d0cad74..61d880e 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -10,6 +10,7 @@ use std::hash::Hasher; pub use self::segmented_button::SegmentedButton; use cosmic_theme::Component; +use cosmic_theme::LayeredTheme; use iced_core::BorderRadius; use iced_style::application; use iced_style::button; @@ -29,6 +30,7 @@ use iced_style::text_input; use iced_style::toggler; use iced_core::{Background, Color}; +use palette::Srgba; type CosmicColor = ::palette::rgb::Srgba; type CosmicComponent = cosmic_theme::Component; @@ -47,36 +49,66 @@ lazy_static::lazy_static! { focus: CosmicColor::new(0.0, 0.0, 0.0, 0.0), disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0), on: CosmicColor::new(0.0, 0.0, 0.0, 0.0), - on_disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0) + on_disabled: CosmicColor::new(0.0, 0.0, 0.0, 0.0), + divider: CosmicColor::new(0.0, 0.0, 0.0, 0.0), }; } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Theme { - Light, - Dark, -} - -impl Theme { - #[must_use] - pub fn cosmic(self) -> &'static CosmicTheme { - match self { - Self::Dark => &COSMIC_DARK, - Self::Light => &COSMIC_LIGHT, - } - } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Theme { + pub cosmic: cosmic_theme::Theme, } impl Default for Theme { fn default() -> Self { - Self::Dark + Self::dark() } } -#[derive(Debug, Clone, Copy)] +impl Theme { + #[must_use] + pub fn new(cosmic: cosmic_theme::Theme) -> Self { + Self { cosmic } + } + + #[must_use] + pub fn cosmic(&self) -> &cosmic_theme::Theme { + &self.cosmic + } + + pub fn cosmic_mut(&mut self) -> &mut cosmic_theme::Theme { + &mut self.cosmic + } + + pub fn set_cosmic(&mut self, cosmic: cosmic_theme::Theme) { + self.cosmic = cosmic; + } + + #[must_use] + pub fn dark() -> Self { + Self { + cosmic: COSMIC_DARK.clone(), + } + } + + #[must_use] + pub fn light() -> Self { + Self { + cosmic: COSMIC_LIGHT.clone(), + } + } +} + +impl LayeredTheme for Theme { + fn set_layer(&mut self, layer: cosmic_theme::Layer) { + self.cosmic.layer = layer; + } +} + +#[derive(Clone, Copy)] pub enum Application { Default, - Custom(fn(Theme) -> application::Appearance), + Custom(fn(&Theme) -> application::Appearance), } impl Default for Application { @@ -94,9 +126,9 @@ impl application::StyleSheet for Theme { match style { Application::Default => application::Appearance { background_color: cosmic.bg_color().into(), - text_color: cosmic.on.into(), + text_color: cosmic.on_bg_color().into(), }, - Application::Custom(f) => f(*self), + Application::Custom(f) => f(self), } } } @@ -130,18 +162,18 @@ impl Default for Button { impl Button { #[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::match_same_arms)] - fn cosmic(&self, theme: &Theme) -> &'static CosmicComponent { + fn cosmic<'a>(&'a self, theme: &'a Theme) -> &CosmicComponent { let cosmic = theme.cosmic(); match self { Button::Primary => &cosmic.accent, - Button::Secondary => &cosmic.basic, + Button::Secondary => &cosmic.current_container().component, Button::Positive => &cosmic.success, Button::Destructive => &cosmic.destructive, - Button::Text => &cosmic.basic, + Button::Text => &cosmic.current_container().component, Button::Link => &cosmic.accent, - Button::LinkActive => &cosmic.basic, + Button::LinkActive => &cosmic.current_container().component, Button::Transparent => &TRANSPARENT_COMPONENT, - Button::Deactivated => &cosmic.basic, + Button::Deactivated => &cosmic.current_container().component, Button::Custom { .. } => &TRANSPARENT_COMPONENT, } } @@ -156,7 +188,6 @@ impl button::StyleSheet for Theme { } let component = style.cosmic(self); - let cosmic = self.cosmic(); button::Appearance { border_radius: match style { Button::Link => BorderRadius::from(0.0), @@ -164,7 +195,7 @@ impl button::StyleSheet for Theme { }, background: match style { Button::Link | Button::Text => None, - Button::LinkActive => Some(Background::Color(cosmic.divider.into())), + Button::LinkActive => Some(Background::Color(component.divider.into())), _ => Some(Background::Color(component.base.into())), }, text_color: match style { @@ -183,12 +214,11 @@ impl button::StyleSheet for Theme { let active = self.active(style); let component = style.cosmic(self); - let cosmic = self.cosmic(); button::Appearance { background: match style { Button::Link => None, - Button::LinkActive => Some(Background::Color(cosmic.divider.into())), + Button::LinkActive => Some(Background::Color(component.divider.into())), _ => Some(Background::Color(component.hover.into())), }, ..active @@ -206,7 +236,7 @@ impl button::StyleSheet for Theme { button::Appearance { background: match style { Button::Link => None, - Button::LinkActive => Some(Background::Color(cosmic.divider.into())), + Button::LinkActive => Some(Background::Color(component.divider.into())), _ => Some(Background::Color(component.hover.into())), }, ..active @@ -236,63 +266,150 @@ impl checkbox::StyleSheet for Theme { fn active(&self, style: &Self::Style, is_checked: bool) -> checkbox::Appearance { let palette = self.cosmic(); + let mut neutral_7 = palette.palette.neutral_10.clone(); match style { - Checkbox::Primary => checkbox_appearance( - palette.accent.on, - palette.basic.base, - palette.accent.base, - is_checked, - ), - Checkbox::Secondary => checkbox_appearance( - palette.basic.on, - palette.basic.base, - palette.basic.base, - is_checked, - ), - Checkbox::Success => checkbox_appearance( - palette.success.on, - palette.basic.base, - palette.success.base, - is_checked, - ), - Checkbox::Danger => checkbox_appearance( - palette.destructive.on, - palette.basic.base, - palette.destructive.base, - is_checked, - ), + Checkbox::Primary => checkbox::Appearance { + background: Background::Color(if is_checked { + palette.accent.base.clone().into() + } else { + palette.background.base.into() + }), + checkmark_color: palette.accent.on.into(), + border_radius: 4.0, + border_width: if is_checked { 0.0 } else { 1.0 }, + border_color: if is_checked { + palette.accent.base + } else { + neutral_7 + } + .into(), + text_color: None, + }, + Checkbox::Secondary => checkbox::Appearance { + background: Background::Color(if is_checked { + palette.background.component.base.clone().into() + } else { + palette.background.base.into() + }), + checkmark_color: palette.background.on.into(), + border_radius: 4.0, + border_width: if is_checked { 0.0 } else { 1.0 }, + border_color: neutral_7.into(), + text_color: None, + }, + Checkbox::Success => checkbox::Appearance { + background: Background::Color(if is_checked { + palette.success.base.clone().into() + } else { + palette.background.base.into() + }), + checkmark_color: palette.success.on.into(), + border_radius: 4.0, + border_width: if is_checked { 0.0 } else { 1.0 }, + border_color: if is_checked { + palette.success.base + } else { + neutral_7 + } + .into(), + text_color: None, + }, + Checkbox::Danger => checkbox::Appearance { + background: Background::Color(if is_checked { + palette.destructive.base.clone().into() + } else { + palette.background.base.into() + }), + checkmark_color: palette.destructive.on.into(), + border_radius: 4.0, + border_width: if is_checked { 0.0 } else { 1.0 }, + border_color: if is_checked { + palette.destructive.base + } else { + neutral_7 + } + .into(), + text_color: None, + }, } } fn hovered(&self, style: &Self::Style, is_checked: bool) -> checkbox::Appearance { let palette = self.cosmic(); + let mut neutral_10 = palette.palette.neutral_10.clone(); + let mut neutral_7 = palette.palette.neutral_10.clone(); + neutral_10.alpha = 0.1; match style { - Checkbox::Primary => checkbox_appearance( - palette.accent.on, - palette.basic.hover, - palette.accent.hover, - is_checked, - ), - Checkbox::Secondary => checkbox_appearance( - palette.basic.on, - palette.basic.hover, - palette.basic.hover, - is_checked, - ), - Checkbox::Success => checkbox_appearance( - palette.success.on, - palette.basic.hover, - palette.success.hover, - is_checked, - ), - Checkbox::Danger => checkbox_appearance( - palette.destructive.on, - palette.basic.hover, - palette.destructive.hover, - is_checked, - ), + Checkbox::Primary => checkbox::Appearance { + background: Background::Color(if is_checked { + palette.destructive.base.clone().into() + } else { + neutral_10.into() + }), + checkmark_color: palette.destructive.on.into(), + border_radius: 4.0, + border_width: if is_checked { 0.0 } else { 1.0 }, + border_color: if is_checked { + palette.destructive.base + } else { + neutral_7 + } + .into(), + text_color: None, + }, + Checkbox::Secondary => checkbox::Appearance { + background: Background::Color(if is_checked { + palette.destructive.base.clone().into() + } else { + neutral_10.into() + }), + checkmark_color: palette.destructive.on.into(), + border_radius: 4.0, + border_width: if is_checked { 0.0 } else { 1.0 }, + border_color: if is_checked { + palette.destructive.base + } else { + neutral_7 + } + .into(), + text_color: None, + }, + Checkbox::Success => checkbox::Appearance { + background: Background::Color(if is_checked { + palette.destructive.base.clone().into() + } else { + neutral_10.into() + }), + checkmark_color: palette.destructive.on.into(), + border_radius: 4.0, + border_width: if is_checked { 0.0 } else { 1.0 }, + border_color: if is_checked { + palette.destructive.base + } else { + neutral_7 + } + .into(), + text_color: None, + }, + Checkbox::Danger => checkbox::Appearance { + background: Background::Color(if is_checked { + palette.destructive.base.clone().into() + } else { + neutral_10.into() + }), + checkmark_color: palette.destructive.on.into(), + border_radius: 4.0, + border_width: if is_checked { 0.0 } else { 1.0 }, + border_color: if is_checked { + palette.destructive.base + } else { + neutral_7 + } + .into(), + text_color: None, + }, } } } @@ -443,19 +560,18 @@ impl slider::StyleSheet for Theme { 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), - }; + let mut border_color = self.cosmic.palette.neutral_10.clone(); + border_color.alpha = 0.1; + style.handle.border_color = border_color.into(); 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), - }; + let mut border_color = self.cosmic.palette.neutral_10.clone(); + border_color.alpha = 0.2; + style.handle.border_color = border_color.into(); + style } } @@ -470,13 +586,14 @@ impl menu::StyleSheet for Theme { let cosmic = self.cosmic(); menu::Appearance { - text_color: cosmic.on.into(), + text_color: cosmic.on_bg_color().into(), background: Background::Color(cosmic.background.base.into()), border_width: 0.0, border_radius: 16.0, border_color: Color::TRANSPARENT, - selected_text_color: cosmic.on.into(), - selected_background: Background::Color(cosmic.basic.hover.into()), + selected_text_color: cosmic.on_bg_color().into(), + // TODO doesn't seem to be specified + selected_background: Background::Color(cosmic.background.component.hover.into()), } } } @@ -491,9 +608,9 @@ impl pick_list::StyleSheet for Theme { let cosmic = &self.cosmic(); pick_list::Appearance { - text_color: cosmic.on.into(), + text_color: cosmic.on_bg_color().into(), background: Color::TRANSPARENT.into(), - placeholder_color: cosmic.on.into(), + placeholder_color: cosmic.on_bg_color().into(), border_radius: 24.0, border_width: 0.0, border_color: Color::TRANSPARENT, @@ -502,10 +619,10 @@ impl pick_list::StyleSheet for Theme { } fn hovered(&self, style: &()) -> pick_list::Appearance { - let cosmic = &self.cosmic().basic; + let cosmic = &self.cosmic(); pick_list::Appearance { - background: Background::Color(cosmic.hover.into()), + background: Background::Color(cosmic.background.base.into()), ..self.active(style) } } @@ -525,11 +642,16 @@ impl radio::StyleSheet for Theme { Color::from(theme.accent.base).into() } else { // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.basic.base).into() + Color::from(theme.background.base).into() }, dot_color: theme.accent.on.into(), border_width: 1.0, - border_color: theme.on.into(), + border_color: if is_selected { + Color::from(theme.accent.base).into() + } else { + // TODO: this seems to be defined weirdly in FIGMA + Color::from(theme.palette.neutral_7).into() + }, text_color: None, } } @@ -537,16 +659,25 @@ impl radio::StyleSheet for Theme { fn hovered(&self, style: &Self::Style, is_selected: bool) -> radio::Appearance { let active = self.active(style, is_selected); let theme = self.cosmic(); + let mut neutral_10 = theme.palette.neutral_10.clone(); + neutral_10.alpha = 0.1; radio::Appearance { - dot_color: theme.accent.on.into(), background: if is_selected { - Color::from(theme.accent.hover).into() + Color::from(theme.accent.base).into() } else { // TODO: this seems to be defined weirdly in FIGMA - Color::from(theme.basic.hover).into() + Color::from(neutral_10).into() }, - ..active + dot_color: theme.accent.on.into(), + border_width: 1.0, + border_color: if is_selected { + Color::from(theme.accent.base).into() + } else { + // TODO: this seems to be defined weirdly in FIGMA + Color::from(theme.palette.neutral_7).into() + }, + text_color: None, } } } @@ -575,24 +706,18 @@ impl toggler::StyleSheet for Theme { } fn hovered(&self, style: &Self::Style, is_active: bool) -> toggler::Appearance { + let cosmic = self.cosmic(); //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) - }, + let mut neutral_10 = cosmic.palette.neutral_10.clone(); + neutral_10.alpha = 0.1; + toggler::Appearance { + background: if is_active { + cosmic.accent.hover + } else { + neutral_10 + } + .into(), + ..self.active(style, is_active) } } } @@ -647,17 +772,17 @@ impl progress_bar::StyleSheet for Theme { match style { ProgressBar::Primary => progress_bar::Appearance { - background: Color::from(theme.divider).into(), + background: Color::from(theme.background.divider).into(), bar: Color::from(theme.accent.base).into(), border_radius: 2.0, }, ProgressBar::Success => progress_bar::Appearance { - background: Color::from(theme.divider).into(), + background: Color::from(theme.background.divider).into(), bar: Color::from(theme.success.base).into(), border_radius: 2.0, }, ProgressBar::Danger => progress_bar::Appearance { - background: Color::from(theme.divider).into(), + background: Color::from(theme.background.divider).into(), bar: Color::from(theme.destructive.base).into(), border_radius: 2.0, }, @@ -691,7 +816,7 @@ impl rule::StyleSheet for Theme { match style { Rule::Default => rule::Appearance { - color: palette.on.into(), + color: palette.current_container().divider.into(), width: 1, radius: 0.0, fill_mode: rule::FillMode::Full, @@ -699,7 +824,7 @@ impl rule::StyleSheet for Theme { Rule::LightDivider => { let cosmic = &self.cosmic(); rule::Appearance { - color: cosmic.divider.into(), + color: cosmic.current_container().divider.into(), width: 1, radius: 0.0, fill_mode: rule::FillMode::Padded(10), @@ -708,7 +833,7 @@ impl rule::StyleSheet for Theme { Rule::HeavyDivider => { let cosmic = &self.cosmic(); rule::Appearance { - color: cosmic.divider.into(), + color: cosmic.current_container().divider.into(), width: 4, radius: 4.0, fill_mode: rule::FillMode::Full, @@ -729,12 +854,14 @@ impl scrollable::StyleSheet for Theme { let theme = self.cosmic(); scrollable::Scrollbar { - background: Some(Background::Color(theme.basic.base.into())), + background: Some(Background::Color( + theme.current_container().component.base.into(), + )), border_radius: 4.0, border_width: 0.0, border_color: Color::TRANSPARENT, scroller: scrollable::Scroller { - color: theme.divider.into(), + color: theme.current_container().component.divider.into(), border_radius: 4.0, border_width: 0.0, border_color: Color::TRANSPARENT, @@ -746,7 +873,9 @@ impl scrollable::StyleSheet for Theme { let theme = self.cosmic(); scrollable::Scrollbar { - background: Some(Background::Color(theme.basic.base.into())), + background: Some(Background::Color( + theme.current_container().component.hover.into(), + )), border_radius: 4.0, border_width: 0.0, border_color: Color::TRANSPARENT, @@ -801,7 +930,7 @@ impl svg::StyleSheet for Theme { Svg::Default => svg::Appearance::default(), Svg::Custom(appearance) => appearance(self), Svg::Symbolic => svg::Appearance { - color: Some(self.cosmic().on.into()), + color: Some(self.cosmic().current_container().on.into()), }, Svg::SymbolicActive => svg::Appearance { color: Some(self.cosmic().accent.base.into()), @@ -864,16 +993,17 @@ impl text_input::StyleSheet for Theme { fn active(&self, style: &Self::Style) -> text_input::Appearance { let palette = self.cosmic(); - + let mut bg = palette.palette.neutral_7; + bg.alpha = 0.75; match style { TextInput::Default => text_input::Appearance { - background: Background::Color(palette.basic.base.into()), + background: Color::from(bg).into(), border_radius: 2.0, border_width: 1.0, - border_color: palette.divider.into(), + border_color: palette.current_container().component.divider.into(), }, TextInput::Search => text_input::Appearance { - background: Background::Color(Color::TRANSPARENT), + background: Color::from(bg).into(), border_radius: 0.0, border_width: 0.0, border_color: Color::TRANSPARENT, @@ -883,16 +1013,18 @@ impl text_input::StyleSheet for Theme { fn hovered(&self, style: &Self::Style) -> text_input::Appearance { let palette = self.cosmic(); + let mut bg = palette.palette.neutral_7; + bg.alpha = 0.75; match style { TextInput::Default => text_input::Appearance { - background: Background::Color(palette.basic.base.into()), + background: Color::from(bg).into(), border_radius: 2.0, border_width: 1.0, - border_color: palette.on.into(), + border_color: palette.accent.base.into(), }, TextInput::Search => text_input::Appearance { - background: Background::Color(Color::TRANSPARENT), + background: Color::from(bg).into(), border_radius: 0.0, border_width: 0.0, border_color: Color::TRANSPARENT, @@ -902,16 +1034,18 @@ impl text_input::StyleSheet for Theme { fn focused(&self, style: &Self::Style) -> text_input::Appearance { let palette = self.cosmic(); + let mut bg = palette.palette.neutral_7; + bg.alpha = 0.75; match style { TextInput::Default => text_input::Appearance { - background: Background::Color(palette.basic.base.into()), + background: Color::from(bg).into(), border_radius: 2.0, border_width: 1.0, border_color: palette.accent.base.into(), }, TextInput::Search => text_input::Appearance { - background: Background::Color(Color::TRANSPARENT), + background: Color::from(bg).into(), border_radius: 0.0, border_width: 0.0, border_color: Color::TRANSPARENT, @@ -921,19 +1055,20 @@ impl text_input::StyleSheet for Theme { fn placeholder_color(&self, _style: &Self::Style) -> Color { let palette = self.cosmic(); - - palette.divider.into() + let mut neutral_9 = palette.palette.neutral_9; + neutral_9.alpha = 0.7; + neutral_9.into() } fn value_color(&self, _style: &Self::Style) -> Color { let palette = self.cosmic(); - palette.on.into() + palette.palette.neutral_9.into() } fn selection_color(&self, _style: &Self::Style) -> Color { let palette = self.cosmic(); - palette.basic.selected_text.into() + palette.accent.base.into() } } diff --git a/src/theme/segmented_button.rs b/src/theme/segmented_button.rs index fbaf3a6..ac76ef4 100644 --- a/src/theme/segmented_button.rs +++ b/src/theme/segmented_button.rs @@ -45,7 +45,7 @@ impl StyleSheet for Theme { border_bottom: Some((1.0, cosmic.accent.base.into())), ..Default::default() }, - text_color: cosmic.on.into(), + text_color: cosmic.on_bg_color().into(), }, hover: hover(cosmic, &active), focus: focus(cosmic, &active), @@ -56,10 +56,12 @@ impl StyleSheet for Theme { SegmentedButton::Selection => { let cosmic = self.cosmic(); let active = horizontal::selection_active(cosmic); + let mut neutral_5 = cosmic.palette.neutral_5; + neutral_5.alpha = 0.25; Appearance { border_radius: BorderRadius::from(0.0), inactive: ItemStatusAppearance { - background: Some(Background::Color(cosmic.basic.base.into())), + background: Some(Background::Color(neutral_5.into())), first: ItemAppearance { border_radius: BorderRadius::from([24.0, 0.0, 0.0, 24.0]), ..Default::default() @@ -72,7 +74,7 @@ impl StyleSheet for Theme { border_radius: BorderRadius::from([0.0, 24.0, 24.0, 0.0]), ..Default::default() }, - text_color: cosmic.on.into(), + text_color: cosmic.on_bg_color().into(), }, hover: hover(cosmic, &active), focus: focus(cosmic, &active), @@ -94,7 +96,7 @@ impl StyleSheet for Theme { border_radius: BorderRadius::from(0.0), inactive: ItemStatusAppearance { background: None, - text_color: cosmic.on.into(), + text_color: cosmic.on_bg_color().into(), ..active }, hover: hover(cosmic, &active), @@ -106,10 +108,12 @@ impl StyleSheet for Theme { SegmentedButton::Selection => { let cosmic = self.cosmic(); let active = vertical::selection_active(cosmic); + let mut neutral_5 = cosmic.palette.neutral_5; + neutral_5.alpha = 0.25; Appearance { border_radius: BorderRadius::from(0.0), inactive: ItemStatusAppearance { - background: Some(Background::Color(cosmic.basic.base.into())), + background: Some(Background::Color(neutral_5.into())), first: ItemAppearance { border_radius: BorderRadius::from([24.0, 24.0, 0.0, 0.0]), ..Default::default() @@ -122,7 +126,7 @@ impl StyleSheet for Theme { border_radius: BorderRadius::from([0.0, 0.0, 24.0, 24.0]), ..Default::default() }, - text_color: cosmic.on.into(), + text_color: cosmic.on_bg_color().into(), }, hover: hover(cosmic, &active), focus: focus(cosmic, &active), @@ -141,8 +145,10 @@ mod horizontal { use palette::{rgb::Rgb, Alpha}; pub fn selection_active(cosmic: &cosmic_theme::Theme>) -> ItemStatusAppearance { + let mut neutral_5 = cosmic.palette.neutral_5; + neutral_5.alpha = 0.25; ItemStatusAppearance { - background: Some(Background::Color(cosmic.divider.into())), + background: Some(Background::Color(neutral_5.into())), first: ItemAppearance { border_radius: BorderRadius::from([24.0, 0.0, 0.0, 24.0]), ..Default::default() @@ -162,8 +168,10 @@ mod horizontal { pub fn view_switcher_active( cosmic: &cosmic_theme::Theme>, ) -> ItemStatusAppearance { + let mut neutral_5 = cosmic.palette.neutral_5; + neutral_5.alpha = 0.25; ItemStatusAppearance { - background: Some(Background::Color(cosmic.basic.base.into())), + background: Some(Background::Color(neutral_5.into())), first: ItemAppearance { border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]), border_bottom: Some((4.0, cosmic.accent.base.into())), @@ -188,9 +196,11 @@ pub fn focus( cosmic: &cosmic_theme::Theme>, default: &ItemStatusAppearance, ) -> ItemStatusAppearance { + let mut neutral_5 = cosmic.palette.neutral_5; + neutral_5.alpha = 0.25; ItemStatusAppearance { - background: Some(Background::Color(cosmic.basic.focus.into())), - text_color: cosmic.primary.base.into(), + background: Some(Background::Color(neutral_5.into())), + text_color: cosmic.accent.base.into(), ..*default } } @@ -199,8 +209,10 @@ pub fn hover( cosmic: &cosmic_theme::Theme>, default: &ItemStatusAppearance, ) -> ItemStatusAppearance { + let mut neutral_10 = cosmic.palette.neutral_10; + neutral_10.alpha = 0.1; ItemStatusAppearance { - background: Some(Background::Color(cosmic.basic.hover.into())), + background: Some(Background::Color(neutral_10.into())), text_color: cosmic.accent.base.into(), ..*default } @@ -212,8 +224,10 @@ mod vertical { use palette::{rgb::Rgb, Alpha}; pub fn selection_active(cosmic: &cosmic_theme::Theme>) -> ItemStatusAppearance { + let mut neutral_5 = cosmic.palette.neutral_5; + neutral_5.alpha = 0.25; ItemStatusAppearance { - background: Some(Background::Color(cosmic.divider.into())), + background: Some(Background::Color(neutral_5.into())), first: ItemAppearance { border_radius: BorderRadius::from([24.0, 24.0, 0.0, 0.0]), ..Default::default() @@ -233,8 +247,10 @@ mod vertical { pub fn view_switcher_active( cosmic: &cosmic_theme::Theme>, ) -> ItemStatusAppearance { + let mut neutral_5 = cosmic.palette.neutral_5; + neutral_5.alpha = 0.25; ItemStatusAppearance { - background: Some(Background::Color(cosmic.divider.into())), + background: Some(Background::Color(neutral_5.into())), first: ItemAppearance { border_radius: BorderRadius::from(24.0), ..Default::default() diff --git a/src/widget/cosmic_container.rs b/src/widget/cosmic_container.rs new file mode 100644 index 0000000..8b2ffeb --- /dev/null +++ b/src/widget/cosmic_container.rs @@ -0,0 +1,228 @@ +use cosmic_theme::LayeredTheme; +use iced::widget::Container; +use iced_native::alignment; +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::widget::{Operation, Tree}; +use iced_native::{Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Widget}; +pub use iced_style::container::{Appearance, StyleSheet}; + +pub fn cosmic_container<'a, Message: 'static, T>( + content: T, + layer: cosmic_theme::Layer, +) -> CosmicContainer<'a, Message, crate::Renderer> +where + T: Into>, +{ + CosmicContainer::new(content, layer) +} + +/// An element decorating some content. +/// +/// It is normally used for alignment purposes. +#[allow(missing_debug_implementations)] +pub struct CosmicContainer<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, + Renderer::Theme: StyleSheet + Clone + cosmic_theme::LayeredTheme, +{ + layer: cosmic_theme::Layer, + container: Container<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> CosmicContainer<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, + Renderer::Theme: StyleSheet + Clone + cosmic_theme::LayeredTheme, +{ + /// Creates an empty [`Container`]. + pub(crate) fn new(content: T, layer: cosmic_theme::Layer) -> Self + where + T: Into>, + { + CosmicContainer { + layer, + container: Container::new(content), + } + } + + /// Sets the [`Padding`] of the [`Container`]. + #[must_use] + pub fn padding>(mut self, padding: P) -> Self { + self.container = self.container.padding(padding); + self + } + + /// Sets the width of the [`self.`]. + #[must_use] + pub fn width(mut self, width: Length) -> Self { + self.container = self.container.width(width); + self + } + + /// Sets the height of the [`Container`]. + #[must_use] + pub fn height(mut self, height: Length) -> Self { + self.container = self.container.height(height); + self + } + + /// Sets the maximum width of the [`Container`]. + #[must_use] + pub fn max_width(mut self, max_width: u32) -> Self { + self.container = self.container.max_width(max_width); + self + } + + /// Sets the maximum height of the [`Container`] in pixels. + #[must_use] + pub fn max_height(mut self, max_height: u32) -> Self { + self.container = self.container.max_height(max_height); + self + } + + /// Sets the content alignment for the horizontal axis of the [`Container`]. + #[must_use] + pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { + self.container = self.container.align_x(alignment); + self + } + + /// Sets the content alignment for the vertical axis of the [`Container`]. + #[must_use] + pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { + self.container = self.container.align_y(alignment); + self + } + + /// Centers the contents in the horizontal axis of the [`Container`]. + #[must_use] + pub fn center_x(mut self) -> Self { + self.container = self.container.center_x(); + self + } + + /// Centers the contents in the vertical axis of the [`Container`]. + #[must_use] + pub fn center_y(mut self) -> Self { + self.container = self.container.center_y(); + self + } + + /// Sets the style of the [`Container`]. + #[must_use] + pub fn style(mut self, style: impl Into<::Style>) -> Self { + self.container = self.container.style(style); + self + } +} + +impl<'a, Message, Renderer> Widget for CosmicContainer<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, + Renderer::Theme: StyleSheet + Clone + cosmic_theme::LayeredTheme, +{ + fn children(&self) -> Vec { + self.container.children() + } + + fn diff(&self, tree: &mut Tree) { + self.container.diff(tree); + } + + fn width(&self) -> Length { + Widget::width(&self.container) + } + + fn height(&self) -> Length { + Widget::height(&self.container) + } + + fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + self.container.layout(renderer, &limits) + } + + fn operate(&self, tree: &mut Tree, layout: Layout<'_>, operation: &mut dyn Operation) { + self.container.operate(tree, layout, operation); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.container.on_event( + tree, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.container + .mouse_interaction(tree, layout, cursor_position, viewport, renderer) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + let mut theme = theme.clone(); + theme.set_layer(self.layer); + self.container.draw( + tree, + renderer, + &theme, + renderer_style, + layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.container.overlay(tree, layout, renderer) + } +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Message: 'a, + Renderer: 'a + iced_native::Renderer, + Renderer::Theme: StyleSheet + Clone + cosmic_theme::LayeredTheme, +{ + fn from(column: CosmicContainer<'a, Message, Renderer>) -> Element<'a, Message, Renderer> { + Element::new(column) + } +} diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs index 9783e01..e7545ba 100644 --- a/src/widget/list/column.rs +++ b/src/widget/list/column.rs @@ -60,9 +60,10 @@ impl<'a, Message: 'static> From> for Element<'a, Message #[allow(clippy::trivially_copy_pass_by_ref)] pub fn style(theme: &crate::Theme) -> iced::widget::container::Appearance { let cosmic = &theme.cosmic(); + let container = cosmic.current_container(); iced::widget::container::Appearance { - text_color: Some(cosmic.on.into()), - background: Some(Background::Color(cosmic.basic.base.into())), + text_color: Some(container.on.into()), + background: Some(Background::Color(container.base.into())), border_radius: 8.0, border_width: 0.0, border_color: Color::TRANSPARENT, diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 93c27e4..28db790 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -56,6 +56,9 @@ pub use view_switcher::vertical as vertical_view_switcher; pub mod warning; pub use warning::*; +pub mod cosmic_container; +pub use cosmic_container::*; + /// An element to distinguish a boundary between two elements. pub mod divider { /// Horizontal variant of a divider. diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index be09c26..3057b30 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -43,7 +43,7 @@ where pub fn nav_bar_style(theme: &Theme) -> iced_style::container::Appearance { let cosmic = &theme.cosmic(); iced_style::container::Appearance { - text_color: Some(cosmic.on.into()), + text_color: Some(cosmic.on_bg_color().into()), background: Some(Background::Color(cosmic.primary.base.into())), border_radius: 8.0, border_width: 0.0, diff --git a/src/widget/search/field.rs b/src/widget/search/field.rs index 6853498..3a2ae64 100644 --- a/src/widget/search/field.rs +++ b/src/widget/search/field.rs @@ -79,9 +79,11 @@ fn clear_button() -> Button<'static, Message, Renderer> { #[allow(clippy::trivially_copy_pass_by_ref)] fn active_style(theme: &crate::Theme) -> container::Appearance { let cosmic = &theme.cosmic(); + let mut neutral_7 = cosmic.palette.neutral_7; + neutral_7.alpha = 0.25; iced::widget::container::Appearance { - text_color: Some(cosmic.on.into()), - background: Some(Background::Color(cosmic.divider.into())), + text_color: Some(cosmic.palette.neutral_9.into()), + background: Some(Background::Color(neutral_7.into())), border_radius: 24.0, border_width: 2.0, border_color: cosmic.accent.focus.into(), diff --git a/src/widget/spin_button/mod.rs b/src/widget/spin_button/mod.rs index 840c497..eb2f73d 100644 --- a/src/widget/spin_button/mod.rs +++ b/src/widget/spin_button/mod.rs @@ -97,11 +97,13 @@ impl<'a, Message: 'static> From> for Element<'a, Message #[allow(clippy::trivially_copy_pass_by_ref)] fn container_style(theme: &crate::Theme) -> iced_style::container::Appearance { - let basic = &theme.cosmic().basic; + let basic = &theme.cosmic(); + let mut neutral_10 = basic.palette.neutral_10; + neutral_10.alpha = 0.1; let accent = &theme.cosmic().accent; iced_style::container::Appearance { - text_color: None, - background: Some(Background::Color(basic.base.into())), + text_color: Some(basic.palette.neutral_10.into()), + background: Some(Background::Color(neutral_10.into())), border_radius: 24.0, border_width: 0.0, border_color: accent.base.into(),