Compare commits

..

No commits in common. "post-1.7-merge-20260827" and "master" have entirely different histories.

259 changed files with 3643 additions and 9953 deletions

View file

@ -6,20 +6,26 @@ on:
- master
pull_request:
jobs:
format:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v3
- name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
components: rustfmt
rustflags: ""
- name: Cargo cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-rust_stable-${{ hashFiles('**/Cargo.toml') }}
- name: Format
run: cargo +nightly fmt -- --check
run: cargo fmt -- --check
tests:
needs:
@ -28,28 +34,38 @@ jobs:
fail-fast: false
matrix:
test_args:
- --no-default-features --features "" # for cosmic-comp, don't remove!
- --no-default-features --features "winit_debug"
- --no-default-features --features "winit_tokio"
- --no-default-features --features "winit"
- --no-default-features --features "winit_wgpu"
- --no-default-features --features "wayland"
- --no-default-features --features "applet"
- --no-default-features --features "desktop,smol"
- --no-default-features --features "desktop,tokio"
- -p cosmic-theme
runs-on: ubuntu-latest
- --no-default-features --features "" # for cosmic-comp, don't remove!
- --no-default-features --features "winit_debug"
- --no-default-features --features "winit_tokio"
- --no-default-features --features "winit"
- --no-default-features --features "winit_wgpu"
- --no-default-features --features "wayland"
- --no-default-features --features "applet"
- --no-default-features --features "desktop,smol"
- --no-default-features --features "desktop,tokio"
- -p cosmic-theme
runs-on: ubuntu-22.04
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: recursive
- name: Get date for registry cache
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: Cargo registry cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
restore-keys: ${{ runner.os }}-cargo-registry-
- name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
uses: dtolnay/rust-toolchain@stable
- name: Test features
run: cargo test ${{ matrix.test_args }} -- --test-threads=1
env:
@ -66,18 +82,28 @@ jobs:
- "open-dialog"
- "context-menu"
- "nav-context"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: recursive
- name: Get date for registry cache
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: Cargo registry cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
restore-keys: ${{ runner.os }}-cargo-registry-
- name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
uses: dtolnay/rust-toolchain@stable
- name: Check example
run: cargo check -p "${{ matrix.examples }}"
env:

View file

@ -11,26 +11,25 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: recursive
- name: Install Rust nightly
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-04-27
rustflags: ""
toolchain: nightly-2025-07-31
- name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Build documentation
run: |
RUSTDOCFLAGS="--cfg docsrs" \
cargo +nightly-2026-04-27 doc --no-deps \
cargo +nightly-2025-07-31 doc --no-deps \
-p cosmic-client-toolkit \
-p cosmic-protocols \
-p libcosmic \
--verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window
- name: Deploy documentation
uses: peaceiris/actions-gh-pages@v4
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc

View file

@ -1,15 +0,0 @@
{
"format_on_save": "on",
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"command": "clippy",
},
"rustfmt": {
"extraArgs": ["+nightly"],
},
},
},
},
}

View file

@ -1,23 +1,20 @@
[package]
# Yoda fork: hard-renamed. Every consumer (leyoda/cosmic-files fork + each
# leyoda/cosmic-* app) depends directly on `libcosmic-yoda` by path, bypassing
# pop-os/libcosmic entirely. No [patch] shenanigans needed — transitive deps
# that used to ask for `libcosmic` are replaced by deps on our forks that ask
# for `libcosmic-yoda`.
name = "libcosmic-yoda"
version = "0.1.0-yoda.2"
name = "libcosmic"
version = "1.0.0"
edition = "2024"
rust-version = "1.93"
rust-version = "1.90"
[lib]
name = "cosmic"
[features]
default = [
"winit",
"tokio",
"a11y",
"dbus-config",
"wayland",
"x11",
"iced-wayland",
"multi-window",
]
advanced-shaping = ["iced/advanced-shaping"]
@ -38,6 +35,7 @@ animated-image = [
autosize = []
applet = [
"autosize",
"winit",
"wayland",
"tokio",
"cosmic-panel-config",
@ -74,7 +72,6 @@ serde-keycode = ["iced_core/serde"]
single-instance = ["zbus/blocking-api", "ron"]
# smol async runtime
smol = ["dep:smol", "iced/smol", "zbus?/async-io", "rfd?/async-std"]
# Tokio async runtime
tokio = [
"dep:tokio",
"ashpd?/tokio",
@ -84,25 +81,20 @@ tokio = [
"cosmic-config/tokio",
]
# Tokio async runtime
# Wayland window support (yoda fork is Wayland-only; always active in default).
# We still need iced/winit because pop-os/iced hosts the runtime dispatcher
# (`iced_winit as shell`) there — the name is a misnomer, it's the same crate
# that provides both the winit path AND the sctk/cctk wayland path.
# COSMIC 1.5/1.6 renamed the iced `cctk` features to `wayland`; the local
# `winit` feature stays empty in this fork, so iced/winit is listed explicitly
# instead of going through it.
wayland = [
# Wayland window support
iced-wayland = [
"ashpd?/wayland",
"autosize",
"multi-window",
"surface-message",
"winit",
"iced/winit",
"iced/wayland",
"iced_winit/wayland",
"iced_runtime/wayland",
"iced_wgpu/wayland",
"dep:iced_winit",
"surface-message",
]
wayland = [
"iced-wayland",
"iced_runtime/cctk",
"iced_winit/cctk",
"iced_wgpu/cctk",
"iced/cctk",
"dep:cctk",
]
surface-message = []
@ -110,11 +102,11 @@ surface-message = []
multi-window = []
# Render with wgpu
wgpu = ["iced/wgpu", "iced_wgpu"]
# Compat stubs — kept empty so upstream deps (cosmic-files, cosmic-text, …)
# that still ask for `winit` / `x11` features resolve cleanly against the
# yoda fork. Activating them has no effect: no code is gated on these.
winit = []
x11 = []
# X11 window support via winit
winit = ["iced/winit", "iced_winit"]
winit_debug = ["winit", "debug"]
winit_tokio = ["winit", "tokio"]
winit_wgpu = ["winit", "wgpu"]
# Enables XDG portal integrations
xdg-portal = ["ashpd"]
qr_code = ["iced/qr_code"]
@ -127,15 +119,18 @@ async-std = [
"zbus?/async-io",
"iced/async-std",
]
x11 = ["iced/x11", "iced_winit/x11"]
[dependencies]
apply = "0.3.0"
ashpd = { version = "0.12.3", default-features = false, optional = true }
async-fs = { version = "2.2", optional = true }
async-std = { workspace = true, 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 = "160b086", optional = true }
jiff = "0.2"
cosmic-config = { path = "cosmic-config" }
cosmic-settings-config = { path = "../cosmic-settings-daemon/config", optional = true }
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
# Internationalization
i18n-embed = { version = "0.16.0", features = [
"fluent-system",
@ -155,38 +150,34 @@ image-extras = { version = "0.1.0", default-features = false, features = [
"xpm",
"xbm",
], optional = true }
libc = { version = "0.2.186", optional = true }
libc = { version = "0.2.183", optional = true }
log = "0.4"
mime = { version = "0.3.17", optional = true }
palette.workspace = true
palette = "0.7.6"
rfd = { version = "0.16.0", default-features = false, features = [
"xdg-portal",
], optional = true }
rustix = { version = "1.1", features = ["pipe", "process"], optional = true }
serde = { workspace = true, features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] }
slotmap = "1.1.1"
smol = { version = "2.0.2", optional = true }
thiserror.workspace = true
thiserror = "2.0.18"
taffy = { version = "0.9.2", features = ["grid"] }
tokio = { workspace = true, optional = true }
tracing.workspace = true
unicode-segmentation = "1.13"
tokio = { version = "1.50.0", optional = true }
tracing = "0.1.44"
unicode-segmentation = "1.12"
url = "2.5.8"
zbus = { workspace = true, optional = true }
zbus = { version = "5.14.0", default-features = false, optional = true }
float-cmp = "0.10.0"
ron = { workspace = true, optional = true }
enumflags2 = "0.7.12"
# Enable DBus feature on Linux targets
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
cctk = { path = "../cosmic-protocols/client-toolkit", package = "cosmic-client-toolkit", optional = true }
ashpd = { version = "0.12.3", default-features = false, optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
cosmic-config = { path = "cosmic-config", features = ["dbus"] }
cosmic-settings-daemon = { path = "../dbus-settings-bindings/cosmic-settings-daemon" }
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
zbus = { version = "5.14.0", default-features = false }
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
freedesktop-icons = { package = "cosmic-freedesktop-icons", path = "../cosmic-freedesktop-icons" }
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 }
@ -232,83 +223,35 @@ optional = true
[dependencies.iced_tiny_skia]
path = "./iced/tiny_skia"
# Yoda: drop the x11 default → softbuffer no longer pulls tiny-xlib/x11-dl/etc.
default-features = false
features = ["wayland"]
[dependencies.iced_winit]
path = "./iced/winit"
optional = true
# Yoda: drop the x11 default → winit won't pull winit-x11/tiny-xlib/x11-dl.
# Keep wayland + wayland-dlopen (default behaviour minus x11).
default-features = false
features = ["wayland", "wayland-dlopen"]
[dependencies.iced_wgpu]
path = "./iced/wgpu"
optional = true
[dependencies.cosmic-panel-config]
path = "../cosmic-panel/cosmic-panel-config"
git = "https://github.com/pop-os/cosmic-panel"
# path = "../cosmic-panel/cosmic-panel-config"
optional = true
[patch.'https://github.com/pop-os/freedesktop-icons']
cosmic-freedesktop-icons = { path = "../cosmic-freedesktop-icons" }
[patch.'https://github.com/pop-os/softbuffer']
softbuffer = { path = "../softbuffer" }
[patch.'https://github.com/pop-os/smithay-clipboard']
smithay-clipboard = { path = "../smithay-clipboard" }
[patch.'https://github.com/pop-os/winit.git']
dpi = { path = "../winit/dpi" }
winit = { path = "../winit/winit" }
winit-android = { path = "../winit/winit-android" }
winit-appkit = { path = "../winit/winit-appkit" }
winit-common = { path = "../winit/winit-common" }
winit-core = { path = "../winit/winit-core" }
winit-orbital = { path = "../winit/winit-orbital" }
winit-uikit = { path = "../winit/winit-uikit" }
winit-wayland = { path = "../winit/winit-wayland" }
winit-web = { path = "../winit/winit-web" }
winit-win32 = { path = "../winit/winit-win32" }
winit-x11 = { path = "../winit/winit-x11" }
[dependencies.ron]
version = "0.12"
optional = true
[workspace]
members = [
"cosmic-config",
"cosmic-config-derive",
"cosmic-theme",
"examples/*",
]
# examples/* excluded — many depend on the removed winit/x11 features.
# They will be revisited and adapted in a later phase.
exclude = ["iced", "examples"]
exclude = ["iced"]
[workspace.dependencies]
async-std = "1.13"
dirs = "6.0"
palette = "0.7"
ron = "0.12"
serde = "1.0"
thiserror = "2.0"
tracing = "0.1"
tokio = "1.52"
zbus = { version = "5.15", default-features = false }
# Speed up snapshot diffing in cosmic-theme tests. Cargo silently ignores
# [profile.*] blocks in non-root manifests, so this lives at the
# workspace root.
[profile.dev.package.insta]
opt-level = 3
[profile.dev.package.similar]
opt-level = 3
dirs = "6.0.0"
[dev-dependencies]
tempfile = "3.27.0"
[workspace.package]
edition = "2024"
[build-dependencies]
build_helpers = { path = "./iced/build_helpers" }

View file

@ -20,24 +20,6 @@ While libcosmic is written entirely in Rust, some of its dependencies may requir
sudo apt install cargo cmake just libexpat1-dev libfontconfig-dev libfreetype-dev libxkbcommon-dev pkgconf
```
## Made-for-COSMIC Flatpak IDs
To identify a project as a COSMIC Application, add `<id>com.system76.CosmicApplication</id>` to the provides section of the project's metainfo.
```xml
<provides>
<id>com.system76.CosmicApplication</id>
</provides>
```
For COSMIC Applets, use `com.system76.CosmicApplet`.
```xml
<provides>
<id>com.system76.CosmicApplet</id>
</provides>
```
## Examples
Some examples are included in the [examples](./examples) directory to to kickstart your

View file

@ -8,7 +8,6 @@ fn main() {
{
generate_bundled_icons();
}
build_helpers::cfg_aliases_setup();
}
fn generate_bundled_icons() {

View file

@ -1,32 +1,17 @@
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
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_cosmic_config_entry_macro(&ast)
}
fn get_cosmic_config_attrs(field: &syn::Field) -> Result<Option<syn::Type>, 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
@ -63,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),
}
}
});
@ -118,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);
}
}
}

View file

@ -10,21 +10,21 @@ macro = ["cosmic-config-derive"]
subscription = ["iced_futures"]
[dependencies]
cosmic-settings-daemon = { path = "../../dbus-settings-bindings/cosmic-settings-daemon", optional = true }
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
zbus = { version = "5.14.0", default-features = false, optional = true }
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
calloop = { version = "0.14.4", optional = true }
notify = "8.2.0"
ron.workspace = true
serde.workspace = true
ron = "0.12.0"
serde = "1.0.228"
cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true }
iced = { path = "../iced/", default-features = false, optional = true }
iced_futures = { path = "../iced/futures/", default-features = false, optional = true }
futures-util = { version = "0.3", optional = true }
dirs.workspace = true
tokio = { workspace = true, optional = true, features = ["time"] }
async-std = { workspace = true, optional = true }
tracing.workspace = true
tokio = { version = "1.50", optional = true, features = ["time"] }
async-std = { version = "1.13", optional = true }
tracing = "0.1"
[target.'cfg(unix)'.dependencies]
xdg = "3.0"

View file

@ -1,12 +1,13 @@
use std::any::TypeId;
use std::ops::Deref;
use std::{any::TypeId, ops::Deref};
use crate::{CosmicConfigEntry, Update};
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
use futures_util::SinkExt;
use iced_futures::futures::future::pending;
use iced_futures::futures::{self, StreamExt};
use iced_futures::{Subscription, stream};
use iced_futures::{
Subscription,
futures::{self, StreamExt, future::pending},
stream,
};
pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> {
let conn = zbus::Connection::session().await?;
@ -67,8 +68,6 @@ struct Wrapper(
impl std::hash::Hash for Wrapper {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
self.2.hash(state);
self.3.hash(state);
}
}

View file

@ -1,13 +1,16 @@
//! Integrations for cosmic-config — the cosmic configuration system.
use notify::event::{EventKind, ModifyKind, RenameMode};
use notify::{RecommendedWatcher, Watcher};
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::{env, fmt, fs};
use notify::{
RecommendedWatcher, Watcher,
event::{EventKind, ModifyKind, RenameMode},
};
use serde::{Serialize, de::DeserializeOwned};
use std::{
env, fmt, fs,
io::Write,
path::{Path, PathBuf},
sync::Mutex,
};
/// Get the config directory, with Flatpak sandbox support.
/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory,
@ -48,12 +51,7 @@ fn get_state_dir() -> Option<PathBuf> {
return Some(PathBuf::from(home).join(".local").join("state"));
}
}
if let Some(state_dir) = dirs::state_dir() {
return Some(state_dir);
}
// On platforms without state_dir, fall back to ~/.local/state
let home_dir = dirs::home_dir()?;
Some(home_dir.join(".local").join(".state"))
dirs::state_dir()
}
#[cfg(feature = "subscription")]
@ -164,7 +162,6 @@ pub trait ConfigSet {
pub struct Config {
system_path: Option<PathBuf>,
user_path: Option<PathBuf>,
previous: Option<Box<Config>>,
}
/// Check that the name is relative and doesn't contain . or ..
@ -183,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, Error> {
Self::system_inner(name, version, true)
}
fn system_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
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 =
@ -199,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
},
})
}
@ -213,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, Error> {
Self::new_inner(name, version, true)
}
fn new_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version));
@ -241,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, Error> {
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<Self, Error> {
// 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.
@ -273,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
},
})
}
@ -289,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, Error> {
Self::new_state_inner(name, version, true)
}
fn new_state_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version));
@ -306,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
},
})
}
@ -423,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),
}
}

View file

@ -1,9 +1,7 @@
use iced_futures::futures::channel::mpsc;
use iced_futures::futures::{SinkExt, Stream};
use iced_futures::stream;
use iced_futures::{futures::channel::mpsc, stream};
use notify::RecommendedWatcher;
use std::borrow::Cow;
use std::hash::Hash;
use std::{borrow::Cow, hash::Hash};
use crate::{Config, CosmicConfigEntry};
@ -79,8 +77,7 @@ async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicCo
state: ConfigState<T>,
output: &mut mpsc::Sender<crate::Update<T>>,
) -> ConfigState<T> {
use iced_futures::futures::StreamExt;
use iced_futures::futures::future::pending;
use iced_futures::futures::{StreamExt, future::pending};
match state {
ConfigState::Init(config_id, version, is_state) => {

View file

@ -15,14 +15,13 @@ export = ["serde_json"]
no-default = []
[dependencies]
palette = { workspace = true, features = ["serializing"] }
hex_color = { version = "3", features = ["serde"] }
palette = { version = "0.7.6", features = ["serializing"] }
almost = "0.2"
serde = { workspace = true, features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.149", optional = true, features = [
"preserve_order",
] }
ron.workspace = true
ron = "0.12.0"
csscolorparser = { version = "0.8.3", features = ["serde"] }
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
"subscription",
@ -30,8 +29,11 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features
] }
configparser = "3.1.0"
dirs.workspace = true
thiserror.workspace = true
thiserror = "2.0.18"
[dev-dependencies]
insta = "1.47.2"
[profile.dev.package]
insta.opt-level = 3
similar.opt-level = 3

View file

@ -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<ColorRepr>);
impl From<Srgb> for ColorRepr {
fn from(color: Srgb) -> Self {
let rgb_u8: Srgb<u8> = color.into_format();
ColorRepr::Hex(HexColor {
r: rgb_u8.red,
g: rgb_u8.green,
b: rgb_u8.blue,
a: 255,
})
}
}
impl From<Srgba> for ColorRepr {
fn from(color: Srgba) -> Self {
let rgba_u8: Srgba<u8> = color.into_format();
ColorRepr::Hex(HexColor {
r: rgba_u8.red,
g: rgba_u8.green,
b: rgba_u8.blue,
a: rgba_u8.alpha,
})
}
}
impl From<ColorRepr> for Srgb {
fn from(value: ColorRepr) -> Self {
match value {
ColorRepr::Hex(hex) => Srgb::<u8>::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<ColorRepr> for Srgba {
fn from(value: ColorRepr) -> Self {
match value {
ColorRepr::Hex(hex) => Srgba::<u8>::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<ColorReprOption> for Option<Srgb> {
fn from(value: ColorReprOption) -> Self {
value.0.map(std::convert::Into::into)
}
}
impl From<ColorReprOption> for Option<Srgba> {
fn from(value: ColorReprOption) -> Self {
value.0.map(std::convert::Into::into)
}
}
impl From<Option<Srgb>> for ColorReprOption {
fn from(value: Option<Srgb>) -> Self {
ColorReprOption(value.map(std::convert::Into::into))
}
}
impl From<Option<Srgba>> for ColorReprOption {
fn from(value: Option<Srgba>) -> 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<T, S>(color: &T, serializer: S) -> Result<S::Ok, S::Error>
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<T, D::Error>
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<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: ConvColorRepr,
S: Serializer,
{
match value {
Some(v) => Some(v.to_repr()).serialize(serializer),
None => serializer.serialize_none(),
}
}
/// Deserialize an optional color
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: ConvColorRepr,
D: Deserializer<'de>,
{
let opt = Option::<ColorRepr>::deserialize(deserializer)?;
Ok(opt.map(T::from_repr))
}
}
}

View file

@ -29,51 +29,3 @@ impl Default for CornerRadii {
}
}
}
/// Roundness options for the Cosmic theme
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum Roundness {
/// Round style
#[default]
Round,
/// Slightly round style
SlightlyRound,
/// Square style
Square,
}
impl From<Roundness> for CornerRadii {
fn from(value: Roundness) -> Self {
match value {
Roundness::Round => CornerRadii::default(),
Roundness::SlightlyRound => CornerRadii {
radius_0: [0.0; 4],
radius_xs: [2.0; 4],
radius_s: [8.0; 4],
radius_m: [8.0; 4],
radius_l: [8.0; 4],
radius_xl: [8.0; 4],
},
Roundness::Square => CornerRadii {
radius_0: [0.0; 4],
radius_xs: [2.0; 4],
radius_s: [2.0; 4],
radius_m: [2.0; 4],
radius_l: [2.0; 4],
radius_xl: [2.0; 4],
},
}
}
}
impl From<CornerRadii> for Roundness {
fn from(value: CornerRadii) -> Self {
if (value.radius_m[0] - 16.0).abs() < 0.01 {
Self::Round
} else if (value.radius_m[0] - 8.0).abs() < 0.01 {
Self::SlightlyRound
} else {
Self::Square
}
}
}

View file

@ -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,
}

View file

@ -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)))

View file

@ -0,0 +1,69 @@
use crate::Spacing;
use serde::{Deserialize, Serialize};
/// Density options for the Cosmic theme
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum Density {
/// Lower padding/spacing of elements
Compact,
/// Higher padding/spacing of elements
Spacious,
/// Standard padding/spacing of elements
#[default]
Standard,
}
impl From<Density> for Spacing {
fn from(value: Density) -> Self {
match value {
Density::Compact => Spacing {
space_none: 0,
space_xxxs: 4,
space_xxs: 4,
space_xs: 8,
space_s: 8,
space_m: 16,
space_l: 24,
space_xl: 32,
space_xxl: 48,
space_xxxl: 64,
},
Density::Spacious => Spacing {
space_none: 4,
space_xxxs: 8,
space_xxs: 12,
space_xs: 16,
space_s: 24,
space_m: 32,
space_l: 48,
space_xl: 64,
space_xxl: 128,
space_xxxl: 160,
},
Density::Standard => Spacing {
space_none: 0,
space_xxxs: 4,
space_xxs: 8,
space_xs: 12,
space_s: 16,
space_m: 24,
space_l: 32,
space_xl: 48,
space_xxl: 64,
space_xxxl: 128,
},
}
}
}
impl From<Spacing> for Density {
fn from(value: Spacing) -> Self {
if value.space_m.saturating_sub(16) == 0 {
Self::Compact
} else if value.space_m.saturating_sub(24) == 0 {
Self::Standard
} else {
Self::Spacious
}
}
}

View file

@ -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,
}

View file

@ -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)))

View file

@ -1,13 +1,14 @@
pub use corner::*;
pub use cosmic_palette::*;
pub use density::*;
pub use derivation::*;
pub use mode::*;
pub use spacing::*;
pub use theme::*;
pub mod color;
mod corner;
mod cosmic_palette;
mod density;
mod derivation;
mod mode;
mod spacing;

View file

@ -41,59 +41,3 @@ impl Default for Spacing {
}
}
}
/// Density options for the Cosmic theme
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum Density {
/// Lower padding/spacing of elements
Compact,
/// Standard padding/spacing of elements
#[default]
Standard,
/// Higher padding/spacing of elements
Spacious,
}
impl From<Density> for Spacing {
fn from(value: Density) -> Self {
match value {
Density::Compact => Spacing {
space_none: 0,
space_xxxs: 4,
space_xxs: 4,
space_xs: 8,
space_s: 8,
space_m: 16,
space_l: 24,
space_xl: 32,
space_xxl: 48,
space_xxxl: 64,
},
Density::Standard => Spacing::default(),
Density::Spacious => Spacing {
space_none: 4,
space_xxxs: 8,
space_xxs: 12,
space_xs: 16,
space_s: 24,
space_m: 32,
space_l: 48,
space_xl: 64,
space_xxl: 128,
space_xxxl: 160,
},
}
}
}
impl From<Spacing> for Density {
fn from(value: Spacing) -> Self {
if value.space_m.saturating_sub(16) == 0 {
Self::Compact
} else if value.space_m.saturating_sub(24) == 0 {
Self::Standard
} else {
Self::Spacious
}
}
}

View file

@ -1,15 +1,13 @@
use crate::color::color_serde::option as color_serde_option;
use crate::color::{ColorRepr, ColorReprOption, color_serde};
use crate::composite::over;
use crate::steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps};
use crate::{
Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE,
LIGHT_PALETTE, NAME, Spacing, ThemeMode,
composite::over,
steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps},
};
use cosmic_config::{Config, CosmicConfigEntry};
use palette::color_difference::Wcag21RelativeContrast;
use palette::rgb::Rgb;
use palette::{IntoColor, Oklcha, Srgb, Srgba, WithAlpha};
use palette::{
IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb,
};
use serde::{Deserialize, Serialize};
use std::num::NonZeroUsize;
@ -47,24 +45,16 @@ pub enum Layer {
PartialEq,
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
)]
#[version = 2]
#[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
@ -85,10 +75,10 @@ pub struct Theme {
pub icon_button: Component,
/// link button element colors
pub link_button: Component,
/// list button element colors
pub list_button: Component,
/// text button element colors
pub text_button: Component,
/// button component styling
pub button: Component,
/// palette
pub palette: CosmicPaletteInner,
/// spacing
@ -105,43 +95,16 @@ pub struct Theme {
pub active_hint: u32,
/// cosmic-comp custom window hint color
pub window_hint: Option<Srgb>,
#[serde(default)]
/// enables blurred transparency
pub frosted: BlurStrength,
#[serde(default)]
/// frosted windows
pub frosted_windows: bool,
#[serde(default)]
/// frosted system interface
pub frosted_system_interface: bool,
#[serde(default)]
/// frosted panel
pub frosted_panel: bool,
#[serde(default)]
/// frosted applet popups
pub frosted_applets: bool,
#[serde(default)]
/// frosted apps when maximized
pub frosted_maximized_apps: bool,
#[serde(default)]
/// alpha map
pub alpha_map: AlphaMap,
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<Srgba>,
/// control tint color
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub control_tint: Option<Srgb>,
/// text tint color
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub text_tint: Option<Srgb>,
}
@ -207,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]
@ -809,7 +739,7 @@ impl Theme {
if color_scheme.trim().contains("default") || color_scheme.trim().contains("light") {
return Self::light_default();
}
}
};
Self::dark_default()
}
@ -818,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()
@ -844,7 +774,7 @@ impl From<CosmicPalette> for Theme {
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
PartialEq,
)]
#[version = 2]
#[version = 1]
pub struct ThemeBuilder {
/// override the palette for the builder
pub palette: CosmicPalette,
@ -853,70 +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<Srgb>,
/// override bg_color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub bg_color: Option<Srgba>,
/// override the primary container bg color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub primary_container_bg: Option<Srgba>,
/// override the secontary container bg color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub secondary_container_bg: Option<Srgba>,
/// override the text tint for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub text_tint: Option<Srgb>,
/// override the accent color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub accent: Option<Srgb>,
/// override the success color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub success: Option<Srgb>,
/// override the warning color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub warning: Option<Srgb>,
/// override the destructive color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub destructive: Option<Srgb>,
#[serde(default)]
/// 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<Srgb>,
#[serde(default)]
/// frosted windows
pub frosted_windows: bool,
#[serde(default)]
/// frosted system interface
pub frosted_system_interface: bool,
#[serde(default)]
/// frosted panel
pub frosted_panel: bool,
#[serde(default)]
/// frosted applet popups
pub frosted_applets: bool,
#[serde(default)]
/// frosted apps when maximized
pub frosted_maximized_apps: bool,
#[serde(default)]
/// alpha map
pub alpha_map: AlphaMap,
}
impl Default for ThemeBuilder {
@ -934,17 +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,
frosted_maximized_apps: false,
alpha_map: AlphaMap::default(),
}
}
}
@ -1068,12 +953,6 @@ impl ThemeBuilder {
}
#[allow(clippy::too_many_lines)]
// The component_hovered/pressed_overlay vars are seeded once near the
// top of this fn and then reassigned inside each container block
// (primary, secondary, …) before being read again. The initial seed
// is therefore overwritten before any read, which is what the
// unused_assignments lint flags below.
#[allow(unused_assignments)]
/// build the theme
pub fn build(self) -> Theme {
let Self {
@ -1092,23 +971,9 @@ impl ThemeBuilder {
gaps,
active_hint,
window_hint,
frosted,
frosted_windows,
frosted_system_interface,
frosted_panel,
frosted_applets,
frosted_maximized_apps,
alpha_map,
is_frosted,
} = self;
let container_alpha = alpha_map.blurred_alpha(frosted);
let actual_alpha =
if (frosted_windows || frosted_system_interface || frosted_panel || frosted_applets) {
container_alpha
} else {
1.0
};
let is_dark = palette.is_dark();
let is_high_contrast = palette.is_high_contrast();
@ -1163,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 {
@ -1191,26 +1052,10 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
);
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()),
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
);
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])
@ -1240,7 +1085,6 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
container_alpha,
),
component_hovered_overlay,
component_pressed_overlay,
@ -1253,53 +1097,6 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
container_alpha,
),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
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 t_step_array = steps(container_bg, NonZeroUsize::new(100).unwrap());
let base_index: usize = color_index(container_bg, t_step_array.len());
let component_base = get_surface_color(
base_index,
6,
&t_step_array,
is_dark,
&control_steps_array[3],
);
Container::new(
Component::component(
component_base,
accent,
get_text(
color_index(component_base, t_step_array.len()),
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
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(),
actual_alpha,
),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
@ -1373,7 +1170,6 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
@ -1386,15 +1182,10 @@ impl ThemeBuilder {
get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2])
};
let t_step_array = steps(container_bg, NonZeroUsize::new(100).unwrap());
let base_index = color_index(container_bg, t_step_array.len());
let secondary_component = get_surface_color(
base_index,
3,
&t_step_array,
is_dark,
&control_steps_array[4],
);
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]);
component_hovered_overlay = if base_index < 91 {
control_steps_array[10]
@ -1411,11 +1202,10 @@ impl ThemeBuilder {
secondary_component,
accent,
get_text(
color_index(secondary_component, t_step_array.len()),
color_index(secondary_component, step_array.len()),
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
component_hovered_overlay,
component_pressed_overlay,
@ -1428,7 +1218,6 @@ impl ThemeBuilder {
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
@ -1441,24 +1230,14 @@ impl ThemeBuilder {
button_hovered_overlay,
button_pressed_overlay,
),
accent_button: {
let step_array = steps(control_steps_array[5], NonZeroUsize::new(100).unwrap());
Component::colored_button(
accent,
control_steps_array[1],
get_text(
color_index(accent, step_array.len()),
&step_array,
&step_array[0],
text_steps_array.as_deref(),
1., // accent is opaque anyway
),
accent,
button_hovered_overlay,
button_pressed_overlay,
)
},
accent_button: Component::colored_button(
accent,
control_steps_array[1],
control_steps_array[0],
accent,
button_hovered_overlay,
button_pressed_overlay,
),
button: Component::component(
button_bg,
accent,
@ -1506,15 +1285,6 @@ impl ThemeBuilder {
component.on_disabled = over(component.on.with_alpha(0.5), component.base);
component
},
list_button: Component::component(
Srgba::new(0.0, 0.0, 0.0, 0.0),
accent,
on_bg_component,
Srgba::new(0.0, 0.0, 0.0, 0.0),
button_pressed_overlay,
is_high_contrast,
control_steps_array[8],
),
success: Component::colored_component(
success,
control_steps_array[0],
@ -1562,80 +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,
frosted_maximized_apps,
alpha_map,
transparent_background: Container::new(
Component::component(
transparent_bg_component,
accent,
on_transparent_bg_component,
component_hovered_overlay,
component_pressed_overlay,
is_high_contrast,
control_steps_array[8],
),
transparent_bg,
get_text(
bg_index,
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
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(),
actual_alpha,
),
component_hovered_overlay,
component_pressed_overlay,
is_high_contrast,
control_steps_array[8],
),
container_bg,
get_text(
base_index,
&step_array,
&control_steps_array[8],
text_steps_array.as_deref(),
actual_alpha,
),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast,
)
},
};
theme.spacing = spacing;
theme.corner_radii = corner_radii;
@ -1654,181 +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(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum BlurStrength {
ExtremelyLow,
ExtremelyLow2,
VeryLow,
VeryLow2,
Low,
Low2,
Medium,
Medium2,
High,
High2,
VeryHigh,
VeryHigh2,
ExtremelyHigh,
ExtremelyHigh2,
}
impl Default for BlurStrength {
fn default() -> Self {
Self::Medium
}
}
impl TryFrom<u8> for BlurStrength {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
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(()),
}
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AlphaMap {
pub extremely_low: f32,
pub extremely_low_2: f32,
pub very_low: f32,
pub very_low_2: f32,
pub low: f32,
pub low_2: f32,
pub medium: f32,
pub medium_2: f32,
pub high: f32,
pub high_2: f32,
pub very_high: f32,
pub very_high_2: f32,
pub extremely_high: f32,
pub extremely_high_2: f32,
}
impl AlphaMap {
pub fn blurred_alpha(&self, blur: BlurStrength) -> f32 {
match blur {
BlurStrength::ExtremelyLow => self.extremely_low,
BlurStrength::ExtremelyLow2 => self.extremely_low_2,
BlurStrength::VeryLow => self.very_low,
BlurStrength::VeryLow2 => self.very_low_2,
BlurStrength::Low => self.low,
BlurStrength::Low2 => self.low_2,
BlurStrength::Medium => self.medium,
BlurStrength::Medium2 => self.medium_2,
BlurStrength::High => self.high,
BlurStrength::High2 => self.high_2,
BlurStrength::VeryHigh => self.very_high,
BlurStrength::VeryHigh2 => self.very_high_2,
BlurStrength::ExtremelyHigh => self.extremely_high,
BlurStrength::ExtremelyHigh2 => self.extremely_high_2,
}
}
pub fn offset(self, offset: f32) -> Self {
let Self {
mut extremely_low,
mut extremely_low_2,
mut very_low,
mut very_low_2,
mut low,
mut low_2,
mut medium,
mut medium_2,
mut high,
mut high_2,
mut very_high,
mut very_high_2,
mut extremely_high,
mut extremely_high_2,
} = self;
extremely_low += offset;
extremely_low_2 += offset;
very_low += offset;
very_low_2 += offset;
low += offset;
low_2 += offset;
medium += offset;
medium_2 += offset;
high += offset;
high_2 += offset;
very_high += offset;
very_high_2 += offset;
extremely_high += offset;
extremely_high_2 += offset;
Self {
extremely_low,
extremely_low_2,
very_low,
very_low_2,
low,
low_2,
medium,
medium_2,
high,
high_2,
very_high,
very_high_2,
extremely_high,
extremely_high_2,
}
}
pub fn guess_offset(&self) -> f32 {
let Self {
extremely_low,
extremely_high_2,
..
} = self;
let default_low = Self::default().extremely_low;
let default_high = Self::default().extremely_high_2;
if *extremely_low > default_low {
0.5 * (*extremely_low - default_low) / (1.0 - default_low) + 0.5
} else if *extremely_high_2 < default_high {
0.5 - (0.5 * (default_high - *extremely_high_2) / default_high)
} else {
0.5
}
}
}
impl Default for AlphaMap {
fn default() -> Self {
Self {
extremely_low: 0.90,
extremely_low_2: 0.87692,
very_low: 0.85385,
very_low_2: 0.83076,
low: 0.80769,
low_2: 0.78461,
medium: 0.76154,
medium_2: 0.73846,
high: 0.71538,
high_2: 0.69231,
very_high: 0.66023,
very_high_2: 0.64615,
extremely_high: 0.62308,
extremely_high_2: 0.6,
}
}
}

View file

@ -1,12 +1,10 @@
use crate::{Component, Theme, composite::over, steps::steps};
use configparser::ini::Ini;
use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha, rgb::Rgba};
use std::{
fs::{self, File},
io::{self, Write},
num::NonZeroUsize,
path::Path,
process::Command,
};
use super::{OutputError, to_rgba};
@ -219,50 +217,6 @@ impl Theme {
Ok(())
}
/// Apply the preferred GTK client-side decoration button layout.
///
/// This writes the GTK 3/4 `settings.ini` value used by GTK header bars and
/// also best-effort updates GNOME's `button-layout` GSettings key for apps
/// that still consult it.
///
/// # Errors
///
/// Returns an `OutputError` if the GTK settings files cannot be written.
#[cold]
pub fn apply_gtk_decoration_layout(buttons_at_start: bool) -> Result<(), OutputError> {
let Some(config_dir) = dirs::config_dir() else {
return Err(OutputError::MissingConfigDir);
};
let layout = if buttons_at_start {
"close,minimize,maximize:"
} else {
":minimize,maximize,close"
};
for gtk_version in ["gtk-3.0", "gtk-4.0"] {
let gtk_dir = config_dir.join(gtk_version);
fs::create_dir_all(&gtk_dir).map_err(OutputError::Io)?;
Self::write_gtk_settings_key(
&gtk_dir.join("settings.ini"),
"gtk-decoration-layout",
layout,
)?;
}
// best-effort: gsettings is absent on non-GNOME systems
let _ = Command::new("gsettings")
.args([
"set",
"org.gnome.desktop.wm.preferences",
"button-layout",
layout,
])
.status();
Ok(())
}
/// Reset the applied gtk css
///
/// # Errors
@ -302,20 +256,6 @@ impl Theme {
Ok(())
}
#[cold]
fn write_gtk_settings_key(path: &Path, key: &str, value: &str) -> Result<(), OutputError> {
let mut ini = Ini::new_cs();
if path.exists() {
let file_content = fs::read_to_string(path).map_err(OutputError::Io)?;
ini.read(file_content).map_err(OutputError::Ini)?;
}
ini.setstr("Settings", key, Some(value));
ini.pretty_write(path, &super::qt_settings_ini_style())
.map_err(OutputError::Io)
}
fn is_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result<Option<bool>> {
if !path.exists() {
return Ok(None);

View file

@ -1,6 +1,5 @@
use configparser::ini::WriteOptions;
use palette::Srgba;
use palette::rgb::Rgba;
use palette::{Srgba, rgb::Rgba};
use thiserror::Error;
use crate::Theme;
@ -34,8 +33,10 @@ impl Theme {
/// Apply COSMIC theme exports for GTK and Qt applications.
pub fn apply_exports(&self) -> Result<(), OutputError> {
let gtk_res = Theme::apply_gtk(self.is_dark);
let qt_res = Theme::apply_qt(self.is_dark);
let qt56ct_res = Theme::apply_qt56ct(self.is_dark);
gtk_res?;
qt_res?;
qt56ct_res?;
Ok(())
}
@ -44,8 +45,10 @@ impl Theme {
/// Write COSMIC theme exports for GTK and Qt applications.
pub fn write_exports(&self) -> Result<(), OutputError> {
let gtk_res = self.write_gtk4();
let qt_res = self.write_qt();
let qt56ct_res = self.write_qt56ct();
gtk_res?;
qt_res?;
qt56ct_res?;
Ok(())
}

View file

@ -1,12 +1,12 @@
use crate::Theme;
use configparser::ini::Ini;
use palette::blend::Compose;
use palette::rgb::Rgba;
use palette::{Mix, Srgba, WithAlpha};
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use std::vec;
use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba};
use std::{
fs::{self, File},
io::Write,
path::PathBuf,
vec,
};
use super::{OutputError, qt_settings_ini_style};

View file

@ -1,21 +1,281 @@
use crate::Theme;
use configparser::ini::Ini;
use std::path::PathBuf;
use std::{fs, io};
use cosmic_config::CosmicConfigEntry;
use palette::{Mix, Srgba, blend::Compose};
use std::{
fs::{self, File},
io::{self, Write},
path::{Path, PathBuf},
};
use super::OutputError;
use super::{OutputError, qt_settings_ini_style};
impl Theme {
/// Reset the applied qt colors:
/// - Remove relevant keys from the `~/.config/kdeglobals` file
/// - Delete `~/.local/share/color-schemes/CosmicDark.colors` and `CosmicLight.colors`
/// Produces a color scheme ini file for Qt.
///
/// Some high-level documentation for this file can be found at:
/// - https://api.kde.org/kcolorscheme.html
/// - https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/
#[must_use]
#[cold]
pub fn as_kcolorscheme(&self) -> String {
// Usually, disabled elements will have strongly reduced contrast and are often notably darker or lighter
let disabled_color_effects = IniColorEffects {
color: self.button.disabled,
color_amount: 0.0,
color_effect: ColorEffect::Desaturate,
contrast_amount: 0.65,
contrast_effect: ColorEffect::Fade,
intensity_amount: 0.1,
intensity_effect: IntensityEffect::Lighten,
};
// Usually, inactive elements will have reduced contrast (text fades slightly into the background) and may have slightly reduced intensity
let inactive_color_effects = IniColorEffects {
color: self.palette.gray_1,
color_amount: 0.025,
color_effect: ColorEffect::Tint,
contrast_amount: 0.1,
contrast_effect: ColorEffect::Tint,
intensity_amount: 0.0,
intensity_effect: IntensityEffect::Shade,
};
let bg = self.background.base;
// the background container
let window_colors = IniColors {
background_alternate: bg.mix(self.accent.base, 0.05),
background_normal: bg,
decoration_focus: self.accent_text_color(),
decoration_hover: self.accent_text_color(),
foreground_active: self.accent_text_color(),
foreground_inactive: self.background.on.mix(bg, 0.1),
foreground_link: self.link_button.on,
foreground_negative: self.destructive_text_color(),
foreground_neutral: self.warning_text_color(),
foreground_normal: self.background.on,
foreground_positive: self.success_text_color(),
foreground_visited: self.accent_text_color(),
};
// components inside the background container
let view_colors = IniColors {
background_alternate: self.background.component.base.mix(self.accent.base, 0.05),
background_normal: self.background.component.base,
..window_colors
};
// selected text and items
let selection_colors = {
// selection colors are swapped to fix menu bar contrast
let selected = self.background.component.selected_text;
let selected_text = self.background.component.selected;
IniColors {
background_alternate: selected.mix(bg, 0.5),
background_normal: selected,
decoration_focus: selected,
decoration_hover: selected,
foreground_active: selected_text,
foreground_inactive: selected_text.mix(selected, 0.5),
foreground_link: self.link_button.base,
foreground_negative: self.destructive_color(),
foreground_neutral: self.warning_color(),
foreground_normal: selected_text,
foreground_positive: self.success_color(),
foreground_visited: self.accent_color(),
}
};
let button_colors = IniColors {
background_alternate: self.accent_button.base,
background_normal: self.button.base,
..view_colors
};
// Complementary: Areas of applications with an alternative color scheme; usually with a dark background for light color schemes.
let complementary_colors = {
let dark = if self.is_dark {
self.clone()
} else if cfg!(test) {
// For reproducible results in tests, use the default dark theme
Theme::dark_default()
} else {
Theme::dark_config()
.ok()
.as_ref()
.and_then(|conf| Theme::get_entry(conf).ok())
.unwrap_or_else(|| self.clone())
};
IniColors {
background_alternate: dark.accent.base,
background_normal: dark.background.base,
decoration_focus: dark.accent_text_color(),
decoration_hover: dark.accent_text_color(),
foreground_active: dark.accent_text_color(),
foreground_inactive: dark.background.on.mix(dark.background.base, 0.1),
foreground_link: dark.link_button.on,
foreground_negative: dark.destructive_text_color(),
foreground_neutral: dark.warning_text_color(),
foreground_normal: dark.background.on,
foreground_positive: dark.success_text_color(),
foreground_visited: dark.accent_text_color(),
}
};
// headers in cosmic don't have a background
let header_colors = &window_colors;
let header_colors_inactive = &window_colors;
// tool tips, "What's This" tips, and similar elements
let tooltip_colors = &view_colors;
let general_color_scheme = if self.is_dark {
"CosmicDark"
} else {
"CosmicLight"
};
let general_name = if self.is_dark {
"COSMIC Dark"
} else {
"COSMIC Light"
};
// COSMIC icons are stuck in light mode, so use breeze icons instead
let icons_theme = if self.is_dark {
"breeze-dark"
} else {
"breeze"
};
format!(
r#"# GENERATED BY COSMIC
[ColorEffects:Disabled]
{}
[ColorEffects:Inactive]
ChangeSelectionColor=false
Enable=false
{}
[Colors:Button]
{}
[Colors:Complementary]
{}
[Colors:Header]
{}
[Colors:Header][Inactive]
{}
[Colors:Selection]
{}
[Colors:Tooltip]
{}
[Colors:View]
{}
[Colors:Window]
{}
[General]
ColorScheme={general_color_scheme}
Name={general_name}
shadeSortColumn=true
[Icons]
Theme={icons_theme}
[KDE]
contrast=4
widgetStyle=qt6ct-style
[WM]
{}
"#,
format_ini_color_effects(&disabled_color_effects, bg),
format_ini_color_effects(&inactive_color_effects, bg),
format_ini_colors(&button_colors, bg),
format_ini_colors(&complementary_colors, bg),
format_ini_colors(&header_colors, bg),
format_ini_colors(&header_colors_inactive, bg),
format_ini_colors(&selection_colors, bg),
format_ini_colors(&tooltip_colors, bg),
format_ini_colors(&view_colors, bg),
format_ini_colors(&window_colors, bg),
format_ini_wm_colors(&window_colors, self.is_dark),
)
}
/// Write the color scheme to the appropriate directory.
/// Should be written in `~/.local/share/color-schemes/`.
///
/// See the docs: https://develop.kde.org/docs/plasma/#color-scheme
///
/// # Errors
///
/// Returns an `OutputError` if there is an error writing the colors file.
#[cold]
pub fn write_qt(&self) -> Result<(), OutputError> {
let kcolorscheme = self.as_kcolorscheme();
let file_path = Self::get_kcolorscheme_path(self.is_dark)?;
let tmp_file_path = file_path.with_extension("colors.new");
// Write to tmp_file_path first, then move it to file_path
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
let res = tmp_file
.write_all(kcolorscheme.as_bytes())
.and_then(|_| tmp_file.flush())
.and_then(|_| std::fs::rename(&tmp_file_path, file_path));
if let Err(e) = res {
_ = std::fs::remove_file(&tmp_file_path);
return Err(OutputError::Io(e));
}
Ok(())
}
/// Apply the color scheme by copying its values to `~/.config/kdeglobals`.
///
/// See the docs: https://develop.kde.org/docs/plasma/#color-scheme
///
/// # Errors
///
/// Returns an `OutputError` if there is an error applying the color scheme.
#[cold]
pub fn apply_qt(is_dark: bool) -> Result<(), OutputError> {
let Some(config_dir) = dirs::config_dir() else {
return Err(OutputError::MissingConfigDir);
};
let kdeglobals_file = config_dir.join("kdeglobals");
let mut kdeglobals_ini = Self::read_ini(&kdeglobals_file)?;
let src_file = Self::get_kcolorscheme_path(is_dark)?;
let src_ini = Self::read_ini(&src_file)?;
Self::backup_non_cosmic_kdeglobals(&kdeglobals_ini, &kdeglobals_file)
.map_err(OutputError::Io)?;
for (section, key_value) in src_ini.get_map_ref() {
for (key, value) in key_value {
kdeglobals_ini.set(section, key, value.clone());
}
}
kdeglobals_ini
.pretty_write(kdeglobals_file, &qt_settings_ini_style())
.map_err(OutputError::Io)?;
Ok(())
}
/// Reset the applied qt colors by removing color scheme values from the
/// `~/.config/kdeglobals` file.
///
/// This does not restore the backed up kdeglobals file.
///
/// # Errors
///
/// Returns an `OutputError` if there is an error resetting the kdeglobals
/// file or deleting the color schemes.
/// Returns an `OutputError` if there is an error resetting the CSS file.
#[cold]
pub fn reset_qt() -> Result<(), OutputError> {
let Some(config_dir) = dirs::config_dir() else {
@ -32,29 +292,19 @@ impl Theme {
return Ok(());
}
let light_scheme = Self::get_kcolorscheme_path(false)?;
let dark_scheme = Self::get_kcolorscheme_path(true)?;
if light_scheme.exists() {
let src_ini = Self::read_ini(&light_scheme)?;
let is_dark = false; // doesn't matter since we're only reading keys
let src_file = Self::get_kcolorscheme_path(is_dark)?;
let src_ini = Self::read_ini(&src_file)?;
// Remove color scheme keys from kdeglobals
for (section, key_value) in src_ini.get_map_ref() {
for key in key_value.keys() {
kdeglobals_ini.remove_key(section, key);
}
for (section, key_value) in src_ini.get_map_ref() {
for (key, _) in key_value {
kdeglobals_ini.remove_key(section, key);
}
kdeglobals_ini
.write(kdeglobals_file)
.map_err(OutputError::Io)?;
}
// Delete now-unused kcolorscheme files
let delete_light_res = fs::remove_file(&light_scheme).map_err(OutputError::Io);
let delete_dark_res = fs::remove_file(&dark_scheme).map_err(OutputError::Io);
delete_light_res?;
delete_dark_res?;
kdeglobals_ini
.write(kdeglobals_file)
.map_err(OutputError::Io)?;
Ok(())
}
@ -63,7 +313,6 @@ impl Theme {
let Some(mut data_dir) = dirs::data_dir() else {
return Err(OutputError::MissingDataDir);
};
data_dir.push("color-schemes");
let file_name = if is_dark {
"CosmicDark.colors"
@ -71,6 +320,11 @@ impl Theme {
"CosmicLight.colors"
};
data_dir.push("color-schemes");
if !data_dir.exists() {
std::fs::create_dir_all(&data_dir).map_err(OutputError::Io)?;
}
Ok(data_dir.join(file_name))
}
@ -85,6 +339,15 @@ impl Theme {
Ok(ini)
}
#[cold]
fn backup_non_cosmic_kdeglobals(ini: &Ini, path: &Path) -> io::Result<()> {
if !Self::is_cosmic_kdeglobals(&ini)?.unwrap_or(true) {
let backup_path = path.with_extension("bak");
fs::copy(path, &backup_path)?;
}
Ok(())
}
#[cold]
fn is_cosmic_kdeglobals(ini: &Ini) -> io::Result<Option<bool>> {
let color_scheme = ini.get("General", "ColorScheme");
@ -98,43 +361,208 @@ impl Theme {
}
}
/// Formats a color in the form `r,g,b` e.g. `255,255,255`.
/// If the color has transparency, it is mixed with bg first.
fn to_rgb(c: Srgba, bg: Srgba) -> String {
let c_u8: Srgba<u8> = c.over(bg).into_format();
format!("{},{},{}", c_u8.red, c_u8.green, c_u8.blue)
}
fn format_ini_color_effects(color_effects: &IniColorEffects, bg: Srgba) -> String {
format!(
r#"Color={}
ColorAmount={}
ColorEffect={}
ContrastAmount={}
ContrastEffect={}
IntensityAmount={}
IntensityEffect={}"#,
to_rgb(color_effects.color, bg),
color_effects.color_amount,
color_effects.color_effect.as_u8(),
color_effects.contrast_amount,
color_effects.contrast_effect.as_u8(),
color_effects.intensity_amount,
color_effects.intensity_effect.as_u8(),
)
}
fn format_ini_colors(colors: &IniColors, bg: Srgba) -> String {
format!(
r#"BackgroundAlternate={}
BackgroundNormal={}
DecorationFocus={}
DecorationHover={}
ForegroundActive={}
ForegroundInactive={}
ForegroundLink={}
ForegroundNegative={}
ForegroundNeutral={}
ForegroundNormal={}
ForegroundPositive={}
ForegroundVisited={}"#,
to_rgb(colors.background_alternate, bg),
to_rgb(colors.background_normal, bg),
to_rgb(colors.decoration_focus, bg),
to_rgb(colors.decoration_hover, bg),
to_rgb(colors.foreground_active, bg),
to_rgb(colors.foreground_inactive, bg),
to_rgb(colors.foreground_link, bg),
to_rgb(colors.foreground_negative, bg),
to_rgb(colors.foreground_neutral, bg),
to_rgb(colors.foreground_normal, bg),
to_rgb(colors.foreground_positive, bg),
to_rgb(colors.foreground_visited, bg),
)
}
/// Sets the colors for the titlebars of active and inactive windows.
fn format_ini_wm_colors(view_colors: &IniColors, is_dark: bool) -> String {
let bg = view_colors.background_normal;
let fg = view_colors.foreground_active;
let blend = if is_dark { fg } else { bg };
format!(
r#"activeBackground={}
activeBlend={}
activeForeground={}
inactiveBackground={}
inactiveBlend={}
inactiveForeground={}"#,
to_rgb(bg, bg),
to_rgb(blend, bg),
to_rgb(fg, bg),
to_rgb(bg, bg),
to_rgb(blend, bg),
to_rgb(fg, bg),
)
}
struct IniColorEffects {
color: Srgba,
color_amount: f32,
color_effect: ColorEffect,
contrast_amount: f32,
/// Applied to the text, using the background as the reference color.
contrast_effect: ColorEffect,
intensity_amount: f32,
intensity_effect: IntensityEffect,
}
/// Each color set is made up of a number of roles which are available in all other sets.
/// In addition, except for Inactive Text, there is a corresponding background role for each of the text roles. Currently (except for Normal and Alternate Background), these colors are not chosen here but are automatically determined based on Normal Background and the corresponding Text color.
struct IniColors {
/// used when there is a need to subtly change the background to aid in item association. This might be used e.g. as the background of a heading, but is mostly used for alternating rows in lists, especially multi-column lists, to aid in visually tracking rows.
background_alternate: Srgba,
/// Normal background
background_normal: Srgba,
/// Used for drawing lines or shading UI elements to indicate the item which has active input focus.
/// Typically the same as foreground_active.
decoration_focus: Srgba,
/// Used for drawing lines or shading UI elements for mouse-over effects, e.g. the "illumination" effects for buttons.
/// Typically the same as foreground_active.
decoration_hover: Srgba,
/// used to indicate an active element or attract attention, e.g. alerts, notifications; also for hovered hyperlinks
foreground_active: Srgba,
/// used for text which should be unobtrusive, e.g. comments, "subtitles", unimportant information, etc.
foreground_inactive: Srgba,
/// used for hyperlinks or to otherwise indicate "something which may be visited", or to show relationships
foreground_link: Srgba,
/// used for errors, failure notices, notifications that an action may be dangerous (e.g. unsafe web page or security context), etc.
foreground_negative: Srgba,
/// used to draw attention when another role is not appropriate; e.g. warnings, to indicate secure/encrypted content, etc.
foreground_neutral: Srgba,
/// Normal foreground
foreground_normal: Srgba,
/// used for success notices, to indicate trusted content, etc.
foreground_positive: Srgba,
/// used for "something (e.g. a hyperlink) that has been visited", or to indicate something that is "old".
foreground_visited: Srgba,
}
/// Intensity allows the overall color to be lightened or darkened.
#[allow(dead_code)]
enum IntensityEffect {
/// Makes everything lighter or darker in a controlled manner.
///
/// intensity_amount increases or decreases the overall intensity (i.e. perceived brightness) by an absolute amount.
Shade,
/// Changes the intensity to a percentage of the initial value.
Darken,
/// Conceptually the opposite of darken; lighten can be thought of as working with "distance from white", where darken works with "distance from black".
Lighten,
}
impl IntensityEffect {
pub fn as_u8(&self) -> u8 {
match self {
Self::Shade => 0,
Self::Darken => 1,
Self::Lighten => 2,
}
}
}
/// This also changes the overall color like [IntensityEffect],
/// but is not limited to intensity.
#[allow(dead_code)]
enum ColorEffect {
/// changes the relative chroma
///
/// This is available for "ColorEffect" but not "ContrastEffect".
Desaturate,
/// smoothly blends the original color into a reference color
Fade,
/// similar to Fade, except that the color (hue and chroma) changes more quickly while the intensity changes more slowly as the amount is increased
Tint,
}
impl ColorEffect {
pub fn as_u8(&self) -> u8 {
match self {
Self::Desaturate => 0,
Self::Fade => 1,
Self::Tint => 2,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_cosmic_kdeglobals_dark() -> io::Result<()> {
let mut ini = Ini::new_cs();
ini.setstr("General", "ColorScheme", Some("CosmicDark"));
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?;
assert_eq!(is_cosmic, Some(true));
Ok(())
fn test_opaque_color_to_rgb() {
let color = Srgba::new(30.0 / 255.0, 50.0 / 255.0, 70.0 / 255.0, 1.0);
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
let result = to_rgb(color, bg);
assert_eq!(result, "30,50,70");
}
#[test]
fn test_is_cosmic_kdeglobals_light() -> io::Result<()> {
let mut ini = Ini::new_cs();
ini.setstr("General", "ColorScheme", Some("CosmicLight"));
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?;
assert_eq!(is_cosmic, Some(true));
Ok(())
fn test_transparent_color_to_rgb() {
let color = Srgba::new(0.0, 0.0, 0.0, 0.0);
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
let result = to_rgb(color, bg);
assert_eq!(result, "255,255,255");
}
#[test]
fn test_is_cosmic_kdeglobals_breeze() -> io::Result<()> {
let mut ini = Ini::new_cs();
ini.setstr("General", "ColorScheme", Some("BreezeDark"));
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?;
assert_eq!(is_cosmic, Some(false));
Ok(())
fn test_translucent_color_to_rgb() {
let color = Srgba::new(0.0, 0.0, 0.0, 0.9);
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
let result = to_rgb(color, bg);
assert_eq!(result, "26,26,26");
}
#[test]
fn test_is_cosmic_kdeglobals_blank() -> io::Result<()> {
let mut ini = Ini::new_cs();
ini.setstr("General", "ColorScheme", None);
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?;
assert_eq!(is_cosmic, None);
Ok(())
fn test_light_default_kcolorscheme() {
let light_default_kcolorscheme = Theme::light_default().as_kcolorscheme();
insta::assert_snapshot!(light_default_kcolorscheme);
}
#[test]
fn test_dark_default_kcolorscheme() {
let dark_default_kcolorscheme = Theme::dark_default().as_kcolorscheme();
insta::assert_snapshot!(dark_default_kcolorscheme);
}
}

View file

@ -0,0 +1,157 @@
---
source: cosmic-theme/src/output/qt_output.rs
expression: dark_default_kcolorscheme
---
# GENERATED BY COSMIC
[ColorEffects:Disabled]
Color=43,43,43
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
ContrastEffect=1
IntensityAmount=0.1
IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=false
Enable=false
Color=27,27,27
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
ContrastEffect=2
IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate=99,208,223
BackgroundNormal=60,60,60
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Complementary]
BackgroundAlternate=99,208,223
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Header]
BackgroundAlternate=31,36,37
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Header][Inactive]
BackgroundAlternate=31,36,37
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Selection]
BackgroundAlternate=63,118,125
BackgroundNormal=99,208,223
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=67,67,67
ForegroundInactive=83,138,145
ForegroundLink=27,27,27
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=67,67,67
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Tooltip]
BackgroundAlternate=49,55,55
BackgroundNormal=46,46,46
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:View]
BackgroundAlternate=49,55,55
BackgroundNormal=46,46,46
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Window]
BackgroundAlternate=31,36,37
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[General]
ColorScheme=CosmicDark
Name=COSMIC Dark
shadeSortColumn=true
[Icons]
Theme=breeze-dark
[KDE]
contrast=4
widgetStyle=qt6ct-style
[WM]
activeBackground=27,27,27
activeBlend=99,208,223
activeForeground=99,208,223
inactiveBackground=27,27,27
inactiveBlend=99,208,223
inactiveForeground=99,208,223

View file

@ -0,0 +1,157 @@
---
source: cosmic-theme/src/output/qt_output.rs
expression: light_default_kcolorscheme
---
# GENERATED BY COSMIC
[ColorEffects:Disabled]
Color=194,194,194
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
ContrastEffect=1
IntensityAmount=0.1
IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=false
Enable=false
Color=215,215,215
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
ContrastEffect=2
IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate=0,82,90
BackgroundNormal=173,173,173
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Complementary]
BackgroundAlternate=99,208,223
BackgroundNormal=27,27,27
DecorationFocus=99,208,223
DecorationHover=99,208,223
ForegroundActive=99,208,223
ForegroundInactive=211,211,211
ForegroundLink=99,208,223
ForegroundNegative=255,160,154
ForegroundNeutral=255,163,125
ForegroundNormal=231,231,231
ForegroundPositive=94,219,140
ForegroundVisited=99,208,223
[Colors:Header]
BackgroundAlternate=204,208,209
BackgroundNormal=215,215,215
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Header][Inactive]
BackgroundAlternate=204,208,209
BackgroundNormal=215,215,215
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Selection]
BackgroundAlternate=108,149,152
BackgroundNormal=0,82,90
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=246,246,246
ForegroundInactive=123,164,168
ForegroundLink=215,215,215
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=246,246,246
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Tooltip]
BackgroundAlternate=233,237,237
BackgroundNormal=245,245,245
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:View]
BackgroundAlternate=233,237,237
BackgroundNormal=245,245,245
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[Colors:Window]
BackgroundAlternate=204,208,209
BackgroundNormal=215,215,215
DecorationFocus=0,82,90
DecorationHover=0,82,90
ForegroundActive=0,82,90
ForegroundInactive=38,38,38
ForegroundLink=0,82,90
ForegroundNegative=137,4,24
ForegroundNeutral=121,44,0
ForegroundNormal=18,18,18
ForegroundPositive=0,87,44
ForegroundVisited=0,82,90
[General]
ColorScheme=CosmicLight
Name=COSMIC Light
shadeSortColumn=true
[Icons]
Theme=breeze
[KDE]
contrast=4
widgetStyle=qt6ct-style
[WM]
activeBackground=215,215,215
activeBlend=215,215,215
activeForeground=0,82,90
inactiveBackground=215,215,215
inactiveBlend=215,215,215
inactiveForeground=0,82,90

View file

@ -266,14 +266,6 @@ impl From<Theme> for VsTheme {
}
impl Theme {
/// Write this theme to VS Code's `settings.json` as a
/// `workbench.colorCustomizations` entry, and enable
/// `window.autoDetectColorScheme` so VS Code follows the system theme.
///
/// # Errors
///
/// Returns an `OutputError` if the user config dir is missing, the
/// settings file cannot be read/written, or its JSON is invalid.
#[cold]
pub fn apply_vs_code(self) -> Result<(), OutputError> {
let vs_theme = VsTheme::from(self);
@ -299,13 +291,6 @@ impl Theme {
Ok(())
}
/// Remove the `workbench.colorCustomizations` entry previously written
/// by [`Theme::apply_vs_code`] from VS Code's `settings.json`.
///
/// # Errors
///
/// Returns an `OutputError` if the user config dir is missing, the
/// settings file cannot be read/written, or its JSON is invalid.
#[cold]
pub fn reset_vs_code() -> Result<(), OutputError> {
let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;

View file

@ -1,8 +1,7 @@
use std::num::NonZeroUsize;
use almost::equal;
use palette::convert::FromColorUnclamped;
use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba};
use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba, convert::FromColorUnclamped};
/// Get an array of 100 colors with a specific hue and chroma
/// over the full range of lightness.
@ -81,7 +80,6 @@ pub fn get_text(
step_array: &[Srgba],
fallback: &Srgba,
tint_array: Option<&[Srgba]>,
alpha: f32,
) -> Srgba {
assert!(step_array.len() == 100);
let step_array = if let Some(tint_array) = tint_array {
@ -91,28 +89,11 @@ pub fn get_text(
step_array
};
let alpha_extra_steps = if alpha < 1.0 {
((1. - alpha) * 100.0).round() as usize
} else {
0
};
let is_dark = base_index < 60;
let index = get_index(
base_index,
70 + alpha_extra_steps,
step_array.len(),
is_dark,
)
.or_else(|| {
get_index(
base_index,
50 + alpha_extra_steps,
step_array.len(),
is_dark,
)
})
.unwrap_or(if is_dark { 99 } else { 0 });
let index = get_index(base_index, 70, step_array.len(), is_dark)
.or_else(|| get_index(base_index, 50, step_array.len(), is_dark))
.unwrap_or(if is_dark { 99 } else { 0 });
*step_array.get(index).unwrap_or(fallback)
}

View file

@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
open = "5.3.3"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
features = [
"debug",

View file

@ -8,8 +8,7 @@ use cosmic::app::{Core, Settings, Task};
use cosmic::executor;
use cosmic::iced::{alignment, Length, Size};
use cosmic::prelude::*;
use cosmic::widget::about::About;
use cosmic::widget::{self, nav_bar};
use cosmic::widget::{self, about::About, nav_bar};
/// Runs application with these settings
#[rustfmt::skip]

View file

@ -12,7 +12,7 @@ tracing = "0.1"
env_logger = "0.10.2"
log = "0.4.29"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
default-features = false
features = ["applet-token"]

View file

@ -4,8 +4,7 @@ use cosmic::iced::core::window;
use cosmic::iced::window::Id;
use cosmic::iced::{Length, Rectangle};
use cosmic::surface::action::{app_popup, destroy_popup};
use cosmic::widget::dropdown::popup_dropdown;
use cosmic::widget::{list_column, settings, toggler};
use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler};
use cosmic::Element;
const ID: &str = "com.system76.CosmicAppletExample";
@ -101,7 +100,6 @@ impl cosmic::Application for Window {
Message::Surface(destroy_popup(id))
} else {
Message::Surface(app_popup::<Window>(
|_| Default::default(),
move |state: &mut Window| {
let new_id = Id::unique();
state.popup = Some(new_id);
@ -124,6 +122,8 @@ impl cosmic::Application for Window {
},
Some(Box::new(move |state: &Window| {
let content_list = list_column()
.padding(5)
.spacing(0)
.add(settings::item(
"Example row",
cosmic::widget::container(

View file

@ -5,12 +5,12 @@ edition = "2021"
[features]
default = ["wayland"]
wayland = ["libcosmic-yoda/wayland"]
wayland = ["libcosmic/wayland"]
[dependencies]
env_logger = "0.11"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
features = [
"debug",
@ -22,5 +22,4 @@ features = [
"surface-message",
"multi-window",
"wgpu",
"wayland",
]

View file

@ -5,10 +5,9 @@
use cosmic::app::Settings;
use cosmic::iced::{Alignment, Length, Size};
use cosmic::prelude::*;
use cosmic::widget::menu::{self, KeyBind};
use cosmic::widget::{nav_bar, RcElementWrapper};
use cosmic::{executor, iced, widget, Core};
use cosmic::widget::nav_bar;
use cosmic::{executor, iced, prelude::*, widget, Core};
use std::collections::HashMap;
use std::sync::LazyLock;
@ -43,7 +42,7 @@ pub enum Action {
impl widget::menu::Action for Action {
type Message = Message;
fn message(&self) -> Self::Message {
fn message(&self) -> Message {
match self {
Action::Hi => Message::Hi,
Action::Hi2 => Message::Hi2,
@ -52,25 +51,6 @@ impl widget::menu::Action for Action {
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Action2 {
Hi,
Hi2,
Hi3,
}
impl widget::menu::Action for Action2 {
type Message = cosmic::Action<Message>;
fn message(&self) -> Self::Message {
cosmic::Action::App(match self {
Action2::Hi => Message::Hi,
Action2::Hi2 => Message::Hi2,
Action2::Hi3 => Message::Hi3,
})
}
}
/// Runs application with these settings
#[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -103,7 +83,6 @@ pub enum Message {
Hi2,
Hi3,
Tick,
ValueChanged(f32),
}
/// The [`App`] stores application-specific state.
@ -115,7 +94,6 @@ pub struct App {
hidden: bool,
keybinds: HashMap<KeyBind, Action>,
progress: f32,
progress_slider: f32,
}
/// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -158,7 +136,6 @@ impl cosmic::Application for App {
hidden: true,
keybinds: HashMap::new(),
progress: 0.0,
progress_slider: 0.0,
};
let command = app.update_title();
@ -177,70 +154,6 @@ impl cosmic::Application for App {
self.update_title()
}
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
Some(menu::nav_context(
&HashMap::new(),
vec![
vec![cosmic::widget::menu::Item::Button(
"Hi",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
)],
vec![
cosmic::widget::menu::Item::Button(
"Hi 2",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 22",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
],
vec![
cosmic::widget::menu::Item::Button(
"Hi 3",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 33",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 333",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
],
vec![
cosmic::widget::menu::Item::Button(
"Hi 44",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 4444",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 444444",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
cosmic::widget::menu::Item::Button(
"Hi 444444444444",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action2::Hi,
),
],
],
))
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> cosmic::app::Task<Self::Message> {
match message {
@ -271,9 +184,6 @@ impl cosmic::Application for App {
Message::Tick => {
self.progress = (self.progress + 0.01) % 1.0;
}
Message::ValueChanged(value) => {
self.progress_slider = value;
}
}
Task::none()
}
@ -290,7 +200,7 @@ impl cosmic::Application for App {
.map_or("No page selected", String::as_str);
let centered = widget::container(
widget::column::with_capacity(16)
widget::column::with_capacity(14)
.push(widget::text::body(page_content))
.push(
widget::text_input::text_input("", &self.input_1)
@ -331,18 +241,6 @@ impl cosmic::Application for App {
.progress(self.progress)
.width(Length::Fill),
)
.push(
widget::slider(0.0..=1.0, self.progress_slider, Message::ValueChanged)
.step(0.001),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(self.progress_slider)
.width(Length::Fill)
.markers([0.25, 0.5, 0.75])
.segment_spacing(2),
)
.push(
widget::progress_bar::circular::Circular::new()
.size(50.0)
@ -365,7 +263,6 @@ impl cosmic::Application for App {
.progress(1.0)
.width(Length::Fill),
)
.push(widget::button::suggested("asdf").on_press(Message::Ignore))
.spacing(cosmic::theme::spacing().space_s)
.width(Length::Fill)
.height(Length::Shrink)

View file

@ -8,6 +8,6 @@ edition = "2024"
[dependencies]
jiff = "0.2"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]

View file

@ -8,7 +8,7 @@ tracing = "0.1.44"
tracing-subscriber = "0.3.22"
tracing-log = "0.2.0"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
features = [
"debug",

View file

@ -30,7 +30,6 @@ pub enum Message {
WindowClose,
Surface(cosmic::surface::Action),
ToggleHideContent,
ToggleSomeAction,
WindowNew,
}
@ -83,7 +82,6 @@ impl cosmic::Application for App {
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
tracing::error!("Clicked {message:?}");
match message {
Message::Clicked => {
self.button_label = format!("Clicked {message:?}");
@ -93,10 +91,9 @@ impl cosmic::Application for App {
cosmic::app::Action::Surface(action),
));
}
Message::WindowClose
| Message::ToggleHideContent
| Message::ToggleSomeAction
| Message::WindowNew => {}
Message::WindowClose => {}
Message::ToggleHideContent => {}
Message::WindowNew => {}
}
Task::none()
@ -105,7 +102,7 @@ impl cosmic::Application for App {
/// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> {
let widget = cosmic::widget::context_menu(
cosmic::widget::button::text(self.button_label.clone()).on_press(Message::Clicked),
cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked),
self.context_menu(),
)
.on_surface_action(Message::Surface);
@ -129,20 +126,12 @@ impl App {
menu::Item::Divider,
menu::Item::Folder(
"View",
vec![
menu::Item::CheckBox(
"Hide content",
None,
self.hide_content,
ContextMenuAction::ToggleHideContent,
),
menu::Item::CheckBox(
"Test content",
None,
self.hide_content,
ContextMenuAction::ToggleSomeAction,
),
],
vec![menu::Item::CheckBox(
"Hide content",
None,
self.hide_content,
ContextMenuAction::ToggleHideContent,
)],
),
menu::Item::Divider,
menu::Item::Button("Quit", None, ContextMenuAction::WindowClose),
@ -155,7 +144,6 @@ impl App {
pub enum ContextMenuAction {
WindowClose,
ToggleHideContent,
ToggleSomeAction,
WindowNew,
}
@ -165,7 +153,6 @@ impl menu::Action for ContextMenuAction {
match self {
ContextMenuAction::WindowClose => Message::WindowClose,
ContextMenuAction::ToggleHideContent => Message::ToggleHideContent,
ContextMenuAction::ToggleSomeAction => Message::ToggleSomeAction,
ContextMenuAction::WindowNew => Message::WindowNew,
}
}

View file

@ -8,7 +8,7 @@ publish = false
[dependencies]
apply = "0.3.0"
fraction = "0.15.3"
libcosmic-yoda = { path = "../..", features = [
libcosmic = { path = "../..", features = [
"debug",
"winit",
"tokio",

View file

@ -23,11 +23,15 @@ use cosmic::{
Element,
};
use cosmic_time::{Instant, Timeline};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::vec;
use std::{
cell::RefCell,
rc::Rc,
sync::{
atomic::{AtomicU32, Ordering},
Arc,
},
vec,
};
// XXX The use of button is removed because it assigns the same ID to multiple buttons, causing a crash when a11y is enabled...
// static BTN: Lazy<id::Id> = Lazy::new(|| id::Id::new("BTN"));

View file

@ -1,7 +1,9 @@
use super::{Page, Window};
use cosmic::iced::widget::{column, text};
use cosmic::widget::{list_column, settings, toggler};
use cosmic::Element;
use cosmic::{
iced::widget::{column, text},
widget::{list_column, settings, toggler},
Element,
};
#[derive(Clone, Copy, Debug)]
pub enum Message {

View file

@ -1,17 +1,19 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::{cell::RefCell, rc::Rc};
use apply::Apply;
use cosmic::iced::widget::{checkbox, column, progress_bar, radio, slider, text};
use cosmic::iced::{Alignment, Length};
use cosmic::iced_core::id;
use cosmic::theme::ThemeType;
use cosmic::widget::color_picker::ColorPickerUpdate;
use cosmic::widget::{
button, dropdown, icon, layer_container as container, segmented_button, segmented_control,
settings, spin_button, tab_bar, toggler, ColorPickerModel,
use cosmic::{
cosmic_theme,
iced::widget::{checkbox, column, progress_bar, radio, slider, text},
iced::{Alignment, Length},
iced_core::id,
theme::ThemeType,
widget::{
button, color_picker::ColorPickerUpdate, dropdown, icon, layer_container as container,
segmented_button, segmented_control, settings, spin_button, tab_bar, toggler,
ColorPickerModel,
},
Element,
};
use cosmic::{cosmic_theme, Element};
use cosmic_time::{anim, chain, Timeline};
use fraction::{Decimal, ToPrimitive};
use once_cell::sync::Lazy;

View file

@ -1,7 +1,10 @@
use cosmic::iced::widget::{column, container, horizontal_space, image, row, svg, text};
use cosmic::iced::Length;
use cosmic::widget::{list_column, settings, toggler};
use cosmic::{theme, Element};
use cosmic::{
iced::widget::{column, container, horizontal_space, image, row, svg, text},
iced::Length,
theme,
widget::{list_column, settings, toggler},
Element,
};
use super::{Page, SubPage, Window};

View file

@ -1,7 +1,9 @@
use cosmic::iced::widget::{horizontal_space, row, text};
use cosmic::iced::Length;
use cosmic::widget::{icon, list_column, settings};
use cosmic::Element;
use cosmic::{
iced::widget::{horizontal_space, row, text},
iced::Length,
widget::{icon, list_column, settings},
Element,
};
use super::{Message, Page, SubPage, Window};

View file

@ -7,6 +7,6 @@ edition = "2021"
tracing = "0.1.44"
tracing-subscriber = "0.3.22"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
features = ["debug", "winit", "wgpu", "tokio"]

View file

@ -8,6 +8,6 @@ tracing = "0.1.44"
tracing-subscriber = "0.3.22"
tracing-log = "0.2.0"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]

View file

@ -9,9 +9,11 @@ use std::{env, process};
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::keyboard::Key;
use cosmic::iced::{event, keyboard, window, Event, Length, Size, Subscription};
use cosmic::iced::window;
use cosmic::iced::{Length, Size};
use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::{KeyBind, Modifier};
use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::key_bind::Modifier;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth};
use cosmic::widget::RcElementWrapper;
use cosmic::{executor, Element};
@ -112,26 +114,6 @@ impl cosmic::Application for App {
vec![menu_bar(&self.config, &self.key_binds)]
}
fn subscription(&self) -> Subscription<Self::Message> {
event::listen_with(|event, _status, _window_id| {
let Event::Keyboard(keyboard::Event::KeyPressed {
key,
modifiers,
physical_key,
..
}) = event
else {
return None;
};
let key_binds = key_binds();
key_binds
.iter()
.find(|(key_bind, _)| key_bind.matches(modifiers, &key, Some(&physical_key)))
.map(|(_, action)| action.message())
})
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message {

View file

@ -6,4 +6,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libcosmic-yoda = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] }
libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] }

View file

@ -1,11 +1,13 @@
use std::collections::HashMap;
use cosmic::app::Core;
use cosmic::iced::core::{id, Alignment, Length, Point};
use cosmic::iced::widget::{column, container, scrollable, text};
use cosmic::iced::{self, event, window, Subscription};
use cosmic::prelude::*;
use cosmic::widget::{button, header_bar, selectable_text};
use cosmic::{
app::Core,
iced::core::{id, Alignment, Length, Point},
iced::widget::{column, container, scrollable, text},
iced::{self, event, window, Subscription},
prelude::*,
widget::{button, header_bar},
};
#[derive(Debug, Clone, PartialEq)]
pub enum Message {
@ -132,14 +134,10 @@ impl cosmic::Application for MultiWindow {
let new_window_button = button::custom(text("New Window")).on_press(Message::NewWindow);
let content = scrollable(
column![
input,
new_window_button,
selectable_text::title1("I'm selectable...")
]
.spacing(50)
.width(Length::Fill)
.align_x(Alignment::Center),
column![input, new_window_button]
.spacing(50)
.width(Length::Fill)
.align_x(Alignment::Center),
);
let window_content = container(container(content).center_x(Length::Fixed(200.)))
@ -150,14 +148,7 @@ impl cosmic::Application for MultiWindow {
if id == self.core.main_window_id().unwrap() {
window_content.into()
} else {
container(column![
header_bar()
.on_close(Message::CloseWindow(id))
.focused(focused),
window_content
])
.class(cosmic::theme::Container::WindowBackground)
.into()
column![header_bar().focused(focused), window_content].into()
}
}

View file

@ -8,6 +8,6 @@ tracing = "0.1.44"
tracing-subscriber = "0.3.22"
tracing-log = "0.2.0"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]

View file

@ -7,7 +7,7 @@ use std::collections::HashMap;
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Size;
use cosmic::widget::{menu, nav_bar, space};
use cosmic::widget::{menu, nav_bar};
use cosmic::{executor, iced, ApplicationExt, Element};
#[derive(Clone, Copy)]
@ -127,20 +127,18 @@ impl cosmic::Application for App {
Some(&self.nav_model)
}
/// The context menu list.
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
Some(menu::nav_context(
/// The context menu to display for the given nav bar item ID.
fn nav_context_menu(
&self,
id: nav_bar::Id,
) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
Some(menu::items(
&HashMap::new(),
self.nav_model
.iter()
.map(|e| {
vec![
menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(e)),
menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(e)),
menu::Item::Button("Delete", None, NavMenuAction::Delete(e)),
]
})
.collect(),
vec![
menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(id)),
menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(id)),
menu::Item::Button("Delete", None, NavMenuAction::Delete(id)),
],
))
}

View file

@ -5,8 +5,8 @@ edition = "2021"
[features]
default = ["xdg-portal"]
rfd = ["libcosmic-yoda/rfd"]
xdg-portal = ["libcosmic-yoda/xdg-portal"]
rfd = ["libcosmic/rfd"]
xdg-portal = ["libcosmic/xdg-portal"]
[dependencies]
apply = "0.3.0"
@ -15,6 +15,6 @@ tracing = "0.1.44"
tracing-subscriber = "0.3.22"
url = "2.5.8"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
features = ["debug", "winit", "wgpu", "wayland", "tokio"]
path = "../../"

View file

@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
fraction = "0.15.3"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
features = ["debug", "wgpu", "winit", "desktop", "tokio"]
path = "../.."
default-features = false

View file

@ -1,8 +1,15 @@
use cosmic::app::{Core, Task};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::{self, Alignment, Length, Size};
use cosmic::iced::Length;
use cosmic::widget::{column, container, spin_button};
use cosmic::{Application, Apply, Element};
use cosmic::Apply;
use cosmic::{
app::{Core, Task},
iced::{
self,
alignment::{Horizontal, Vertical},
Alignment, Size,
},
Application, Element,
};
use fraction::Decimal;
pub struct SpinButtonExamplApp {

View file

@ -5,6 +5,6 @@ edition = "2024"
[dependencies]
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"]

View file

@ -5,8 +5,7 @@
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Subscription;
use cosmic::prelude::*;
use cosmic::{executor, widget};
use cosmic::{executor, prelude::*, widget};
/// Runs application with these settings
fn main() -> Result<(), Box<dyn std::error::Error>> {

View file

@ -9,6 +9,6 @@ tracing-subscriber = "0.3.22"
tracing-log = "0.2.0"
chrono = "*"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
features = ["debug", "wgpu", "winit", "desktop", "tokio"]
path = "../.."

View file

@ -9,7 +9,8 @@ use chrono::Datelike;
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Size;
use cosmic::prelude::*;
use cosmic::widget::{self, nav_bar, table};
use cosmic::widget::table;
use cosmic::widget::{self, nav_bar};
use cosmic::{executor, iced};
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]

View file

@ -8,6 +8,6 @@ tracing = "0.1.44"
tracing-subscriber = "0.3.22"
tracing-log = "0.2.0"
[dependencies.libcosmic-yoda]
[dependencies.libcosmic]
path = "../../"
features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"]

View file

@ -1,3 +0,0 @@
license = Llicència
close = Tancar
links = Enllaços

View file

@ -1,34 +0,0 @@
close = Κλείσιμο
february = Φεβρουάριος { $year }
documenters = Τεκμηριωτές
november = Νοέμβριος { $year }
may = Μάιος { $year }
april = Απρίλιος { $year }
translators = Μεταφραστές
artists = Καλλιτέχνες
license = Άδεια χρήσης
december = Δεκέμβριος { $year }
links = Σύνδεσμοι
march = Μάρτιος { $year }
june = Ιούνιος { $year }
august = Αύγουστος { $year }
developers = Προγραμματιστές
july = Ιούλιος { $year }
september = Σεπτέμβριος { $year }
designers = Σχεδιαστές
october = Οκτώβριος { $year }
january = Ιανουάριος { $year }
monday = Δευτέρα
mon = Δευ
tuesday = Τρίτη
tue = Τρί
wednesday = Τετάρτη
wed = Τετ
thursday = Πέμπτη
thu = Πέμ
friday = Παρασκευή
fri = Παρ
saturday = Σάββατο
sat = Σάβ
sunday = Κυριακή
sun = Κυρ

View file

@ -1,34 +0,0 @@
close = Pechar
license = Licenza
links = Enderezos
developers = Desenvolvedores
designers = Deseñadores
artists = Artistas
translators = Traductores
documenters = Documentadores
january = Xaneiro { $year }
february = Febreiro { $year }
march = Marzo { $year }
april = Abril { $year }
may = Maio { $year }
june = Xuño { $year }
july = Xullo { $year }
august = Agosto { $year }
september = Setembro { $year }
october = Outubro { $year }
november = Novembro { $year }
december = Decembro { $year }
monday = Luns
mon = Lun
tuesday = Martes
tue = Mar
wednesday = Mércores
wed = Mér
thursday = Xoves
thu = Xov
friday = Venres
fri = Ven
saturday = Sábado
sat = Sáb
sunday = Domingo
sun = Dom

Some files were not shown because too many files have changed in this diff Show more