diff --git a/Cargo.toml b/Cargo.toml index 5ccaaf7b..d73da2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ desktop = [ "process", "dep:cosmic-settings-config", "dep:freedesktop-desktop-entry", + "dep:image-extras", "dep:mime", "dep:shlex", "tokio?/io-util", @@ -126,7 +127,7 @@ ashpd = { version = "0.12.3", default-features = false, optional = true } async-fs = { version = "2.2", optional = true } async-std = { version = "1.13", optional = true } auto_enums = "0.8.8" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "a7d2d7a", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } jiff = "0.2" cosmic-config = { path = "cosmic-config" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } @@ -141,9 +142,14 @@ css-color = "0.2.8" derive_setters = "0.1.9" futures = "0.3" image = { version = "0.25.10", default-features = false, features = [ + "ico", "jpeg", "png", ] } +image-extras = { version = "0.1.0", default-features = false, features = [ + "xpm", + "xbm", +], optional = true } libc = { version = "0.2.183", optional = true } log = "0.4" mime = { version = "0.3.17", optional = true } @@ -170,12 +176,12 @@ cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } zbus = { version = "5.14.0", default-features = false } -[target.'cfg(unix)'.dependencies] +[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } freedesktop-desktop-entry = { version = "0.8.1", optional = true } shlex = { version = "1.3.0", optional = true } -[target.'cfg(not(unix))'.dependencies] +[target.'cfg(any(not(unix), target_os = "macos"))'.dependencies] # Used to embed bundled icons for non-unix platforms. phf = { version = "0.13.1", features = ["macros"] } diff --git a/build.rs b/build.rs index c69feaf5..4ce0aa9e 100644 --- a/build.rs +++ b/build.rs @@ -3,7 +3,9 @@ use std::env; fn main() { println!("cargo::rerun-if-changed=build.rs"); - if env::var_os("CARGO_CFG_UNIX").is_none() { + if env::var_os("CARGO_CFG_UNIX").is_none() + || env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") + { generate_bundled_icons(); } } diff --git a/cosmic-config-derive/src/lib.rs b/cosmic-config-derive/src/lib.rs index 861398e4..cc19a91e 100644 --- a/cosmic-config-derive/src/lib.rs +++ b/cosmic-config-derive/src/lib.rs @@ -1,8 +1,8 @@ use proc_macro::TokenStream; use quote::quote; -use syn; +use syn::{self}; -#[proc_macro_derive(CosmicConfigEntry, attributes(version, id, cosmic_config_entry))] +#[proc_macro_derive(CosmicConfigEntry, attributes(version, id))] pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree // that we can manipulate @@ -12,25 +12,6 @@ pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream { impl_cosmic_config_entry_macro(&ast) } -fn get_cosmic_config_attrs(field: &syn::Field) -> Result, syn::Error> { - let mut with = None; - - for attr in &field.attrs { - if !attr.path().is_ident("cosmic_config_entry") { - continue; - } - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("with") { - let value = meta.value()?; - with = Some(value.parse()?); - } - Ok(()) - })?; - } - - Ok(with) -} - fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let attributes = &ast.attrs; let version = attributes @@ -67,54 +48,19 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let write_each_config_field = fields.iter().map(|field| { let field_name = &field.ident; - let with = match get_cosmic_config_attrs(field) { - Ok(attrs) => attrs, - Err(e) => { - return e.to_compile_error(); - } - }; - - if let Some(with) = with { - quote! { - { - let conv = self.#field_name.clone().into(); - cosmic_config::ConfigSet::set::<#with>(&tx, stringify!(#field_name), conv)?; - } - } - } else { - quote! { - cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?; - } + quote! { + cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?; } }); let get_each_config_field = fields.iter().map(|field| { let field_name = &field.ident; let field_type = &field.ty; - let with = match get_cosmic_config_attrs(field) { - Ok(attrs) => attrs, - Err(e) => { - return e.to_compile_error(); - } - }; - - if let Some(with) = with { - quote! { - match cosmic_config::ConfigGet::get::<#with>(config, stringify!(#field_name)) { - Ok(value) => { - default.#field_name = value.into(); - } - Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (), - Err(e) => errors.push(e), - } - } - } else { - quote! { - match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) { - Ok(#field_name) => default.#field_name = #field_name, - Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (), - Err(e) => errors.push(e), - } + quote! { + match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) { + Ok(#field_name) => default.#field_name = #field_name, + Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (), + Err(e) => errors.push(e), } } }); @@ -122,39 +68,17 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream { let update_each_config_field = fields.iter().map(|field| { let field_name = &field.ident; let field_type = &field.ty; - let with = match get_cosmic_config_attrs(field) { - Ok(attrs) => attrs, - Err(e) => { - return e.to_compile_error(); - } - }; - - if let Some(with) = with { - quote! { - stringify!(#field_name) => { - match cosmic_config::ConfigGet::get::<#with>(config, stringify!(#field_name)) { - Ok(value) => { - let value = value.into(); - if self.#field_name != value { - keys.push(stringify!(#field_name)); - } - self.#field_name = value; - }, - Err(e) => errors.push(e), - } - } - } - } else { - quote! { - stringify!(#field_name) => { - match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) { - Ok(value) => { - if self.#field_name != value { - keys.push(stringify!(#field_name)); - } - self.#field_name = value; - }, - Err(e) => errors.push(e), + quote! { + stringify!(#field_name) => { + match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) { + Ok(value) => { + if self.#field_name != value { + keys.push(stringify!(#field_name)); + } + self.#field_name = value; + }, + Err(e) => { + errors.push(e); } } } diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index b315f194..c8eda064 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -162,7 +162,6 @@ pub trait ConfigSet { pub struct Config { system_path: Option, user_path: Option, - previous: Option>, } /// Check that the name is relative and doesn't contain . or .. @@ -181,13 +180,9 @@ fn sanitize_name(name: &str) -> Result<&Path, Error> { impl Config { /// Get a system config for the given name and config version pub fn system(name: &str, version: u64) -> Result { - Self::system_inner(name, version, true) - } - - fn system_inner(name: &str, version: u64, look_for_previous: bool) -> Result { let path = sanitize_name(name)?.join(format!("v{version}")); #[cfg(unix)] - let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(&path); + let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(path); #[cfg(windows)] let system_path = @@ -197,13 +192,6 @@ impl Config { Ok(Self { system_path, user_path: None, - previous: if version > 1 && look_for_previous { - Self::system_inner(name, version - 1, false) - .ok() - .map(Box::new) - } else { - None - }, }) } @@ -211,10 +199,6 @@ impl Config { // Use folder at XDG config/name for config storage, return Config if successful //TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy) pub fn new(name: &str, version: u64) -> Result { - Self::new_inner(name, version, true) - } - - fn new_inner(name: &str, version: u64, look_for_previous: bool) -> Result { // Look for [name]/v[version] let path = sanitize_name(name)?.join(format!("v{}", version)); @@ -239,29 +223,15 @@ impl Config { Ok(Self { system_path, user_path: Some(user_path), - previous: if version > 1 && look_for_previous { - Self::new_inner(name, version - 1, false).ok().map(Box::new) - } else { - None - }, }) } /// Get config for the given application name and config version and custom path. pub fn with_custom_path(name: &str, version: u64, custom_path: PathBuf) -> Result { - Self::with_custom_path_inner(name, version, custom_path, true) - } - - fn with_custom_path_inner( - name: &str, - version: u64, - custom_path: PathBuf, - look_for_previous: bool, - ) -> Result { // Look for [name]/v[version] let path = sanitize_name(name)?.join(format!("v{version}")); - let mut user_path = custom_path.clone(); + let mut user_path = custom_path; user_path.push("cosmic"); user_path.push(path); // Create new configuration directory if not found. @@ -271,13 +241,6 @@ impl Config { Ok(Self { system_path: None, user_path: Some(user_path), - previous: if version > 1 && look_for_previous { - Self::with_custom_path_inner(name, version - 1, custom_path.clone(), false) - .ok() - .map(Box::new) - } else { - None - }, }) } @@ -287,10 +250,6 @@ impl Config { // Use folder at XDG config/name for config storage, return Config if successful //TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy) pub fn new_state(name: &str, version: u64) -> Result { - Self::new_state_inner(name, version, true) - } - - fn new_state_inner(name: &str, version: u64, look_for_previous: bool) -> Result { // Look for [name]/v[version] let path = sanitize_name(name)?.join(format!("v{}", version)); @@ -304,13 +263,6 @@ impl Config { Ok(Self { system_path: None, user_path: Some(user_path), - previous: if version > 1 && look_for_previous { - Self::new_state_inner(name, version - 1, false) - .ok() - .map(Box::new) - } else { - None - }, }) } @@ -421,13 +373,7 @@ impl ConfigGet for Config { Ok(ron::from_str(&data)?) } - _ => { - if let Some(previous) = self.previous.as_ref() { - previous.get_local(key) - } else { - Err(Error::NotFound) - } - } + _ => Err(Error::NotFound), } } diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index faec2fd5..7e408d8d 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -15,7 +15,6 @@ export = ["serde_json"] no-default = [] [dependencies] -hex_color = { version = "3", features = ["serde"] } palette = { version = "0.7.6", features = ["serializing"] } almost = "0.2" serde = { version = "1.0.228", features = ["derive"] } diff --git a/cosmic-theme/src/model/color.rs b/cosmic-theme/src/model/color.rs deleted file mode 100644 index cd64ec7f..00000000 --- a/cosmic-theme/src/model/color.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! Color representation and serde helpers for the Cosmic theme - -use hex_color::HexColor; -use palette::{Srgb, Srgba}; -use serde::{Deserialize, Serialize}; - -/// A color in the Cosmic theme for serialization and deserialization -#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)] -#[serde(untagged)] -pub enum ColorRepr { - /// A color represented as a hex string - #[serde(with = "hex_color::rgba")] - Hex(HexColor), - /// A color represented as an RGBA value - Rgba(Srgba), - /// A color represented as an RGB value - Rgb(Srgb), -} - -/// An optional color in the Cosmic theme for serialization and deserialization -#[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)] -#[serde(transparent)] -pub struct ColorReprOption(Option); - -impl From for ColorRepr { - fn from(color: Srgb) -> Self { - let rgb_u8: Srgb = color.into_format(); - ColorRepr::Hex(HexColor { - r: rgb_u8.red, - g: rgb_u8.green, - b: rgb_u8.blue, - a: 255, - }) - } -} - -impl From for ColorRepr { - fn from(color: Srgba) -> Self { - let rgba_u8: Srgba = color.into_format(); - ColorRepr::Hex(HexColor { - r: rgba_u8.red, - g: rgba_u8.green, - b: rgba_u8.blue, - a: rgba_u8.alpha, - }) - } -} - -impl From for Srgb { - fn from(value: ColorRepr) -> Self { - match value { - ColorRepr::Hex(hex) => Srgb::::new(hex.r, hex.g, hex.b).into_format(), - ColorRepr::Rgb(rgb) => rgb, - ColorRepr::Rgba(rgba) => Srgb::new(rgba.red, rgba.green, rgba.blue), - } - } -} - -impl From for Srgba { - fn from(value: ColorRepr) -> Self { - match value { - ColorRepr::Hex(hex) => Srgba::::new(hex.r, hex.g, hex.b, hex.a).into_format(), - ColorRepr::Rgb(rgb) => Srgba::new(rgb.red, rgb.green, rgb.blue, 1.0), - ColorRepr::Rgba(rgba) => rgba, - } - } -} - -impl From for Option { - fn from(value: ColorReprOption) -> Self { - value.0.map(std::convert::Into::into) - } -} - -impl From for Option { - fn from(value: ColorReprOption) -> Self { - value.0.map(std::convert::Into::into) - } -} - -impl From> for ColorReprOption { - fn from(value: Option) -> Self { - ColorReprOption(value.map(std::convert::Into::into)) - } -} - -impl From> for ColorReprOption { - fn from(value: Option) -> Self { - ColorReprOption(value.map(std::convert::Into::into)) - } -} - -/// A trait for converting between a color type and its representation for serialization and deserialization -pub trait ConvColorRepr: Sized { - /// Convert from a color representation to the color type - fn from_repr(repr: ColorRepr) -> Self; - /// Convert from the color type to its representation for serialization - fn to_repr(&self) -> ColorRepr; -} - -impl ConvColorRepr for Srgba { - fn from_repr(repr: ColorRepr) -> Self { - repr.into() - } - - fn to_repr(&self) -> ColorRepr { - (*self).into() - } -} - -impl ConvColorRepr for Srgb { - fn from_repr(repr: ColorRepr) -> Self { - repr.into() - } - - fn to_repr(&self) -> ColorRepr { - (*self).into() - } -} - -/// Serde helpers for serializing and deserializing colors in the Cosmic theme -pub mod color_serde { - use super::*; - use serde::{Deserialize, Deserializer, Serializer}; - - /// Serialize a color to a hex string - pub fn serialize(color: &T, serializer: S) -> Result - where - T: ConvColorRepr, - S: Serializer, - { - let repr = color.to_repr(); - repr.serialize(serializer) - } - - /// Deserialize a color from a hex string or RGB/RGBA - pub fn deserialize<'de, T, D>(deserializer: D) -> Result - where - T: ConvColorRepr, - D: Deserializer<'de>, - { - let repr = ColorRepr::deserialize(deserializer)?; - Ok(T::from_repr(repr)) - } - - /// Serde helpers for serializing and deserializing optional colors in the Cosmic theme - pub mod option { - use super::*; - - /// Serialize an optional color - pub fn serialize(value: &Option, serializer: S) -> Result - where - T: ConvColorRepr, - S: Serializer, - { - match value { - Some(v) => super::serialize(v, serializer), - None => serializer.serialize_none(), - } - } - - /// Deserialize an optional color - pub fn deserialize<'de, T, D>(deserializer: D) -> Result, D::Error> - where - T: ConvColorRepr, - D: Deserializer<'de>, - { - let opt = Option::::deserialize(deserializer)?; - Ok(opt.map(T::from_repr)) - } - } -} diff --git a/cosmic-theme/src/model/cosmic_palette.rs b/cosmic-theme/src/model/cosmic_palette.rs index 4360f8f4..3852742b 100644 --- a/cosmic-theme/src/model/cosmic_palette.rs +++ b/cosmic-theme/src/model/cosmic_palette.rs @@ -1,4 +1,3 @@ -use crate::color::color_serde; use palette::Srgba; use serde::{Deserialize, Serialize}; use std::sync::LazyLock; @@ -96,107 +95,75 @@ pub struct CosmicPaletteInner { /// Utility Colors /// Colors used for various points of emphasis in the UI. - #[serde(with = "color_serde")] pub bright_red: Srgba, /// Colors used for various points of emphasis in the UI. - #[serde(with = "color_serde")] pub bright_green: Srgba, /// Colors used for various points of emphasis in the UI. - #[serde(with = "color_serde")] pub bright_orange: Srgba, /// Surface Grays /// Colors used for three levels of surfaces in the UI. - #[serde(with = "color_serde")] pub gray_1: Srgba, /// Colors used for three levels of surfaces in the UI. - #[serde(with = "color_serde")] pub gray_2: Srgba, /// System Neutrals /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_0: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_1: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_2: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_3: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_4: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_5: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_6: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_7: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_8: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_9: Srgba, /// A wider spread of dark colors for more general use. - #[serde(with = "color_serde")] pub neutral_10: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_blue: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_indigo: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_purple: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_pink: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_red: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_orange: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_yellow: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_green: Srgba, /// Potential Accent Color Combos - #[serde(with = "color_serde")] pub accent_warm_grey: Srgba, /// Extended Color Palette /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_warm_grey: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_orange: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_yellow: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_blue: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_purple: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_pink: Srgba, /// Colors used for themes, app icons, illustrations, and other brand purposes. - #[serde(with = "color_serde")] pub ext_indigo: Srgba, } diff --git a/cosmic-theme/src/model/dark.ron b/cosmic-theme/src/model/dark.ron index ffec0818..4453b8bf 100644 --- a/cosmic-theme/src/model/dark.ron +++ b/cosmic-theme/src/model/dark.ron @@ -1 +1 @@ -Dark((name: "cosmic-dark",bright_red: "#FFA09AFF",bright_green: "#5EDB8CFF",bright_orange: "#FFA37DFF",gray_1: "#1B1B1BFF",gray_2: "#262626FF",neutral_0: "#000000FF",neutral_1: "#030303FF",neutral_2: "#161616FF",neutral_3: "#2E2E2EFF",neutral_4: "#484848FF",neutral_5: "#636363FF",neutral_6: "#808080FF",neutral_7: "#9E9E9EFF",neutral_8: "#BEBEBEFF",neutral_9: "#DEDEDEFF",neutral_10: "#FFFFFFFF",accent_blue: "#63D0DFFF",accent_indigo: "#A1C0EBFF",accent_purple: "#E79CFEFF",accent_pink: "#FF9CB1FF",accent_red: "#FDA1A0FF",accent_orange: "#FFAD00FF",accent_yellow: "#F7E062FF",accent_green: "#92CF9CFF",accent_warm_grey: "#CABAB4FF",ext_warm_grey: "#9B8E8AFF",ext_orange: "#FFAD00FF",ext_yellow: "#FEDB40FF",ext_blue: "#48B9C7FF",ext_purple: "#CF7DFFFF",ext_pink: "#F93A83FF",ext_indigo: "#3E88FFFF",)) +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))) diff --git a/cosmic-theme/src/model/derivation.rs b/cosmic-theme/src/model/derivation.rs index 796ddab3..dce653e5 100644 --- a/cosmic-theme/src/model/derivation.rs +++ b/cosmic-theme/src/model/derivation.rs @@ -1,4 +1,3 @@ -use crate::color::color_serde; use palette::{Srgba, WithAlpha}; use serde::{Deserialize, Serialize}; @@ -9,18 +8,14 @@ use crate::composite::over; #[must_use] pub struct Container { /// the color of the container - #[serde(with = "color_serde")] pub base: Srgba, /// the color of components in the container pub component: Component, /// the color of dividers in the container - #[serde(with = "color_serde")] pub divider: Srgba, /// the color of text in the container - #[serde(with = "color_serde")] pub on: Srgba, /// the color of @small_widget_container - #[serde(with = "color_serde")] pub small_widget: Srgba, } @@ -50,42 +45,30 @@ impl Container { #[must_use] pub struct Component { /// The base color of the widget - #[serde(with = "color_serde")] pub base: Srgba, /// The color of the widget when it is hovered - #[serde(with = "color_serde")] pub hover: Srgba, /// the color of the widget when it is pressed - #[serde(with = "color_serde")] pub pressed: Srgba, /// the color of the widget when it is selected - #[serde(with = "color_serde")] pub selected: Srgba, /// the color of the widget when it is selected - #[serde(with = "color_serde")] pub selected_text: Srgba, /// the color of the widget when it is focused - #[serde(with = "color_serde")] pub focus: Srgba, /// the color of dividers for this widget - #[serde(with = "color_serde")] pub divider: Srgba, /// the color of text for this widget - #[serde(with = "color_serde")] pub on: Srgba, // the color of text with opacity 80 for this widget // pub text_opacity_80: Srgba, /// the color of the widget when it is disabled - #[serde(with = "color_serde")] pub disabled: Srgba, /// the color of text in the widget when it is disabled - #[serde(with = "color_serde")] pub on_disabled: Srgba, /// the color of the border for the widget - #[serde(with = "color_serde")] pub border: Srgba, /// the color of the border for the widget when it is disabled - #[serde(with = "color_serde")] pub disabled_border: Srgba, } diff --git a/cosmic-theme/src/model/light.ron b/cosmic-theme/src/model/light.ron index c9fcc6ce..29b3ad65 100644 --- a/cosmic-theme/src/model/light.ron +++ b/cosmic-theme/src/model/light.ron @@ -1 +1 @@ -Light((name: "cosmic-light",bright_red: "#890418FF",bright_green: "#00572CFF",bright_orange: "#792C00FF",gray_1: "#D7D7D7FF",gray_2: "#E4E4E4FF",neutral_0: "#FFFFFFFF",neutral_1: "#DEDEDEFF",neutral_2: "#BEBEBEFF",neutral_3: "#9E9E9EFF",neutral_4: "#808080FF",neutral_5: "#636363FF",neutral_6: "#484848FF",neutral_7: "#2E2E2EFF",neutral_8: "#161616FF",neutral_9: "#030303FF",neutral_10: "#000000FF",accent_blue: "#00525AFF",accent_indigo: "#2E496DFF",accent_purple: "#68217CFF",accent_pink: "#86043AFF",accent_red: "#78292EFF",accent_orange: "#624000FF",accent_yellow: "#534800FF",accent_green: "#185529FF",accent_warm_grey: "#554742FF",ext_warm_grey: "#9B8E8AFF",ext_orange: "#FBB86CFF",ext_yellow: "#F7E062FF",ext_blue: "#6ACAD8FF",ext_purple: "#D58CFFFF",ext_pink: "#FF9CDDFF",ext_indigo: "#95C4FCFF",)) +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))) diff --git a/cosmic-theme/src/model/mod.rs b/cosmic-theme/src/model/mod.rs index ff8eed3e..f48d1a8d 100644 --- a/cosmic-theme/src/model/mod.rs +++ b/cosmic-theme/src/model/mod.rs @@ -6,7 +6,6 @@ pub use mode::*; pub use spacing::*; pub use theme::*; -pub mod color; mod corner; mod cosmic_palette; mod density; diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 6076b9c8..5db0f32c 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,16 +1,15 @@ use crate::{ Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE, LIGHT_PALETTE, NAME, Spacing, ThemeMode, - color::{ColorRepr, ColorReprOption, color_serde, color_serde::option as color_serde_option}, composite::over, steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps}, }; -use cosmic_config::{Config, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry}; +use cosmic_config::{Config, CosmicConfigEntry}; use palette::{ IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb, }; use serde::{Deserialize, Serialize}; -use std::{default, 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"; @@ -38,25 +37,24 @@ pub enum Layer { #[must_use] /// Cosmic Theme data structure with all colors and its name -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, CosmicConfigEntry)] -#[version = 2] +#[derive( + Clone, + Debug, + Serialize, + Deserialize, + PartialEq, + cosmic_config::cosmic_config_derive::CosmicConfigEntry, +)] +#[version = 1] pub struct Theme { /// name of the theme pub name: String, /// background element colors - pub(crate) background: Container, + pub background: Container, /// primary element colors - pub(crate) primary: Container, + pub primary: Container, /// secondary element colors - pub(crate) secondary: Container, - /// background element colors - pub(crate) transparent_background: Container, - /// primary element colors - pub(crate) transparent_primary: Container, - /// secondary element colors - pub(crate) transparent_secondary: Container, - /// button component styling - pub button: Component, + pub secondary: Container, /// accent element colors pub accent: Component, /// suggested element colors @@ -79,6 +77,8 @@ pub struct Theme { pub link_button: Component, /// text button element colors pub text_button: Component, + /// button component styling + pub button: Component, /// palette pub palette: CosmicPaletteInner, /// spacing @@ -96,31 +96,15 @@ pub struct Theme { /// cosmic-comp custom window hint color pub window_hint: Option, /// enables blurred transparency - pub frosted: BlurStrength, - /// frosted windows - pub frosted_windows: bool, - /// frosted system interface - pub frosted_system_interface: bool, - /// frosted panel - pub frosted_panel: bool, - /// frosted applet popups - pub frosted_applets: bool, + pub is_frosted: bool, /// shade color for dialogs - #[serde(with = "color_serde")] - #[cosmic_config_entry(with = ColorRepr)] pub shade: Srgba, /// accent text colors /// If None, accent base color is the accent text color. - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub accent_text: Option, /// control tint color - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub control_tint: Option, /// text tint color - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub text_tint: Option, } @@ -186,39 +170,6 @@ impl Theme { todo!(); } - #[allow(clippy::doc_markdown)] - #[inline] - /// get opaque or transparent background based on whether blur is active - pub fn background(&self, transparent: bool) -> &Container { - if transparent { - &self.transparent_background - } else { - &self.background - } - } - - #[allow(clippy::doc_markdown)] - #[inline] - /// get opaque or transparent primary based on whether blur is active - pub fn primary(&self, transparent: bool) -> &Container { - if transparent { - &self.transparent_primary - } else { - &self.primary - } - } - - #[allow(clippy::doc_markdown)] - #[inline] - /// get opaque or transparent secondary based on whether blur is active - pub fn secondary(&self, transparent: bool) -> &Container { - if transparent { - &self.transparent_secondary - } else { - &self.secondary - } - } - #[must_use] #[allow(clippy::doc_markdown)] #[inline] @@ -788,7 +739,7 @@ impl Theme { if color_scheme.trim().contains("default") || color_scheme.trim().contains("light") { return Self::light_default(); } - } + }; Self::dark_default() } @@ -797,10 +748,10 @@ impl Theme { pub fn preferred_theme() -> Self { let current_desktop = std::env::var("XDG_CURRENT_DESKTOP"); - if let Ok(desktop) = current_desktop - && desktop.trim().to_lowercase().contains("gnome") - { - return Self::gtk_prefer_colorscheme(); + if let Ok(desktop) = current_desktop { + if desktop.trim().to_lowercase().contains("gnome") { + return Self::gtk_prefer_colorscheme(); + } } Self::dark_default() @@ -815,8 +766,15 @@ impl From for Theme { #[must_use] /// Helper for building customized themes -#[derive(Clone, Debug, Serialize, Deserialize, CosmicConfigEntry, PartialEq)] -#[version = 2] +#[derive( + Clone, + Debug, + Serialize, + Deserialize, + cosmic_config::cosmic_config_derive::CosmicConfigEntry, + PartialEq, +)] +#[version = 1] pub struct ThemeBuilder { /// override the palette for the builder pub palette: CosmicPalette, @@ -825,59 +783,31 @@ pub struct ThemeBuilder { /// override corner radii for the builder pub corner_radii: CornerRadii, /// override neutral_tint for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub neutral_tint: Option, /// override bg_color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub bg_color: Option, /// override the primary container bg color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub primary_container_bg: Option, /// override the secontary container bg color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub secondary_container_bg: Option, /// override the text tint for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub text_tint: Option, /// override the accent color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub accent: Option, /// override the success color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub success: Option, /// override the warning color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub warning: Option, /// override the destructive color for the builder - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub destructive: Option, /// enabled blurred transparency - pub frosted: BlurStrength, + 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 - #[serde(with = "color_serde_option")] - #[cosmic_config_entry(with = ColorReprOption)] pub window_hint: Option, - /// frosted windows - pub frosted_windows: bool, - /// frosted system interface - pub frosted_system_interface: bool, - /// frosted panel - pub frosted_panel: bool, - /// frosted applet popups - pub frosted_applets: bool, } impl Default for ThemeBuilder { @@ -895,15 +825,11 @@ impl Default for ThemeBuilder { success: Default::default(), warning: Default::default(), destructive: Default::default(), - frosted: BlurStrength::default(), + is_frosted: false, // cosmic-comp theme settings gaps: (0, 8), active_hint: 3, window_hint: None, - frosted_windows: false, - frosted_system_interface: false, - frosted_panel: false, - frosted_applets: false, } } } @@ -1045,15 +971,9 @@ impl ThemeBuilder { gaps, active_hint, window_hint, - frosted, - frosted_windows, - frosted_system_interface, - frosted_panel, - frosted_applets, + is_frosted, } = self; - let container_alpha = frosted.alpha(); - let is_dark = palette.is_dark(); let is_high_contrast = palette.is_high_contrast(); @@ -1108,10 +1028,6 @@ impl ThemeBuilder { let step_array = steps(bg, NonZeroUsize::new(100).unwrap()); let bg_index = color_index(bg, step_array.len()); - let mut transparent_bg = bg; - transparent_bg.alpha = container_alpha; - let transparent_bg_steps_array = steps(transparent_bg, NonZeroUsize::new(100).unwrap()); - let mut component_hovered_overlay = if bg_index < 91 { control_steps_array[10] } else { @@ -1138,22 +1054,8 @@ impl ThemeBuilder { text_steps_array.as_deref(), ); - let transparent_bg_component = get_surface_color( - bg_index, - 8, - &transparent_bg_steps_array, - is_dark, - &p_ref.neutral_2, - ); - let on_transparent_bg_component = get_text( - color_index(transparent_bg_component, transparent_bg_steps_array.len()), - &transparent_bg_steps_array, - &control_steps_array[8], - text_steps_array.as_deref(), - ); - let primary = { - let mut container_bg = if let Some(primary_container_bg_color) = primary_container_bg { + let container_bg = if let Some(primary_container_bg_color) = primary_container_bg { primary_container_bg_color } else { get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1]) @@ -1200,45 +1102,6 @@ impl ThemeBuilder { is_high_contrast, ) }; - let transparent_primary = { - let mut container_bg = if let Some(primary_container_bg_color) = primary_container_bg { - primary_container_bg_color - } else { - get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1]) - }; - container_bg.alpha = (container_alpha + if is_dark { 0.3 } else { 0.25 }).min(1.0); - - let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap()); - let base_index: usize = color_index(container_bg, step_array.len()); - let component_base = - get_surface_color(base_index, 6, &step_array, is_dark, &control_steps_array[3]); - - Container::new( - Component::component( - component_base, - accent, - get_text( - color_index(component_base, step_array.len()), - &step_array, - &control_steps_array[8], - text_steps_array.as_deref(), - ), - Srgba::new(0., 0., 0., 0.0), - Srgba::new(0., 0., 0., 0.0), - is_high_contrast, - control_steps_array[8], - ), - container_bg, - get_text( - base_index, - &step_array, - &control_steps_array[8], - text_steps_array.as_deref(), - ), - get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), - is_high_contrast, - ) - }; let accent_text = if is_dark { (primary.base.relative_contrast(accent.color) < 4.).then(|| { @@ -1469,75 +1332,10 @@ impl ThemeBuilder { gaps, active_hint, window_hint, - frosted, + is_frosted, accent_text, control_tint: neutral_tint, text_tint, - frosted_windows, - frosted_system_interface, - frosted_panel, - frosted_applets, - transparent_background: Container::new( - Component::component( - transparent_bg_component, - accent, - on_transparent_bg_component, - Srgba::new(0., 0., 0., 0.0), - Srgba::new(0., 0., 0., 0.0), - is_high_contrast, - control_steps_array[8], - ), - transparent_bg, - get_text( - bg_index, - &transparent_bg_steps_array, - &control_steps_array[8], - text_steps_array.as_deref(), - ), - get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]), - is_high_contrast, - ), - transparent_primary, - transparent_secondary: { - let mut container_bg = if let Some(secondary_container_bg) = secondary_container_bg - { - secondary_container_bg - } else { - get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2]) - }; - container_bg.alpha = (container_alpha + if is_dark { 0.6 } else { 0.5 }).min(1.0); - - let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap()); - let base_index = color_index(container_bg, step_array.len()); - let secondary_component = - get_surface_color(base_index, 3, &step_array, is_dark, &control_steps_array[4]); - - Container::new( - Component::component( - secondary_component, - accent, - get_text( - color_index(secondary_component, step_array.len()), - &step_array, - &control_steps_array[8], - text_steps_array.as_deref(), - ), - Srgba::new(0., 0., 0., 0.0), - Srgba::new(0., 0., 0., 0.0), - is_high_contrast, - control_steps_array[8], - ), - container_bg, - get_text( - base_index, - &step_array, - &control_steps_array[8], - text_steps_array.as_deref(), - ), - get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), - is_high_contrast, - ) - }, }; theme.spacing = spacing; theme.corner_radii = corner_radii; @@ -1556,73 +1354,3 @@ impl ThemeBuilder { Config::new(LIGHT_THEME_BUILDER_ID, Self::VERSION) } } - -/// Actual blur radius is decided by cosmic-comp, -/// but this represents the strength of the blur effect. -#[allow(missing_docs)] -#[repr(u8)] -#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] -pub enum BlurStrength { - ExtremelyLow, - ExtremelyLow2, - VeryLow, - VeryLow2, - Low, - Low2, - #[default] - Medium, - Medium2, - High, - High2, - VeryHigh, - VeryHigh2, - ExtremelyHigh, - ExtremelyHigh2, -} - -impl BlurStrength { - /// Get the alpha value corresponding to the blur strength - /// Lower alpha values correspond to stronger blur effects, and higher alpha values correspond to weaker blur effects. The mapping is as follows: - pub fn alpha(&self) -> f32 { - match self { - Self::ExtremelyLow => 0.90, - Self::ExtremelyLow2 => 0.85, - Self::VeryLow => 0.8, - Self::VeryLow2 => 0.75, - Self::Low => 0.7, - Self::Low2 => 0.65, - Self::Medium => 0.6, - Self::Medium2 => 0.55, - Self::High => 0.5, - Self::High2 => 0.45, - Self::VeryHigh => 0.4, - Self::VeryHigh2 => 0.35, - Self::ExtremelyHigh => 0.25, - Self::ExtremelyHigh2 => 0.2, - } - } -} - -impl TryFrom for BlurStrength { - type Error = (); - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(BlurStrength::ExtremelyLow), - 1 => Ok(BlurStrength::ExtremelyLow2), - 2 => Ok(BlurStrength::VeryLow), - 3 => Ok(BlurStrength::VeryLow2), - 4 => Ok(BlurStrength::Low), - 5 => Ok(BlurStrength::Low2), - 6 => Ok(BlurStrength::Medium), - 7 => Ok(BlurStrength::Medium2), - 8 => Ok(BlurStrength::High), - 9 => Ok(BlurStrength::High2), - 10 => Ok(BlurStrength::VeryHigh), - 11 => Ok(BlurStrength::VeryHigh2), - 12 => Ok(BlurStrength::ExtremelyHigh), - 13 => Ok(BlurStrength::ExtremelyHigh2), - _ => Err(()), - } - } -} diff --git a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap index 40363f95..ae2bcb66 100644 --- a/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap +++ b/cosmic-theme/src/output/snapshots/cosmic_theme__output__qt_output__tests__light_default_kcolorscheme.snap @@ -81,7 +81,7 @@ ForegroundPositive=0,87,44 ForegroundVisited=0,82,90 [Colors:Selection] -BackgroundAlternate=108,149,153 +BackgroundAlternate=108,149,152 BackgroundNormal=0,82,90 DecorationFocus=0,82,90 DecorationHover=0,82,90 diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index c494238f..7a6083e0 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -21,4 +21,5 @@ features = [ "single-instance", "surface-message", "multi-window", + "wgpu", ] diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index bceece6e..f6e571e0 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -200,7 +200,7 @@ impl cosmic::Application for App { .map_or("No page selected", String::as_str); let centered = widget::container( - widget::column::with_capacity(5) + widget::column::with_capacity(14) .push(widget::text::body(page_content)) .push( widget::text_input::text_input("", &self.input_1) @@ -223,6 +223,7 @@ impl cosmic::Application for App { .on_clear(Message::Ignore), ) .push(widget::progress_bar::circular::Circular::new().size(50.0)) + .push(widget::progress_bar::circular::Circular::new().size(20.0)) .push( widget::progress_bar::linear::Linear::new() .girth(10.0) diff --git a/i18n/de/libcosmic.ftl b/i18n/de/libcosmic.ftl index 238000f5..2d3704a6 100644 --- a/i18n/de/libcosmic.ftl +++ b/i18n/de/libcosmic.ftl @@ -6,7 +6,7 @@ links = Links developers = Entwickler(innen) designers = Designer(innen) artists = Künstler(innen) -translators = Übersetzer*innen +translators = Übersetzer(innen) documenters = Dokumentierer(innen) # Calendar january = Januar { $year } diff --git a/i18n/eu/libcosmic.ftl b/i18n/eu/libcosmic.ftl new file mode 100644 index 00000000..e69de29b diff --git a/i18n/kab/libcosmic.ftl b/i18n/kab/libcosmic.ftl index e69de29b..6eac2bc7 100644 --- a/i18n/kab/libcosmic.ftl +++ b/i18n/kab/libcosmic.ftl @@ -0,0 +1,33 @@ +close = Mdel +license = Turagt +links = Iseɣwan +developers = Ineflayen +artists = Inaẓuren +translators = Imsuqlen +january = Yennayer { $year } +february = Fuṛar { $year } +march = Meɣres { $year } +april = Yebrir { $year } +may = Mayyu { $year } +june = Yunyu { $year } +july = Yulyu { $year } +august = Ɣuct { $year } +september = Ctembeṛ { $year } +october = Tubeṛ { $year } +november = Wambeṛ { $year } +december = Dujembeṛ { $year } +documenters = Imeskaren +monday = Arim +mon = Ari +tuesday = Aram +tue = Ara +wednesday = Ahad +wed = Aha +thursday = Amhad +thu = Amh +friday = Sem +fri = Sm +saturday = Sed +sat = Sd +sunday = Acer +sun = Ace diff --git a/i18n/ko/libcosmic.ftl b/i18n/ko/libcosmic.ftl index 8d499756..6cc0adbc 100644 --- a/i18n/ko/libcosmic.ftl +++ b/i18n/ko/libcosmic.ftl @@ -2,26 +2,33 @@ february = { $year }년 2월 close = 닫기 documenters = 문서 작성자 november = { $year }년 11월 -friday = 금 -tuesday = 화 +friday = 금요일 +tuesday = 화요일 may = { $year }년 5월 -wednesday = 수 +wednesday = 수요일 april = { $year }년 4월 -monday = 월 +monday = 월요일 translators = 번역가 artists = 아티스트 license = 라이선스 december = { $year }년 12월 -sunday = 일 +sunday = 일요일 links = 링크 march = { $year }년 3월 june = { $year }년 6월 -saturday = 토 +saturday = 토요일 august = { $year }년 8월 developers = 개발자 july = { $year }년 7월 -thursday = 목 +thursday = 목요일 september = { $year }년 9월 designers = 디자이너 october = { $year }년 10월 january = { $year }년 1월 +mon = 월 +tue = 화 +wed = 수 +thu = 목 +fri = 금 +sat = 토 +sun = 일 diff --git a/i18n/zh-Hant/libcosmic.ftl b/i18n/zh-Hant/libcosmic.ftl index e69de29b..8c9b201c 100644 --- a/i18n/zh-Hant/libcosmic.ftl +++ b/i18n/zh-Hant/libcosmic.ftl @@ -0,0 +1,34 @@ +close = 關閉 +developers = 開發人員 +designers = 設計人員 +artists = 美編設計 +translators = 翻譯人員 +documenters = 文件編輯人員 +january = { $year } 年 1 月 +monday = 星期一 +tuesday = 星期二 +wednesday = 星期三 +thursday = 星期四 +friday = 星期五 +saturday = 星期六 +sunday = 星期日 +mon = 週一 +tue = 週二 +wed = 週三 +thu = 週四 +fri = 週五 +sat = 週六 +sun = 週日 +license = 授權 +links = 連結 +february = { $year } 年 2 月 +march = { $year } 年 3 月 +april = { $year } 年 4 月 +may = { $year } 年 5 月 +june = { $year } 年 6 月 +july = { $year } 年 7 月 +august = { $year } 年 8 月 +september = { $year } 年 9 月 +october = { $year } 年 10 月 +november = { $year } 年 11 月 +december = { $year } 年 12 月 diff --git a/iced b/iced index 0a093b3a..78caabba 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 0a093b3ab0d5ad1b3ad6b457c1715880276e0ce1 +Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece diff --git a/src/app/action.rs b/src/app/action.rs index f894bd7b..fb982acb 100644 --- a/src/app/action.rs +++ b/src/app/action.rs @@ -64,8 +64,6 @@ pub enum Action { Unfocus(iced::window::Id), /// Windowing system initialized WindowingSystemInitialized, - /// Blur support enabled - BlurEnabled, /// Updates the window maximized state WindowMaximized(iced::window::Id, bool), /// Updates the tracked window geometry. diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index c22b8dd6..030ed041 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -6,7 +6,6 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; use super::{Action, Application, ApplicationExt, Subscription}; -use crate::core::AppType; use crate::theme::{THEME, Theme, ThemeType}; use crate::{Core, Element, keyboard_nav}; #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -95,7 +94,6 @@ pub struct Cosmic { >, pub tracked_windows: HashSet, pub opened_surfaces: HashMap, - blur_enabled: bool, } impl Cosmic @@ -117,8 +115,8 @@ where ( Self::new(model), Task::batch([ - iced_runtime::window::run_with_handle(id, init_windowing_system), command, + iced_runtime::window::run_with_handle(id, init_windowing_system), ]), ) } @@ -462,9 +460,6 @@ where )) => { return Some(Action::WindowState(id, s)); } - wayland::Event::BlurEnabled => { - return Some(Action::BlurEnabled); - } _ => (), } } @@ -678,13 +673,34 @@ impl Cosmic { state.intersects(WindowState::MAXIMIZED | WindowState::FULLSCREEN); } if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; let theme = THEME.lock().unwrap(); + let t = theme.cosmic(); + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, + }; let rounded = !self.app.core().window.sharp_corners; - - let cur_rad = self.app.core().app_type.corners(&theme, rounded); - return Task::batch([corner_radius(id, Some(cur_rad)).discard()]); + return Task::batch([corner_radius( + id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard()]); } } @@ -757,61 +773,18 @@ impl Cosmic { if a.distance_squared(*t_inner.accent_color()) > 0.00001 { theme = Theme::system(Arc::new(t_inner.with_accent(a))); } - } - } - - let new_blur = self.blur_enabled && { - let t = theme.cosmic(); - match self.app.core().app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - theme.transparent = new_blur; - let mut guard = THEME.lock().unwrap(); - guard.set_theme(theme.theme_type); - guard.transparent = new_blur; - drop(guard); - - let core = self.app.core(); - if core.auto_blur { - let mut cmds = Vec::with_capacity(1 + self.tracked_windows.len()); - let blur = if new_blur { - iced::window::enable_blur - } else { - iced::window::disable_blur }; - cmds.push(blur( - self.app - .core() - .main_window_id() - .unwrap_or(window::Id::RESERVED), - )); - for id in &self.tracked_windows { - cmds.push(blur(*id)); - } - return Task::batch(cmds); } + + THEME.lock().unwrap().set_theme(theme.theme_type); } - Action::SystemThemeChange(keys, mut theme) => { + Action::SystemThemeChange(keys, theme) => { let cur_is_dark = THEME.lock().unwrap().theme_type.is_dark(); // Ignore updates if the current theme mode does not match. if cur_is_dark != theme.cosmic().is_dark { return iced::Task::none(); } - // update transparent - let new_blur = self.blur_enabled && { - let t = theme.cosmic(); - match self.app.core().app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - theme.transparent = new_blur; - let cmd = self.app.system_theme_update(&keys, theme.cosmic()); // Record the last-known system theme in event that the current theme is custom. self.app.core_mut().system_theme = theme.clone(); @@ -835,38 +808,89 @@ impl Cosmic { } else { theme }; - new_theme.transparent = new_blur; new_theme.theme_type.prefer_dark(prefer_dark); cosmic_theme.set_theme(new_theme.theme_type); - cosmic_theme.transparent = new_blur; - #[cfg(all(feature = "wayland", target_os = "linux"))] if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; let t = cosmic_theme.cosmic(); + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, + }; + let rounded = !self.app.core().window.sharp_corners; - - let cur_rad = self.app.core().app_type.corners(&cosmic_theme, rounded); - // Update radius for the main window let main_window_id = self .app .core() .main_window_id() .unwrap_or(window::Id::RESERVED); - let mut cmds = - vec![corner_radius(main_window_id, Some(cur_rad)).discard()]; + let mut cmds = vec![ + corner_radius( + main_window_id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ]; // Update radius for each tracked view with the window surface type for (id, (_, surface_type, _)) in self.surface_views.iter() { - let cur_rad = corners(*surface_type, rounded, &cosmic_theme); - cmds.push(corner_radius(*id, Some(cur_rad)).discard()); + if let SurfaceIdWrapper::Window(_) = surface_type { + cmds.push( + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ); + } } // Update radius for all tracked windows - for id in &self.tracked_windows { - cmds.push(corner_radius(*id, Some(cur_rad)).discard()); + for id in self.tracked_windows.iter() { + cmds.push( + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ); } return Task::batch(cmds); @@ -897,7 +921,6 @@ impl Cosmic { } { return iced::Task::none(); } - let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)]; let core = self.app.core_mut(); @@ -926,15 +949,6 @@ impl Cosmic { } else { new_theme }; - let new_blur = self.blur_enabled && { - let t = new_theme.cosmic(); - match core.app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - new_theme.transparent = new_blur; core.system_theme = new_theme.clone(); { @@ -943,14 +957,21 @@ impl Cosmic { // 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(new_theme.theme_type); - cosmic_theme.transparent = new_blur; #[cfg(all(feature = "wayland", target_os = "linux"))] if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; + let t = cosmic_theme.cosmic(); + + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, + }; let rounded = !self.app.core().window.sharp_corners; - let cur_rad = - self.app.core().app_type.corners(&cosmic_theme, rounded); // Update radius for the main window let main_window_id = self @@ -958,35 +979,64 @@ impl Cosmic { .core() .main_window_id() .unwrap_or(window::Id::RESERVED); - let mut cmds = - vec![corner_radius(main_window_id, Some(cur_rad)).discard()]; + let mut cmds = vec![ + corner_radius( + main_window_id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ]; // Update radius for each tracked view with the window surface type for (id, (_, surface_type, _)) in self.surface_views.iter() { - let cur_rad = corners(*surface_type, rounded, &cosmic_theme); - cmds.push(corner_radius(*id, Some(cur_rad)).discard()); + if let SurfaceIdWrapper::Window(_) = surface_type { + cmds.push( + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ); + } } // Update radius for all tracked windows - for id in &self.tracked_windows { - cmds.push(corner_radius(*id, Some(cur_rad)).discard()); - } - - let core = self.app.core(); - if core.auto_blur { - let blur = if cosmic_theme.transparent { - iced::window::enable_blur - } else { - iced::window::disable_blur - }; - - cmds.push(blur( - self.app - .core() - .main_window_id() - .unwrap_or(window::Id::RESERVED), - )); - for id in &self.tracked_windows { - cmds.push(blur(*id)); - } + for id in self.tracked_windows.iter() { + cmds.push( + corner_radius( + *id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), + ); } return Task::batch(cmds); @@ -1009,7 +1059,7 @@ impl Cosmic { #[allow(clippy::used_underscore_binding)] _token, ), - ); + ) } #[cfg(not(all(feature = "wayland", target_os = "linux")))] @@ -1086,48 +1136,18 @@ impl Cosmic { if changed { core.theme_sub_counter += 1; - let mut new_theme = if is_dark { + let new_theme = if is_dark { crate::theme::system_dark() } else { crate::theme::system_light() }; - if let ThemeType::System { .. } = new_theme.theme_type { - let new_blur = self.blur_enabled && { - let t = new_theme.cosmic(); - match core.app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - new_theme.transparent = new_blur; - } core.system_theme = new_theme.clone(); { let mut cosmic_theme = THEME.lock().unwrap(); // Only apply update if the theme is set to load a system theme if let ThemeType::System { theme: _, .. } = cosmic_theme.theme_type { - let mut cmds = Vec::with_capacity(1 + self.tracked_windows.len()); - - if core.auto_blur { - let blur = if cosmic_theme.transparent { - iced::window::enable_blur - } else { - iced::window::disable_blur - }; - cmds.push(blur( - self.app - .core() - .main_window_id() - .unwrap_or(window::Id::RESERVED), - )); - for id in &self.tracked_windows { - cmds.push(blur(*id)); - } - } cosmic_theme.set_theme(new_theme.theme_type); - return Task::batch(cmds); } } } @@ -1229,91 +1249,43 @@ impl Cosmic { Action::Opened(id) => { #[cfg(all(feature = "wayland", target_os = "linux"))] if self.app.core().sync_window_border_radii_to_theme() { + use iced_runtime::platform_specific::wayland::CornerRadius; use iced_winit::platform_specific::commands::corner_radius::corner_radius; - let mut theme = THEME.lock().unwrap(); - + let theme = THEME.lock().unwrap(); + let t = theme.cosmic(); + let radii = t.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); + let cur_rad = CornerRadius { + top_left: radii[0].round() as u32, + top_right: radii[1].round() as u32, + bottom_right: radii[2].round() as u32, + bottom_left: radii[3].round() as u32, + }; // TODO do we need per window sharp corners? let rounded = !self.app.core().window.sharp_corners; - let core = self.app.core(); - let new_blur = self.blur_enabled && { - let t = theme.cosmic(); - match self.app.core().app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - theme.transparent = new_blur; - let blur_cmd = if core.auto_blur { - let blur = if new_blur { - iced::window::enable_blur - } else { - iced::window::disable_blur - }; - let mut cmds = Vec::with_capacity(1 + self.tracked_windows.len()); - cmds.push(blur(id)); - Task::batch(cmds) - } else { - Task::none() - }; - let corner_task = if let Some(s) = self.surface_views.get(&id) { - corner_radius(id, Some(corners(s.1, rounded, &theme))).discard() - } else if id - == self - .app - .core() - .main_window_id() - .unwrap_or(window::Id::RESERVED) - || self.tracked_windows.contains(&id) - { - corner_radius(id, Some(self.app.core().app_type.corners(&theme, rounded))) - .discard() - } else { - Task::none() - }; return Task::batch([ - blur_cmd, - corner_task, + corner_radius( + id, + if rounded { + Some(cur_rad) + } else { + let rad_0 = t.radius_0(); + Some(CornerRadius { + top_left: rad_0[0].round() as u32, + top_right: rad_0[1].round() as u32, + bottom_right: rad_0[2].round() as u32, + bottom_left: rad_0[3].round() as u32, + }) + }, + ) + .discard(), iced_runtime::window::run_with_handle(id, init_windowing_system), ]); } return iced_runtime::window::run_with_handle(id, init_windowing_system); } - Action::BlurEnabled => { - // TODO do this after blur event confirms support instead of for all wayland windows - let core = self.app.core(); - self.blur_enabled = true; - let new_blur = self.blur_enabled && { - let t = core.system_theme.cosmic(); - match self.app.core().app_type() { - crate::core::AppType::Window => t.frosted_windows, - crate::core::AppType::System => t.frosted_system_interface, - crate::core::AppType::Applet => t.frosted_applets, - } - }; - let mut t = THEME.lock().unwrap(); - - t.transparent = matches!(&t.theme_type, ThemeType::System { .. }) && new_blur; - - if core.auto_blur { - let blur = if new_blur { - iced::window::enable_blur - } else { - iced::window::disable_blur - }; - let mut cmds = Vec::with_capacity(1 + self.tracked_windows.len()); - if let Some(main_id) = core.main_window_id() { - cmds.push(blur(main_id)); - } - for id in &self.tracked_windows { - cmds.push(blur(*id)); - } - return Task::batch(cmds); - } - } - _ => (), + _ => {} } iced::Task::none() @@ -1328,7 +1300,6 @@ impl Cosmic { surface_views: HashMap::new(), tracked_windows: HashSet::new(), opened_surfaces: HashMap::new(), - blur_enabled: false, } } @@ -1403,39 +1374,3 @@ impl Cosmic { .discard() } } - -#[cfg(all(feature = "wayland", target_os = "linux"))] -fn corners( - surface_type: SurfaceIdWrapper, - rounded: bool, - theme: &Theme, -) -> iced_runtime::platform_specific::wayland::CornerRadius { - let theme = theme.cosmic(); - if let SurfaceIdWrapper::Popup(_) = surface_type { - let radius_m = theme.radius_m(); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_m[0].round() as u32, - top_right: radius_m[1].round() as u32, - bottom_right: radius_m[2].round() as u32, - bottom_left: radius_m[3].round() as u32, - } - } else if let SurfaceIdWrapper::Window(_) = surface_type - && rounded - { - let radius_0 = theme.radius_0(); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_0[0].round() as u32, - top_right: radius_0[1].round() as u32, - bottom_right: radius_0[2].round() as u32, - bottom_left: radius_0[3].round() as u32, - } - } else { - let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_s[0].round() as u32, - top_right: radius_s[1].round() as u32, - bottom_right: radius_s[2].round() as u32, - bottom_left: radius_s[3].round() as u32, - } - } -} diff --git a/src/app/mod.rs b/src/app/mod.rs index fedbcef7..f78beac7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -128,6 +128,9 @@ impl BootFn, crate::Action(settings: Settings, flags: App::Flags) -> iced::Result { + #[cfg(feature = "desktop")] + image_extras::register(); + #[cfg(all(target_env = "gnu", not(target_os = "windows")))] if let Some(threshold) = settings.default_mmap_threshold { crate::malloc::limit_mmap_threshold(threshold); @@ -194,6 +197,9 @@ where App::Flags: CosmicFlags, App::Message: Clone + std::fmt::Debug + Send + 'static, { + #[cfg(feature = "desktop")] + image_extras::register(); + use std::collections::HashMap; let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok(); @@ -820,7 +826,7 @@ impl ApplicationExt for App { let cosmic = theme.cosmic(); container::Style { background: Some(iced::Background::Color( - cosmic.background(theme.transparent).base.into(), + cosmic.background.base.into(), )), border: iced::Border { radius: [ @@ -849,7 +855,7 @@ impl ApplicationExt for App { container::Style { background: if content_container { Some(iced::Background::Color( - theme.cosmic().background(theme.transparent).base.into(), + theme.cosmic().background.base.into(), )) } else { None diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 400229f4..48721e1c 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -227,7 +227,7 @@ impl Context { let icon = widget::icon(icon) .class(if symbolic { theme::Svg::Custom(Rc::new(|theme| iced_widget::svg::Style { - color: Some(theme.cosmic().background(theme.transparent).on.into()), + color: Some(theme.cosmic().background.on.into()), })) } else { theme::Svg::default() @@ -378,18 +378,16 @@ impl Context { Container::::new(content).style(|theme| { let cosmic = theme.cosmic(); let corners = cosmic.corner_radii; - let mut bg = cosmic.background(theme.transparent).base; - bg.alpha = (bg.alpha + if cosmic.is_dark { 0.6 } else { 0.5 }).min(1.); iced_widget::container::Style { - text_color: Some(cosmic.background(theme.transparent).on.into()), - background: Some(Color::from(bg).into()), + text_color: Some(cosmic.background.on.into()), + background: Some(Color::from(cosmic.background.base).into()), border: iced::Border { radius: corners.radius_m.into(), width: 1.0, - color: cosmic.background(theme.transparent).divider.into(), + color: cosmic.background.divider.into(), }, shadow: Shadow::default(), - icon_color: Some(cosmic.background(theme.transparent).on.into()), + icon_color: Some(cosmic.background.on.into()), snap: true, } }), @@ -567,7 +565,6 @@ pub fn run(flags: App::Flags) -> iced::Result { core.window.show_maximize = false; core.window.show_minimize = false; core.window.use_template = false; - core.app_type = crate::core::AppType::Applet; window_settings.decorations = false; window_settings.exit_on_close_request = true; diff --git a/src/command.rs b/src/command.rs index 6bb16e8d..1d6f635c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -65,6 +65,7 @@ pub fn file_transfer_send( /// Returns a list of file paths. #[cfg(feature = "xdg-portal")] pub fn file_transfer_receive(key: String) -> iced::Task>> { + dbg!(&key); iced::Task::future(async move { let file_transfer = ashpd::documents::FileTransfer::new().await?; file_transfer.retrieve_files(&key).await diff --git a/src/core.rs b/src/core.rs index e725547f..970a5351 100644 --- a/src/core.rs +++ b/src/core.rs @@ -101,59 +101,6 @@ pub struct Core { #[cfg(all(feature = "wayland", target_os = "linux"))] pub(crate) sync_window_border_radii_to_theme: bool, - - pub(crate) auto_blur: bool, - - pub(crate) app_type: AppType, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum AppType { - /// A regular application - Window, - /// A system application - System, - /// An applet - Applet, -} - -impl AppType { - /// Calculate suggested corners for each app type main window - #[cfg(all(feature = "wayland", target_os = "linux"))] - pub fn corners( - &self, - theme: &Theme, - rounded: bool, - ) -> iced_runtime::platform_specific::wayland::CornerRadius { - let theme = theme.cosmic(); - if let Self::Applet = self { - let radius_l = theme.radius_l(); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_l[0].round() as u32, - top_right: radius_l[1].round() as u32, - bottom_right: radius_l[2].round() as u32, - bottom_left: radius_l[3].round() as u32, - } - } else if let Self::Window = self - && rounded - { - let radius_0 = theme.radius_0(); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_0[0].round() as u32, - top_right: radius_0[1].round() as u32, - bottom_right: radius_0[2].round() as u32, - bottom_left: radius_0[3].round() as u32, - } - } else { - let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); - iced_runtime::platform_specific::wayland::CornerRadius { - top_left: radius_s[0].round() as u32, - top_right: radius_s[1].round() as u32, - bottom_right: radius_s[2].round() as u32, - bottom_left: radius_s[3].round() as u32, - } - } - } } impl Default for Core { @@ -214,8 +161,6 @@ impl Default for Core { menu_bars: HashMap::new(), #[cfg(all(feature = "wayland", target_os = "linux"))] sync_window_border_radii_to_theme: true, - auto_blur: true, - app_type: AppType::Window, } } } @@ -557,20 +502,4 @@ impl Core { pub fn sync_window_border_radii_to_theme(&self) -> bool { self.sync_window_border_radii_to_theme } - - pub fn set_auto_blur(&mut self, auto_blur: bool) { - self.auto_blur = auto_blur; - } - - pub fn auto_blur(&self) -> bool { - self.auto_blur - } - - pub fn set_app_type(&mut self, app_type: AppType) { - self.app_type = app_type; - } - - pub fn app_type(&self) -> AppType { - self.app_type - } } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index baac5e52..093bac05 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -50,7 +50,6 @@ pub static TRANSPARENT_COMPONENT: LazyLock = LazyLock::new(|| Compone pub(crate) static THEME: Mutex = Mutex::new(Theme { theme_type: ThemeType::Dark, layer: cosmic_theme::Layer::Background, - transparent: false, }); /// Currently-defined theme. @@ -214,7 +213,6 @@ impl ThemeType { pub struct Theme { pub theme_type: ThemeType, pub layer: cosmic_theme::Layer, - pub transparent: bool, } impl Theme { @@ -285,9 +283,9 @@ impl Theme { /// can be used in a component that is intended to be a child of a `CosmicContainer` pub fn current_container(&self) -> &cosmic_theme::Container { match self.layer { - cosmic_theme::Layer::Background => &self.cosmic().background(self.transparent), - cosmic_theme::Layer::Primary => &self.cosmic().primary(self.transparent), - cosmic_theme::Layer::Secondary => &self.cosmic().secondary(self.transparent), + cosmic_theme::Layer::Background => &self.cosmic().background, + cosmic_theme::Layer::Primary => &self.cosmic().primary, + cosmic_theme::Layer::Secondary => &self.cosmic().secondary, } } @@ -309,7 +307,7 @@ impl DefaultStyle for Theme { fn default_style(&self) -> Appearance { let cosmic = self.cosmic(); Appearance { - icon_color: cosmic.bg_color().into(), + icon_color: cosmic.on_bg_color().into(), background_color: cosmic.bg_color().into(), text_color: cosmic.on_bg_color().into(), } diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index b15bc364..bb52d9a6 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -27,7 +27,7 @@ pub enum Button { IconVertical, Image, Link, - ListItem, + ListItem([f32; 4]), MenuFolder, MenuItem, MenuRoot, @@ -128,34 +128,33 @@ pub fn appearance( let (background, _, _) = color(&cosmic.text_button); appearance.background = Some(Background::Color(background)); - appearance.icon_color = Some(cosmic.background(theme.transparent).on.into()); - appearance.text_color = Some(cosmic.background(theme.transparent).on.into()); + appearance.icon_color = Some(cosmic.background.on.into()); + appearance.text_color = Some(cosmic.background.on.into()); corner_radii = &cosmic.corner_radii.radius_0; } Button::AppletIcon => { let (background, _, _) = color(&cosmic.text_button); appearance.background = Some(Background::Color(background)); - appearance.icon_color = Some(cosmic.background(theme.transparent).on.into()); - appearance.text_color = Some(cosmic.background(theme.transparent).on.into()); + appearance.icon_color = Some(cosmic.background.on.into()); + appearance.text_color = Some(cosmic.background.on.into()); } Button::MenuFolder => { // Menu folders cannot be disabled, ignore customized icon and text color - let component = &cosmic.background(theme.transparent).component; + let component = &cosmic.background.component; let (background, _, _) = color(component); appearance.background = Some(Background::Color(background)); appearance.icon_color = Some(component.on.into()); appearance.text_color = Some(component.on.into()); corner_radii = &cosmic.corner_radii.radius_s; } - Button::ListItem => { - corner_radii = &[0.0; 4]; - let (background, text, icon) = color(&cosmic.background(theme.transparent).component); + Button::ListItem(radii) => { + corner_radii = radii; + let (background, text, icon) = color(&cosmic.background.component); if selected { - appearance.background = Some(Background::Color( - cosmic.primary(theme.transparent).component.hover.into(), - )); + 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()); } else { @@ -165,7 +164,7 @@ pub fn appearance( } } Button::MenuItem => { - let (background, text, icon) = color(&cosmic.background(theme.transparent).component); + let (background, text, icon) = color(&cosmic.background.component); appearance.background = Some(Background::Color(background)); appearance.icon_color = icon; appearance.text_color = text; @@ -198,7 +197,7 @@ impl Catalog for crate::Theme { return active(focused, self); } - appearance(self, focused, selected, false, style, move |component| { + let mut s = appearance(self, focused, selected, false, style, move |component| { let text_color = if matches!( style, Button::Icon | Button::IconVertical | Button::HeaderBar @@ -210,7 +209,15 @@ impl Catalog for crate::Theme { }; (component.base.into(), text_color, text_color) - }) + }); + + if let Button::ListItem(_) = style { + if !selected { + s.background = None; + } + } + + s } fn disabled(&self, style: &Self::Class) -> Style { @@ -238,7 +245,7 @@ impl Catalog for crate::Theme { return hovered(focused, self); } - appearance( + let mut s = appearance( self, focused || matches!(style, Button::Image), selected, @@ -257,7 +264,15 @@ impl Catalog for crate::Theme { (component.hover.into(), text_color, text_color) }, - ) + ); + + if let Button::ListItem(_) = style { + if !selected { + s.background = None; + } + } + + s } fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style { @@ -281,6 +296,6 @@ impl Catalog for crate::Theme { } fn selection_background(&self) -> Background { - Background::Color(self.cosmic().primary(self.transparent).base.into()) + Background::Color(self.cosmic().primary.base.into()) } } diff --git a/src/theme/style/dropdown.rs b/src/theme/style/dropdown.rs index 56f5536e..cc89a399 100644 --- a/src/theme/style/dropdown.rs +++ b/src/theme/style/dropdown.rs @@ -13,28 +13,18 @@ impl dropdown::menu::StyleSheet for Theme { dropdown::menu::Appearance { text_color: cosmic.on_bg_color().into(), - background: Background::Color( - cosmic.background(self.transparent).component.base.into(), - ), + background: Background::Color(cosmic.background.component.base.into()), border_width: 0.0, border_radius: cosmic.corner_radii.radius_m.into(), border_color: Color::TRANSPARENT, hovered_text_color: cosmic.on_bg_color().into(), - hovered_background: Background::Color( - cosmic.primary(self.transparent).component.hover.into(), - ), + hovered_background: Background::Color(cosmic.primary.component.hover.into()), selected_text_color: cosmic.accent_text_color().into(), - selected_background: Background::Color( - cosmic.primary(self.transparent).component.hover.into(), - ), + selected_background: Background::Color(cosmic.primary.component.hover.into()), - description_color: cosmic - .primary(self.transparent) - .component - .on_disabled - .into(), + description_color: cosmic.primary.component.on_disabled.into(), } } } diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 83066b41..aa6f4b33 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -43,7 +43,7 @@ pub mod application { iced::theme::Style { background_color: cosmic.bg_color().into(), text_color: cosmic.on_bg_color().into(), - icon_color: cosmic.bg_color().into(), + icon_color: cosmic.on_bg_color().into(), } } } @@ -228,11 +228,11 @@ impl iced_checkbox::Catalog for Theme { }, Checkbox::Secondary => iced_checkbox::Style { background: Background::Color(if is_checked { - cosmic.background(self.transparent).component.base.into() + cosmic.background.component.base.into() } else { self.current_container().small_widget.into() }), - icon_color: cosmic.background(self.transparent).on.into(), + icon_color: cosmic.background.on.into(), border: Border { radius: corners.radius_xs.into(), width: if is_checked { 0.0 } else { 1.0 }, @@ -413,13 +413,11 @@ impl<'a> Container<'a> { } #[must_use] - pub fn background(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style { + pub fn background(theme: &cosmic_theme::Theme) -> iced_container::Style { iced_container::Style { - icon_color: Some(Color::from(theme.background(transparent).on)), - text_color: Some(Color::from(theme.background(transparent).on)), - background: Some(iced::Background::Color( - theme.background(transparent).base.into(), - )), + icon_color: Some(Color::from(theme.background.on)), + text_color: Some(Color::from(theme.background.on)), + background: Some(iced::Background::Color(theme.background.base.into())), border: Border { radius: theme.corner_radii.radius_s.into(), ..Default::default() @@ -430,13 +428,11 @@ impl<'a> Container<'a> { } #[must_use] - pub fn primary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style { + pub fn primary(theme: &cosmic_theme::Theme) -> iced_container::Style { iced_container::Style { - icon_color: Some(Color::from(theme.primary(transparent).on)), - text_color: Some(Color::from(theme.primary(transparent).on)), - background: Some(iced::Background::Color( - theme.primary(transparent).base.into(), - )), + icon_color: Some(Color::from(theme.primary.on)), + text_color: Some(Color::from(theme.primary.on)), + background: Some(iced::Background::Color(theme.primary.base.into())), border: Border { radius: theme.corner_radii.radius_s.into(), ..Default::default() @@ -447,13 +443,11 @@ impl<'a> Container<'a> { } #[must_use] - pub fn secondary(theme: &cosmic_theme::Theme, transparent: bool) -> iced_container::Style { + pub fn secondary(theme: &cosmic_theme::Theme) -> iced_container::Style { iced_container::Style { - icon_color: Some(Color::from(theme.secondary(transparent).on)), - text_color: Some(Color::from(theme.secondary(transparent).on)), - background: Some(iced::Background::Color( - theme.secondary(transparent).base.into(), - )), + icon_color: Some(Color::from(theme.secondary.on)), + text_color: Some(Color::from(theme.secondary.on)), + background: Some(iced::Background::Color(theme.secondary.base.into())), border: Border { radius: theme.corner_radii.radius_s.into(), ..Default::default() @@ -484,30 +478,14 @@ impl iced_container::Catalog for Theme { let window_corner_radius = cosmic.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 }); match class { - Container::Transparent => { - let component = &self.current_container().component; - - iced_container::Style { - icon_color: Some(component.on.into()), - text_color: Some(component.on.into()), - background: None, - border: Border { - radius: 0.into(), - ..Default::default() - }, - shadow: Shadow::default(), - snap: true, - } - } + Container::Transparent => iced_container::Style::default(), Container::Custom(f) => f(self), Container::WindowBackground => iced_container::Style { - icon_color: Some(Color::from(cosmic.background(self.transparent).on)), - text_color: Some(Color::from(cosmic.background(self.transparent).on)), - background: Some(iced::Background::Color( - cosmic.background(self.transparent).base.into(), - )), + 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())), border: Border { radius: [ cosmic.corner_radii.radius_0[0], @@ -545,13 +523,12 @@ impl iced_container::Catalog for Theme { let (icon_color, text_color) = if *focused { ( Color::from(cosmic.accent_text_color()), - Color::from(cosmic.background(self.transparent).on), + Color::from(cosmic.background.on), ) } else { use crate::ext::ColorExt; - let unfocused_color = - Color::from(cosmic.background(self.transparent).component.on) - .blend_alpha(cosmic.background(self.transparent).base.into(), 0.5); + let unfocused_color = Color::from(cosmic.background.component.on) + .blend_alpha(cosmic.background.base.into(), 0.5); (unfocused_color, unfocused_color) }; @@ -561,9 +538,7 @@ impl iced_container::Catalog for Theme { background: if *transparent { None } else { - Some(iced::Background::Color( - cosmic.background(self.transparent).base.into(), - )) + Some(iced::Background::Color(cosmic.background.base.into())) }, border: Border { radius: [ @@ -589,23 +564,20 @@ impl iced_container::Catalog for Theme { } Container::ContextDrawer => { - let mut a = Container::primary(cosmic, self.transparent); - if let Some(Background::Color(ref mut color)) = a.background { - color.a = (color.a + if cosmic.is_dark { 0.60 } else { 0.5 }).min(1.); - } + let mut a = Container::primary(cosmic); if cosmic.is_high_contrast { a.border.width = 1.; - a.border.color = cosmic.primary(self.transparent).divider.into(); + a.border.color = cosmic.primary.divider.into(); } a } - Container::Background => Container::background(cosmic, self.transparent), + Container::Background => Container::background(cosmic), - Container::Primary => Container::primary(cosmic, self.transparent), + Container::Primary => Container::primary(cosmic), - Container::Secondary => Container::secondary(cosmic, self.transparent), + Container::Secondary => Container::secondary(cosmic), Container::Dropdown => iced_container::Style { icon_color: None, @@ -637,14 +609,10 @@ impl iced_container::Catalog for Theme { match self.layer { cosmic_theme::Layer::Background => iced_container::Style { - icon_color: Some(Color::from( - cosmic.background(self.transparent).component.on, - )), - text_color: Some(Color::from( - cosmic.background(self.transparent).component.on, - )), + icon_color: Some(Color::from(cosmic.background.component.on)), + text_color: Some(Color::from(cosmic.background.component.on)), background: Some(iced::Background::Color( - cosmic.background(self.transparent).component.base.into(), + cosmic.background.component.base.into(), )), border: Border { radius: cosmic.corner_radii.radius_s.into(), @@ -654,14 +622,10 @@ impl iced_container::Catalog for Theme { snap: true, }, cosmic_theme::Layer::Primary => iced_container::Style { - icon_color: Some(Color::from( - cosmic.primary(self.transparent).component.on, - )), - text_color: Some(Color::from( - cosmic.primary(self.transparent).component.on, - )), + icon_color: Some(Color::from(cosmic.primary.component.on)), + text_color: Some(Color::from(cosmic.primary.component.on)), background: Some(iced::Background::Color( - cosmic.primary(self.transparent).component.base.into(), + cosmic.primary.component.base.into(), )), border: Border { radius: cosmic.corner_radii.radius_s.into(), @@ -671,14 +635,10 @@ impl iced_container::Catalog for Theme { snap: true, }, cosmic_theme::Layer::Secondary => iced_container::Style { - icon_color: Some(Color::from( - cosmic.secondary(self.transparent).component.on, - )), - text_color: Some(Color::from( - cosmic.secondary(self.transparent).component.on, - )), + icon_color: Some(Color::from(cosmic.secondary.component.on)), + text_color: Some(Color::from(cosmic.secondary.component.on)), background: Some(iced::Background::Color( - cosmic.secondary(self.transparent).component.base.into(), + cosmic.secondary.component.base.into(), )), border: Border { radius: cosmic.corner_radii.radius_s.into(), @@ -691,13 +651,11 @@ impl iced_container::Catalog for Theme { } Container::Dialog => iced_container::Style { - icon_color: Some(Color::from(cosmic.primary(self.transparent).on)), - text_color: Some(Color::from(cosmic.primary(self.transparent).on)), - background: Some(iced::Background::Color( - cosmic.primary(self.transparent).base.into(), - )), + icon_color: Some(Color::from(cosmic.primary.on)), + text_color: Some(Color::from(cosmic.primary.on)), + background: Some(iced::Background::Color(cosmic.primary.base.into())), border: Border { - color: cosmic.primary(self.transparent).divider.into(), + color: cosmic.primary.divider.into(), width: 1.0, radius: cosmic.corner_radii.radius_m.into(), }, @@ -839,15 +797,13 @@ impl menu::Catalog for Theme { menu::Style { text_color: cosmic.on_bg_color().into(), - background: Background::Color(cosmic.background(self.transparent).base.into()), + background: Background::Color(cosmic.background.base.into()), border: Border { radius: cosmic.corner_radii.radius_m.into(), ..Default::default() }, selected_text_color: cosmic.accent_text_color().into(), - selected_background: Background::Color( - cosmic.background(self.transparent).component.hover.into(), - ), + selected_background: Background::Color(cosmic.background.component.hover.into()), shadow: Default::default(), } } @@ -885,7 +841,7 @@ impl pick_list::Catalog for Theme { match status { pick_list::Status::Active => appearance, pick_list::Status::Hovered => pick_list::Style { - background: Background::Color(cosmic.background(self.transparent).base.into()), + background: Background::Color(cosmic.background.base.into()), ..appearance }, pick_list::Status::Opened { is_hovered: _ } => appearance, @@ -1081,10 +1037,7 @@ impl progress_bar::Catalog for Theme { }, ) } else { - ( - theme.accent.base, - theme.background(self.transparent).divider, - ) + (theme.accent.base, theme.background.divider) }; let border = Border { radius: theme.corner_radii.radius_xl.into(), @@ -1557,7 +1510,7 @@ impl iced_widget::text_editor::Catalog for Theme { let selection = cosmic.accent.base.into(); let value = cosmic.palette.neutral_9.into(); let placeholder = cosmic.palette.neutral_9.with_alpha(0.7).into(); - let icon: Color = cosmic.background(self.transparent).on.into(); + let icon: Color = cosmic.background.on.into(); // TODO do we need to add icon color back? match status { diff --git a/src/theme/style/menu_bar.rs b/src/theme/style/menu_bar.rs index 421d23d5..ed0e657a 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -64,13 +64,11 @@ impl StyleSheet for Theme { fn appearance(&self, style: &Self::Style) -> Appearance { let cosmic = self.cosmic(); - let component = &cosmic.background(self.transparent).component; - let mut bg = component.base; - bg.alpha = (bg.alpha + if cosmic.is_dark { 0.6 } else { 0.5 }).min(1.); + let component = &cosmic.background.component; match style { MenuBarStyle::Default => Appearance { - background: bg.into(), + background: component.base.into(), border_width: 1.0, bar_border_radius: cosmic.corner_radii.radius_xl, menu_border_radius: cosmic.corner_radii.radius_s.map(|x| x + 2.0), diff --git a/src/widget/about.rs b/src/widget/about.rs index 148af02a..9b21e93a 100644 --- a/src/widget/about.rs +++ b/src/widget/about.rs @@ -1,8 +1,9 @@ use crate::{ Apply, Element, fl, iced::{Alignment, Length}, - widget::{self, space}, + widget::{self, list}, }; +use std::rc::Rc; #[derive(Debug, Default, Clone, derive_setters::Setters)] #[setters(into, strip_option)] @@ -104,19 +105,23 @@ pub fn about<'a, Message: Clone + 'static>( space_xxs, space_m, .. } = crate::theme::spacing(); - let section_button = |name: &'a str, url: &'a str| -> Element<'a, Message> { - widget::row::with_capacity(3) - .push(widget::text(name)) - .push(space::horizontal()) + let svg_accent = Rc::new(|theme: &crate::Theme| widget::svg::Style { + color: Some(theme.cosmic().accent_text_color().into()), + }); + + let section_button = |name: &'a str, url: &'a str| -> list::ListButton<'a, Message> { + widget::row::with_capacity(2) + .push(widget::text::body(name).width(Length::Fill)) .push_maybe( - (!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()), + (!url.is_empty()).then_some( + widget::icon::from_name("link-symbolic") + .icon() + .class(crate::theme::Svg::Custom(svg_accent.clone())), + ), ) .align_y(Alignment::Center) - .apply(widget::button::custom) - .class(crate::theme::Button::Link) + .apply(list::button) .on_press(on_url_press(url)) - .width(Length::Fill) - .into() }; let section = |list: &'a Vec<(String, String)>, title: String| { diff --git a/src/widget/card/style.rs b/src/widget/card/style.rs index d8e95277..0e63e846 100644 --- a/src/widget/card/style.rs +++ b/src/widget/card/style.rs @@ -31,26 +31,16 @@ impl crate::widget::card::style::Catalog for crate::Theme { match self.layer { cosmic_theme::Layer::Background => crate::widget::card::style::Style { - card_1: Background::Color( - cosmic.background(self.transparent).component.hover.into(), - ), - card_2: Background::Color( - cosmic.background(self.transparent).component.pressed.into(), - ), + card_1: Background::Color(cosmic.background.component.hover.into()), + card_2: Background::Color(cosmic.background.component.pressed.into()), }, cosmic_theme::Layer::Primary => crate::widget::card::style::Style { - card_1: Background::Color(cosmic.primary(self.transparent).component.hover.into()), - card_2: Background::Color( - cosmic.primary(self.transparent).component.pressed.into(), - ), + card_1: Background::Color(cosmic.primary.component.hover.into()), + card_2: Background::Color(cosmic.primary.component.pressed.into()), }, cosmic_theme::Layer::Secondary => crate::widget::card::style::Style { - card_1: Background::Color( - cosmic.secondary(self.transparent).component.hover.into(), - ), - card_2: Background::Color( - cosmic.secondary(self.transparent).component.pressed.into(), - ), + card_1: Background::Color(cosmic.secondary.component.hover.into()), + card_2: Background::Color(cosmic.secondary.component.pressed.into()), }, } } diff --git a/src/widget/frames.rs b/src/widget/frames.rs index 056a55ba..a542cec6 100644 --- a/src/widget/frames.rs +++ b/src/widget/frames.rs @@ -14,10 +14,10 @@ use iced_core::image::Renderer as ImageRenderer; use iced_core::mouse::Cursor; use iced_core::widget::{Tree, tree}; use iced_core::{ - Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, Widget, - event, layout, renderer, window, + Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Rotation, Shell, Size, + Widget, event, layout, renderer, window, }; -use iced_widget::image::{self, Handle}; +use iced_widget::image::{self, FilterMethod, Handle}; use image_rs::AnimationDecoder; use image_rs::codecs::gif::GifDecoder; use image_rs::codecs::png::PngDecoder; @@ -146,7 +146,7 @@ impl Frames { match image_type { ImageType::Gif => Self::from_decoder(GifDecoder::new(io::Cursor::new(bytes))?), - ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()), + ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()?), ImageType::WebP => Self::from_decoder(WebPDecoder::new(io::Cursor::new(bytes))?), } } @@ -168,10 +168,10 @@ impl Frames { let first = frames.first().cloned().unwrap(); let total_bytes = frames .iter() - .map(|f| match f.handle.data() { - iced_core::image::Handle::Path(..) => 0, - iced_core::image::Handle::Bytes(_, b) => b.len(), - iced_core::image::Handle::Rgba { pixels, .. } => pixels.len(), + .map(|f| match &f.handle { + Handle::Path(..) => 0, + Handle::Bytes(_, b) => b.len(), + Handle::Rgba { pixels, .. } => pixels.len(), }) .sum::() .try_into() @@ -324,7 +324,11 @@ where &self.frames.first.handle, self.width, self.height, + None, self.content_fit, + Rotation::default(), + false, + [0.0; 4], ) } @@ -371,37 +375,18 @@ where ) { let state = tree.state.downcast_ref::(); - // Pulled from iced_native::widget::::draw - // - // TODO: export iced_native::widget::image::draw as standalone function - { - let Size { width, height } = renderer.dimensions(&state.current.frame.handle); - let image_size = Size::new(width as f32, height as f32); - - let bounds = layout.bounds(); - let adjusted_fit = self.content_fit.fit(image_size, bounds.size()); - - let render = |renderer: &mut Renderer| { - let offset = Vector::new( - (bounds.width - adjusted_fit.width).max(0.0) / 2.0, - (bounds.height - adjusted_fit.height).max(0.0) / 2.0, - ); - - let drawing_bounds = Rectangle { - width: adjusted_fit.width, - height: adjusted_fit.height, - ..bounds - }; - - renderer.draw(state.current.frame.handle.clone(), drawing_bounds + offset); - }; - - if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height { - renderer.with_layer(bounds, render); - } else { - render(renderer); - } - } + iced_widget::image::draw( + renderer, + layout, + &state.current.frame.handle, + None, + iced_core::border::Radius::default(), + self.content_fit, + FilterMethod::default(), + Rotation::default(), + 1.0, + 1.0, + ); } } diff --git a/src/widget/icon/bundle.rs b/src/widget/icon/bundle.rs index 9d0877d0..bb6ce244 100644 --- a/src/widget/icon/bundle.rs +++ b/src/widget/icon/bundle.rs @@ -4,12 +4,12 @@ //! Embedded icons for platforms which do not support icon themes yet. /// Icon bundling is not enabled on unix platforms. -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "macos")))] pub fn get(icon_name: &str) -> Option { None } -#[cfg(not(unix))] +#[cfg(any(not(unix), target_os = "macos"))] /// Get a bundled icon on non-unix platforms. pub fn get(icon_name: &str) -> Option { ICONS @@ -17,5 +17,5 @@ pub fn get(icon_name: &str) -> Option { .map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes))) } -#[cfg(not(unix))] +#[cfg(any(not(unix), target_os = "macos"))] include!(concat!(env!("OUT_DIR"), "/bundled_icons.rs")); diff --git a/src/widget/icon/named.rs b/src/widget/icon/named.rs index 8405e080..dfd66cf5 100644 --- a/src/widget/icon/named.rs +++ b/src/widget/icon/named.rs @@ -52,7 +52,7 @@ impl Named { } } - #[cfg(not(windows))] + #[cfg(all(unix, not(target_os = "macos")))] #[must_use] pub fn path(self) -> Option { let name = &*self.name; @@ -107,7 +107,7 @@ impl Named { result } - #[cfg(windows)] + #[cfg(any(not(unix), target_os = "macos"))] #[must_use] pub fn path(self) -> Option { //TODO: implement icon lookup for Windows diff --git a/src/widget/list/column.rs b/src/widget/list/column.rs deleted file mode 100644 index 945b9140..00000000 --- a/src/widget/list/column.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -use iced_core::Padding; -use iced_widget::container::Catalog; - -use crate::{ - Apply, Element, theme, - widget::{container, divider, space::vertical}, -}; - -#[inline] -pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> { - ListColumn::default() -} - -#[must_use] -pub struct ListColumn<'a, Message> { - spacing: u16, - padding: Padding, - list_item_padding: Padding, - divider_padding: u16, - style: theme::Container<'a>, - children: Vec>, -} - -impl Default for ListColumn<'_, Message> { - fn default() -> Self { - let cosmic_theme::Spacing { - space_xxs, space_m, .. - } = theme::spacing(); - - Self { - spacing: 0, - padding: Padding::from(0), - divider_padding: 16, - list_item_padding: [space_xxs, space_m].into(), - style: theme::Container::List, - children: Vec::with_capacity(4), - } - } -} - -impl<'a, Message: 'static> ListColumn<'a, Message> { - #[inline] - pub fn new() -> Self { - Self::default() - } - - #[allow(clippy::should_implement_trait)] - pub fn add(self, item: impl Into>) -> Self { - #[inline(never)] - fn inner<'a, Message: 'static>( - mut this: ListColumn<'a, Message>, - item: Element<'a, Message>, - ) -> ListColumn<'a, Message> { - if !this.children.is_empty() { - this.children.push( - container(divider::horizontal::default()) - .padding([0, this.divider_padding]) - .into(), - ); - } - - // Ensure a minimum height of 32. - let list_item = crate::widget::row![ - container(item).align_y(iced::Alignment::Center), - vertical().height(iced::Length::Fixed(32.)) - ] - .padding(this.list_item_padding) - .align_y(iced::Alignment::Center); - - this.children.push(list_item.into()); - this - } - - inner(self, item.into()) - } - - #[inline] - pub fn spacing(mut self, spacing: u16) -> Self { - self.spacing = spacing; - self - } - - /// Sets the style variant of this [`Circular`]. - #[inline] - pub fn style(mut self, style: ::Class<'a>) -> Self { - self.style = style; - self - } - - #[inline] - pub fn padding(mut self, padding: impl Into) -> Self { - self.padding = padding.into(); - self - } - - #[inline] - pub fn divider_padding(mut self, padding: u16) -> Self { - self.divider_padding = padding; - self - } - - pub fn list_item_padding(mut self, padding: impl Into) -> Self { - self.list_item_padding = padding.into(); - self - } - - #[must_use] - pub fn into_element(self) -> Element<'a, Message> { - crate::widget::column::with_children(self.children) - .spacing(self.spacing) - .padding(self.padding) - .width(iced::Length::Fill) - .apply(container) - .padding([self.spacing, 0]) - .class(self.style) - .width(iced::Length::Fill) - .into() - } -} - -impl<'a, Message: 'static> From> for Element<'a, Message> { - fn from(column: ListColumn<'a, Message>) -> Self { - column.into_element() - } -} diff --git a/src/widget/list/list_column.rs b/src/widget/list/list_column.rs new file mode 100644 index 00000000..4ef3fc01 --- /dev/null +++ b/src/widget/list/list_column.rs @@ -0,0 +1,213 @@ +// Copyright 2022 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::widget::container::Catalog; +use crate::widget::{button, column, container, divider, row, space::vertical}; +use crate::{Apply, Element, theme}; +use iced::{Length, Padding}; + +/// A button list item for use in a [`ListColumn`]. +pub struct ListButton<'a, Message> { + content: Element<'a, Message>, + on_press: Option, + selected: bool, +} + +/// Creates a [`ListButton`] with the given content. +pub fn button<'a, Message>(content: impl Into>) -> ListButton<'a, Message> { + ListButton { + content: content.into(), + on_press: None, + selected: false, + } +} + +impl<'a, Message: 'static> ListButton<'a, Message> { + pub fn on_press(mut self, on_press: Message) -> Self { + self.on_press = Some(on_press); + self + } + + pub fn on_press_maybe(mut self, on_press: Option) -> Self { + self.on_press = on_press; + self + } + + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } +} + +pub enum ListItem<'a, Message> { + Element(Element<'a, Message>), + Button(ListButton<'a, Message>), +} + +/// A trait for types that can be added to a [`ListColumn`]. +pub trait IntoListItem<'a, Message> { + fn into_list_item(self) -> ListItem<'a, Message>; +} + +impl<'a, Message, T> IntoListItem<'a, Message> for T +where + T: Into>, +{ + fn into_list_item(self) -> ListItem<'a, Message> { + ListItem::Element(self.into()) + } +} + +impl<'a, Message> IntoListItem<'a, Message> for ListButton<'a, Message> { + fn into_list_item(self) -> ListItem<'a, Message> { + ListItem::Button(self) + } +} + +// Snapshots the padding values at the moment an item is added +struct ListEntry<'a, Message> { + item: ListItem<'a, Message>, + item_padding: Padding, + divider_padding: u16, +} + +#[must_use] +pub struct ListColumn<'a, Message> { + list_item_padding: Padding, + divider_padding: u16, + style: theme::Container<'a>, + children: Vec>, +} + +#[inline] +pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> { + ListColumn::default() +} + +pub fn with_capacity<'a, Message: 'static>(capacity: usize) -> ListColumn<'a, Message> { + let cosmic_theme::Spacing { + space_xxs, space_m, .. + } = theme::spacing(); + + ListColumn { + list_item_padding: [space_xxs, space_m].into(), + divider_padding: 0, + style: theme::Container::List, + children: Vec::with_capacity(capacity), + } +} + +impl Default for ListColumn<'_, Message> { + fn default() -> Self { + with_capacity(4) + } +} + +impl<'a, Message: Clone + 'static> ListColumn<'a, Message> { + #[inline] + pub fn new() -> Self { + Self::default() + } + + /// Adds a [`ListItem`] to the [`ListColumn`]. + #[allow(clippy::should_implement_trait)] + pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self { + self.children.push(ListEntry { + item: item.into_list_item(), + item_padding: self.list_item_padding, + divider_padding: self.divider_padding, + }); + self + } + + /// Sets the style variant of this [`ListColumn`]. + #[inline] + pub fn style(mut self, style: ::Class<'a>) -> Self { + self.style = style; + self + } + + pub fn list_item_padding(mut self, padding: impl Into) -> Self { + self.list_item_padding = padding.into(); + self + } + + #[inline] + pub fn divider_padding(mut self, padding: u16) -> Self { + self.divider_padding = padding; + self + } + + #[must_use] + pub fn into_element(self) -> Element<'a, Message> { + let count = self.children.len(); + let last_index = count.saturating_sub(1); + let radius_s = theme::active().cosmic().radius_s(); + let mut col = column::with_capacity((2 * count).saturating_sub(1)); + + // Ensure minimum height of 32 + let content_row = |content| { + row![container(content), vertical().height(32)].align_y(iced::Alignment::Center) + }; + + for ( + i, + ListEntry { + item, + item_padding, + divider_padding, + }, + ) in self.children.into_iter().enumerate() + { + if i > 0 { + col = col + .push(container(divider::horizontal::default()).padding([0, divider_padding])); + } + + col = match item { + ListItem::Element(content) => col.push( + content_row(content) + .padding(item_padding) + .width(Length::Fill), + ), + ListItem::Button(ListButton { + content, + on_press, + selected, + }) => col.push( + content_row(content) + .apply(button::custom) + .padding(item_padding) + .width(Length::Fill) + .on_press_maybe(on_press) + .selected(selected) + .class(theme::Button::ListItem(get_radius( + radius_s, + i == 0, + i == last_index, + ))), + ), + }; + } + + col.width(Length::Fill) + .apply(container) + .class(self.style) + .into() + } +} + +impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { + fn from(column: ListColumn<'a, Message>) -> Self { + column.into_element() + } +} + +fn get_radius(radius: [f32; 4], first: bool, last: bool) -> [f32; 4] { + match (first, last) { + (true, true) => radius, + (true, false) => [radius[0], radius[1], 0.0, 0.0], + (false, true) => [0.0, 0.0, radius[2], radius[3]], + (false, false) => [0.0, 0.0, 0.0, 0.0], + } +} diff --git a/src/widget/list/mod.rs b/src/widget/list/mod.rs index c6e2051c..71eda086 100644 --- a/src/widget/list/mod.rs +++ b/src/widget/list/mod.rs @@ -1,6 +1,6 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -pub mod column; +pub mod list_column; -pub use self::column::{ListColumn, list_column}; +pub use self::list_column::{ListButton, ListColumn, button, list_column}; diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index cb70d8db..41cf1dff 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -230,7 +230,7 @@ pub fn menu_items< } fn key_style(theme: &crate::Theme) -> TextStyle { - let mut color = theme.cosmic().background(theme.transparent).component.on; + let mut color = theme.cosmic().background.component.on; color.alpha *= 0.75; TextStyle { color: Some(color.into()), diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index 1d57777d..ad6f9206 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -173,9 +173,7 @@ 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(theme.transparent).base.into(), - )), + background: Some(Background::Color(cosmic.primary.base.into())), border: Border { width: 0.0, color: Color::TRANSPARENT, diff --git a/src/widget/progress_bar/circular.rs b/src/widget/progress_bar/circular.rs index 7e8177d6..fa8c38fe 100644 --- a/src/widget/progress_bar/circular.rs +++ b/src/widget/progress_bar/circular.rs @@ -15,8 +15,6 @@ use std::f32::consts::PI; use std::time::Duration; const MIN_ANGLE: Radians = Radians(PI / 8.0); -const WRAP_ANGLE: Radians = Radians(2.0 * PI - PI / 4.0); -const BASE_ROTATION_SPEED: u32 = u32::MAX / 80; #[must_use] pub struct Circular @@ -83,6 +81,12 @@ where self.progress = Some(progress.clamp(0.0, 1.0)); self } + + fn min_wrap_angle(&self, track_radius: f32) -> (f32, f32) { + let cap_angle = self.bar_height / track_radius; + let gap = MIN_ANGLE.0.max(cap_angle); + (gap - cap_angle, 2.0 * PI - gap * 2.0) + } } impl Default for Circular @@ -122,7 +126,7 @@ impl Default for Animation { } impl Animation { - fn next(&self, additional_rotation: u32, now: Instant) -> Self { + fn next(&self, additional_rotation: u32, wrap_angle: f32, now: Instant) -> Self { match self { Self::Expanding { rotation, .. } => Self::Contracting { start: now, @@ -133,9 +137,9 @@ impl Animation { Self::Contracting { rotation, .. } => Self::Expanding { start: now, progress: 0.0, - rotation: rotation.wrapping_add(BASE_ROTATION_SPEED.wrapping_add( - (f64::from(WRAP_ANGLE / (2.0 * Radians::PI)) * f64::from(u32::MAX)) as u32, - )), + rotation: rotation.wrapping_add( + (f64::from((wrap_angle) / (2.0 * PI)) * f64::from(u32::MAX)) as u32, + ), last: now, }, } @@ -157,6 +161,7 @@ impl Animation { &self, cycle_duration: Duration, rotation_duration: Duration, + wrap_angle: f32, now: Instant, ) -> Self { let elapsed = now.duration_since(self.start()); @@ -165,7 +170,7 @@ impl Animation { * (u32::MAX) as f32) as u32; match elapsed { - elapsed if elapsed > cycle_duration => self.next(additional_rotation, now), + elapsed if elapsed > cycle_duration => self.next(additional_rotation, wrap_angle, now), _ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now), } } @@ -267,10 +272,13 @@ where return; } if let Event::Window(window::Event::RedrawRequested(now)) = event { - state.animation = - state - .animation - .timed_transition(self.cycle_duration, self.rotation_duration, *now); + let (_, wrap_angle) = self.min_wrap_angle(self.size / 2.0 - self.bar_height); + state.animation = state.animation.timed_transition( + self.cycle_duration, + self.rotation_duration, + wrap_angle, + *now, + ); state.cache.clear(); shell.request_redraw(); @@ -380,22 +388,23 @@ where } else { let mut builder = canvas::path::Builder::new(); - let start = Radians(state.animation.rotation() * 2.0 * PI); + let start = state.animation.rotation() * 2.0 * PI; + let (min_angle, wrap_angle) = self.min_wrap_angle(track_radius); let (start_angle, end_angle) = match state.animation { Animation::Expanding { progress, .. } => ( start, - start + MIN_ANGLE + WRAP_ANGLE * (smootherstep(progress)), + start + min_angle + wrap_angle * smootherstep(progress), ), Animation::Contracting { progress, .. } => ( - start + WRAP_ANGLE * (smootherstep(progress)), - start + MIN_ANGLE + WRAP_ANGLE, + start + wrap_angle * smootherstep(progress), + start + min_angle + wrap_angle, ), }; builder.arc(canvas::path::Arc { center: frame.center(), radius: track_radius, - start_angle, - end_angle, + start_angle: Radians(start_angle), + end_angle: Radians(end_angle), }); let bar_path = builder.build(); @@ -410,23 +419,23 @@ where let mut builder = canvas::path::Builder::new(); // get center of end of arc for rounded cap - let end_center = frame.center() - + Vector::new(end_angle.0.cos(), end_angle.0.sin()) * track_radius; + let end_center = + frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius; builder.arc(canvas::path::Arc { center: end_center, radius: self.bar_height / 2.0, - start_angle: Radians(end_angle.0), - end_angle: Radians(end_angle.0 + PI), + start_angle: Radians(end_angle), + end_angle: Radians(end_angle + PI), }); // get center of start of arc for rounded cap let start_center = frame.center() - + Vector::new(start_angle.0.cos(), start_angle.0.sin()) * track_radius; + + Vector::new(start_angle.cos(), start_angle.sin()) * track_radius; builder.arc(canvas::path::Arc { center: start_center, radius: self.bar_height / 2.0, - start_angle: Radians(start_angle.0 - PI), - end_angle: Radians(start_angle.0), + start_angle: Radians(start_angle - PI), + end_angle: Radians(start_angle), }); let cap_path = builder.build(); diff --git a/src/widget/radio.rs b/src/widget/radio.rs index 338c0a4e..c3f115c0 100644 --- a/src/widget/radio.rs +++ b/src/widget/radio.rs @@ -1,5 +1,5 @@ //! Create choices using radio buttons. -use crate::Theme; +use crate::{Theme, theme}; use iced::border; use iced_core::event::{self, Event}; use iced_core::layout; @@ -92,7 +92,7 @@ where { is_selected: bool, on_click: Message, - label: Element<'a, Message, Theme, Renderer>, + label: Option>, width: Length, size: f32, spacing: f32, @@ -106,9 +106,6 @@ where /// The default size of a [`Radio`] button. pub const DEFAULT_SIZE: f32 = 16.0; - /// The default spacing of a [`Radio`] button. - pub const DEFAULT_SPACING: f32 = 8.0; - /// Creates a new [`Radio`] button. /// /// It expects: @@ -126,10 +123,29 @@ where Radio { is_selected: Some(value) == selected, on_click: f(value), - label: label.into(), + label: Some(label.into()), width: Length::Shrink, size: Self::DEFAULT_SIZE, - spacing: Self::DEFAULT_SPACING, + spacing: theme::spacing().space_xs as f32, + } + } + + /// Creates a new [`Radio`] button without a label. + /// + /// This is intended for internal use with the settings item builder, + /// where the label comes from the settings item title instead. + pub(crate) fn new_no_label(value: V, selected: Option, f: F) -> Self + where + V: Eq + Copy, + F: FnOnce(V) -> Message, + { + Radio { + is_selected: Some(value) == selected, + on_click: f(value), + label: None, + width: Length::Shrink, + size: Self::DEFAULT_SIZE, + spacing: theme::spacing().space_xs as f32, } } @@ -161,11 +177,17 @@ where Renderer: iced_core::Renderer, { fn children(&self) -> Vec { - vec![Tree::new(&self.label)] + if let Some(label) = &self.label { + vec![Tree::new(label)] + } else { + vec![] + } } fn diff(&mut self, tree: &mut Tree) { - tree.diff_children(std::slice::from_mut(&mut self.label)); + if let Some(label) = &mut self.label { + tree.diff_children(std::slice::from_mut(label)); + } } fn size(&self) -> Size { Size { @@ -180,16 +202,20 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout::next_to_each_other( - &limits.width(self.width), - self.spacing, - |_| layout::Node::new(Size::new(self.size, self.size)), - |limits| { - self.label - .as_widget_mut() - .layout(&mut tree.children[0], renderer, limits) - }, - ) + if let Some(label) = &mut self.label { + layout::next_to_each_other( + &limits.width(self.width), + self.spacing, + |_| layout::Node::new(Size::new(self.size, self.size)), + |limits| { + label + .as_widget_mut() + .layout(&mut tree.children[0], renderer, limits) + }, + ) + } else { + layout::Node::new(Size::new(self.size, self.size)) + } } fn operate( @@ -199,12 +225,14 @@ where renderer: &Renderer, operation: &mut dyn iced_core::widget::Operation<()>, ) { - self.label.as_widget_mut().operate( - &mut tree.children[0], - layout.children().nth(1).unwrap(), - renderer, - operation, - ); + if let Some(label) = &mut self.label { + label.as_widget_mut().operate( + &mut tree.children[0], + layout.children().nth(1).unwrap(), + renderer, + operation, + ); + } } fn update( @@ -218,24 +246,25 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.label.as_widget_mut().update( - &mut tree.children[0], - event, - layout.children().nth(1).unwrap(), - cursor, - renderer, - clipboard, - shell, - viewport, - ); + if let Some(label) = &mut self.label { + label.as_widget_mut().update( + &mut tree.children[0], + event, + layout.children().nth(1).unwrap(), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } if !shell.is_event_captured() { match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) => { if cursor.is_over(layout.bounds()) { shell.publish(self.on_click.clone()); - shell.capture_event(); return; } @@ -253,13 +282,17 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - let interaction = self.label.as_widget().mouse_interaction( - &tree.children[0], - layout.children().nth(1).unwrap(), - cursor, - viewport, - renderer, - ); + let interaction = if let Some(label) = &self.label { + label.as_widget().mouse_interaction( + &tree.children[0], + layout.children().nth(1).unwrap(), + cursor, + viewport, + renderer, + ) + } else { + mouse::Interaction::default() + }; if interaction == mouse::Interaction::default() { if cursor.is_over(layout.bounds()) { @@ -284,8 +317,6 @@ where ) { let is_mouse_over = cursor.is_over(layout.bounds()); - let mut children = layout.children(); - let custom_style = if is_mouse_over { theme.style( &(), @@ -302,16 +333,21 @@ where ) }; - { - let layout = children.next().unwrap(); - let bounds = layout.bounds(); + let (dot_bounds, label_layout) = if self.label.is_some() { + let mut children = layout.children(); + let dot_bounds = children.next().unwrap().bounds(); + (dot_bounds, children.next()) + } else { + (layout.bounds(), None) + }; - let size = bounds.width; + { + let size = dot_bounds.width; let dot_size = 6.0; renderer.fill_quad( renderer::Quad { - bounds, + bounds: dot_bounds, border: Border { radius: (size / 2.0).into(), width: custom_style.border_width, @@ -326,8 +362,8 @@ where renderer.fill_quad( renderer::Quad { bounds: Rectangle { - x: bounds.x + (size - dot_size) / 2.0, - y: bounds.y + (size - dot_size) / 2.0, + x: dot_bounds.x + (size - dot_size) / 2.0, + y: dot_bounds.y + (size - dot_size) / 2.0, width: dot_size, height: dot_size, }, @@ -339,9 +375,8 @@ where } } - { - let label_layout = children.next().unwrap(); - self.label.as_widget().draw( + if let (Some(label), Some(label_layout)) = (&self.label, label_layout) { + label.as_widget().draw( &tree.children[0], renderer, theme, @@ -361,7 +396,7 @@ where viewport: &Rectangle, translation: Vector, ) -> Option> { - self.label.as_widget_mut().overlay( + self.label.as_mut()?.as_widget_mut().overlay( &mut tree.children[0], layout.children().nth(1).unwrap(), renderer, @@ -377,12 +412,14 @@ where renderer: &Renderer, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { - self.label.as_widget().drag_destinations( - &state.children[0], - layout.children().nth(1).unwrap(), - renderer, - dnd_rectangles, - ); + if let Some(label) = &self.label { + label.as_widget().drag_destinations( + &state.children[0], + layout.children().nth(1).unwrap(), + renderer, + dnd_rectangles, + ); + } } } diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 349d93d8..5abb464c 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use crate::{ Element, Theme, theme, - widget::{FlexRow, Row, column, container, flex_row, row, text}, + widget::{FlexRow, Row, column, container, flex_row, list, row, text}, }; use derive_setters::Setters; use iced_core::{Length, text::Wrapping}; @@ -103,7 +103,7 @@ pub struct Item<'a, Message> { icon: Option>, } -impl<'a, Message: 'static> Item<'a, Message> { +impl<'a, Message: Clone + 'static> Item<'a, Message> { /// Assigns a control to the item. pub fn control(self, widget: impl Into>) -> Row<'a, Message, Theme> { item_row(self.control_(widget.into())) @@ -114,39 +114,109 @@ impl<'a, Message: 'static> Item<'a, Message> { flex_item_row(self.control_(widget.into())) } - #[inline(never)] - fn control_(self, widget: Element<'a, Message>) -> Vec> { - let mut contents = Vec::with_capacity(4); - - if let Some(icon) = self.icon { - contents.push(icon); - } - + fn label(self) -> Element<'a, Message> { if let Some(description) = self.description { - let column = column::with_capacity(2) + column::with_capacity(2) .spacing(2) .push(text::body(self.title).wrapping(Wrapping::Word)) .push(text::caption(description).wrapping(Wrapping::Word)) - .width(Length::Fill); - - contents.push(column.into()); + .width(Length::Fill) + .into() } else { - contents.push(text(self.title).width(Length::Fill).into()); + text(self.title).width(Length::Fill).into() } + } + #[inline(never)] + fn control_(mut self, widget: Element<'a, Message>) -> Vec> { + let mut contents = Vec::with_capacity(3); + if let Some(icon) = self.icon.take() { + contents.push(icon); + } + contents.push(self.label()); contents.push(widget); contents } + fn control_start(self, widget: impl Into>) -> Row<'a, Message, Theme> { + item_row(vec![widget.into(), self.label()]) + } + pub fn toggler( self, is_checked: bool, message: impl Fn(bool) -> Message + 'static, - ) -> Row<'a, Message, Theme> { - self.control( - crate::widget::toggler(is_checked) - .width(Length::Shrink) - .on_toggle(message), + ) -> list::ListButton<'a, Message> { + let on_press = message(!is_checked); + list::button( + self.control( + crate::widget::toggler(is_checked) + .width(Length::Shrink) + .on_toggle(message), + ), ) + .on_press(on_press) + } + + pub fn toggler_maybe( + self, + is_checked: bool, + message: Option Message + 'static>, + ) -> list::ListButton<'a, Message> { + let on_press = message.as_ref().map(|f| f(!is_checked)); + list::button( + self.control( + crate::widget::toggler(is_checked) + .width(Length::Shrink) + .on_toggle_maybe(message), + ), + ) + .on_press_maybe(on_press) + } + + pub fn checkbox( + self, + is_checked: bool, + message: impl Fn(bool) -> Message + 'static, + ) -> list::ListButton<'a, Message> { + let on_press = message(!is_checked); + list::button( + self.control_start( + crate::widget::checkbox(is_checked) + .width(Length::Shrink) + .on_toggle(message), + ), + ) + .on_press(on_press) + } + + pub fn checkbox_maybe( + self, + is_checked: bool, + message: Option Message + 'static>, + ) -> list::ListButton<'a, Message> { + let on_press = message.as_ref().map(|f| f(!is_checked)); + list::button( + self.control_start( + crate::widget::checkbox(is_checked) + .width(Length::Shrink) + .on_toggle_maybe(message), + ), + ) + .on_press_maybe(on_press) + } + + pub fn radio(self, value: V, selected: Option, f: F) -> list::ListButton<'a, Message> + where + V: Eq + Copy, + F: Fn(V) -> Message, + { + let on_press = f(value); + list::button( + self.control_start(crate::widget::radio::Radio::new_no_label( + value, selected, f, + )), + ) + .on_press(on_press) } } diff --git a/src/widget/settings/section.rs b/src/widget/settings/section.rs index ab95b5ad..3dddb1a1 100644 --- a/src/widget/settings/section.rs +++ b/src/widget/settings/section.rs @@ -2,16 +2,24 @@ // SPDX-License-Identifier: MPL-2.0 use crate::Element; -use crate::widget::{ListColumn, column, text}; +use crate::widget::list_column::IntoListItem; +use crate::widget::{ListColumn, column, list_column, text}; use std::borrow::Cow; /// A section within a settings view column. -pub fn section<'a, Message: 'static>() -> Section<'a, Message> { +pub fn section<'a, Message: Clone + 'static>() -> Section<'a, Message> { with_column(ListColumn::default()) } +/// A section with a pre-defined list column of a given capacity. +pub fn with_capacity<'a, Message: Clone + 'static>(capacity: usize) -> Section<'a, Message> { + with_column(list_column::with_capacity(capacity)) +} + /// A section with a pre-defined list column. -pub fn with_column(children: ListColumn<'_, Message>) -> Section<'_, Message> { +pub fn with_column( + children: ListColumn<'_, Message>, +) -> Section<'_, Message> { Section { header: None, children, @@ -24,9 +32,9 @@ pub struct Section<'a, Message> { children: ListColumn<'a, Message>, } -impl<'a, Message: 'static> Section<'a, Message> { +impl<'a, Message: Clone + 'static> Section<'a, Message> { /// Define an optional title for the section. - pub fn title(mut self, title: impl Into>) -> Self { + pub fn title(self, title: impl Into>) -> Self { self.header(text::heading(title.into())) } @@ -38,13 +46,13 @@ impl<'a, Message: 'static> Section<'a, Message> { /// Add a child element to the section's list column. #[allow(clippy::should_implement_trait)] - pub fn add(mut self, item: impl Into>) -> Self { - self.children = self.children.add(item.into()); + pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self { + self.children = self.children.add(item); self } /// Add a child element to the section's list column, if `Some`. - pub fn add_maybe(self, item: Option>>) -> Self { + pub fn add_maybe(self, item: Option>) -> Self { if let Some(item) = item { self.add(item) } else { @@ -55,13 +63,13 @@ impl<'a, Message: 'static> Section<'a, Message> { /// Extends the [`Section`] with the given children. pub fn extend( self, - children: impl IntoIterator>>, + children: impl IntoIterator>, ) -> Self { children.into_iter().fold(self, Self::add) } } -impl<'a, Message: 'static> From> for Element<'a, Message> { +impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { fn from(data: Section<'a, Message>) -> Self { column::with_capacity(2) .spacing(8) diff --git a/src/widget/toggler.rs b/src/widget/toggler.rs index 05371a17..b95b596e 100644 --- a/src/widget/toggler.rs +++ b/src/widget/toggler.rs @@ -161,7 +161,10 @@ impl<'a, Message> Widget for Toggler<'a, } fn state(&self) -> tree::State { - tree::State::new(State::default()) + tree::State::new(State { + prev_toggled: self.is_toggled, + ..State::default() + }) } fn id(&self) -> Option { @@ -238,6 +241,14 @@ impl<'a, Message> Widget for Toggler<'a, return; }; let state = tree.state.downcast_mut::(); + + // animate external changes + if state.prev_toggled != self.is_toggled { + state.anim.changed(self.duration); + shell.request_redraw(); + state.prev_toggled = self.is_toggled; + } + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -246,6 +257,7 @@ impl<'a, Message> Widget for Toggler<'a, if mouse_over { shell.publish((on_toggle)(!self.is_toggled)); state.anim.changed(self.duration); + state.prev_toggled = !self.is_toggled; shell.capture_event(); } } @@ -430,4 +442,5 @@ pub fn next_to_each_other( pub struct State { text: widget::text::State<::Paragraph>, anim: anim::State, + prev_toggled: bool, }