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
|
|
@ -28,7 +28,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
let write_each_config_field = fields.iter().map(|field| {
|
let write_each_config_field = fields.iter().map(|field| {
|
||||||
let field_name = &field.ident;
|
let field_name = &field.ident;
|
||||||
quote! {
|
quote! {
|
||||||
config.set(stringify!(#field_name), &self.#field_name)?;
|
cosmic_config::ConfigSet::set(config, stringify!(#field_name), &self.#field_name)?;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
let field_name = &field.ident;
|
let field_name = &field.ident;
|
||||||
let field_type = &field.ty;
|
let field_type = &field.ty;
|
||||||
quote! {
|
quote! {
|
||||||
match config.get::<#field_type>(stringify!(#field_name)) {
|
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
|
||||||
Ok(#field_name) => default.#field_name = #field_name,
|
Ok(#field_name) => default.#field_name = #field_name,
|
||||||
Err(e) => errors.push(e),
|
Err(e) => errors.push(e),
|
||||||
}
|
}
|
||||||
|
|
@ -60,13 +60,13 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
|
|
||||||
let gen = quote! {
|
let gen = quote! {
|
||||||
impl CosmicConfigEntry for #name {
|
impl CosmicConfigEntry for #name {
|
||||||
fn write_entry(&self, config: &Config) -> Result<(), cosmic_config::Error> {
|
fn write_entry(&self, config: &cosmic_config::Config) -> Result<(), cosmic_config::Error> {
|
||||||
let tx = config.transaction();
|
let tx = config.transaction();
|
||||||
#(#write_each_config_field)*
|
#(#write_each_config_field)*
|
||||||
tx.commit()
|
tx.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_entry(config: &Config) -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
|
fn get_entry(config: &cosmic_config::Config) -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
|
||||||
let mut default = Self::default();
|
let mut default = Self::default();
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -390,8 +390,6 @@ async fn start_listening<
|
||||||
Ok(w) => w,
|
Ok(w) => w,
|
||||||
Err(_) => return ConfigState::Failed,
|
Err(_) => return ConfigState::Failed,
|
||||||
};
|
};
|
||||||
let msg = T::get_entry(&config);
|
|
||||||
_ = output.send((id, msg)).await;
|
|
||||||
|
|
||||||
match T::get_entry(&config) {
|
match T::get_entry(&config) {
|
||||||
Ok(t) => {
|
Ok(t) => {
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,4 @@ serde = { version = "1.0.129", features = ["derive"] }
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
csscolorparser = {version = "0.6.2", features = ["serde"]}
|
csscolorparser = {version = "0.6.2", features = ["serde"]}
|
||||||
cosmic-config = { path = "../cosmic-config/", default-features = false, features = ["subscription"] }
|
cosmic-config = { path = "../cosmic-config/", default-features = false, features = ["subscription", "macro"] }
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,14 @@ pub struct CosmicPaletteInner<C> {
|
||||||
/// A wider spread of dark colors for more general use.
|
/// A wider spread of dark colors for more general use.
|
||||||
pub neutral_10: C,
|
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
|
/// Extended Color Palette
|
||||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||||
pub ext_warm_grey: C,
|
pub ext_warm_grey: C,
|
||||||
|
|
@ -159,6 +167,12 @@ pub struct CosmicPaletteInner<C> {
|
||||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||||
pub ext_indigo: C,
|
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
|
/// Potential Accent Color Combos
|
||||||
pub accent_warm_grey: C,
|
pub accent_warm_grey: C,
|
||||||
/// Potential Accent Color Combos
|
/// Potential Accent Color Combos
|
||||||
|
|
@ -195,6 +209,9 @@ impl From<CosmicPaletteInner<CssColor>> for CosmicPaletteInner<Srgba> {
|
||||||
neutral_8: p.neutral_8.into(),
|
neutral_8: p.neutral_8.into(),
|
||||||
neutral_9: p.neutral_9.into(),
|
neutral_9: p.neutral_9.into(),
|
||||||
neutral_10: p.neutral_10.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_warm_grey: p.ext_warm_grey.into(),
|
||||||
ext_orange: p.ext_orange.into(),
|
ext_orange: p.ext_orange.into(),
|
||||||
ext_yellow: p.ext_yellow.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_purple: p.ext_purple.into(),
|
||||||
ext_pink: p.ext_pink.into(),
|
ext_pink: p.ext_pink.into(),
|
||||||
ext_indigo: p.ext_indigo.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_warm_grey: p.accent_warm_grey.into(),
|
||||||
accent_orange: p.accent_orange.into(),
|
accent_orange: p.accent_orange.into(),
|
||||||
accent_yellow: p.accent_yellow.into(),
|
accent_yellow: p.accent_yellow.into(),
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,15 @@ Dark (
|
||||||
neutral_10: (
|
neutral_10: (
|
||||||
c: "#FFFFFF",
|
c: "#FFFFFF",
|
||||||
),
|
),
|
||||||
|
bright_green: (
|
||||||
|
c: "#5EDB8C",
|
||||||
|
),
|
||||||
|
bright_red: (
|
||||||
|
c: "#FFA090",
|
||||||
|
),
|
||||||
|
bright_orange: (
|
||||||
|
c: "#FFA37D",
|
||||||
|
),
|
||||||
ext_warm_grey: (
|
ext_warm_grey: (
|
||||||
c: "#9B8E8A",
|
c: "#9B8E8A",
|
||||||
),
|
),
|
||||||
|
|
@ -76,23 +85,32 @@ Dark (
|
||||||
ext_indigo: (
|
ext_indigo: (
|
||||||
c: "#3E88FF",
|
c: "#3E88FF",
|
||||||
),
|
),
|
||||||
|
accent_blue: (
|
||||||
|
c: "#63D0DF",
|
||||||
|
),
|
||||||
|
accent_green: (
|
||||||
|
c: "#92CF9C",
|
||||||
|
),
|
||||||
accent_warm_grey: (
|
accent_warm_grey: (
|
||||||
c: "#554742",
|
c: "#CABAB4",
|
||||||
),
|
),
|
||||||
accent_orange: (
|
accent_orange: (
|
||||||
c: "#AF5C02",
|
c: "#FFAD00",
|
||||||
),
|
),
|
||||||
accent_yellow: (
|
accent_yellow: (
|
||||||
c: "#966800",
|
c: "#F7E062",
|
||||||
),
|
),
|
||||||
accent_purple: (
|
accent_purple: (
|
||||||
c: "#813FFF",
|
c: "#E79CFE",
|
||||||
),
|
),
|
||||||
accent_pink: (
|
accent_pink: (
|
||||||
c: "#F93A83",
|
c: "#FF9CB1",
|
||||||
|
),
|
||||||
|
accent_red: (
|
||||||
|
c: "#FDA1A0",
|
||||||
),
|
),
|
||||||
accent_indigo: (
|
accent_indigo: (
|
||||||
c: "#3E88FF",
|
c: "#A1C0EB",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -55,6 +55,15 @@ Light (
|
||||||
neutral_10: (
|
neutral_10: (
|
||||||
c: "#000000",
|
c: "#000000",
|
||||||
),
|
),
|
||||||
|
bright_green: (
|
||||||
|
c: "#00572C",
|
||||||
|
),
|
||||||
|
bright_red: (
|
||||||
|
c: "#890418",
|
||||||
|
),
|
||||||
|
bright_orange: (
|
||||||
|
c: "#792C00",
|
||||||
|
),
|
||||||
ext_warm_grey: (
|
ext_warm_grey: (
|
||||||
c: "#9B8E8A",
|
c: "#9B8E8A",
|
||||||
),
|
),
|
||||||
|
|
@ -76,23 +85,32 @@ Light (
|
||||||
ext_indigo: (
|
ext_indigo: (
|
||||||
c: "#95C4FC",
|
c: "#95C4FC",
|
||||||
),
|
),
|
||||||
|
accent_blue: (
|
||||||
|
c: "#00525A",
|
||||||
|
),
|
||||||
|
accent_red: (
|
||||||
|
c: "#78292E",
|
||||||
|
),
|
||||||
|
accent_green: (
|
||||||
|
c: "#185529",
|
||||||
|
),
|
||||||
accent_warm_grey: (
|
accent_warm_grey: (
|
||||||
c: "#ADA29E",
|
c: "#554742",
|
||||||
),
|
),
|
||||||
accent_orange: (
|
accent_orange: (
|
||||||
c: "#FFD7A1",
|
c: "#624000",
|
||||||
),
|
),
|
||||||
accent_yellow: (
|
accent_yellow: (
|
||||||
c: "#FFF19E",
|
c: "#534800",
|
||||||
),
|
),
|
||||||
accent_purple: (
|
accent_purple: (
|
||||||
c: "#D58CFF",
|
c: "#68217C",
|
||||||
),
|
),
|
||||||
accent_pink: (
|
accent_pink: (
|
||||||
c: "#FF9CDD",
|
c: "#86043A",
|
||||||
),
|
),
|
||||||
accent_indigo: (
|
accent_indigo: (
|
||||||
c: "#95C4FC",
|
c: "#2E496D",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
pub use corner::*;
|
pub use corner::*;
|
||||||
pub use cosmic_palette::*;
|
pub use cosmic_palette::*;
|
||||||
pub use derivation::*;
|
pub use derivation::*;
|
||||||
|
pub use mode::*;
|
||||||
pub use spacing::*;
|
pub use spacing::*;
|
||||||
pub use theme::*;
|
pub use theme::*;
|
||||||
|
|
||||||
mod corner;
|
mod corner;
|
||||||
mod cosmic_palette;
|
mod cosmic_palette;
|
||||||
mod derivation;
|
mod derivation;
|
||||||
|
mod mode;
|
||||||
mod spacing;
|
mod spacing;
|
||||||
mod theme;
|
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::{
|
use crate::{
|
||||||
composite::over, steps::*, Component, Container, CornerRadii, CosmicPalette,
|
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 cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry};
|
||||||
use palette::{IntoColor, Srgb, Srgba};
|
use palette::{IntoColor, Srgb, Srgba};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::num::NonZeroUsize;
|
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)]
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
/// Theme layer type
|
/// Theme layer type
|
||||||
pub enum Layer {
|
pub enum Layer {
|
||||||
|
|
@ -64,9 +76,17 @@ pub struct Theme<C> {
|
||||||
pub is_dark: bool,
|
pub is_dark: bool,
|
||||||
/// is high contrast
|
/// is high contrast
|
||||||
pub is_high_contrast: bool,
|
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> {
|
fn write_entry(&self, config: &Config) -> Result<(), cosmic_config::Error> {
|
||||||
let self_ = self.clone();
|
let self_ = self.clone();
|
||||||
// TODO do as transaction
|
// TODO do as transaction
|
||||||
|
|
@ -80,11 +100,22 @@ impl CosmicConfigEntry for Theme<Srgba> {
|
||||||
tx.set("success", self_.success)?;
|
tx.set("success", self_.success)?;
|
||||||
tx.set("destructive", self_.destructive)?;
|
tx.set("destructive", self_.destructive)?;
|
||||||
tx.set("warning", self_.warning)?;
|
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("palette", self_.palette)?;
|
||||||
tx.set("is_dark", self_.is_dark)?;
|
tx.set("is_dark", self_.is_dark)?;
|
||||||
tx.set("is_high_contrast", self_.is_high_contrast)?;
|
tx.set("is_high_contrast", self_.is_high_contrast)?;
|
||||||
tx.set("spacing", self_.spacing)?;
|
tx.set("spacing", self_.spacing)?;
|
||||||
tx.set("corner_radii", self_.corner_radii)?;
|
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()
|
tx.commit()
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +156,38 @@ impl CosmicConfigEntry for Theme<Srgba> {
|
||||||
Ok(warning) => default.warning = warning,
|
Ok(warning) => default.warning = warning,
|
||||||
Err(e) => errors.push(e),
|
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") {
|
match config.get::<CosmicPaletteInner<Srgba>>("palette") {
|
||||||
Ok(palette) => default.palette = palette,
|
Ok(palette) => default.palette = palette,
|
||||||
Err(e) => errors.push(e),
|
Err(e) => errors.push(e),
|
||||||
|
|
@ -145,7 +208,18 @@ impl CosmicConfigEntry for Theme<Srgba> {
|
||||||
Ok(corner_radii) => default.corner_radii = corner_radii,
|
Ok(corner_radii) => default.corner_radii = corner_radii,
|
||||||
Err(e) => errors.push(e),
|
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() {
|
if errors.is_empty() {
|
||||||
Ok(default)
|
Ok(default)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -176,6 +250,16 @@ impl<C> Theme<C> {
|
||||||
pub fn id() -> &'static str {
|
pub fn id() -> &'static str {
|
||||||
NAME
|
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> {
|
impl Theme<Srgba> {
|
||||||
|
|
@ -410,6 +494,20 @@ impl Theme<Srgba> {
|
||||||
pub fn radius_xl(&self) -> [f32; 4] {
|
pub fn radius_xl(&self) -> [f32; 4] {
|
||||||
self.corner_radii.radius_xl
|
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>
|
impl<C> From<CosmicPalette<C>> for Theme<Srgba>
|
||||||
|
|
@ -422,20 +520,47 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper for building customized themes
|
/// Helper for building customized themes
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Debug,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
||||||
|
PartialEq,
|
||||||
|
)]
|
||||||
pub struct ThemeBuilder {
|
pub struct ThemeBuilder {
|
||||||
palette: CosmicPalette<Srgba>,
|
/// override the palette for the builder
|
||||||
spacing: Spacing,
|
pub palette: CosmicPalette<Srgba>,
|
||||||
corner_radii: CornerRadii,
|
/// override spacing for the builder
|
||||||
neutral_tint: Option<Srgb>,
|
pub spacing: Spacing,
|
||||||
bg_color: Option<Srgba>,
|
/// override corner radii for the builder
|
||||||
primary_container_bg: Option<Srgba>,
|
pub corner_radii: CornerRadii,
|
||||||
secondary_container_bg: Option<Srgba>,
|
/// override neutral_tint for the builder
|
||||||
text_tint: Option<Srgb>,
|
pub neutral_tint: Option<Srgb>,
|
||||||
accent: Option<Srgb>,
|
/// override bg_color for the builder
|
||||||
success: Option<Srgb>,
|
pub bg_color: Option<Srgba>,
|
||||||
warning: Option<Srgb>,
|
/// override the primary container bg color for the builder
|
||||||
destructive: Option<Srgb>,
|
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 {
|
impl Default for ThemeBuilder {
|
||||||
|
|
@ -453,6 +578,11 @@ impl Default for ThemeBuilder {
|
||||||
success: Default::default(),
|
success: Default::default(),
|
||||||
warning: Default::default(),
|
warning: Default::default(),
|
||||||
destructive: 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 {
|
pub fn dark_high_contrast() -> Self {
|
||||||
let palette: CosmicPalette<Srgba> = DARK_PALETTE.to_owned().into();
|
let palette: CosmicPalette<Srgba> = DARK_PALETTE.to_owned().into();
|
||||||
Self {
|
Self {
|
||||||
palette: CosmicPalette::HighContrastLight(palette.inner()),
|
palette: CosmicPalette::HighContrastDark(palette.inner()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -575,6 +705,10 @@ impl ThemeBuilder {
|
||||||
success,
|
success,
|
||||||
warning,
|
warning,
|
||||||
destructive,
|
destructive,
|
||||||
|
gaps,
|
||||||
|
active_hint,
|
||||||
|
window_hint,
|
||||||
|
is_frosted,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let is_dark = palette.is_dark();
|
let is_dark = palette.is_dark();
|
||||||
|
|
@ -885,9 +1019,28 @@ impl ThemeBuilder {
|
||||||
corner_radii,
|
corner_radii,
|
||||||
is_dark,
|
is_dark,
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
|
gaps,
|
||||||
|
active_hint,
|
||||||
|
window_hint,
|
||||||
|
is_frosted,
|
||||||
};
|
};
|
||||||
theme.spacing = spacing;
|
theme.spacing = spacing;
|
||||||
theme.corner_radii = corner_radii;
|
theme.corner_radii = corner_radii;
|
||||||
theme
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@ use cosmic::{
|
||||||
iced_core::Color,
|
iced_core::Color,
|
||||||
theme::ThemeType,
|
theme::ThemeType,
|
||||||
widget::{
|
widget::{
|
||||||
button, cosmic_container::container, icon, segmented_button, segmented_selection, settings,
|
button, color_picker::ColorPickerUpdate, cosmic_container::container, icon,
|
||||||
spin_button, toggler, view_switcher, ColorPickerModel, ColorPickerUpdate,
|
segmented_button, segmented_selection, settings, spin_button, toggler, view_switcher,
|
||||||
|
ColorPickerModel,
|
||||||
},
|
},
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
|
|
@ -160,12 +161,7 @@ impl Default for State {
|
||||||
"card 4".to_string(),
|
"card 4".to_string(),
|
||||||
],
|
],
|
||||||
timeline: Rc::new(RefCell::new(Default::default())),
|
timeline: Rc::new(RefCell::new(Default::default())),
|
||||||
color_picker_model: ColorPickerModel::new(
|
color_picker_model: ColorPickerModel::new("Hex", "RGB", None, None),
|
||||||
"Hex",
|
|
||||||
"RGB",
|
|
||||||
Color::new(0.8, 0.3, 0.8, 1.0),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -524,7 +520,9 @@ impl State {
|
||||||
.on_input(Message::InputChanged)
|
.on_input(Message::InputChanged)
|
||||||
.into(),
|
.into(),
|
||||||
self.color_picker_model
|
self.color_picker_model
|
||||||
.picker_button(Message::ColorPickerUpdate)
|
.picker_button(Message::ColorPickerUpdate, None)
|
||||||
|
.width(Length::Fixed(128.0))
|
||||||
|
.height(Length::Fixed(128.0))
|
||||||
.into(),
|
.into(),
|
||||||
if self.color_picker_model.get_is_active() {
|
if self.color_picker_model.get_is_active() {
|
||||||
self.color_picker_model
|
self.color_picker_model
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 13b5a947df963232cafb938de62e81b1df0dbac8
|
Subproject commit ed4af4e6431e30702f6de8031f3a3a11b82da3f0
|
||||||
2
iced
2
iced
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9fafd1815da3ad2c31889a32e5daccaddd79ee9d
|
Subproject commit 1e45d23ac83ed1416a6ceb2c65c4f64a416577f9
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use cosmic_config::CosmicConfigEntry;
|
||||||
|
use cosmic_theme::ThemeMode;
|
||||||
|
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
|
|
||||||
/// Status of the nav bar and its panels.
|
/// Status of the nav bar and its panels.
|
||||||
|
|
@ -49,6 +52,9 @@ pub struct Core {
|
||||||
/// Last known system theme
|
/// Last known system theme
|
||||||
pub(super) system_theme: Theme,
|
pub(super) system_theme: Theme,
|
||||||
|
|
||||||
|
/// Theme mode
|
||||||
|
pub(super) system_theme_mode: ThemeMode,
|
||||||
|
|
||||||
pub(super) title: String,
|
pub(super) title: String,
|
||||||
|
|
||||||
pub window: Window,
|
pub window: Window,
|
||||||
|
|
@ -70,6 +76,16 @@ impl Default for Core {
|
||||||
scale_factor: 1.0,
|
scale_factor: 1.0,
|
||||||
title: String::new(),
|
title: String::new(),
|
||||||
system_theme: crate::theme::active(),
|
system_theme: crate::theme::active(),
|
||||||
|
system_theme_mode: ThemeMode::config()
|
||||||
|
.map(|c| {
|
||||||
|
ThemeMode::get_entry(&c).unwrap_or_else(|(errors, mode)| {
|
||||||
|
for e in errors {
|
||||||
|
tracing::error!("{e}");
|
||||||
|
}
|
||||||
|
mode
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
window: Window {
|
window: Window {
|
||||||
context_title: String::new(),
|
context_title: String::new(),
|
||||||
header_title: String::new(),
|
header_title: String::new(),
|
||||||
|
|
@ -169,4 +185,15 @@ impl Core {
|
||||||
self.window.width = new_width;
|
self.window.width = new_width;
|
||||||
self.is_condensed_update();
|
self.is_condensed_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current system theme
|
||||||
|
pub fn system_theme(&self) -> &Theme {
|
||||||
|
&self.system_theme
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
/// Get the current system theme mode
|
||||||
|
pub fn system_theme_mode(&self) -> ThemeMode {
|
||||||
|
self.system_theme_mode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use super::{command, Application, ApplicationExt, Core, Subscription};
|
||||||
use crate::theme::{self, Theme, ThemeType, THEME};
|
use crate::theme::{self, Theme, ThemeType, THEME};
|
||||||
use crate::widget::nav_bar;
|
use crate::widget::nav_bar;
|
||||||
use crate::{keyboard_nav, Element};
|
use crate::{keyboard_nav, Element};
|
||||||
|
use cosmic_theme::ThemeMode;
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
use iced::event::wayland::{self, WindowEvent};
|
use iced::event::wayland::{self, WindowEvent};
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
|
|
@ -44,6 +45,8 @@ pub enum Message {
|
||||||
ToggleNavBarCondensed,
|
ToggleNavBarCondensed,
|
||||||
/// Notification of system theme changes.
|
/// Notification of system theme changes.
|
||||||
SystemThemeChange(Theme),
|
SystemThemeChange(Theme),
|
||||||
|
/// Notification of system theme mode changes.
|
||||||
|
SystemThemeModeChange(ThemeMode),
|
||||||
/// Updates the tracked window geometry.
|
/// Updates the tracked window geometry.
|
||||||
WindowResize(window::Id, u32, u32),
|
WindowResize(window::Id, u32, u32),
|
||||||
/// Tracks updates to window state.
|
/// Tracks updates to window state.
|
||||||
|
|
@ -152,9 +155,24 @@ where
|
||||||
keyboard_nav::subscription()
|
keyboard_nav::subscription()
|
||||||
.map(Message::KeyboardNav)
|
.map(Message::KeyboardNav)
|
||||||
.map(super::Message::Cosmic),
|
.map(super::Message::Cosmic),
|
||||||
theme::subscription(0)
|
theme::subscription(0, self.app.core().system_theme_mode.is_dark)
|
||||||
.map(Message::SystemThemeChange)
|
.map(Message::SystemThemeChange)
|
||||||
.map(super::Message::Cosmic),
|
.map(super::Message::Cosmic),
|
||||||
|
cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>(
|
||||||
|
0,
|
||||||
|
cosmic_theme::THEME_MODE_ID.into(),
|
||||||
|
cosmic_theme::ThemeMode::version(),
|
||||||
|
)
|
||||||
|
.map(|(_, u)| match u {
|
||||||
|
Ok(t) => Message::SystemThemeModeChange(t),
|
||||||
|
Err((errors, t)) => {
|
||||||
|
for e in errors {
|
||||||
|
tracing::error!("{e}");
|
||||||
|
}
|
||||||
|
Message::SystemThemeModeChange(t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(super::Message::Cosmic),
|
||||||
window_events.map(super::Message::Cosmic),
|
window_events.map(super::Message::Cosmic),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
@ -195,6 +213,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
iced::Command::single(Action::Window(WindowAction::Close))
|
iced::Command::single(Action::Window(WindowAction::Close))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn cosmic_update(&mut self, message: Message) -> iced::Command<super::Message<T::Message>> {
|
fn cosmic_update(&mut self, message: Message) -> iced::Command<super::Message<T::Message>> {
|
||||||
match message {
|
match message {
|
||||||
Message::WindowResize(id, width, height) => {
|
Message::WindowResize(id, width, height) => {
|
||||||
|
|
@ -295,7 +314,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
THEME.with(move |t| {
|
THEME.with(move |t| {
|
||||||
let mut cosmic_theme = t.borrow_mut();
|
let mut cosmic_theme = t.borrow_mut();
|
||||||
|
|
||||||
// Anly apply update if the theme is set to load a system theme
|
// Only apply update if the theme is set to load a system theme
|
||||||
if let ThemeType::System(_) = cosmic_theme.theme_type {
|
if let ThemeType::System(_) = cosmic_theme.theme_type {
|
||||||
cosmic_theme.set_theme(theme.theme_type);
|
cosmic_theme.set_theme(theme.theme_type);
|
||||||
}
|
}
|
||||||
|
|
@ -310,6 +329,21 @@ impl<T: Application> Cosmic<T> {
|
||||||
self.app.on_app_exit();
|
self.app.on_app_exit();
|
||||||
return self.close();
|
return self.close();
|
||||||
}
|
}
|
||||||
|
Message::SystemThemeModeChange(mode) => {
|
||||||
|
let core = self.app.core_mut();
|
||||||
|
let changed = core.system_theme_mode.is_dark != mode.is_dark;
|
||||||
|
core.system_theme_mode = mode;
|
||||||
|
if changed {
|
||||||
|
THEME.with(move |t| {
|
||||||
|
let mut cosmic_theme = t.borrow_mut();
|
||||||
|
|
||||||
|
// Only apply update if the theme is set to load a system theme
|
||||||
|
if let ThemeType::System(_) = cosmic_theme.theme_type {
|
||||||
|
cosmic_theme.set_theme(crate::theme::system_preference().theme_type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iced::Command::none()
|
iced::Command::none()
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@
|
||||||
//! Contains the [`Theme`] type and its widget stylesheet implementations.
|
//! Contains the [`Theme`] type and its widget stylesheet implementations.
|
||||||
|
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
use cosmic_theme::ThemeMode;
|
||||||
pub use style::*;
|
pub use style::*;
|
||||||
|
|
||||||
use cosmic_config::config_subscription;
|
use cosmic_config::config_subscription;
|
||||||
use cosmic_config::CosmicConfigEntry;
|
use cosmic_config::CosmicConfigEntry;
|
||||||
use cosmic_theme::util::CssColor;
|
|
||||||
use cosmic_theme::Component;
|
use cosmic_theme::Component;
|
||||||
use cosmic_theme::LayeredTheme;
|
use cosmic_theme::LayeredTheme;
|
||||||
use iced_futures::Subscription;
|
use iced_futures::Subscription;
|
||||||
|
|
@ -68,10 +68,15 @@ pub fn is_high_contrast() -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Watches for changes to the system's theme preference.
|
/// Watches for changes to the system's theme preference.
|
||||||
pub fn subscription(id: u64) -> Subscription<crate::theme::Theme> {
|
pub fn subscription(id: u64, is_dark: bool) -> Subscription<crate::theme::Theme> {
|
||||||
config_subscription::<u64, crate::cosmic_theme::Theme<Srgba>>(
|
config_subscription::<_, crate::cosmic_theme::Theme<Srgba>>(
|
||||||
id,
|
(id, is_dark),
|
||||||
crate::cosmic_theme::NAME.into(),
|
if is_dark {
|
||||||
|
cosmic_theme::DARK_THEME_ID
|
||||||
|
} else {
|
||||||
|
cosmic_theme::LIGHT_THEME_ID
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
crate::cosmic_theme::Theme::<Srgba>::version(),
|
crate::cosmic_theme::Theme::<Srgba>::version(),
|
||||||
)
|
)
|
||||||
.map(|(_, res)| {
|
.map(|(_, res)| {
|
||||||
|
|
@ -88,10 +93,21 @@ pub fn subscription(id: u64) -> Subscription<crate::theme::Theme> {
|
||||||
|
|
||||||
/// Loads the preferred system theme from `cosmic-config`.
|
/// Loads the preferred system theme from `cosmic-config`.
|
||||||
pub fn system_preference() -> Theme {
|
pub fn system_preference() -> Theme {
|
||||||
let Ok(helper) = crate::cosmic_config::Config::new(
|
let Ok(mode_config) = ThemeMode::config() else {
|
||||||
crate::cosmic_theme::NAME,
|
return Theme::dark();
|
||||||
crate::cosmic_theme::Theme::<CssColor>::version(),
|
};
|
||||||
) else {
|
|
||||||
|
let Ok(is_dark) = ThemeMode::is_dark(&mode_config) else {
|
||||||
|
return Theme::dark();
|
||||||
|
};
|
||||||
|
|
||||||
|
let helper = if is_dark {
|
||||||
|
crate::cosmic_theme::Theme::<Srgba>::dark_config()
|
||||||
|
} else {
|
||||||
|
crate::cosmic_theme::Theme::<Srgba>::light_config()
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(helper) = helper else {
|
||||||
return Theme::dark();
|
return Theme::dark();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -412,7 +412,12 @@ impl container::StyleSheet for Theme {
|
||||||
.add_stop(0.0, header_top.into())
|
.add_stop(0.0, header_top.into())
|
||||||
.add_stop(1.0, header_bottom.into()),
|
.add_stop(1.0, header_bottom.into()),
|
||||||
))),
|
))),
|
||||||
border_radius: BorderRadius::from([16.0, 16.0, 0.0, 0.0]),
|
border_radius: BorderRadius::from([
|
||||||
|
palette.corner_radii.radius_xs[0],
|
||||||
|
palette.corner_radii.radius_xs[3],
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
]),
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,20 +17,21 @@ use iced_core::gradient::{ColorStop, Linear};
|
||||||
use iced_core::renderer::Quad;
|
use iced_core::renderer::Quad;
|
||||||
use iced_core::widget::{tree, Tree};
|
use iced_core::widget::{tree, Tree};
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
layout, mouse, renderer, Clipboard, Color, Layout, Length, Radians, Rectangle, Renderer, Shell,
|
layout, mouse, renderer, Background, Clipboard, Color, Layout, Length, Radians, Rectangle,
|
||||||
Vector, Widget,
|
Renderer, Shell, Vector, Widget,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
use iced_sctk::commands::data_device::set_selection;
|
use iced_sctk::commands::data_device::set_selection;
|
||||||
use iced_style::slider::{HandleShape, RailBackground};
|
use iced_style::slider::{HandleShape, RailBackground};
|
||||||
use iced_widget::{canvas, column, scrollable, vertical_space, Row};
|
use iced_widget::{canvas, column, horizontal_space, row, scrollable, vertical_space, Row};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use palette::{FromColor, RgbHue};
|
use palette::{FromColor, RgbHue};
|
||||||
|
|
||||||
|
use super::button::StyleSheet;
|
||||||
use super::divider::horizontal;
|
use super::divider::horizontal;
|
||||||
use super::icon::from_name;
|
use super::icon::{self, from_name};
|
||||||
use super::segmented_button::{self, Model, SingleSelect};
|
use super::segmented_button::{self, Model, SingleSelect};
|
||||||
use super::{button, segmented_selection, text, text_input, tooltip};
|
use super::{button, segmented_selection, text, text_input, tooltip, Icon};
|
||||||
|
|
||||||
// TODO is this going to look correct enough?
|
// TODO is this going to look correct enough?
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
@ -72,9 +73,9 @@ pub struct ColorPickerModel {
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
input_color: String,
|
input_color: String,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
applied_color: Color,
|
applied_color: Option<Color>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
initial_color: Color,
|
fallback_color: Option<Color>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
recent_colors: Vec<Color>,
|
recent_colors: Vec<Color>,
|
||||||
active: bool,
|
active: bool,
|
||||||
|
|
@ -91,11 +92,11 @@ impl ColorPickerModel {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
hex: impl Into<Cow<'static, str>> + Clone,
|
hex: impl Into<Cow<'static, str>> + Clone,
|
||||||
rgb: impl Into<Cow<'static, str>> + Clone,
|
rgb: impl Into<Cow<'static, str>> + Clone,
|
||||||
fallback_color: Color,
|
fallback_color: Option<Color>,
|
||||||
initial_color: Option<Color>,
|
initial_color: Option<Color>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let initial = initial_color.unwrap_or(fallback_color);
|
let initial = initial_color.or(fallback_color);
|
||||||
let initial_srgb = palette::Srgb::from(initial);
|
let initial_srgb = palette::Srgb::from(initial.unwrap_or(Color::BLACK));
|
||||||
let hsv = palette::Hsv::from_color(initial_srgb);
|
let hsv = palette::Hsv::from_color(initial_srgb);
|
||||||
Self {
|
Self {
|
||||||
segmented_model: segmented_button::Model::builder()
|
segmented_model: segmented_button::Model::builder()
|
||||||
|
|
@ -103,10 +104,10 @@ impl ColorPickerModel {
|
||||||
.insert(move |b| b.text(rgb.clone()))
|
.insert(move |b| b.text(rgb.clone()))
|
||||||
.build(),
|
.build(),
|
||||||
active_color: hsv,
|
active_color: hsv,
|
||||||
save_next: Some(initial),
|
save_next: None,
|
||||||
input_color: color_to_string(hsv, true),
|
input_color: color_to_string(hsv, true),
|
||||||
applied_color: fallback_color,
|
applied_color: initial,
|
||||||
initial_color: initial,
|
fallback_color,
|
||||||
recent_colors: Vec::new(), // TODO should all color pickers show the same recent colors?
|
recent_colors: Vec::new(), // TODO should all color pickers show the same recent colors?
|
||||||
active: false,
|
active: false,
|
||||||
width: Length::Fixed(300.0),
|
width: Length::Fixed(300.0),
|
||||||
|
|
@ -118,13 +119,15 @@ impl ColorPickerModel {
|
||||||
|
|
||||||
/// Get a color picker button that displays the applied color
|
/// Get a color picker button that displays the applied color
|
||||||
///
|
///
|
||||||
pub fn picker_button<'a, Message: 'a, T: Fn(ColorPickerUpdate) -> Message>(
|
pub fn picker_button<'a, Message: 'static, T: Fn(ColorPickerUpdate) -> Message>(
|
||||||
&self,
|
&self,
|
||||||
f: T,
|
f: T,
|
||||||
|
icon_portion: Option<u16>,
|
||||||
) -> crate::widget::Button<'a, Message, crate::Renderer> {
|
) -> crate::widget::Button<'a, Message, crate::Renderer> {
|
||||||
color_button(
|
color_button(
|
||||||
Some(f(ColorPickerUpdate::ToggleColorPicker)),
|
Some(f(ColorPickerUpdate::ToggleColorPicker)),
|
||||||
self.applied_color,
|
self.applied_color,
|
||||||
|
Length::FillPortion(icon_portion.unwrap_or(12)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,8 +145,10 @@ impl ColorPickerModel {
|
||||||
}
|
}
|
||||||
ColorPickerUpdate::AppliedColor => {
|
ColorPickerUpdate::AppliedColor => {
|
||||||
let srgb = palette::Srgb::from_color(self.active_color);
|
let srgb = palette::Srgb::from_color(self.active_color);
|
||||||
self.recent_colors.push(self.applied_color);
|
if let Some(applied_color) = self.applied_color.take() {
|
||||||
self.applied_color = Color::from(srgb);
|
self.recent_colors.push(applied_color);
|
||||||
|
}
|
||||||
|
self.applied_color = Some(Color::from(srgb));
|
||||||
self.active = false;
|
self.active = false;
|
||||||
}
|
}
|
||||||
ColorPickerUpdate::ActivateSegmented(e) => {
|
ColorPickerUpdate::ActivateSegmented(e) => {
|
||||||
|
|
@ -167,10 +172,10 @@ impl ColorPickerModel {
|
||||||
ColorPickerUpdate::Reset => {
|
ColorPickerUpdate::Reset => {
|
||||||
self.must_clear_cache.store(true, Ordering::SeqCst);
|
self.must_clear_cache.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
let initial_srgb = palette::Srgb::from(self.initial_color);
|
let initial_srgb = palette::Srgb::from(self.fallback_color.unwrap_or(Color::BLACK));
|
||||||
let hsv = palette::Hsv::from_color(initial_srgb);
|
let hsv = palette::Hsv::from_color(initial_srgb);
|
||||||
self.active_color = hsv;
|
self.active_color = hsv;
|
||||||
self.applied_color = self.initial_color;
|
self.applied_color = self.fallback_color;
|
||||||
self.copied_at = None;
|
self.copied_at = None;
|
||||||
}
|
}
|
||||||
ColorPickerUpdate::Cancel => {
|
ColorPickerUpdate::Cancel => {
|
||||||
|
|
@ -216,7 +221,7 @@ impl ColorPickerModel {
|
||||||
|
|
||||||
/// Get the applied color of the picker
|
/// Get the applied color of the picker
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_applied_color(&self) -> Color {
|
pub fn get_applied_color(&self) -> Option<Color> {
|
||||||
self.applied_color
|
self.applied_color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,7 +360,8 @@ where
|
||||||
.leading_icon(
|
.leading_icon(
|
||||||
color_button(
|
color_button(
|
||||||
None,
|
None,
|
||||||
Color::from(palette::Srgb::from_color(self.active_color))
|
Some(Color::from(palette::Srgb::from_color(self.active_color))),
|
||||||
|
Length::FillPortion(12)
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
)
|
)
|
||||||
|
|
@ -411,7 +417,8 @@ where
|
||||||
let hsv = palette::Hsv::from_color(initial_srgb);
|
let hsv = palette::Hsv::from_color(initial_srgb);
|
||||||
color_button(
|
color_button(
|
||||||
Some(on_update(ColorPickerUpdate::ActiveColor(hsv))),
|
Some(on_update(ColorPickerUpdate::ActiveColor(hsv))),
|
||||||
*c,
|
Some(*c),
|
||||||
|
Length::FillPortion(12),
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
|
|
@ -738,93 +745,122 @@ fn color_to_string(c: palette::Hsv, is_hex: bool) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_button<'a, Message: 'a>(
|
pub fn color_button<'a, Message: 'static>(
|
||||||
on_press: Option<Message>,
|
on_press: Option<Message>,
|
||||||
color: Color,
|
color: Option<Color>,
|
||||||
|
icon_portion: Length,
|
||||||
) -> crate::widget::Button<'a, Message, crate::Renderer> {
|
) -> crate::widget::Button<'a, Message, crate::Renderer> {
|
||||||
let spacing = THEME.with(|t| t.borrow().cosmic().spacing);
|
let spacing = THEME.with(|t| t.borrow().cosmic().spacing);
|
||||||
|
|
||||||
button(vertical_space(Length::Fixed(f32::from(spacing.space_s))))
|
button(if color.is_some() {
|
||||||
.width(Length::Fixed(f32::from(spacing.space_s)))
|
Element::from(vertical_space(Length::Fixed(f32::from(spacing.space_s))))
|
||||||
.height(Length::Fixed(f32::from(spacing.space_s)))
|
} else {
|
||||||
.on_press_maybe(on_press)
|
Element::from(column![
|
||||||
.style(crate::theme::Button::Custom {
|
vertical_space(Length::FillPortion(6)),
|
||||||
active: Box::new(move |focused, theme| {
|
row![
|
||||||
let cosmic = theme.cosmic();
|
horizontal_space(Length::FillPortion(6)),
|
||||||
|
Icon::from(
|
||||||
|
icon::from_name("list-add-symbolic")
|
||||||
|
.prefer_svg(true)
|
||||||
|
.symbolic(true)
|
||||||
|
.size(64)
|
||||||
|
)
|
||||||
|
.width(icon_portion)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.content_fit(iced_core::ContentFit::Contain),
|
||||||
|
horizontal_space(Length::FillPortion(6)),
|
||||||
|
]
|
||||||
|
.height(icon_portion)
|
||||||
|
.width(Length::Fill),
|
||||||
|
vertical_space(Length::FillPortion(6)),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.width(Length::Fixed(f32::from(spacing.space_s)))
|
||||||
|
.height(Length::Fixed(f32::from(spacing.space_s)))
|
||||||
|
.on_press_maybe(on_press)
|
||||||
|
.style(crate::theme::Button::Custom {
|
||||||
|
active: Box::new(move |focused, theme| {
|
||||||
|
let cosmic = theme.cosmic();
|
||||||
|
|
||||||
let (outline_width, outline_color) = if focused {
|
let (outline_width, outline_color) = if focused {
|
||||||
(1.0, cosmic.accent_color().into())
|
(1.0, cosmic.accent_color().into())
|
||||||
} else {
|
} else {
|
||||||
(0.0, Color::TRANSPARENT)
|
(0.0, Color::TRANSPARENT)
|
||||||
};
|
};
|
||||||
button::Appearance {
|
let standard = theme.active(focused, &Button::Standard);
|
||||||
shadow_offset: Vector::default(),
|
button::Appearance {
|
||||||
background: Some(color.into()),
|
shadow_offset: Vector::default(),
|
||||||
border_radius: cosmic.radius_xs().into(),
|
background: color.map(Background::from).or(standard.background),
|
||||||
border_width: 1.0,
|
border_radius: cosmic.radius_xs().into(),
|
||||||
border_color: cosmic.on_bg_color().into(),
|
border_width: 1.0,
|
||||||
outline_width,
|
border_color: cosmic.on_bg_color().into(),
|
||||||
outline_color,
|
outline_width,
|
||||||
icon_color: None,
|
outline_color,
|
||||||
text_color: None,
|
icon_color: None,
|
||||||
}
|
text_color: None,
|
||||||
}),
|
}
|
||||||
disabled: Box::new(move |theme| {
|
}),
|
||||||
let cosmic = theme.cosmic();
|
disabled: Box::new(move |theme| {
|
||||||
|
let cosmic = theme.cosmic();
|
||||||
|
|
||||||
button::Appearance {
|
let standard = theme.disabled(&Button::Standard);
|
||||||
shadow_offset: Vector::default(),
|
button::Appearance {
|
||||||
background: Some(color.into()),
|
shadow_offset: Vector::default(),
|
||||||
border_radius: cosmic.radius_xs().into(),
|
background: color.map(Background::from).or(standard.background),
|
||||||
border_width: 1.0,
|
border_radius: cosmic.radius_xs().into(),
|
||||||
border_color: cosmic.on_bg_color().into(),
|
border_width: 1.0,
|
||||||
outline_width: 0.0,
|
border_color: cosmic.on_bg_color().into(),
|
||||||
outline_color: Color::TRANSPARENT,
|
outline_width: 0.0,
|
||||||
icon_color: None,
|
outline_color: Color::TRANSPARENT,
|
||||||
text_color: None,
|
icon_color: None,
|
||||||
}
|
text_color: None,
|
||||||
}),
|
}
|
||||||
hovered: Box::new(move |focused, theme| {
|
}),
|
||||||
let cosmic = theme.cosmic();
|
hovered: Box::new(move |focused, theme| {
|
||||||
|
let cosmic = theme.cosmic();
|
||||||
|
|
||||||
let (outline_width, outline_color) = if focused {
|
let (outline_width, outline_color) = if focused {
|
||||||
(1.0, cosmic.accent_color().into())
|
(1.0, cosmic.accent_color().into())
|
||||||
} else {
|
} else {
|
||||||
(0.0, Color::TRANSPARENT)
|
(0.0, Color::TRANSPARENT)
|
||||||
};
|
};
|
||||||
button::Appearance {
|
|
||||||
shadow_offset: Vector::default(),
|
|
||||||
background: Some(color.into()),
|
|
||||||
border_radius: cosmic.radius_xs().into(),
|
|
||||||
border_width: 1.0,
|
|
||||||
border_color: cosmic.on_bg_color().into(),
|
|
||||||
outline_width,
|
|
||||||
outline_color,
|
|
||||||
icon_color: None,
|
|
||||||
text_color: None,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
pressed: Box::new(move |focused, theme| {
|
|
||||||
let cosmic = theme.cosmic();
|
|
||||||
|
|
||||||
let (outline_width, outline_color) = if focused {
|
let standard = theme.hovered(focused, &Button::Standard);
|
||||||
(1.0, cosmic.accent_color().into())
|
button::Appearance {
|
||||||
} else {
|
shadow_offset: Vector::default(),
|
||||||
(0.0, Color::TRANSPARENT)
|
background: color.map(Background::from).or(standard.background),
|
||||||
};
|
border_radius: cosmic.radius_xs().into(),
|
||||||
button::Appearance {
|
border_width: 1.0,
|
||||||
shadow_offset: Vector::default(),
|
border_color: cosmic.on_bg_color().into(),
|
||||||
background: Some(color.into()),
|
outline_width,
|
||||||
border_radius: cosmic.radius_xs().into(),
|
outline_color,
|
||||||
border_width: 1.0,
|
icon_color: None,
|
||||||
border_color: cosmic.on_bg_color().into(),
|
text_color: None,
|
||||||
outline_width,
|
}
|
||||||
outline_color,
|
}),
|
||||||
icon_color: None,
|
pressed: Box::new(move |focused, theme| {
|
||||||
text_color: None,
|
let cosmic = theme.cosmic();
|
||||||
}
|
|
||||||
}),
|
let (outline_width, outline_color) = if focused {
|
||||||
})
|
(1.0, cosmic.accent_color().into())
|
||||||
|
} else {
|
||||||
|
(0.0, Color::TRANSPARENT)
|
||||||
|
};
|
||||||
|
|
||||||
|
let standard = theme.pressed(focused, &Button::Standard);
|
||||||
|
button::Appearance {
|
||||||
|
shadow_offset: Vector::default(),
|
||||||
|
background: color.map(Background::from).or(standard.background),
|
||||||
|
border_radius: cosmic.radius_xs().into(),
|
||||||
|
border_width: 1.0,
|
||||||
|
border_color: cosmic.on_bg_color().into(),
|
||||||
|
outline_width,
|
||||||
|
outline_color,
|
||||||
|
icon_color: None,
|
||||||
|
text_color: None,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message> From<ColorPicker<'a, Message>> for iced::Element<'a, Message, crate::Renderer>
|
impl<'a, Message> From<ColorPicker<'a, Message>> for iced::Element<'a, Message, crate::Renderer>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ use crate::{
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
|
use iced_core::Length;
|
||||||
|
use iced_widget::container;
|
||||||
|
|
||||||
/// A settings item aligned in a row
|
/// A settings item aligned in a row
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -70,15 +72,20 @@ impl<'a, Message: 'static> Item<'a, Message> {
|
||||||
let column = column::with_capacity(2)
|
let column = column::with_capacity(2)
|
||||||
.spacing(2)
|
.spacing(2)
|
||||||
.push(text(self.title))
|
.push(text(self.title))
|
||||||
.push(text(description).size(10));
|
.push(text(description).size(10))
|
||||||
|
.width(Length::FillPortion(12));
|
||||||
|
|
||||||
contents.push(column.into());
|
contents.push(column.into());
|
||||||
} else {
|
} else {
|
||||||
contents.push(text(self.title).into());
|
contents.push(text(self.title).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
contents.push(horizontal_space(iced::Length::Fill).into());
|
contents.push(
|
||||||
contents.push(widget.into());
|
container(widget.into())
|
||||||
|
.width(Length::FillPortion(4))
|
||||||
|
.align_x(iced_core::alignment::Horizontal::Right)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
item_row(contents)
|
item_row(contents)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue