From 3bfc65d6346702e37ec7e1fa27e36b8174776586 Mon Sep 17 00:00:00 2001 From: Will McCormick Date: Thu, 19 Mar 2026 13:17:32 -0400 Subject: [PATCH] WIP: libscosmic reskin --- cosmic-theme/src/model/dark.ron | 2 +- cosmic-theme/src/model/light.ron | 2 +- cosmic-theme/src/model/theme.rs | 30 ++++- src/app/cosmic.rs | 34 +++-- src/app/mod.rs | 88 +++++-------- src/theme/mod.rs | 16 ++- src/theme/style/button.rs | 26 +++- src/theme/style/dropdown.rs | 11 +- src/theme/style/iced.rs | 97 ++++++-------- src/theme/style/segmented_button.rs | 88 +++++++++++-- src/widget/header_bar.rs | 174 ++++++++++++-------------- src/widget/nav_bar.rs | 4 +- src/widget/segmented_button/widget.rs | 8 +- 13 files changed, 326 insertions(+), 254 deletions(-) diff --git a/cosmic-theme/src/model/dark.ron b/cosmic-theme/src/model/dark.ron index 4453b8bf..54bee498 100644 --- a/cosmic-theme/src/model/dark.ron +++ b/cosmic-theme/src/model/dark.ron @@ -1 +1 @@ -Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0))) +Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.0,green:0.58823529,blue:0.53333333,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0))) diff --git a/cosmic-theme/src/model/light.ron b/cosmic-theme/src/model/light.ron index 29b3ad65..fa7aa81d 100644 --- a/cosmic-theme/src/model/light.ron +++ b/cosmic-theme/src/model/light.ron @@ -1 +1 @@ -Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0))) +Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.58823529,blue:0.53333333,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0))) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 7d070ad7..edf176f9 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -145,9 +145,14 @@ impl Theme { /// This produces a clean, minimal light theme with pure white backgrounds, /// generous window rounding, and subtle gray surfaces. pub fn light_default() -> Self { + // stateDefault: rgb(0, 150, 136) + let state_default = Srgb::new(0.0, 150.0 / 255.0, 136.0 / 255.0); + let mut builder = ThemeBuilder::light() .bg_color(Srgba::new(1.0, 1.0, 1.0, 1.0)) .primary_container_bg(Srgba::new(0.96, 0.96, 0.96, 1.0)) + .accent(state_default) + .text_tint(Srgb::new(0.0, 0.0, 0.0)) .corner_radii(CornerRadii { radius_window: [16.0; 4], ..CornerRadii::default() @@ -159,7 +164,10 @@ impl Theme { #[inline] /// get the built in dark theme pub fn dark_default() -> Self { - DARK_PALETTE.clone().into() + let state_default = Srgb::new(0.0, 150.0 / 255.0, 136.0 / 255.0); + ThemeBuilder::dark() + .accent(state_default) + .build() } #[inline] @@ -1358,6 +1366,26 @@ impl ThemeBuilder { }; theme.spacing = spacing; theme.corner_radii = corner_radii; + + // Force white container/component backgrounds for light themes + // to eliminate any brownish derived tint from color stepping. + if !is_dark { + let white = Srgba::new(1.0, 1.0, 1.0, 1.0); + let light_gray = Srgba::new(0.96, 0.96, 0.96, 1.0); + + // Background container: pure white base, white component + theme.background.base = white; + theme.background.component.base = white; + + // Primary container: very light gray base, white component + theme.primary.base = light_gray; + theme.primary.component.base = white; + + // Secondary container: light gray base, white component + theme.secondary.base = Srgba::new(0.93, 0.93, 0.93, 1.0); + theme.secondary.component.base = white; + } + theme } diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 2f61972c..4513e6de 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -747,10 +747,11 @@ impl Cosmic { self.app.core_mut().theme_sub_counter += 1; let portal_accent = self.app.core().portal_accent; - if let Some(a) = portal_accent { + if let Some(_a) = portal_accent { let t_inner = theme.cosmic(); - if a.distance_squared(*t_inner.accent_color()) > 0.00001 { - theme = Theme::system(Arc::new(t_inner.with_accent(a))); + let state_default = crate::theme::STATE_DEFAULT_ACCENT; + if state_default.distance_squared(*t_inner.accent_color()) > 0.00001 { + theme = Theme::system(Arc::new(t_inner.with_accent(state_default))); } }; } @@ -777,15 +778,14 @@ impl Cosmic { prefer_dark, } = cosmic_theme.theme_type { - let mut new_theme = if let Some(a) = portal_accent { + let state_default = crate::theme::STATE_DEFAULT_ACCENT; + let mut new_theme = { let t_inner = theme.cosmic(); - if a.distance_squared(*t_inner.accent_color()) > 0.00001 { - Theme::system(Arc::new(t_inner.with_accent(a))) + if state_default.distance_squared(*t_inner.accent_color()) > 0.00001 { + Theme::system(Arc::new(t_inner.with_accent(state_default))) } else { theme } - } else { - theme }; new_theme.theme_type.prefer_dark(prefer_dark); @@ -918,15 +918,14 @@ impl Cosmic { cmds.push(self.app.system_theme_update(&[], new_theme.cosmic())); let core = self.app.core_mut(); - new_theme = if let Some(a) = core.portal_accent { + new_theme = { + let state_default = crate::theme::STATE_DEFAULT_ACCENT; let t_inner = new_theme.cosmic(); - if a.distance_squared(*t_inner.accent_color()) > 0.00001 { - Theme::system(Arc::new(t_inner.with_accent(a))) + if state_default.distance_squared(*t_inner.accent_color()) > 0.00001 { + Theme::system(Arc::new(t_inner.with_accent(state_default))) } else { new_theme } - } else { - new_theme }; core.system_theme = new_theme.clone(); @@ -1120,13 +1119,12 @@ impl Cosmic { } #[cfg(feature = "xdg-portal")] Action::DesktopSettings(crate::theme::portal::Desktop::Accent(c)) => { - use palette::Srgba; - let c = Srgba::new(c.red() as f32, c.green() as f32, c.blue() as f32, 1.0); + let state_default = crate::theme::STATE_DEFAULT_ACCENT; let core = self.app.core_mut(); - core.portal_accent = Some(c); + core.portal_accent = Some(state_default); let cur_accent = core.system_theme.cosmic().accent_color(); - if cur_accent.distance_squared(*c) < 0.00001 { + if cur_accent.distance_squared(*state_default) < 0.00001 { // skip calculations if we already have the same color return iced::Task::none(); } @@ -1141,7 +1139,7 @@ impl Cosmic { } = cosmic_theme.theme_type.clone() { cosmic_theme.set_theme(ThemeType::System { - theme: Arc::new(t.with_accent(c)), + theme: Arc::new(t.with_accent(state_default)), prefer_dark, }); } diff --git a/src/app/mod.rs b/src/app/mod.rs index fd36a033..4b13d11a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -563,22 +563,12 @@ impl ApplicationExt for App { let maximized = core.window.is_maximized; let content_container = core.window.content_container; let show_context = core.window.show_context; - let nav_bar_active = core.nav_bar_active(); let focused = core .focus_chain() .iter() .any(|i| Some(*i) == self.core().main_window_id()); - let border_padding = if maximized { 8 } else { 7 }; - - let main_content_padding = if !content_container { - [0, 0, 0, 0] - } else { - let right_padding = if show_context { 0 } else { border_padding }; - let left_padding = if nav_bar_active { 0 } else { border_padding }; - - [0, right_padding, 0, left_padding] - }; + let main_content_padding = [0, 0, 0, 0]; let content_row = crate::widget::row::with_children({ let mut widgets = Vec::with_capacity(3); @@ -590,14 +580,27 @@ impl ApplicationExt for App { { widgets.push( container(nav) - .padding([ - 0, - if is_condensed { border_padding } else { 8 }, - border_padding, - border_padding, - ]) + .padding([0, 0, 0, 0]) .into(), ); + + // Vertical divider between nav bar and content + { + use iced::widget::{vertical_rule, rule}; + widgets.push( + vertical_rule(1) + .class(crate::theme::Rule::Custom(Box::new( + |_: &crate::Theme| rule::Style { + color: iced_core::Color::from_rgba8(224, 224, 224, 1.0), + width: 1, + radius: 0.0.into(), + fill_mode: rule::FillMode::Full, + }, + ))) + .into(), + ); + } + true } else { false @@ -628,7 +631,7 @@ impl ApplicationExt for App { )) }) .apply(container) - .padding([0, if content_container { border_padding } else { 0 }, 0, 0]) + .padding([0, 0, 0, 0]) .apply(Element::from) .map(crate::Action::App), ); @@ -668,13 +671,9 @@ impl ApplicationExt for App { )) }) .apply(container) - .padding(if content_container { - [0, border_padding, border_padding, border_padding] - } else { - [0, 0, 0, 0] - }) + .padding([0, 0, 0, 0]) .into(), - ) + ); } else { //TODO: this element is added to workaround state issues widgets.push(horizontal_space().width(Length::Shrink).into()); @@ -687,25 +686,19 @@ impl ApplicationExt for App { let content_col = crate::widget::column::with_capacity(2) .push(content_row) .push_maybe(self.footer().map(|footer| { - container(footer.map(crate::Action::App)).padding([ - 0, - border_padding, - border_padding, - border_padding, - ]) + container(footer.map(crate::Action::App)).padding([0, 0, 0, 0]) })); - let content_inset = if maximized { 0 } else { 16 }; let content: Element<_> = if content_container { let inner: Element<_> = content_col .apply(container) - .padding([7, 0, 0, 0]) + .padding([0, 0, 0, 0]) .width(iced::Length::Fill) .height(iced::Length::Fill) .class(crate::theme::Container::ContentArea) .into(); container(inner) - .padding([8, content_inset, content_inset, content_inset]) + .padding([0, 0, 0, 0]) .width(iced::Length::Fill) .height(iced::Length::Fill) .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container"))) @@ -782,20 +775,13 @@ impl ApplicationExt for App { // Needed to avoid header bar corner gaps for apps without a content container header .apply(container) - .class(crate::theme::Container::custom(move |theme| { - let cosmic = theme.cosmic(); + .class(crate::theme::Container::custom(move |_theme| { container::Style { background: Some(iced::Background::Color( - cosmic.background.base.into(), + iced_core::Color::WHITE, )), border: iced::Border { - radius: [ - (window_corner_radius[0] - 1.0).max(0.0), - (window_corner_radius[1] - 1.0).max(0.0), - cosmic.radius_0()[2], - cosmic.radius_0()[3], - ] - .into(), + radius: [0.0; 4].into(), ..Default::default() }, ..Default::default() @@ -810,19 +796,13 @@ impl ApplicationExt for App { // The content element contains every element beneath the header. .push(content) .apply(container) - .padding(if maximized { 0 } else { 1 }) - .class(crate::theme::Container::custom(move |theme| { + .padding(0) + .class(crate::theme::Container::custom(move |_theme| { container::Style { - background: if content_container { - Some(iced::Background::Color( - theme.cosmic().background.base.into(), - )) - } else { - None - }, + background: Some(iced::Background::Color(iced_core::Color::WHITE)), border: iced::Border { - color: theme.cosmic().bg_divider().into(), - width: if maximized { 0.0 } else { 1.0 }, + color: iced_core::Color::TRANSPARENT, + width: 0.0, radius: window_corner_radius.into(), }, ..Default::default() diff --git a/src/theme/mod.rs b/src/theme/mod.rs index b7e85237..5232c058 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -8,16 +8,26 @@ pub mod portal; pub mod style; use cosmic_config::CosmicConfigEntry; -use cosmic_config::config_subscription; use cosmic_theme::Component; use cosmic_theme::LayeredTheme; use cosmic_theme::Spacing; use cosmic_theme::ThemeMode; -use iced_futures::Subscription; use iced_runtime::{Appearance, DefaultStyle}; +use palette::Srgba; use std::sync::{Arc, LazyLock, Mutex}; pub use style::*; +/// `state_default` color: rgb(0, 150, 136) +pub const STATE_DEFAULT_ACCENT: Srgba = Srgba::new(0.0, 0.588_235_3, 0.533_333_36, 1.0); + +/// `state_default` as an iced Color +pub const STATE_DEFAULT_COLOR: iced_core::Color = + iced_core::Color::from_rgb(0.0, 0.588_235_3, 0.533_333_3); + +/// `state_default` as an iced Color at 10% opacity +pub const STATE_DEFAULT_BG: iced_core::Color = + iced_core::Color::from_rgba(0.0, 0.588_235_3, 0.533_333_3, 0.10); + pub type CosmicColor = ::palette::rgb::Srgba; pub type CosmicComponent = cosmic_theme::Component; pub type CosmicTheme = cosmic_theme::Theme; @@ -126,6 +136,7 @@ pub fn system_dark() -> Theme { theme }); + let t = t.with_accent(STATE_DEFAULT_ACCENT); Theme::system(Arc::new(t)) } @@ -141,6 +152,7 @@ pub fn system_light() -> Theme { theme }); + let t = t.with_accent(STATE_DEFAULT_ACCENT); Theme::system(Arc::new(t)) } diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 0575ce67..e6ae8ff6 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -154,9 +154,9 @@ pub fn appearance( if selected { appearance.background = - Some(Background::Color(cosmic.primary.component.hover.into())); - appearance.icon_color = Some(cosmic.accent.base.into()); - appearance.text_color = Some(cosmic.accent_text_color().into()); + Some(Background::Color(crate::theme::STATE_DEFAULT_BG)); + appearance.icon_color = Some(crate::theme::STATE_DEFAULT_COLOR); + appearance.text_color = Some(crate::theme::STATE_DEFAULT_COLOR); } else { appearance.background = Some(Background::Color(background)); appearance.icon_color = icon; @@ -254,7 +254,15 @@ impl Catalog for crate::Theme { Some(component.on.into()) }; - (component.hover.into(), text_color, text_color) + if matches!(style, Button::ListItem) { + ( + crate::theme::STATE_DEFAULT_BG, + text_color, + text_color, + ) + } else { + (component.hover.into(), text_color, text_color) + } }, ) } @@ -275,7 +283,15 @@ impl Catalog for crate::Theme { Some(component.on.into()) }; - (component.pressed.into(), text_color, text_color) + if matches!(style, Button::ListItem) { + ( + crate::theme::STATE_DEFAULT_BG, + text_color, + text_color, + ) + } else { + (component.pressed.into(), text_color, text_color) + } }) } diff --git a/src/theme/style/dropdown.rs b/src/theme/style/dropdown.rs index cc89a399..a13d16e4 100644 --- a/src/theme/style/dropdown.rs +++ b/src/theme/style/dropdown.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: MPL-2.0 use crate::Theme; +use crate::theme::{STATE_DEFAULT_BG, STATE_DEFAULT_COLOR}; use crate::widget::dropdown; -use iced::{Background, Color}; +use iced::Background; impl dropdown::menu::StyleSheet for Theme { type Style = (); @@ -16,13 +17,13 @@ impl dropdown::menu::StyleSheet for Theme { background: Background::Color(cosmic.background.component.base.into()), border_width: 0.0, border_radius: cosmic.corner_radii.radius_m.into(), - border_color: Color::TRANSPARENT, + border_color: iced::Color::TRANSPARENT, hovered_text_color: cosmic.on_bg_color().into(), - hovered_background: Background::Color(cosmic.primary.component.hover.into()), + hovered_background: Background::Color(STATE_DEFAULT_BG), - selected_text_color: cosmic.accent_text_color().into(), - selected_background: Background::Color(cosmic.primary.component.hover.into()), + selected_text_color: STATE_DEFAULT_COLOR, + selected_background: Background::Color(STATE_DEFAULT_BG), description_color: cosmic.primary.component.on_disabled.into(), } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 0fb5ae57..40bec8d7 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -599,11 +599,11 @@ impl iced_container::Catalog for Theme { Container::ContentArea => iced_container::Style { icon_color: Some(Color::from(cosmic.background.on)), text_color: Some(Color::from(cosmic.background.on)), - background: Some(iced::Background::Color(cosmic.background.base.into())), + background: Some(iced::Background::Color(Color::from_rgb8(245, 245, 245))), border: Border { - radius: cosmic.corner_radii.radius_s.into(), - width: 1.0, - color: cosmic.background.divider.into(), + radius: [0.0; 4].into(), + width: 0.0, + color: Color::TRANSPARENT, }, shadow: Shadow::default(), }, @@ -693,56 +693,42 @@ impl slider::Catalog for Theme { fn style(&self, class: &Self::Class<'_>, status: slider::Status) -> slider::Style { let cosmic: &cosmic_theme::Theme = self.cosmic(); - let hc = self.theme_type.is_high_contrast(); - let is_dark = self.theme_type.is_dark(); let mut appearance = match class { Slider::Standard => //TODO: no way to set rail thickness { - let (active_track, inactive_track) = if hc { - ( - cosmic.accent_text_color(), - if is_dark { - cosmic.palette.neutral_5 - } else { - cosmic.palette.neutral_3 - }, - ) - } else { - (cosmic.accent.base, cosmic.palette.neutral_6) - }; + let empty_track: Color = Color::from_rgb8(224, 224, 224); slider::Style { rail: Rail { backgrounds: ( - Background::Color(active_track.into()), - Background::Color(inactive_track.into()), + Background::Color(crate::theme::STATE_DEFAULT_COLOR), + Background::Color(empty_track), ), border: Border { radius: cosmic.corner_radii.radius_xs.into(), - color: if hc && !is_dark { - self.current_container().component.border.into() - } else { - Color::TRANSPARENT - }, - width: if hc && !is_dark { 1. } else { 0. }, + color: Color::TRANSPARENT, + width: 0.0, }, width: 4.0, }, handle: slider::Handle { - shape: slider::HandleShape::Rectangle { - height: 20, - width: 20, - border_radius: cosmic.corner_radii.radius_m.into(), + shape: slider::HandleShape::Circle { + radius: 8.0, + }, + border_color: Color::from_rgba8(0, 0, 0, 0.12), + border_width: 1.0, + background: Background::Color(Color::WHITE), + shadow: Shadow { + color: Color::from_rgba8(0, 0, 0, 0.10), + offset: Vector::new(0.0, 1.0), + blur_radius: 3.0, }, - border_color: Color::TRANSPARENT, - border_width: 0.0, - background: Background::Color(cosmic.accent.base.into()), }, breakpoint: slider::Breakpoint { - color: cosmic.on_bg_color().into(), + color: crate::theme::STATE_DEFAULT_COLOR, }, } } @@ -752,34 +738,33 @@ impl slider::Catalog for Theme { slider::Status::Active => appearance, slider::Status::Hovered => match class { Slider::Standard => { - appearance.handle.shape = slider::HandleShape::Rectangle { - height: 26, - width: 26, - border_radius: cosmic.corner_radii.radius_m.into(), + appearance.handle.shape = slider::HandleShape::Circle { + radius: 10.0, + }; + appearance.handle.border_width = 1.0; + appearance.handle.border_color = Color::from_rgba8(0, 0, 0, 0.12); + appearance.handle.shadow = Shadow { + color: Color::from_rgba8(0, 0, 0, 0.12), + offset: Vector::new(0.0, 1.0), + blur_radius: 4.0, }; - appearance.handle.border_width = 3.0; - appearance.handle.border_color = - self.cosmic().palette.neutral_10.with_alpha(0.1).into(); appearance } Slider::Custom { hovered, .. } => hovered(self), }, slider::Status::Dragged => match class { Slider::Standard => { - let mut style = { - appearance.handle.shape = slider::HandleShape::Rectangle { - height: 26, - width: 26, - border_radius: cosmic.corner_radii.radius_m.into(), - }; - appearance.handle.border_width = 3.0; - appearance.handle.border_color = - self.cosmic().palette.neutral_10.with_alpha(0.1).into(); - appearance + appearance.handle.shape = slider::HandleShape::Circle { + radius: 10.0, }; - style.handle.border_color = - self.cosmic().palette.neutral_10.with_alpha(0.2).into(); - style + appearance.handle.border_width = 1.0; + appearance.handle.border_color = Color::from_rgba8(0, 0, 0, 0.12); + appearance.handle.shadow = Shadow { + color: Color::from_rgba8(0, 0, 0, 0.12), + offset: Vector::new(0.0, 1.0), + blur_radius: 4.0, + }; + appearance } Slider::Custom { dragging, .. } => dragging(self), }, @@ -802,8 +787,8 @@ impl menu::Catalog for Theme { radius: cosmic.corner_radii.radius_m.into(), ..Default::default() }, - selected_text_color: cosmic.accent_text_color().into(), - selected_background: Background::Color(cosmic.background.component.hover.into()), + selected_text_color: crate::theme::STATE_DEFAULT_COLOR, + selected_background: Background::Color(crate::theme::STATE_DEFAULT_BG), } } } diff --git a/src/theme/style/segmented_button.rs b/src/theme/style/segmented_button.rs index b9863c88..6f62e5d6 100644 --- a/src/theme/style/segmented_button.rs +++ b/src/theme/style/segmented_button.rs @@ -71,10 +71,41 @@ impl StyleSheet for Theme { } } - SegmentedButton::NavBar | SegmentedButton::FileNav => Appearance { - active_width: 0.0, - ..horizontal::tab_bar(cosmic, container) - }, + SegmentedButton::NavBar | SegmentedButton::FileNav => { + let rad_m = cosmic.corner_radii.radius_m; + let item_border = ItemAppearance { + border: Border { + radius: rad_m.into(), + ..Default::default() + }, + }; + let active = ItemStatusAppearance { + background: Some(Background::Color(crate::theme::STATE_DEFAULT_BG)), + first: item_border, + middle: item_border, + last: item_border, + text_color: crate::theme::STATE_DEFAULT_COLOR, + }; + let inactive = ItemStatusAppearance { + background: None, + first: item_border, + middle: item_border, + last: item_border, + text_color: container.component.on.into(), + }; + Appearance { + active_width: 0.0, + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + ..Default::default() + }, + inactive, + hover: inactive, + pressed: inactive, + active, + ..Default::default() + } + } SegmentedButton::TabBar => horizontal::tab_bar(cosmic, container), @@ -126,10 +157,51 @@ impl StyleSheet for Theme { } } - SegmentedButton::NavBar | SegmentedButton::FileNav => Appearance { - active_width: 0.0, - ..vertical::tab_bar(cosmic, container) - }, + SegmentedButton::NavBar | SegmentedButton::FileNav => { + let rad_m = cosmic.corner_radii.radius_m; + let active = ItemStatusAppearance { + background: Some(Background::Color(crate::theme::STATE_DEFAULT_BG)), + first: ItemAppearance { + border: Border { + radius: rad_m.into(), + ..Default::default() + }, + }, + middle: ItemAppearance { + border: Border { + radius: rad_m.into(), + ..Default::default() + }, + }, + last: ItemAppearance { + border: Border { + radius: rad_m.into(), + ..Default::default() + }, + }, + text_color: crate::theme::STATE_DEFAULT_COLOR, + }; + let inactive = ItemStatusAppearance { + background: None, + first: active.first, + middle: active.middle, + last: active.last, + text_color: container.component.on.into(), + }; + let hover_state = inactive; + Appearance { + active_width: 0.0, + border: Border { + radius: cosmic.corner_radii.radius_0.into(), + ..Default::default() + }, + inactive, + hover: hover_state, + pressed: hover_state, + active, + ..Default::default() + } + } SegmentedButton::TabBar => vertical::tab_bar(cosmic, container), diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index eebaa5c3..f63a207e 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -441,15 +441,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { (portion, portion) }; - // SSD uses fixed height 48px with 12px horizontal padding; non-SSD uses computed height - let (header_height, header_padding) = if self.is_ssd { - (Length::Fixed(48.0), [8, 12, 8, 12]) - } else { - ( - Length::Fixed(44.0 + padding[0] as f32 + padding[2] as f32), - if self.is_ssd { [0, 8, 0, 8] } else { padding }, - ) - }; + let (header_height, header_padding) = (Length::Fixed(48.0), [8, 12, 8, 12]); // Creates the headerbar widget. let widget = widget::row::with_capacity(3) @@ -498,50 +490,31 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .padding(header_padding) .spacing(8) .apply(widget::container) - .class(if self.is_ssd { - // SSD: custom container with white background and top corner radii - let sharp = self.sharp_corners; - let explicit_radius = self.corner_radius; + .class({ crate::theme::Container::custom(move |theme| { let cosmic = theme.cosmic(); - let window_radius = explicit_radius.unwrap_or_else(|| cosmic.radius_window()); iced_widget::container::Style { icon_color: Some(Color::from(cosmic.background.on)), text_color: Some(Color::from(cosmic.background.on)), - background: Some(iced::Background::Color(Color::from_rgba8( - 255, 255, 255, 0.99, - ))), + background: Some(iced::Background::Color(Color::WHITE)), border: Border { - radius: [ - if sharp { 0.0 } else { window_radius[0] }, - if sharp { 0.0 } else { window_radius[1] }, - 0.0, - 0.0, - ] - .into(), + radius: [0.0; 4].into(), ..Default::default() }, shadow: Default::default(), } }) - } else { - crate::theme::Container::HeaderBar { - focused: self.focused, - sharp_corners: self.sharp_corners, - transparent: self.transparent, - } }) .center_y(Length::Shrink); - // SSD: add 1px horizontal rule below header with color #F0F0F1 - let widget = if self.is_ssd { + let widget = { use iced::widget::{horizontal_rule, rule}; widget::column::with_capacity(2) .push(widget) .push( horizontal_rule(1).class(crate::theme::Rule::Custom(Box::new( |_: &crate::Theme| rule::Style { - color: Color::from_rgba8(240, 240, 241, 1.0), + color: Color::from_rgba8(224, 224, 224, 1.0), width: 1, radius: 0.0.into(), fill_mode: rule::FillMode::Full, @@ -549,8 +522,6 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { ))), ) .apply(widget::mouse_area) - } else { - widget.apply(widget::mouse_area) }; let mut widget = widget; @@ -586,89 +557,98 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { const ICON_RESTORE: &[u8] = include_bytes!("../../res/icons/window-restore.svg"); const ICON_CLOSE: &[u8] = include_bytes!("../../res/icons/window-close.svg"); - // SSD uses custom styling: fill_strong bg, space_xs padding, 2px gap - let is_ssd = self.is_ssd; - - macro_rules! icon { - ($svg_bytes:expr, $size:expr, $on_press:expr) => {{ - let padding = if is_ssd { [6, 6] } else { [8, 8] }; - let result: Element<'a, Message> = if is_ssd { - // SSD: create icon widget with dark tint, wrap in custom button - let icon_w = widget::icon::icon(widget::icon::from_svg_bytes($svg_bytes)) - .size($size) - .class(crate::theme::Svg::custom(|_| iced::widget::svg::Style { - color: Some(Color::from_rgb8(0x1B, 0x1B, 0x1B)), - })); - widget::button::custom(icon_w) - .padding(padding) - .class(crate::theme::Button::HeaderBar) - .selected(self.focused) - .on_press($on_press) - .into() - } else { - widget::icon::from_svg_bytes($svg_bytes) - .apply(widget::button::icon) - .padding(padding) - .class(crate::theme::Button::HeaderBar) - .selected(self.focused) - .icon_size($size) - .on_press($on_press) - .into() - }; - result + macro_rules! wc_icon { + ($svg_bytes:expr, $size:expr, $on_press:expr, $is_close:expr) => {{ + let icon_w = widget::icon::icon(widget::icon::from_svg_bytes($svg_bytes)) + .size($size) + .class(crate::theme::Svg::custom(|_| iced::widget::svg::Style { + color: Some(Color::from_rgb8(0x3D, 0x3D, 0x3D)), + })); + let btn: Element<'a, Message> = widget::button::custom(icon_w) + .padding([4, 6]) + .class(crate::theme::Button::Custom { + active: Box::new(move |_focused, _theme| { + crate::widget::button::Style { + background: Some(iced::Background::Color(Color::TRANSPARENT)), + text_color: Some(Color::from_rgb8(0x3D, 0x3D, 0x3D)), + icon_color: Some(Color::from_rgb8(0x3D, 0x3D, 0x3D)), + border_radius: [6.0; 4].into(), + ..Default::default() + } + }), + disabled: Box::new(|_theme| crate::widget::button::Style::default()), + hovered: Box::new(move |_focused, _theme| { + let bg = if $is_close { + Color::from_rgba8(224, 64, 64, 0.20) + } else { + Color::from_rgb8(208, 208, 208) + }; + let icon_c = if $is_close { + Color::from_rgb8(224, 64, 64) + } else { + Color::from_rgb8(0x3D, 0x3D, 0x3D) + }; + crate::widget::button::Style { + background: Some(iced::Background::Color(bg)), + text_color: Some(icon_c), + icon_color: Some(icon_c), + border_radius: [6.0; 4].into(), + ..Default::default() + } + }), + pressed: Box::new(move |_focused, _theme| { + let bg = if $is_close { + Color::from_rgba8(200, 50, 50, 0.30) + } else { + Color::from_rgb8(190, 190, 190) + }; + let icon_c = if $is_close { + Color::from_rgb8(200, 50, 50) + } else { + Color::from_rgb8(0x3D, 0x3D, 0x3D) + }; + crate::widget::button::Style { + background: Some(iced::Background::Color(bg)), + text_color: Some(icon_c), + icon_color: Some(icon_c), + border_radius: [6.0; 4].into(), + ..Default::default() + } + }), + }) + .on_press($on_press) + .into(); + btn }}; } - // SSD: 2px gap between icons; non-SSD: space_xxs - let icon_spacing = if self.is_ssd { - 2 - } else { - theme::spacing().space_xxs - }; - widget::row::with_capacity(3) .push_maybe( self.on_minimize .take() - .map(|m: Message| icon!(ICON_MINIMIZE, 16, m)), + .map(|m: Message| wc_icon!(ICON_MINIMIZE, 14, m, false)), ) .push_maybe(self.on_maximize.take().map(|m| { if self.maximized { - icon!(ICON_RESTORE, 16, m) + wc_icon!(ICON_RESTORE, 14, m, false) } else { - icon!(ICON_MAXIMIZE, 16, m) + wc_icon!(ICON_MAXIMIZE, 14, m, false) } })) - .push_maybe(self.on_close.take().map(|m| icon!(ICON_CLOSE, 16, m))) - .spacing(icon_spacing) + .push_maybe(self.on_close.take().map(|m| wc_icon!(ICON_CLOSE, 14, m, true))) + .spacing(2) .apply(widget::container) - .class(crate::theme::Container::custom(move |theme| { - let cosmic = theme.cosmic(); - // SSD uses white at 80% opacity; non-SSD uses component base - let background = if is_ssd { - Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.80) - } else { - Color::from(cosmic.background.component.base) - }; + .class(crate::theme::Container::custom(move |_theme| { iced_widget::container::Style { - background: Some(iced::Background::Color(background)), + background: Some(iced::Background::Color(Color::from_rgb8(232, 232, 232))), border: Border { - radius: cosmic.corner_radii.radius_xl.into(), + radius: [8.0; 4].into(), ..Default::default() }, ..Default::default() } })) - // SSD: 8px horizontal, 2px vertical; non-SSD: [4, 8] - .padding(if self.is_ssd { [2, 8] } else { [4, 8] }) - .center_y(Length::Fill) - // SSD: 18px left margin so title never gets too close - .apply(widget::container) - .padding(if self.is_ssd { - iced::Padding::from([0, 0, 0, 18]) - } else { - iced::Padding::ZERO - }) + .padding([2, 4]) .center_y(Length::Fill) .into() } diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index 140385bc..9d4b8b4b 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -173,11 +173,11 @@ pub fn nav_bar_style(theme: &Theme) -> iced_widget::container::Style { iced_widget::container::Style { icon_color: Some(cosmic.on_bg_color().into()), text_color: Some(cosmic.on_bg_color().into()), - background: Some(Background::Color(cosmic.primary.base.into())), + background: Some(Background::Color(Color::WHITE)), border: Border { width: 0.0, color: Color::TRANSPARENT, - radius: cosmic.corner_radii.radius_s.into(), + radius: [0.0; 4].into(), }, shadow: Shadow::default(), } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index e4f416bf..622537b0 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -216,8 +216,8 @@ where minimum_button_width: u16::MIN, maximum_button_width: u16::MAX, indent_spacing: 16, - font_active: crate::font::semibold(), - font_hovered: crate::font::semibold(), + font_active: crate::font::default(), + font_hovered: crate::font::default(), font_inactive: crate::font::default(), font_size: 14.0, height: Length::Shrink, @@ -1796,10 +1796,10 @@ where let key_is_hovered = self.button_is_hovered(state, key); let status_appearance = if self.button_is_pressed(state, key) { appearance.pressed - } else if key_is_hovered || menu_open() { - appearance.hover } else if key_is_active { appearance.active + } else if key_is_hovered || menu_open() { + appearance.hover } else { appearance.inactive };