Compare commits

..

No commits in common. "pre-1.9-merge-20260924" and "master" have entirely different histories.

260 changed files with 3660 additions and 9981 deletions

View file

@ -6,20 +6,26 @@ on:
- master - master
pull_request: pull_request:
jobs: jobs:
format: format:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v6 uses: actions/checkout@v3
- name: Rust toolchain - name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: nightly
components: rustfmt 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 - name: Format
run: cargo +nightly fmt -- --check run: cargo fmt -- --check
tests: tests:
needs: needs:
@ -28,28 +34,38 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
test_args: test_args:
- --no-default-features --features "" # for cosmic-comp, don't remove! - --no-default-features --features "" # for cosmic-comp, don't remove!
- --no-default-features --features "winit_debug" - --no-default-features --features "winit_debug"
- --no-default-features --features "winit_tokio" - --no-default-features --features "winit_tokio"
- --no-default-features --features "winit" - --no-default-features --features "winit"
- --no-default-features --features "winit_wgpu" - --no-default-features --features "winit_wgpu"
- --no-default-features --features "wayland" - --no-default-features --features "wayland"
- --no-default-features --features "applet" - --no-default-features --features "applet"
- --no-default-features --features "desktop,smol" - --no-default-features --features "desktop,smol"
- --no-default-features --features "desktop,tokio" - --no-default-features --features "desktop,tokio"
- -p cosmic-theme - -p cosmic-theme
runs-on: ubuntu-latest runs-on: ubuntu-22.04
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v6 uses: actions/checkout@v3
with: with:
submodules: recursive 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 - name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Rust toolchain - name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: dtolnay/rust-toolchain@stable
with:
rustflags: ""
- name: Test features - name: Test features
run: cargo test ${{ matrix.test_args }} -- --test-threads=1 run: cargo test ${{ matrix.test_args }} -- --test-threads=1
env: env:
@ -66,18 +82,28 @@ jobs:
- "open-dialog" - "open-dialog"
- "context-menu" - "context-menu"
- "nav-context" - "nav-context"
runs-on: ubuntu-latest runs-on: ubuntu-22.04
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v6 uses: actions/checkout@v3
with: with:
submodules: recursive 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 - name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Rust toolchain - name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: dtolnay/rust-toolchain@stable
with:
rustflags: ""
- name: Check example - name: Check example
run: cargo check -p "${{ matrix.examples }}" run: cargo check -p "${{ matrix.examples }}"
env: env:

View file

@ -11,26 +11,25 @@ jobs:
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v6 uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Install Rust nightly - name: Install Rust nightly
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: dtolnay/rust-toolchain@master
with: with:
toolchain: nightly-2026-04-27 toolchain: nightly-2025-07-31
rustflags: ""
- name: System dependencies - name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Build documentation - name: Build documentation
run: | run: |
RUSTDOCFLAGS="--cfg docsrs" \ 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-client-toolkit \
-p cosmic-protocols \ -p cosmic-protocols \
-p libcosmic \ -p libcosmic \
--verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window --verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window
- name: Deploy documentation - name: Deploy documentation
uses: peaceiris/actions-gh-pages@v4 uses: peaceiris/actions-gh-pages@v3
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc 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] [package]
# Yoda fork: hard-renamed. Every consumer (leyoda/cosmic-files fork + each name = "libcosmic"
# leyoda/cosmic-* app) depends directly on `libcosmic-yoda` by path, bypassing version = "1.0.0"
# 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"
edition = "2024" edition = "2024"
rust-version = "1.93" rust-version = "1.90"
[lib] [lib]
name = "cosmic" name = "cosmic"
[features] [features]
default = [ default = [
"winit",
"tokio", "tokio",
"a11y", "a11y",
"dbus-config", "dbus-config",
"wayland", "x11",
"iced-wayland",
"multi-window", "multi-window",
] ]
advanced-shaping = ["iced/advanced-shaping"] advanced-shaping = ["iced/advanced-shaping"]
@ -38,6 +35,7 @@ animated-image = [
autosize = [] autosize = []
applet = [ applet = [
"autosize", "autosize",
"winit",
"wayland", "wayland",
"tokio", "tokio",
"cosmic-panel-config", "cosmic-panel-config",
@ -74,7 +72,6 @@ serde-keycode = ["iced_core/serde"]
single-instance = ["zbus/blocking-api", "ron"] single-instance = ["zbus/blocking-api", "ron"]
# smol async runtime # smol async runtime
smol = ["dep:smol", "iced/smol", "zbus?/async-io", "rfd?/async-std"] smol = ["dep:smol", "iced/smol", "zbus?/async-io", "rfd?/async-std"]
# Tokio async runtime
tokio = [ tokio = [
"dep:tokio", "dep:tokio",
"ashpd?/tokio", "ashpd?/tokio",
@ -84,25 +81,20 @@ tokio = [
"cosmic-config/tokio", "cosmic-config/tokio",
] ]
# Tokio async runtime # Tokio async runtime
# Wayland window support (yoda fork is Wayland-only; always active in default). # Wayland window support
# We still need iced/winit because pop-os/iced hosts the runtime dispatcher iced-wayland = [
# (`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 = [
"ashpd?/wayland", "ashpd?/wayland",
"autosize", "autosize",
"multi-window",
"surface-message",
"winit",
"iced/winit",
"iced/wayland", "iced/wayland",
"iced_winit/wayland", "iced_winit/wayland",
"iced_runtime/wayland", "surface-message",
"iced_wgpu/wayland", ]
"dep:iced_winit", wayland = [
"iced-wayland",
"iced_runtime/cctk",
"iced_winit/cctk",
"iced_wgpu/cctk",
"iced/cctk",
"dep:cctk", "dep:cctk",
] ]
surface-message = [] surface-message = []
@ -110,11 +102,11 @@ surface-message = []
multi-window = [] multi-window = []
# Render with wgpu # Render with wgpu
wgpu = ["iced/wgpu", "iced_wgpu"] wgpu = ["iced/wgpu", "iced_wgpu"]
# Compat stubs — kept empty so upstream deps (cosmic-files, cosmic-text, …) # X11 window support via winit
# that still ask for `winit` / `x11` features resolve cleanly against the winit = ["iced/winit", "iced_winit"]
# yoda fork. Activating them has no effect: no code is gated on these. winit_debug = ["winit", "debug"]
winit = [] winit_tokio = ["winit", "tokio"]
x11 = [] winit_wgpu = ["winit", "wgpu"]
# Enables XDG portal integrations # Enables XDG portal integrations
xdg-portal = ["ashpd"] xdg-portal = ["ashpd"]
qr_code = ["iced/qr_code"] qr_code = ["iced/qr_code"]
@ -127,15 +119,18 @@ async-std = [
"zbus?/async-io", "zbus?/async-io",
"iced/async-std", "iced/async-std",
] ]
x11 = ["iced/x11", "iced_winit/x11"]
[dependencies] [dependencies]
apply = "0.3.0" apply = "0.3.0"
ashpd = { version = "0.12.3", default-features = false, optional = true }
async-fs = { version = "2.2", 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" 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" jiff = "0.2"
cosmic-config = { path = "cosmic-config" } 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 # Internationalization
i18n-embed = { version = "0.16.0", features = [ i18n-embed = { version = "0.16.0", features = [
"fluent-system", "fluent-system",
@ -155,38 +150,34 @@ image-extras = { version = "0.1.0", default-features = false, features = [
"xpm", "xpm",
"xbm", "xbm",
], optional = true } ], optional = true }
libc = { version = "0.2.186", optional = true } libc = { version = "0.2.183", optional = true }
log = "0.4" log = "0.4"
mime = { version = "0.3.17", optional = true } mime = { version = "0.3.17", optional = true }
palette.workspace = true palette = "0.7.6"
rfd = { version = "0.16.0", default-features = false, features = [ rfd = { version = "0.16.0", default-features = false, features = [
"xdg-portal", "xdg-portal",
], optional = true } ], optional = true }
rustix = { version = "1.1", features = ["pipe", "process"], 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" slotmap = "1.1.1"
smol = { version = "2.0.2", optional = true } smol = { version = "2.0.2", optional = true }
thiserror.workspace = true thiserror = "2.0.18"
taffy = { version = "0.9.2", features = ["grid"] } taffy = { version = "0.9.2", features = ["grid"] }
tokio = { workspace = true, optional = true } tokio = { version = "1.50.0", optional = true }
tracing.workspace = true tracing = "0.1.44"
unicode-segmentation = "1.13" unicode-segmentation = "1.12"
url = "2.5.8" url = "2.5.8"
zbus = { workspace = true, optional = true } zbus = { version = "5.14.0", default-features = false, optional = true }
float-cmp = "0.10.0" float-cmp = "0.10.0"
ron = { workspace = true, optional = true }
enumflags2 = "0.7.12"
# Enable DBus feature on Linux targets # 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] [target.'cfg(target_os = "linux")'.dependencies]
cctk = { path = "../cosmic-protocols/client-toolkit", package = "cosmic-client-toolkit", optional = true }
ashpd = { version = "0.12.3", default-features = false, optional = true }
cosmic-config = { path = "cosmic-config", features = ["dbus"] } 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 } zbus = { version = "5.14.0", default-features = false }
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] [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 } freedesktop-desktop-entry = { version = "0.8.1", optional = true }
shlex = { version = "1.3.0", optional = true } shlex = { version = "1.3.0", optional = true }
@ -232,83 +223,35 @@ optional = true
[dependencies.iced_tiny_skia] [dependencies.iced_tiny_skia]
path = "./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] [dependencies.iced_winit]
path = "./iced/winit" path = "./iced/winit"
optional = true 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] [dependencies.iced_wgpu]
path = "./iced/wgpu" path = "./iced/wgpu"
optional = true optional = true
[dependencies.cosmic-panel-config] [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 optional = true
[patch.'https://github.com/pop-os/freedesktop-icons'] [dependencies.ron]
cosmic-freedesktop-icons = { path = "../cosmic-freedesktop-icons" } version = "0.12"
optional = true
[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" }
[workspace] [workspace]
members = [ members = [
"cosmic-config", "cosmic-config",
"cosmic-config-derive", "cosmic-config-derive",
"cosmic-theme", "cosmic-theme",
"examples/*",
] ]
# examples/* excluded — many depend on the removed winit/x11 features. exclude = ["iced"]
# They will be revisited and adapted in a later phase.
exclude = ["iced", "examples"]
[workspace.dependencies] [workspace.dependencies]
async-std = "1.13" dirs = "6.0.0"
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
[dev-dependencies] [dev-dependencies]
tempfile = "3.27.0" 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 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 ## Examples
Some examples are included in the [examples](./examples) directory to to kickstart your Some examples are included in the [examples](./examples) directory to to kickstart your

View file

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

View file

@ -1,32 +1,17 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; 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 { 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(); let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_cosmic_config_entry_macro(&ast) 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 { fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
let attributes = &ast.attrs; let attributes = &ast.attrs;
let version = attributes 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 write_each_config_field = fields.iter().map(|field| {
let field_name = &field.ident; let field_name = &field.ident;
let with = match get_cosmic_config_attrs(field) { quote! {
Ok(attrs) => attrs, cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?;
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)?;
}
} }
}); });
let get_each_config_field = fields.iter().map(|field| { let get_each_config_field = fields.iter().map(|field| {
let field_name = &field.ident; let field_name = &field.ident;
let field_type = &field.ty; let field_type = &field.ty;
let with = match get_cosmic_config_attrs(field) { quote! {
Ok(attrs) => attrs, match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
Err(e) => { Ok(#field_name) => default.#field_name = #field_name,
return e.to_compile_error(); Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
} Err(e) => errors.push(e),
};
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),
}
} }
} }
}); });
@ -118,39 +68,17 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
let update_each_config_field = fields.iter().map(|field| { let update_each_config_field = fields.iter().map(|field| {
let field_name = &field.ident; let field_name = &field.ident;
let field_type = &field.ty; let field_type = &field.ty;
let with = match get_cosmic_config_attrs(field) { quote! {
Ok(attrs) => attrs, stringify!(#field_name) => {
Err(e) => { match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
return e.to_compile_error(); Ok(value) => {
} if self.#field_name != value {
}; keys.push(stringify!(#field_name));
}
if let Some(with) = with { self.#field_name = value;
quote! { },
stringify!(#field_name) => { Err(e) => {
match cosmic_config::ConfigGet::get::<#with>(config, stringify!(#field_name)) { errors.push(e);
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),
} }
} }
} }

View file

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

View file

@ -1,12 +1,13 @@
use std::any::TypeId; use std::{any::TypeId, ops::Deref};
use std::ops::Deref;
use crate::{CosmicConfigEntry, Update}; use crate::{CosmicConfigEntry, Update};
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
use futures_util::SinkExt; use futures_util::SinkExt;
use iced_futures::futures::future::pending; use iced_futures::{
use iced_futures::futures::{self, StreamExt}; Subscription,
use iced_futures::{Subscription, stream}; futures::{self, StreamExt, future::pending},
stream,
};
pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> { pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> {
let conn = zbus::Connection::session().await?; let conn = zbus::Connection::session().await?;
@ -67,8 +68,6 @@ struct Wrapper(
impl std::hash::Hash for Wrapper { impl std::hash::Hash for Wrapper {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state); 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. //! Integrations for cosmic-config — the cosmic configuration system.
use notify::event::{EventKind, ModifyKind, RenameMode}; use notify::{
use notify::{RecommendedWatcher, Watcher}; RecommendedWatcher, Watcher,
use serde::Serialize; event::{EventKind, ModifyKind, RenameMode},
use serde::de::DeserializeOwned; };
use std::io::Write; use serde::{Serialize, de::DeserializeOwned};
use std::path::{Path, PathBuf}; use std::{
use std::sync::Mutex; env, fmt, fs,
use std::{env, fmt, fs}; io::Write,
path::{Path, PathBuf},
sync::Mutex,
};
/// Get the config directory, with Flatpak sandbox support. /// Get the config directory, with Flatpak sandbox support.
/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory, /// 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")); return Some(PathBuf::from(home).join(".local").join("state"));
} }
} }
if let Some(state_dir) = dirs::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"))
} }
#[cfg(feature = "subscription")] #[cfg(feature = "subscription")]
@ -164,7 +162,6 @@ pub trait ConfigSet {
pub struct Config { pub struct Config {
system_path: Option<PathBuf>, system_path: Option<PathBuf>,
user_path: Option<PathBuf>, user_path: Option<PathBuf>,
previous: Option<Box<Config>>,
} }
/// Check that the name is relative and doesn't contain . or .. /// 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 { impl Config {
/// Get a system config for the given name and config version /// Get a system config for the given name and config version
pub fn system(name: &str, version: u64) -> Result<Self, Error> { 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}")); let path = sanitize_name(name)?.join(format!("v{version}"));
#[cfg(unix)] #[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)] #[cfg(windows)]
let system_path = let system_path =
@ -199,13 +192,6 @@ impl Config {
Ok(Self { Ok(Self {
system_path, system_path,
user_path: None, 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 // 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) //TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy)
pub fn new(name: &str, version: u64) -> Result<Self, Error> { 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] // Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version)); let path = sanitize_name(name)?.join(format!("v{}", version));
@ -241,29 +223,15 @@ impl Config {
Ok(Self { Ok(Self {
system_path, system_path,
user_path: Some(user_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. /// 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> { 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] // Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("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("cosmic");
user_path.push(path); user_path.push(path);
// Create new configuration directory if not found. // Create new configuration directory if not found.
@ -273,13 +241,6 @@ impl Config {
Ok(Self { Ok(Self {
system_path: None, system_path: None,
user_path: Some(user_path), 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 // 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) //TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy)
pub fn new_state(name: &str, version: u64) -> Result<Self, Error> { 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] // Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version)); let path = sanitize_name(name)?.join(format!("v{}", version));
@ -306,13 +263,6 @@ impl Config {
Ok(Self { Ok(Self {
system_path: None, system_path: None,
user_path: Some(user_path), 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)?) Ok(ron::from_str(&data)?)
} }
_ => { _ => Err(Error::NotFound),
if let Some(previous) = self.previous.as_ref() {
previous.get_local(key)
} else {
Err(Error::NotFound)
}
}
} }
} }

View file

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

@ -1 +1 @@
Subproject commit 343c007f37cd71716e68f01c43ecf2764b7f7c47 Subproject commit 5252095787cc96e2aed64604158f94e450703455

View file

@ -15,14 +15,13 @@ export = ["serde_json"]
no-default = [] no-default = []
[dependencies] [dependencies]
palette = { workspace = true, features = ["serializing"] } palette = { version = "0.7.6", features = ["serializing"] }
hex_color = { version = "3", features = ["serde"] }
almost = "0.2" almost = "0.2"
serde = { workspace = true, features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.149", optional = true, features = [ serde_json = { version = "1.0.149", optional = true, features = [
"preserve_order", "preserve_order",
] } ] }
ron.workspace = true ron = "0.12.0"
csscolorparser = { version = "0.8.3", features = ["serde"] } csscolorparser = { version = "0.8.3", features = ["serde"] }
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
"subscription", "subscription",
@ -30,8 +29,11 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features
] } ] }
configparser = "3.1.0" configparser = "3.1.0"
dirs.workspace = true dirs.workspace = true
thiserror.workspace = true thiserror = "2.0.18"
[dev-dependencies] [dev-dependencies]
insta = "1.47.2" 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 palette::Srgba;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::LazyLock; use std::sync::LazyLock;
@ -96,107 +95,75 @@ pub struct CosmicPaletteInner {
/// Utility Colors /// Utility Colors
/// Colors used for various points of emphasis in the UI. /// Colors used for various points of emphasis in the UI.
#[serde(with = "color_serde")]
pub bright_red: Srgba, pub bright_red: Srgba,
/// Colors used for various points of emphasis in the UI. /// Colors used for various points of emphasis in the UI.
#[serde(with = "color_serde")]
pub bright_green: Srgba, pub bright_green: Srgba,
/// Colors used for various points of emphasis in the UI. /// Colors used for various points of emphasis in the UI.
#[serde(with = "color_serde")]
pub bright_orange: Srgba, pub bright_orange: Srgba,
/// Surface Grays /// Surface Grays
/// Colors used for three levels of surfaces in the UI. /// Colors used for three levels of surfaces in the UI.
#[serde(with = "color_serde")]
pub gray_1: Srgba, pub gray_1: Srgba,
/// Colors used for three levels of surfaces in the UI. /// Colors used for three levels of surfaces in the UI.
#[serde(with = "color_serde")]
pub gray_2: Srgba, pub gray_2: Srgba,
/// System Neutrals /// System Neutrals
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_0: Srgba, pub neutral_0: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_1: Srgba, pub neutral_1: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_2: Srgba, pub neutral_2: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_3: Srgba, pub neutral_3: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_4: Srgba, pub neutral_4: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_5: Srgba, pub neutral_5: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_6: Srgba, pub neutral_6: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_7: Srgba, pub neutral_7: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_8: Srgba, pub neutral_8: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_9: Srgba, pub neutral_9: Srgba,
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_10: Srgba, pub neutral_10: Srgba,
/// Potential Accent Color Combos /// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_blue: Srgba, pub accent_blue: Srgba,
/// Potential Accent Color Combos /// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_indigo: Srgba, pub accent_indigo: Srgba,
/// Potential Accent Color Combos /// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_purple: Srgba, pub accent_purple: Srgba,
/// Potential Accent Color Combos /// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_pink: Srgba, pub accent_pink: Srgba,
/// Potential Accent Color Combos /// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_red: Srgba, pub accent_red: Srgba,
/// Potential Accent Color Combos /// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_orange: Srgba, pub accent_orange: Srgba,
/// Potential Accent Color Combos /// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_yellow: Srgba, pub accent_yellow: Srgba,
/// Potential Accent Color Combos /// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_green: Srgba, pub accent_green: Srgba,
/// Potential Accent Color Combos /// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_warm_grey: Srgba, pub accent_warm_grey: Srgba,
/// Extended Color Palette /// Extended Color Palette
/// Colors used for themes, app icons, illustrations, and other brand purposes. /// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_warm_grey: Srgba, pub ext_warm_grey: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes. /// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_orange: Srgba, pub ext_orange: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes. /// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_yellow: Srgba, pub ext_yellow: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes. /// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_blue: Srgba, pub ext_blue: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes. /// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_purple: Srgba, pub ext_purple: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes. /// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_pink: Srgba, pub ext_pink: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes. /// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_indigo: Srgba, 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 palette::{Srgba, WithAlpha};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -9,18 +8,14 @@ use crate::composite::over;
#[must_use] #[must_use]
pub struct Container { pub struct Container {
/// the color of the container /// the color of the container
#[serde(with = "color_serde")]
pub base: Srgba, pub base: Srgba,
/// the color of components in the container /// the color of components in the container
pub component: Component, pub component: Component,
/// the color of dividers in the container /// the color of dividers in the container
#[serde(with = "color_serde")]
pub divider: Srgba, pub divider: Srgba,
/// the color of text in the container /// the color of text in the container
#[serde(with = "color_serde")]
pub on: Srgba, pub on: Srgba,
/// the color of @small_widget_container /// the color of @small_widget_container
#[serde(with = "color_serde")]
pub small_widget: Srgba, pub small_widget: Srgba,
} }
@ -50,42 +45,30 @@ impl Container {
#[must_use] #[must_use]
pub struct Component { pub struct Component {
/// The base color of the widget /// The base color of the widget
#[serde(with = "color_serde")]
pub base: Srgba, pub base: Srgba,
/// The color of the widget when it is hovered /// The color of the widget when it is hovered
#[serde(with = "color_serde")]
pub hover: Srgba, pub hover: Srgba,
/// the color of the widget when it is pressed /// the color of the widget when it is pressed
#[serde(with = "color_serde")]
pub pressed: Srgba, pub pressed: Srgba,
/// the color of the widget when it is selected /// the color of the widget when it is selected
#[serde(with = "color_serde")]
pub selected: Srgba, pub selected: Srgba,
/// the color of the widget when it is selected /// the color of the widget when it is selected
#[serde(with = "color_serde")]
pub selected_text: Srgba, pub selected_text: Srgba,
/// the color of the widget when it is focused /// the color of the widget when it is focused
#[serde(with = "color_serde")]
pub focus: Srgba, pub focus: Srgba,
/// the color of dividers for this widget /// the color of dividers for this widget
#[serde(with = "color_serde")]
pub divider: Srgba, pub divider: Srgba,
/// the color of text for this widget /// the color of text for this widget
#[serde(with = "color_serde")]
pub on: Srgba, pub on: Srgba,
// the color of text with opacity 80 for this widget // the color of text with opacity 80 for this widget
// pub text_opacity_80: Srgba, // pub text_opacity_80: Srgba,
/// the color of the widget when it is disabled /// the color of the widget when it is disabled
#[serde(with = "color_serde")]
pub disabled: Srgba, pub disabled: Srgba,
/// the color of text in the widget when it is disabled /// the color of text in the widget when it is disabled
#[serde(with = "color_serde")]
pub on_disabled: Srgba, pub on_disabled: Srgba,
/// the color of the border for the widget /// the color of the border for the widget
#[serde(with = "color_serde")]
pub border: Srgba, pub border: Srgba,
/// the color of the border for the widget when it is disabled /// the color of the border for the widget when it is disabled
#[serde(with = "color_serde")]
pub disabled_border: Srgba, 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 corner::*;
pub use cosmic_palette::*; pub use cosmic_palette::*;
pub use density::*;
pub use derivation::*; pub use derivation::*;
pub use mode::*; pub use mode::*;
pub use spacing::*; pub use spacing::*;
pub use theme::*; pub use theme::*;
pub mod color;
mod corner; mod corner;
mod cosmic_palette; mod cosmic_palette;
mod density;
mod derivation; mod derivation;
mod mode; mod mode;
mod spacing; 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::{ use crate::{
Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE, Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE,
LIGHT_PALETTE, NAME, Spacing, ThemeMode, 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 cosmic_config::{Config, CosmicConfigEntry};
use palette::color_difference::Wcag21RelativeContrast; use palette::{
use palette::rgb::Rgb; IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb,
use palette::{IntoColor, Oklcha, Srgb, Srgba, WithAlpha}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
@ -47,24 +45,16 @@ pub enum Layer {
PartialEq, PartialEq,
cosmic_config::cosmic_config_derive::CosmicConfigEntry, cosmic_config::cosmic_config_derive::CosmicConfigEntry,
)] )]
#[version = 2] #[version = 1]
pub struct Theme { pub struct Theme {
/// name of the theme /// name of the theme
pub name: String, pub name: String,
/// background element colors /// background element colors
pub(crate) background: Container, pub background: Container,
/// primary element colors /// primary element colors
pub(crate) primary: Container, pub primary: Container,
/// secondary element colors /// secondary element colors
pub(crate) secondary: Container, pub 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,
/// accent element colors /// accent element colors
pub accent: Component, pub accent: Component,
/// suggested element colors /// suggested element colors
@ -85,10 +75,10 @@ pub struct Theme {
pub icon_button: Component, pub icon_button: Component,
/// link button element colors /// link button element colors
pub link_button: Component, pub link_button: Component,
/// list button element colors
pub list_button: Component,
/// text button element colors /// text button element colors
pub text_button: Component, pub text_button: Component,
/// button component styling
pub button: Component,
/// palette /// palette
pub palette: CosmicPaletteInner, pub palette: CosmicPaletteInner,
/// spacing /// spacing
@ -105,43 +95,16 @@ pub struct Theme {
pub active_hint: u32, pub active_hint: u32,
/// cosmic-comp custom window hint color /// cosmic-comp custom window hint color
pub window_hint: Option<Srgb>, pub window_hint: Option<Srgb>,
#[serde(default)]
/// enables blurred transparency /// enables blurred transparency
pub frosted: BlurStrength, pub is_frosted: bool,
#[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,
/// shade color for dialogs /// shade color for dialogs
#[serde(with = "color_serde")]
#[cosmic_config_entry(with = ColorRepr)]
pub shade: Srgba, pub shade: Srgba,
/// accent text colors /// accent text colors
/// If None, accent base color is the accent text color. /// 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>, pub accent_text: Option<Srgba>,
/// control tint color /// control tint color
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub control_tint: Option<Srgb>, pub control_tint: Option<Srgb>,
/// text tint color /// text tint color
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub text_tint: Option<Srgb>, pub text_tint: Option<Srgb>,
} }
@ -207,39 +170,6 @@ impl Theme {
todo!(); 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] #[must_use]
#[allow(clippy::doc_markdown)] #[allow(clippy::doc_markdown)]
#[inline] #[inline]
@ -809,7 +739,7 @@ impl Theme {
if color_scheme.trim().contains("default") || color_scheme.trim().contains("light") { if color_scheme.trim().contains("default") || color_scheme.trim().contains("light") {
return Self::light_default(); return Self::light_default();
} }
} };
Self::dark_default() Self::dark_default()
} }
@ -818,10 +748,10 @@ impl Theme {
pub fn preferred_theme() -> Self { pub fn preferred_theme() -> Self {
let current_desktop = std::env::var("XDG_CURRENT_DESKTOP"); let current_desktop = std::env::var("XDG_CURRENT_DESKTOP");
if let Ok(desktop) = current_desktop if let Ok(desktop) = current_desktop {
&& desktop.trim().to_lowercase().contains("gnome") if desktop.trim().to_lowercase().contains("gnome") {
{ return Self::gtk_prefer_colorscheme();
return Self::gtk_prefer_colorscheme(); }
} }
Self::dark_default() Self::dark_default()
@ -844,7 +774,7 @@ impl From<CosmicPalette> for Theme {
cosmic_config::cosmic_config_derive::CosmicConfigEntry, cosmic_config::cosmic_config_derive::CosmicConfigEntry,
PartialEq, PartialEq,
)] )]
#[version = 2] #[version = 1]
pub struct ThemeBuilder { pub struct ThemeBuilder {
/// override the palette for the builder /// override the palette for the builder
pub palette: CosmicPalette, pub palette: CosmicPalette,
@ -853,70 +783,31 @@ pub struct ThemeBuilder {
/// override corner radii for the builder /// override corner radii for the builder
pub corner_radii: CornerRadii, pub corner_radii: CornerRadii,
/// override neutral_tint for the builder /// override neutral_tint for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub neutral_tint: Option<Srgb>, pub neutral_tint: Option<Srgb>,
/// override bg_color for the builder /// override bg_color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub bg_color: Option<Srgba>, pub bg_color: Option<Srgba>,
/// override the primary container bg color for the builder /// 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>, pub primary_container_bg: Option<Srgba>,
/// override the secontary container bg color for the builder /// 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>, pub secondary_container_bg: Option<Srgba>,
/// override the text tint for the builder /// override the text tint for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub text_tint: Option<Srgb>, pub text_tint: Option<Srgb>,
/// override the accent color for the builder /// override the accent color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub accent: Option<Srgb>, pub accent: Option<Srgb>,
/// override the success color for the builder /// override the success color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub success: Option<Srgb>, pub success: Option<Srgb>,
/// override the warning color for the builder /// override the warning color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub warning: Option<Srgb>, pub warning: Option<Srgb>,
/// override the destructive color for the builder /// override the destructive color for the builder
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub destructive: Option<Srgb>, pub destructive: Option<Srgb>,
#[serde(default)]
/// enabled blurred transparency /// enabled blurred transparency
pub frosted: BlurStrength, pub is_frosted: bool, // TODO handle
/// cosmic-comp window gaps size (outer, inner) /// cosmic-comp window gaps size (outer, inner)
pub gaps: (u32, u32), pub gaps: (u32, u32),
/// cosmic-comp active hint window outline width /// cosmic-comp active hint window outline width
pub active_hint: u32, pub active_hint: u32,
/// cosmic-comp custom window hint color /// cosmic-comp custom window hint color
#[serde(with = "color_serde_option")]
#[cosmic_config_entry(with = ColorReprOption)]
pub window_hint: Option<Srgb>, 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 { impl Default for ThemeBuilder {
@ -934,17 +825,11 @@ impl Default for ThemeBuilder {
success: Default::default(), success: Default::default(),
warning: Default::default(), warning: Default::default(),
destructive: Default::default(), destructive: Default::default(),
frosted: BlurStrength::default(), is_frosted: false,
// cosmic-comp theme settings // cosmic-comp theme settings
gaps: (0, 8), gaps: (0, 8),
active_hint: 3, active_hint: 3,
window_hint: None, 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)] #[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 /// build the theme
pub fn build(self) -> Theme { pub fn build(self) -> Theme {
let Self { let Self {
@ -1092,23 +971,9 @@ impl ThemeBuilder {
gaps, gaps,
active_hint, active_hint,
window_hint, window_hint,
frosted, is_frosted,
frosted_windows,
frosted_system_interface,
frosted_panel,
frosted_applets,
frosted_maximized_apps,
alpha_map,
} = self; } = 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_dark = palette.is_dark();
let is_high_contrast = palette.is_high_contrast(); let is_high_contrast = palette.is_high_contrast();
@ -1163,10 +1028,6 @@ impl ThemeBuilder {
let step_array = steps(bg, NonZeroUsize::new(100).unwrap()); let step_array = steps(bg, NonZeroUsize::new(100).unwrap());
let bg_index = color_index(bg, step_array.len()); 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 { let mut component_hovered_overlay = if bg_index < 91 {
control_steps_array[10] control_steps_array[10]
} else { } else {
@ -1191,26 +1052,10 @@ impl ThemeBuilder {
&step_array, &step_array,
&control_steps_array[8], &control_steps_array[8],
text_steps_array.as_deref(), 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 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 primary_container_bg_color
} else { } else {
get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1]) get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1])
@ -1240,7 +1085,6 @@ impl ThemeBuilder {
&step_array, &step_array,
&control_steps_array[8], &control_steps_array[8],
text_steps_array.as_deref(), text_steps_array.as_deref(),
container_alpha,
), ),
component_hovered_overlay, component_hovered_overlay,
component_pressed_overlay, component_pressed_overlay,
@ -1253,53 +1097,6 @@ impl ThemeBuilder {
&step_array, &step_array,
&control_steps_array[8], &control_steps_array[8],
text_steps_array.as_deref(), 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]), get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast, is_high_contrast,
@ -1373,7 +1170,6 @@ impl ThemeBuilder {
&step_array, &step_array,
&control_steps_array[8], &control_steps_array[8],
text_steps_array.as_deref(), text_steps_array.as_deref(),
actual_alpha,
), ),
get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]), get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast, is_high_contrast,
@ -1386,15 +1182,10 @@ impl ThemeBuilder {
get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2]) 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 step_array = steps(container_bg, NonZeroUsize::new(100).unwrap());
let base_index = color_index(container_bg, t_step_array.len()); let base_index = color_index(container_bg, step_array.len());
let secondary_component = get_surface_color( let secondary_component =
base_index, get_surface_color(base_index, 3, &step_array, is_dark, &control_steps_array[4]);
3,
&t_step_array,
is_dark,
&control_steps_array[4],
);
component_hovered_overlay = if base_index < 91 { component_hovered_overlay = if base_index < 91 {
control_steps_array[10] control_steps_array[10]
@ -1411,11 +1202,10 @@ impl ThemeBuilder {
secondary_component, secondary_component,
accent, accent,
get_text( get_text(
color_index(secondary_component, t_step_array.len()), color_index(secondary_component, step_array.len()),
&step_array, &step_array,
&control_steps_array[8], &control_steps_array[8],
text_steps_array.as_deref(), text_steps_array.as_deref(),
actual_alpha,
), ),
component_hovered_overlay, component_hovered_overlay,
component_pressed_overlay, component_pressed_overlay,
@ -1428,7 +1218,6 @@ impl ThemeBuilder {
&step_array, &step_array,
&control_steps_array[8], &control_steps_array[8],
text_steps_array.as_deref(), text_steps_array.as_deref(),
actual_alpha,
), ),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast, is_high_contrast,
@ -1441,24 +1230,14 @@ impl ThemeBuilder {
button_hovered_overlay, button_hovered_overlay,
button_pressed_overlay, button_pressed_overlay,
), ),
accent_button: { accent_button: Component::colored_button(
let step_array = steps(control_steps_array[5], NonZeroUsize::new(100).unwrap()); accent,
control_steps_array[1],
Component::colored_button( control_steps_array[0],
accent, accent,
control_steps_array[1], button_hovered_overlay,
get_text( button_pressed_overlay,
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,
)
},
button: Component::component( button: Component::component(
button_bg, button_bg,
accent, accent,
@ -1506,15 +1285,6 @@ impl ThemeBuilder {
component.on_disabled = over(component.on.with_alpha(0.5), component.base); component.on_disabled = over(component.on.with_alpha(0.5), component.base);
component 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: Component::colored_component(
success, success,
control_steps_array[0], control_steps_array[0],
@ -1562,80 +1332,10 @@ impl ThemeBuilder {
gaps, gaps,
active_hint, active_hint,
window_hint, window_hint,
frosted, is_frosted,
accent_text, accent_text,
control_tint: neutral_tint, control_tint: neutral_tint,
text_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.spacing = spacing;
theme.corner_radii = corner_radii; theme.corner_radii = corner_radii;
@ -1654,181 +1354,3 @@ impl ThemeBuilder {
Config::new(LIGHT_THEME_BUILDER_ID, Self::VERSION) 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 crate::{Component, Theme, composite::over, steps::steps};
use configparser::ini::Ini;
use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha, rgb::Rgba}; use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha, rgb::Rgba};
use std::{ use std::{
fs::{self, File}, fs::{self, File},
io::{self, Write}, io::{self, Write},
num::NonZeroUsize, num::NonZeroUsize,
path::Path, path::Path,
process::Command,
}; };
use super::{OutputError, to_rgba}; use super::{OutputError, to_rgba};
@ -219,50 +217,6 @@ impl Theme {
Ok(()) 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 /// Reset the applied gtk css
/// ///
/// # Errors /// # Errors
@ -302,20 +256,6 @@ impl Theme {
Ok(()) 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>> { fn is_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result<Option<bool>> {
if !path.exists() { if !path.exists() {
return Ok(None); return Ok(None);

View file

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

View file

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

View file

@ -1,21 +1,281 @@
use crate::Theme; use crate::Theme;
use configparser::ini::Ini; use configparser::ini::Ini;
use std::path::PathBuf; use cosmic_config::CosmicConfigEntry;
use std::{fs, io}; 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 { impl Theme {
/// Reset the applied qt colors: /// Produces a color scheme ini file for Qt.
/// - Remove relevant keys from the `~/.config/kdeglobals` file ///
/// - Delete `~/.local/share/color-schemes/CosmicDark.colors` and `CosmicLight.colors` /// 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. /// This does not restore the backed up kdeglobals file.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns an `OutputError` if there is an error resetting the kdeglobals /// Returns an `OutputError` if there is an error resetting the CSS file.
/// file or deleting the color schemes.
#[cold] #[cold]
pub fn reset_qt() -> Result<(), OutputError> { pub fn reset_qt() -> Result<(), OutputError> {
let Some(config_dir) = dirs::config_dir() else { let Some(config_dir) = dirs::config_dir() else {
@ -32,29 +292,19 @@ impl Theme {
return Ok(()); return Ok(());
} }
let light_scheme = Self::get_kcolorscheme_path(false)?; let is_dark = false; // doesn't matter since we're only reading keys
let dark_scheme = Self::get_kcolorscheme_path(true)?; let src_file = Self::get_kcolorscheme_path(is_dark)?;
if light_scheme.exists() { let src_ini = Self::read_ini(&src_file)?;
let src_ini = Self::read_ini(&light_scheme)?;
// Remove color scheme keys from kdeglobals for (section, key_value) in src_ini.get_map_ref() {
for (section, key_value) in src_ini.get_map_ref() { for (key, _) in key_value {
for key in key_value.keys() { kdeglobals_ini.remove_key(section, key);
kdeglobals_ini.remove_key(section, key);
}
} }
kdeglobals_ini
.write(kdeglobals_file)
.map_err(OutputError::Io)?;
} }
// Delete now-unused kcolorscheme files kdeglobals_ini
let delete_light_res = fs::remove_file(&light_scheme).map_err(OutputError::Io); .write(kdeglobals_file)
let delete_dark_res = fs::remove_file(&dark_scheme).map_err(OutputError::Io); .map_err(OutputError::Io)?;
delete_light_res?;
delete_dark_res?;
Ok(()) Ok(())
} }
@ -63,7 +313,6 @@ impl Theme {
let Some(mut data_dir) = dirs::data_dir() else { let Some(mut data_dir) = dirs::data_dir() else {
return Err(OutputError::MissingDataDir); return Err(OutputError::MissingDataDir);
}; };
data_dir.push("color-schemes");
let file_name = if is_dark { let file_name = if is_dark {
"CosmicDark.colors" "CosmicDark.colors"
@ -71,6 +320,11 @@ impl Theme {
"CosmicLight.colors" "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)) Ok(data_dir.join(file_name))
} }
@ -85,6 +339,15 @@ impl Theme {
Ok(ini) 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] #[cold]
fn is_cosmic_kdeglobals(ini: &Ini) -> io::Result<Option<bool>> { fn is_cosmic_kdeglobals(ini: &Ini) -> io::Result<Option<bool>> {
let color_scheme = ini.get("General", "ColorScheme"); 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_is_cosmic_kdeglobals_dark() -> io::Result<()> { fn test_opaque_color_to_rgb() {
let mut ini = Ini::new_cs(); let color = Srgba::new(30.0 / 255.0, 50.0 / 255.0, 70.0 / 255.0, 1.0);
ini.setstr("General", "ColorScheme", Some("CosmicDark")); let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?; let result = to_rgb(color, bg);
assert_eq!(is_cosmic, Some(true)); assert_eq!(result, "30,50,70");
Ok(())
} }
#[test] #[test]
fn test_is_cosmic_kdeglobals_light() -> io::Result<()> { fn test_transparent_color_to_rgb() {
let mut ini = Ini::new_cs(); let color = Srgba::new(0.0, 0.0, 0.0, 0.0);
ini.setstr("General", "ColorScheme", Some("CosmicLight")); let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?; let result = to_rgb(color, bg);
assert_eq!(is_cosmic, Some(true)); assert_eq!(result, "255,255,255");
Ok(())
} }
#[test] #[test]
fn test_is_cosmic_kdeglobals_breeze() -> io::Result<()> { fn test_translucent_color_to_rgb() {
let mut ini = Ini::new_cs(); let color = Srgba::new(0.0, 0.0, 0.0, 0.9);
ini.setstr("General", "ColorScheme", Some("BreezeDark")); let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?; let result = to_rgb(color, bg);
assert_eq!(is_cosmic, Some(false)); assert_eq!(result, "26,26,26");
Ok(())
} }
#[test] #[test]
fn test_is_cosmic_kdeglobals_blank() -> io::Result<()> { fn test_light_default_kcolorscheme() {
let mut ini = Ini::new_cs(); let light_default_kcolorscheme = Theme::light_default().as_kcolorscheme();
ini.setstr("General", "ColorScheme", None); insta::assert_snapshot!(light_default_kcolorscheme);
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?; }
assert_eq!(is_cosmic, None);
Ok(()) #[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 { 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] #[cold]
pub fn apply_vs_code(self) -> Result<(), OutputError> { pub fn apply_vs_code(self) -> Result<(), OutputError> {
let vs_theme = VsTheme::from(self); let vs_theme = VsTheme::from(self);
@ -299,13 +291,6 @@ impl Theme {
Ok(()) 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] #[cold]
pub fn reset_vs_code() -> Result<(), OutputError> { pub fn reset_vs_code() -> Result<(), OutputError> {
let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,10 +5,9 @@
use cosmic::app::Settings; use cosmic::app::Settings;
use cosmic::iced::{Alignment, Length, Size}; use cosmic::iced::{Alignment, Length, Size};
use cosmic::prelude::*;
use cosmic::widget::menu::{self, KeyBind}; use cosmic::widget::menu::{self, KeyBind};
use cosmic::widget::{nav_bar, RcElementWrapper}; use cosmic::widget::nav_bar;
use cosmic::{executor, iced, widget, Core}; use cosmic::{executor, iced, prelude::*, widget, Core};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
@ -43,7 +42,7 @@ pub enum Action {
impl widget::menu::Action for Action { impl widget::menu::Action for Action {
type Message = Message; type Message = Message;
fn message(&self) -> Self::Message { fn message(&self) -> Message {
match self { match self {
Action::Hi => Message::Hi, Action::Hi => Message::Hi,
Action::Hi2 => Message::Hi2, 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 /// Runs application with these settings
#[rustfmt::skip] #[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -103,7 +83,6 @@ pub enum Message {
Hi2, Hi2,
Hi3, Hi3,
Tick, Tick,
ValueChanged(f32),
} }
/// The [`App`] stores application-specific state. /// The [`App`] stores application-specific state.
@ -115,7 +94,6 @@ pub struct App {
hidden: bool, hidden: bool,
keybinds: HashMap<KeyBind, Action>, keybinds: HashMap<KeyBind, Action>,
progress: f32, progress: f32,
progress_slider: f32,
} }
/// Implement [`cosmic::Application`] to integrate with COSMIC. /// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -158,7 +136,6 @@ impl cosmic::Application for App {
hidden: true, hidden: true,
keybinds: HashMap::new(), keybinds: HashMap::new(),
progress: 0.0, progress: 0.0,
progress_slider: 0.0,
}; };
let command = app.update_title(); let command = app.update_title();
@ -177,70 +154,6 @@ impl cosmic::Application for App {
self.update_title() 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. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> cosmic::app::Task<Self::Message> { fn update(&mut self, message: Self::Message) -> cosmic::app::Task<Self::Message> {
match message { match message {
@ -271,9 +184,6 @@ impl cosmic::Application for App {
Message::Tick => { Message::Tick => {
self.progress = (self.progress + 0.01) % 1.0; self.progress = (self.progress + 0.01) % 1.0;
} }
Message::ValueChanged(value) => {
self.progress_slider = value;
}
} }
Task::none() Task::none()
} }
@ -290,7 +200,7 @@ impl cosmic::Application for App {
.map_or("No page selected", String::as_str); .map_or("No page selected", String::as_str);
let centered = widget::container( let centered = widget::container(
widget::column::with_capacity(16) widget::column::with_capacity(14)
.push(widget::text::body(page_content)) .push(widget::text::body(page_content))
.push( .push(
widget::text_input::text_input("", &self.input_1) widget::text_input::text_input("", &self.input_1)
@ -331,18 +241,6 @@ impl cosmic::Application for App {
.progress(self.progress) .progress(self.progress)
.width(Length::Fill), .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( .push(
widget::progress_bar::circular::Circular::new() widget::progress_bar::circular::Circular::new()
.size(50.0) .size(50.0)
@ -365,7 +263,6 @@ impl cosmic::Application for App {
.progress(1.0) .progress(1.0)
.width(Length::Fill), .width(Length::Fill),
) )
.push(widget::button::suggested("asdf").on_press(Message::Ignore))
.spacing(cosmic::theme::spacing().space_s) .spacing(cosmic::theme::spacing().space_s)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Shrink) .height(Length::Shrink)

View file

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

View file

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

View file

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

View file

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

View file

@ -23,11 +23,15 @@ use cosmic::{
Element, Element,
}; };
use cosmic_time::{Instant, Timeline}; use cosmic_time::{Instant, Timeline};
use std::cell::RefCell; use std::{
use std::rc::Rc; cell::RefCell,
use std::sync::atomic::{AtomicU32, Ordering}; rc::Rc,
use std::sync::Arc; sync::{
use std::vec; 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... // 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")); // static BTN: Lazy<id::Id> = Lazy::new(|| id::Id::new("BTN"));

View file

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

View file

@ -1,17 +1,19 @@
use std::cell::RefCell; use std::{cell::RefCell, rc::Rc};
use std::rc::Rc;
use apply::Apply; use apply::Apply;
use cosmic::iced::widget::{checkbox, column, progress_bar, radio, slider, text}; use cosmic::{
use cosmic::iced::{Alignment, Length}; cosmic_theme,
use cosmic::iced_core::id; iced::widget::{checkbox, column, progress_bar, radio, slider, text},
use cosmic::theme::ThemeType; iced::{Alignment, Length},
use cosmic::widget::color_picker::ColorPickerUpdate; iced_core::id,
use cosmic::widget::{ theme::ThemeType,
button, dropdown, icon, layer_container as container, segmented_button, segmented_control, widget::{
settings, spin_button, tab_bar, toggler, ColorPickerModel, 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 cosmic_time::{anim, chain, Timeline};
use fraction::{Decimal, ToPrimitive}; use fraction::{Decimal, ToPrimitive};
use once_cell::sync::Lazy; 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::{
use cosmic::iced::Length; iced::widget::{column, container, horizontal_space, image, row, svg, text},
use cosmic::widget::{list_column, settings, toggler}; iced::Length,
use cosmic::{theme, Element}; theme,
widget::{list_column, settings, toggler},
Element,
};
use super::{Page, SubPage, Window}; use super::{Page, SubPage, Window};

View file

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

View file

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

View file

@ -8,6 +8,6 @@ tracing = "0.1.44"
tracing-subscriber = "0.3.22" tracing-subscriber = "0.3.22"
tracing-log = "0.2.0" tracing-log = "0.2.0"
[dependencies.libcosmic-yoda] [dependencies.libcosmic]
path = "../../" path = "../../"
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] 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::app::{Core, Settings, Task};
use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::keyboard::Key; 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::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::menu::{self, ItemHeight, ItemWidth};
use cosmic::widget::RcElementWrapper; use cosmic::widget::RcElementWrapper;
use cosmic::{executor, Element}; use cosmic::{executor, Element};
@ -112,26 +114,6 @@ impl cosmic::Application for App {
vec![menu_bar(&self.config, &self.key_binds)] 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. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [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 std::collections::HashMap;
use cosmic::app::Core; use cosmic::{
use cosmic::iced::core::{id, Alignment, Length, Point}; app::Core,
use cosmic::iced::widget::{column, container, scrollable, text}; iced::core::{id, Alignment, Length, Point},
use cosmic::iced::{self, event, window, Subscription}; iced::widget::{column, container, scrollable, text},
use cosmic::prelude::*; iced::{self, event, window, Subscription},
use cosmic::widget::{button, header_bar, selectable_text}; prelude::*,
widget::{button, header_bar},
};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Message { 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 new_window_button = button::custom(text("New Window")).on_press(Message::NewWindow);
let content = scrollable( let content = scrollable(
column![ column![input, new_window_button]
input, .spacing(50)
new_window_button, .width(Length::Fill)
selectable_text::title1("I'm selectable...") .align_x(Alignment::Center),
]
.spacing(50)
.width(Length::Fill)
.align_x(Alignment::Center),
); );
let window_content = container(container(content).center_x(Length::Fixed(200.))) 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() { if id == self.core.main_window_id().unwrap() {
window_content.into() window_content.into()
} else { } else {
container(column![ column![header_bar().focused(focused), window_content].into()
header_bar()
.on_close(Message::CloseWindow(id))
.focused(focused),
window_content
])
.class(cosmic::theme::Container::WindowBackground)
.into()
} }
} }

View file

@ -8,6 +8,6 @@ tracing = "0.1.44"
tracing-subscriber = "0.3.22" tracing-subscriber = "0.3.22"
tracing-log = "0.2.0" tracing-log = "0.2.0"
[dependencies.libcosmic-yoda] [dependencies.libcosmic]
path = "../../" path = "../../"
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] 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::app::{Core, Settings, Task};
use cosmic::iced::Size; use cosmic::iced::Size;
use cosmic::widget::{menu, nav_bar, space}; use cosmic::widget::{menu, nav_bar};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -127,20 +127,18 @@ impl cosmic::Application for App {
Some(&self.nav_model) Some(&self.nav_model)
} }
/// The context menu list. /// The context menu to display for the given nav bar item ID.
fn nav_context_menu(&self) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> { fn nav_context_menu(
Some(menu::nav_context( &self,
id: nav_bar::Id,
) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
Some(menu::items(
&HashMap::new(), &HashMap::new(),
self.nav_model vec![
.iter() menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(id)),
.map(|e| { menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(id)),
vec![ menu::Item::Button("Delete", None, NavMenuAction::Delete(id)),
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(),
)) ))
} }

View file

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

View file

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

View file

@ -1,8 +1,15 @@
use cosmic::app::{Core, Task}; use cosmic::iced::Length;
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::{self, Alignment, Length, Size};
use cosmic::widget::{column, container, spin_button}; 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; use fraction::Decimal;
pub struct SpinButtonExamplApp { pub struct SpinButtonExamplApp {

View file

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

View file

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

View file

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

View file

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

View file

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