feat: add support for dark / light mode switching (#178)
* feat: add support for dark / light mode switching and simultaneouscustom light / dark mode themes * refactor(color-picker): optional initial color and fallback color * refactor: used FixedPortion for layout of the settings item This makes sure that the control always has at least the specified portion of the available space * refactor: make all members of the ThemeBuilder public * refactor: add and update palette colors * fix(theme): typo and derive PartialEq for ThemeBuilder * fix: update color picker usage * feat: add more variables to the theme * fix: radius on headerbar * fix: Theme CosmicConfigEntry impl * chore: specify rev of taffy * fix: theme CosmicConfigEntry missing variables * fix: apply theme type when theme mode changes * wip: add plus icon to empty color picker button * chore: fix rev and imports * refactor(color-picker): allow custom size for the icon * refactor(color_picker): make color_button public * update iced
This commit is contained in:
parent
a91deacff5
commit
7cc791a3f5
18 changed files with 542 additions and 164 deletions
|
|
@ -143,6 +143,14 @@ pub struct CosmicPaletteInner<C> {
|
|||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_10: C,
|
||||
|
||||
// Utility Colors
|
||||
/// Utility bright green
|
||||
pub bright_green: C,
|
||||
/// Utility bright red
|
||||
pub bright_red: C,
|
||||
/// Utility bright orange
|
||||
pub bright_orange: C,
|
||||
|
||||
/// Extended Color Palette
|
||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||
pub ext_warm_grey: C,
|
||||
|
|
@ -159,6 +167,12 @@ pub struct CosmicPaletteInner<C> {
|
|||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||
pub ext_indigo: C,
|
||||
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_blue: C,
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_red: C,
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_green: C,
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_warm_grey: C,
|
||||
/// Potential Accent Color Combos
|
||||
|
|
@ -195,6 +209,9 @@ impl From<CosmicPaletteInner<CssColor>> for CosmicPaletteInner<Srgba> {
|
|||
neutral_8: p.neutral_8.into(),
|
||||
neutral_9: p.neutral_9.into(),
|
||||
neutral_10: p.neutral_10.into(),
|
||||
bright_green: p.bright_green.into(),
|
||||
bright_red: p.bright_red.into(),
|
||||
bright_orange: p.bright_orange.into(),
|
||||
ext_warm_grey: p.ext_warm_grey.into(),
|
||||
ext_orange: p.ext_orange.into(),
|
||||
ext_yellow: p.ext_yellow.into(),
|
||||
|
|
@ -202,6 +219,9 @@ impl From<CosmicPaletteInner<CssColor>> for CosmicPaletteInner<Srgba> {
|
|||
ext_purple: p.ext_purple.into(),
|
||||
ext_pink: p.ext_pink.into(),
|
||||
ext_indigo: p.ext_indigo.into(),
|
||||
accent_blue: p.accent_blue.into(),
|
||||
accent_red: p.accent_red.into(),
|
||||
accent_green: p.accent_green.into(),
|
||||
accent_warm_grey: p.accent_warm_grey.into(),
|
||||
accent_orange: p.accent_orange.into(),
|
||||
accent_yellow: p.accent_yellow.into(),
|
||||
|
|
|
|||
|
|
@ -55,6 +55,15 @@ Dark (
|
|||
neutral_10: (
|
||||
c: "#FFFFFF",
|
||||
),
|
||||
bright_green: (
|
||||
c: "#5EDB8C",
|
||||
),
|
||||
bright_red: (
|
||||
c: "#FFA090",
|
||||
),
|
||||
bright_orange: (
|
||||
c: "#FFA37D",
|
||||
),
|
||||
ext_warm_grey: (
|
||||
c: "#9B8E8A",
|
||||
),
|
||||
|
|
@ -76,23 +85,32 @@ Dark (
|
|||
ext_indigo: (
|
||||
c: "#3E88FF",
|
||||
),
|
||||
accent_blue: (
|
||||
c: "#63D0DF",
|
||||
),
|
||||
accent_green: (
|
||||
c: "#92CF9C",
|
||||
),
|
||||
accent_warm_grey: (
|
||||
c: "#554742",
|
||||
c: "#CABAB4",
|
||||
),
|
||||
accent_orange: (
|
||||
c: "#AF5C02",
|
||||
c: "#FFAD00",
|
||||
),
|
||||
accent_yellow: (
|
||||
c: "#966800",
|
||||
c: "#F7E062",
|
||||
),
|
||||
accent_purple: (
|
||||
c: "#813FFF",
|
||||
c: "#E79CFE",
|
||||
),
|
||||
accent_pink: (
|
||||
c: "#F93A83",
|
||||
c: "#FF9CB1",
|
||||
),
|
||||
accent_red: (
|
||||
c: "#FDA1A0",
|
||||
),
|
||||
accent_indigo: (
|
||||
c: "#3E88FF",
|
||||
c: "#A1C0EB",
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,15 @@ Light (
|
|||
neutral_10: (
|
||||
c: "#000000",
|
||||
),
|
||||
bright_green: (
|
||||
c: "#00572C",
|
||||
),
|
||||
bright_red: (
|
||||
c: "#890418",
|
||||
),
|
||||
bright_orange: (
|
||||
c: "#792C00",
|
||||
),
|
||||
ext_warm_grey: (
|
||||
c: "#9B8E8A",
|
||||
),
|
||||
|
|
@ -76,23 +85,32 @@ Light (
|
|||
ext_indigo: (
|
||||
c: "#95C4FC",
|
||||
),
|
||||
accent_blue: (
|
||||
c: "#00525A",
|
||||
),
|
||||
accent_red: (
|
||||
c: "#78292E",
|
||||
),
|
||||
accent_green: (
|
||||
c: "#185529",
|
||||
),
|
||||
accent_warm_grey: (
|
||||
c: "#ADA29E",
|
||||
c: "#554742",
|
||||
),
|
||||
accent_orange: (
|
||||
c: "#FFD7A1",
|
||||
c: "#624000",
|
||||
),
|
||||
accent_yellow: (
|
||||
c: "#FFF19E",
|
||||
c: "#534800",
|
||||
),
|
||||
accent_purple: (
|
||||
c: "#D58CFF",
|
||||
c: "#68217C",
|
||||
),
|
||||
accent_pink: (
|
||||
c: "#FF9CDD",
|
||||
c: "#86043A",
|
||||
),
|
||||
accent_indigo: (
|
||||
c: "#95C4FC",
|
||||
c: "#2E496D",
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
pub use corner::*;
|
||||
pub use cosmic_palette::*;
|
||||
pub use derivation::*;
|
||||
pub use mode::*;
|
||||
pub use spacing::*;
|
||||
pub use theme::*;
|
||||
|
||||
mod corner;
|
||||
mod cosmic_palette;
|
||||
mod derivation;
|
||||
mod mode;
|
||||
mod spacing;
|
||||
mod theme;
|
||||
|
|
|
|||
46
cosmic-theme/src/model/mode.rs
Normal file
46
cosmic-theme/src/model/mode.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry};
|
||||
|
||||
/// ID for the ThemeMode config
|
||||
pub const THEME_MODE_ID: &str = "com.system76.CosmicTheme.Mode";
|
||||
|
||||
/// The config for cosmic theme dark / light settings
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
||||
)]
|
||||
pub struct ThemeMode {
|
||||
/// The theme dark mode setting.
|
||||
pub is_dark: bool,
|
||||
/// The theme auto-switch dark and light mode setting.
|
||||
pub auto_switch: bool,
|
||||
}
|
||||
|
||||
impl Default for ThemeMode {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_dark: true,
|
||||
auto_switch: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ThemeMode {
|
||||
/// Check if the theme is currently using dark mode
|
||||
pub fn is_dark(config: &Config) -> Result<bool, cosmic_config::Error> {
|
||||
config.get::<bool>("is_dark")
|
||||
}
|
||||
|
||||
/// version of the theme
|
||||
pub fn version() -> u64 {
|
||||
1
|
||||
}
|
||||
|
||||
/// Set auto-switch from light to dark mode
|
||||
pub fn set_auto_switch(config: &Config, value: bool) -> Result<(), cosmic_config::Error> {
|
||||
config.set("auto_switch", value)
|
||||
}
|
||||
|
||||
/// Get the config for the theme mode
|
||||
pub fn config() -> Result<Config, cosmic_config::Error> {
|
||||
Config::new(THEME_MODE_ID, Self::version())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,24 @@
|
|||
use crate::{
|
||||
composite::over, steps::*, Component, Container, CornerRadii, CosmicPalette,
|
||||
CosmicPaletteInner, Spacing, DARK_PALETTE, LIGHT_PALETTE, NAME,
|
||||
CosmicPaletteInner, Spacing, ThemeMode, DARK_PALETTE, LIGHT_PALETTE, NAME,
|
||||
};
|
||||
use cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry};
|
||||
use palette::{IntoColor, Srgb, Srgba};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
/// ID for the current dark ThemeBuilder config
|
||||
pub const DARK_THEME_BUILDER_ID: &str = "com.system76.CosmicTheme.Dark.Builder";
|
||||
|
||||
/// ID for the current dark Theme config
|
||||
pub const DARK_THEME_ID: &str = "com.system76.CosmicTheme.Dark";
|
||||
|
||||
/// ID for the current light ThemeBuilder config
|
||||
pub const LIGHT_THEME_BUILDER_ID: &str = "com.system76.CosmicTheme.Light.Builder";
|
||||
|
||||
/// ID for the current light Theme config
|
||||
pub const LIGHT_THEME_ID: &str = "com.system76.CosmicTheme.Light";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||
/// Theme layer type
|
||||
pub enum Layer {
|
||||
|
|
@ -64,9 +76,17 @@ pub struct Theme<C> {
|
|||
pub is_dark: bool,
|
||||
/// is high contrast
|
||||
pub is_high_contrast: bool,
|
||||
/// cosmic-comp window gaps size (outer, inner)
|
||||
pub gaps: (u32, u32),
|
||||
/// cosmic-comp active hint window outline width
|
||||
pub active_hint: u32,
|
||||
/// cosmic-comp custom window hint color
|
||||
pub window_hint: Option<Srgb>,
|
||||
/// enables blurred transparency
|
||||
pub is_frosted: bool,
|
||||
}
|
||||
|
||||
impl CosmicConfigEntry for Theme<Srgba> {
|
||||
impl cosmic_config::CosmicConfigEntry for Theme<Srgba> {
|
||||
fn write_entry(&self, config: &Config) -> Result<(), cosmic_config::Error> {
|
||||
let self_ = self.clone();
|
||||
// TODO do as transaction
|
||||
|
|
@ -80,11 +100,22 @@ impl CosmicConfigEntry for Theme<Srgba> {
|
|||
tx.set("success", self_.success)?;
|
||||
tx.set("destructive", self_.destructive)?;
|
||||
tx.set("warning", self_.warning)?;
|
||||
tx.set("accent_button", self_.accent_button)?;
|
||||
tx.set("success_button", self_.success_button)?;
|
||||
tx.set("warning_button", self_.warning_button)?;
|
||||
tx.set("destructive_button", self_.destructive_button)?;
|
||||
tx.set("icon_button", self_.icon_button)?;
|
||||
tx.set("link_button", self_.link_button)?;
|
||||
tx.set("text_button", self_.text_button)?;
|
||||
tx.set("button", self_.button)?;
|
||||
tx.set("palette", self_.palette)?;
|
||||
tx.set("is_dark", self_.is_dark)?;
|
||||
tx.set("is_high_contrast", self_.is_high_contrast)?;
|
||||
tx.set("spacing", self_.spacing)?;
|
||||
tx.set("corner_radii", self_.corner_radii)?;
|
||||
tx.set("active_hint", self_.active_hint)?;
|
||||
tx.set("gaps", self_.gaps)?;
|
||||
tx.set("window_hint", self_.window_hint)?;
|
||||
|
||||
tx.commit()
|
||||
}
|
||||
|
|
@ -125,6 +156,38 @@ impl CosmicConfigEntry for Theme<Srgba> {
|
|||
Ok(warning) => default.warning = warning,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<Srgba>>("success_button") {
|
||||
Ok(b) => default.success_button = b,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<Srgba>>("accent_button") {
|
||||
Ok(b) => default.accent_button = b,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<Srgba>>("destructive_button") {
|
||||
Ok(b) => default.destructive_button = b,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<Srgba>>("warning_button") {
|
||||
Ok(warning) => default.warning_button = warning,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<Srgba>>("icon_button") {
|
||||
Ok(b) => default.link_button = b,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<Srgba>>("link_button") {
|
||||
Ok(b) => default.link_button = b,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<Srgba>>("text_button") {
|
||||
Ok(b) => default.text_button = b,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<Srgba>>("button") {
|
||||
Ok(b) => default.button = b,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<CosmicPaletteInner<Srgba>>("palette") {
|
||||
Ok(palette) => default.palette = palette,
|
||||
Err(e) => errors.push(e),
|
||||
|
|
@ -145,7 +208,18 @@ impl CosmicConfigEntry for Theme<Srgba> {
|
|||
Ok(corner_radii) => default.corner_radii = corner_radii,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
|
||||
match config.get::<u32>("active_hint") {
|
||||
Ok(active_hint) => default.active_hint = active_hint,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<(u32, u32)>("gaps") {
|
||||
Ok(gaps) => default.gaps = gaps,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Option<Srgb>>("window_hint") {
|
||||
Ok(window_hint) => default.window_hint = window_hint,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
if errors.is_empty() {
|
||||
Ok(default)
|
||||
} else {
|
||||
|
|
@ -176,6 +250,16 @@ impl<C> Theme<C> {
|
|||
pub fn id() -> &'static str {
|
||||
NAME
|
||||
}
|
||||
|
||||
/// Get the config for the current dark theme
|
||||
pub fn dark_config() -> Result<Config, cosmic_config::Error> {
|
||||
Config::new(DARK_THEME_ID, Self::version())
|
||||
}
|
||||
|
||||
/// Get the config for the current light theme
|
||||
pub fn light_config() -> Result<Config, cosmic_config::Error> {
|
||||
Config::new(LIGHT_THEME_ID, Self::version())
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme<Srgba> {
|
||||
|
|
@ -410,6 +494,20 @@ impl Theme<Srgba> {
|
|||
pub fn radius_xl(&self) -> [f32; 4] {
|
||||
self.corner_radii.radius_xl
|
||||
}
|
||||
|
||||
/// get the active theme
|
||||
pub fn get_active() -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
|
||||
let config =
|
||||
Config::new(Self::id(), Self::version()).map_err(|e| (vec![e], Self::default()))?;
|
||||
let is_dark = ThemeMode::is_dark(&config).map_err(|e| (vec![e], Self::default()))?;
|
||||
let config = if is_dark {
|
||||
Self::dark_config()
|
||||
} else {
|
||||
Self::light_config()
|
||||
}
|
||||
.map_err(|e| (vec![e], Self::default()))?;
|
||||
Self::get_entry(&config)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> From<CosmicPalette<C>> for Theme<Srgba>
|
||||
|
|
@ -422,20 +520,47 @@ where
|
|||
}
|
||||
|
||||
/// Helper for building customized themes
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
||||
PartialEq,
|
||||
)]
|
||||
pub struct ThemeBuilder {
|
||||
palette: CosmicPalette<Srgba>,
|
||||
spacing: Spacing,
|
||||
corner_radii: CornerRadii,
|
||||
neutral_tint: Option<Srgb>,
|
||||
bg_color: Option<Srgba>,
|
||||
primary_container_bg: Option<Srgba>,
|
||||
secondary_container_bg: Option<Srgba>,
|
||||
text_tint: Option<Srgb>,
|
||||
accent: Option<Srgb>,
|
||||
success: Option<Srgb>,
|
||||
warning: Option<Srgb>,
|
||||
destructive: Option<Srgb>,
|
||||
/// override the palette for the builder
|
||||
pub palette: CosmicPalette<Srgba>,
|
||||
/// override spacing for the builder
|
||||
pub spacing: Spacing,
|
||||
/// override corner radii for the builder
|
||||
pub corner_radii: CornerRadii,
|
||||
/// override neutral_tint for the builder
|
||||
pub neutral_tint: Option<Srgb>,
|
||||
/// override bg_color for the builder
|
||||
pub bg_color: Option<Srgba>,
|
||||
/// override the primary container bg color for the builder
|
||||
pub primary_container_bg: Option<Srgba>,
|
||||
/// override the secontary container bg color for the builder
|
||||
pub secondary_container_bg: Option<Srgba>,
|
||||
/// override the text tint for the builder
|
||||
pub text_tint: Option<Srgb>,
|
||||
/// override the accent color for the builder
|
||||
pub accent: Option<Srgb>,
|
||||
/// override the success color for the builder
|
||||
pub success: Option<Srgb>,
|
||||
/// override the warning color for the builder
|
||||
pub warning: Option<Srgb>,
|
||||
/// override the destructive color for the builder
|
||||
pub destructive: Option<Srgb>,
|
||||
/// enabled blurred transparency
|
||||
pub is_frosted: bool, // TODO handle
|
||||
/// cosmic-comp window gaps size (outer, inner)
|
||||
pub gaps: (u32, u32),
|
||||
/// cosmic-comp active hint window outline width
|
||||
pub active_hint: u32,
|
||||
/// cosmic-comp custom window hint color
|
||||
pub window_hint: Option<Srgb>,
|
||||
}
|
||||
|
||||
impl Default for ThemeBuilder {
|
||||
|
|
@ -453,6 +578,11 @@ impl Default for ThemeBuilder {
|
|||
success: Default::default(),
|
||||
warning: Default::default(),
|
||||
destructive: Default::default(),
|
||||
is_frosted: false,
|
||||
// cosmic-comp theme settings
|
||||
gaps: (0, 4),
|
||||
active_hint: 4,
|
||||
window_hint: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -478,7 +608,7 @@ impl ThemeBuilder {
|
|||
pub fn dark_high_contrast() -> Self {
|
||||
let palette: CosmicPalette<Srgba> = DARK_PALETTE.to_owned().into();
|
||||
Self {
|
||||
palette: CosmicPalette::HighContrastLight(palette.inner()),
|
||||
palette: CosmicPalette::HighContrastDark(palette.inner()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -575,6 +705,10 @@ impl ThemeBuilder {
|
|||
success,
|
||||
warning,
|
||||
destructive,
|
||||
gaps,
|
||||
active_hint,
|
||||
window_hint,
|
||||
is_frosted,
|
||||
} = self;
|
||||
|
||||
let is_dark = palette.is_dark();
|
||||
|
|
@ -885,9 +1019,28 @@ impl ThemeBuilder {
|
|||
corner_radii,
|
||||
is_dark,
|
||||
is_high_contrast,
|
||||
gaps,
|
||||
active_hint,
|
||||
window_hint,
|
||||
is_frosted,
|
||||
};
|
||||
theme.spacing = spacing;
|
||||
theme.corner_radii = corner_radii;
|
||||
theme
|
||||
}
|
||||
|
||||
/// Get the builder for the dark config
|
||||
pub fn dark_config() -> Result<Config, cosmic_config::Error> {
|
||||
Config::new(DARK_THEME_BUILDER_ID, Self::version())
|
||||
}
|
||||
|
||||
/// Get the builder for the light config
|
||||
pub fn light_config() -> Result<Config, cosmic_config::Error> {
|
||||
Config::new(LIGHT_THEME_BUILDER_ID, Self::version())
|
||||
}
|
||||
|
||||
/// version of the theme builder
|
||||
pub fn version() -> u64 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue