Compare commits
357 commits
menubar-po
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95756b1a57 | ||
|
|
c423ad1bfc | ||
|
|
8d7bcab258 | ||
|
|
c162a1f24a | ||
|
|
3f9e93067b | ||
|
|
917af9fda2 | ||
|
|
9b465a8b5c | ||
|
|
9cac422c24 | ||
|
|
0fc4638af3 | ||
|
|
3d8d8915be | ||
|
|
46d9f0c344 | ||
|
|
0d69cd9183 | ||
|
|
52116d2f36 | ||
|
|
0e72508dcc | ||
|
|
1d7113a244 | ||
|
|
e287a789c1 | ||
|
|
6caccaba33 | ||
|
|
a44cff8011 | ||
|
|
47ab72be50 | ||
|
|
c7093beca3 | ||
|
|
77b37f2246 | ||
|
|
6df3f76a33 | ||
|
|
12d2233c6b | ||
|
|
e5955b568d | ||
|
|
5d1dfc4c54 | ||
|
|
d9121d6f0d | ||
|
|
b963fbfea9 | ||
|
|
724351727a | ||
|
|
1f87cbc883 | ||
|
|
9aa87cd66b | ||
|
|
ab3eedd0f2 | ||
|
|
8e3672a7dd | ||
|
|
1d01054993 | ||
|
|
fdf3369cea | ||
|
|
a9e0671075 | ||
|
|
34219d1fd4 | ||
|
|
cdd825b953 | ||
|
|
b0f4e931f2 | ||
|
|
97a805e5a1 | ||
|
|
24464908f6 | ||
|
|
7a02c9a296 | ||
|
|
61e5d882ae | ||
|
|
12be83a8ef | ||
|
|
f6eb314606 | ||
|
|
0ba668eb52 | ||
|
|
aef328238f | ||
|
|
22661fd764 | ||
|
|
e1738d2ea7 | ||
|
|
2299fba69b | ||
|
|
c33455e9ad | ||
|
|
9a72fe6c2d | ||
|
|
39e8300d90 | ||
|
|
f734ccbbde | ||
|
|
e86304cf3f | ||
|
|
672f9047a2 | ||
|
|
8b52592f2d | ||
|
|
d631f9d6d7 | ||
|
|
4541c6a275 | ||
|
|
1433b89e40 | ||
|
|
f06d15ae35 | ||
|
|
413e63f62a | ||
|
|
380b341bdc | ||
|
|
254c13cfc4 | ||
|
|
e63f3196e2 | ||
|
|
a38a6f5d73 | ||
|
|
763f0da64c | ||
|
|
adb3e341fc | ||
|
|
8e439c842c | ||
|
|
d7fd880ac6 | ||
|
|
141261b9bf | ||
|
|
c804d3851d | ||
|
|
dc3ebaa38e | ||
|
|
7a56762422 | ||
|
|
36cba695d2 | ||
|
|
3da55e8074 | ||
|
|
54bcb9ec12 | ||
|
|
6c6d16d34a | ||
|
|
c7ac9cfd31 | ||
|
|
0bb006c5bb | ||
|
|
adb6e30405 | ||
|
|
9602dfd2f1 | ||
|
|
12cc536cd5 | ||
|
|
c52ef97650 | ||
|
|
01e5593741 | ||
|
|
1dc9aa37ed | ||
|
|
ce9e8b5205 | ||
|
|
b4533e3a56 | ||
|
|
c66652df41 | ||
|
|
242fe6c4ac | ||
|
|
26f4086931 | ||
|
|
4b92ee5f80 | ||
|
|
ff6454248f | ||
|
|
5eec820615 | ||
|
|
03d0171bbe | ||
|
|
3d2c018cd1 | ||
|
|
79f8337634 | ||
|
|
14a5d0c0ba | ||
|
|
1970499459 | ||
|
|
1810bedfa5 | ||
|
|
ad65416551 | ||
|
|
8795c506fa | ||
|
|
976e0e214f | ||
|
|
0bfda2e28c | ||
|
|
5432fee112 | ||
|
|
925cc9a39f | ||
|
|
0e1a9d46eb | ||
|
|
89d31e988d | ||
|
|
3d8596287c | ||
|
|
0298487096 | ||
|
|
904133397b | ||
|
|
bee2d591db | ||
|
|
442ce6ad0c | ||
|
|
fb1a7d3640 | ||
|
|
89ee66f251 | ||
|
|
7554540b78 | ||
|
|
71e2c7c99e | ||
|
|
0d37dc69e3 | ||
|
|
e6fe1a6811 | ||
|
|
e8d53b14ea | ||
|
|
e10459fb37 | ||
|
|
86dcf8af6c | ||
|
|
85c27a9960 | ||
|
|
bd1d3d5a73 | ||
|
|
5b648ca03f | ||
|
|
f2caa66f0f | ||
|
|
a37be90e81 | ||
|
|
384e8f6e21 | ||
|
|
b9bd773940 | ||
|
|
1f6086e5ea | ||
|
|
c1c09624bd | ||
|
|
754b064bff | ||
|
|
3ed5c173fd | ||
|
|
dc3c194f09 | ||
|
|
e1dad541b2 | ||
|
|
7c49a736ec | ||
|
|
be98b7dd6f | ||
|
|
cb288070af | ||
|
|
990e2e291b | ||
|
|
b05f040e5f | ||
|
|
a2e903ad94 | ||
|
|
6328c40ef7 | ||
|
|
21c5a4f34a | ||
|
|
ae1f15f37e | ||
|
|
031818c6b0 | ||
|
|
ae830ca21d | ||
|
|
a3cf875793 | ||
|
|
30a02ec0bb | ||
|
|
3e78eb2381 | ||
|
|
fdcba7d8ec | ||
|
|
cf19ac665f | ||
|
|
b71a7c9edf | ||
|
|
9fcd449611 | ||
|
|
f1c43f79ab | ||
|
|
927035809f | ||
|
|
beddbf1770 | ||
|
|
d71c42102d | ||
|
|
689f25be53 | ||
|
|
097c76f0e5 | ||
|
|
3e6c9a6add | ||
|
|
85709b5c29 | ||
|
|
03c440b97a | ||
|
|
b0cbb54bf2 | ||
|
|
f000433690 | ||
|
|
f453db2425 | ||
|
|
b9c24d2421 | ||
|
|
f6039597b7 | ||
|
|
421552dea1 | ||
|
|
e9bb5ed97d | ||
|
|
a9f64c33ce | ||
|
|
6f92465fcb | ||
|
|
dd3610b8ae | ||
|
|
fa26e0e241 | ||
|
|
e4978693b9 | ||
|
|
aabc8dcda5 | ||
|
|
3b8ad45950 | ||
|
|
2f0b333491 | ||
|
|
05c6608842 | ||
|
|
f39ad728c9 | ||
|
|
cdf4eafc9e | ||
|
|
6793950bbc | ||
|
|
2ffd1f32f4 | ||
|
|
8a9cd0da32 | ||
|
|
e13ab24151 | ||
|
|
866da0f94b | ||
|
|
45fd683bc9 | ||
|
|
c2b7d7847a | ||
|
|
54934a961f | ||
|
|
80875d5962 | ||
|
|
18182e5f97 | ||
|
|
14cbebbadc | ||
|
|
882481e518 | ||
|
|
62f661e077 | ||
|
|
639326fcc3 | ||
|
|
ce0868582b | ||
|
|
7f321cb0a3 | ||
|
|
709044891e | ||
|
|
fc85fcac3e | ||
|
|
7eecbe30d7 | ||
|
|
47cc6dbdbf | ||
|
|
8528477355 | ||
|
|
16d095b2cd | ||
|
|
96a51be3e4 | ||
|
|
d6b3720e1f | ||
|
|
690f1d331d | ||
|
|
2296e8e94d | ||
|
|
2c93a4094f | ||
|
|
bc744bd4e3 | ||
|
|
bb6f6e9ac8 | ||
|
|
6439507aa2 | ||
|
|
37ae722320 | ||
|
|
d2f7fdea6d | ||
|
|
b6c6d1cb7b | ||
|
|
2299b46862 | ||
|
|
b110b9ca3f | ||
|
|
8e1d06e7da | ||
|
|
a1b64dde3e | ||
|
|
0c6c85429e | ||
|
|
6204784f20 | ||
|
|
380042396b | ||
|
|
e49a30104b | ||
|
|
1d6a43486e | ||
|
|
bd438a8581 | ||
|
|
840ef21e4d | ||
|
|
2e87bd7c41 | ||
|
|
f2e965c76c | ||
|
|
529eeebaeb | ||
|
|
76c1897d4d | ||
|
|
f44d82a7e8 | ||
|
|
cd3e9c1493 | ||
|
|
483fb2cdd1 | ||
|
|
d82e6a167c | ||
|
|
804250af64 | ||
|
|
a929829521 | ||
|
|
f17cd2928a | ||
|
|
2dda96c07f | ||
|
|
d40e9fa4e4 | ||
|
|
dc4e0edd73 | ||
|
|
4d4f754318 | ||
|
|
4c4eddb50c | ||
|
|
a27bb5e05d | ||
|
|
34f55d6720 | ||
|
|
ad1672b881 | ||
|
|
5cd7742413 | ||
|
|
0059fe182b | ||
|
|
6c5b799b34 | ||
|
|
cc8e5ebdea | ||
|
|
1c83be9d1c | ||
|
|
aeafe447e3 | ||
|
|
5092d19861 | ||
|
|
511fe02624 | ||
|
|
432e43d258 | ||
|
|
6a0c06a368 | ||
|
|
df9df40963 | ||
|
|
ee84ad958f | ||
|
|
59e480a4c6 | ||
|
|
f097b643b3 | ||
|
|
0ca7a99c9f | ||
|
|
00b4a8a9f5 | ||
|
|
4a71189d34 | ||
|
|
7015b8ace4 | ||
|
|
03f07d2f1e | ||
|
|
27f591e5aa | ||
|
|
ab41b83cd8 | ||
|
|
9815d4d981 | ||
|
|
43314e3e6a | ||
|
|
12014b683a | ||
|
|
ad70236a58 | ||
|
|
9ccade723a | ||
|
|
47daaab610 | ||
|
|
f1998afff9 | ||
|
|
4a29788199 | ||
|
|
31fa09a92a | ||
|
|
17fa2cd29a | ||
|
|
66df10ad89 | ||
|
|
19d273ed2e | ||
|
|
9ff208e9d7 | ||
|
|
c01254dd18 | ||
|
|
0e797b2440 | ||
|
|
978bde5720 | ||
|
|
b9a00c6e79 | ||
|
|
e568122083 | ||
|
|
31aa0bd3df | ||
|
|
e83e43bf1e | ||
|
|
39a5607400 | ||
|
|
ac18f009b4 | ||
|
|
066999586b | ||
|
|
ea349aca82 | ||
|
|
b72b15d719 | ||
|
|
c5df9dcf88 | ||
|
|
f5f7c14f03 | ||
|
|
2dd6dce053 | ||
|
|
4d06524f2c | ||
|
|
364c0b9381 | ||
|
|
94ee4e1915 | ||
|
|
6f1fe2a28b | ||
|
|
66a2632e2e | ||
|
|
2d62503fdf | ||
|
|
8415d77b0a | ||
|
|
29f38f83a3 | ||
|
|
ba2f4b193a | ||
|
|
e7b7c3a126 | ||
|
|
6e7a634398 | ||
|
|
c10695600b | ||
|
|
8412dd5939 | ||
|
|
7712ec0021 | ||
|
|
95ebabf149 | ||
|
|
5434dc95d5 | ||
|
|
4f423349a2 | ||
|
|
6a5076ecb7 | ||
|
|
989fcad99e | ||
|
|
8badf73383 | ||
|
|
562e885872 | ||
|
|
b58d864e85 | ||
|
|
05874e8ea2 | ||
|
|
5e136f9499 | ||
|
|
2099dc45cb | ||
|
|
1b988ed1e9 | ||
|
|
c40eb87611 | ||
|
|
3c13669865 | ||
|
|
5aa025af7d | ||
|
|
8ebd06bed0 | ||
|
|
8c4cb2e54f | ||
|
|
b8eaad2a7e | ||
|
|
38dde24f96 | ||
|
|
ec7a531539 | ||
|
|
7748e59ae6 | ||
|
|
0041fc2d12 | ||
|
|
364af2bcdf | ||
|
|
0943f131c2 | ||
|
|
50367b96e3 | ||
|
|
aaa4b83577 | ||
|
|
52b802a11a | ||
|
|
dfdca0ef81 | ||
|
|
46cbce033b | ||
|
|
7555d9dfd1 | ||
|
|
7fb514bfac | ||
|
|
a85b369399 | ||
|
|
1af2f4ffe5 | ||
|
|
90ad3e9e1b | ||
|
|
5be9611c8a | ||
|
|
6be5403852 | ||
|
|
b0f62a5109 | ||
|
|
bf9fc4c29f | ||
|
|
90ed634b06 | ||
|
|
7d7274b801 | ||
|
|
12317d8103 | ||
|
|
00ba16fe01 | ||
|
|
ba72aed6fb | ||
|
|
4c6061d40a | ||
|
|
3f4a50ee2c | ||
|
|
8edbbec1e8 | ||
|
|
f835afa59c | ||
|
|
96416c2a3f | ||
|
|
7f2d34ead4 | ||
|
|
92ec78ba29 | ||
|
|
5b77f37fde | ||
|
|
944c6761f7 |
267 changed files with 17198 additions and 6026 deletions
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
- [ ] I have disclosed use of any AI generated code in my commit messages.
|
||||||
|
- If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR.
|
||||||
|
- In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment.
|
||||||
|
- [ ] I understand these changes in full and will be able to respond to review comments.
|
||||||
|
- [ ] My change is accurately described in the commit message.
|
||||||
|
- [ ] My contribution is tested and working as described.
|
||||||
|
- [ ] I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.
|
||||||
|
|
||||||
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
|
|
@ -33,16 +33,17 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
features:
|
test_args:
|
||||||
- "" # for cosmic-comp, don't remove!
|
- --no-default-features --features "" # for cosmic-comp, don't remove!
|
||||||
- 'winit_debug'
|
- --no-default-features --features "winit_debug"
|
||||||
- 'winit_tokio'
|
- --no-default-features --features "winit_tokio"
|
||||||
- winit
|
- --no-default-features --features "winit"
|
||||||
- winit_wgpu
|
- --no-default-features --features "winit_wgpu"
|
||||||
- wayland
|
- --no-default-features --features "wayland"
|
||||||
- applet
|
- --no-default-features --features "applet"
|
||||||
- desktop,smol
|
- --no-default-features --features "desktop,smol"
|
||||||
- desktop,tokio
|
- --no-default-features --features "desktop,tokio"
|
||||||
|
- -p cosmic-theme
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
|
|
@ -66,7 +67,7 @@ jobs:
|
||||||
- name: Rust toolchain
|
- name: Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Test features
|
- name: Test features
|
||||||
run: cargo test --no-default-features --features "${{ matrix.features }}"
|
run: cargo test ${{ matrix.test_args }} -- --test-threads=1
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: full
|
RUST_BACKTRACE: full
|
||||||
|
|
||||||
|
|
@ -103,7 +104,7 @@ jobs:
|
||||||
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: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Test example
|
- name: Check example
|
||||||
run: cargo check -p "${{ matrix.examples }}"
|
run: cargo check -p "${{ matrix.examples }}"
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: full
|
RUST_BACKTRACE: full
|
||||||
|
|
|
||||||
37
.github/workflows/pages.yml
vendored
37
.github/workflows/pages.yml
vendored
|
|
@ -7,19 +7,30 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pages:
|
pages:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Build documentation
|
- name: Install Rust nightly
|
||||||
run: cargo doc --verbose --features tokio,winit
|
uses: dtolnay/rust-toolchain@master
|
||||||
- name: Deploy documentation
|
with:
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
toolchain: nightly-2025-07-31
|
||||||
with:
|
- name: System dependencies
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
|
||||||
publish_dir: ./target/doc
|
- name: Build documentation
|
||||||
force_orphan: true
|
run: |
|
||||||
|
RUSTDOCFLAGS="--cfg docsrs" \
|
||||||
|
cargo +nightly-2025-07-31 doc --no-deps \
|
||||||
|
-p cosmic-client-toolkit \
|
||||||
|
-p cosmic-protocols \
|
||||||
|
-p libcosmic \
|
||||||
|
--verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window
|
||||||
|
- name: Deploy documentation
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: ./target/doc
|
||||||
|
force_orphan: true
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -2,3 +2,6 @@
|
||||||
path = iced
|
path = iced
|
||||||
url = https://github.com/pop-os/iced.git
|
url = https://github.com/pop-os/iced.git
|
||||||
branch = master
|
branch = master
|
||||||
|
[submodule "icon-theme"]
|
||||||
|
path = cosmic-icons
|
||||||
|
url = https://github.com/pop-os/cosmic-icons
|
||||||
|
|
|
||||||
135
Cargo.toml
135
Cargo.toml
|
|
@ -1,20 +1,36 @@
|
||||||
[package]
|
[package]
|
||||||
name = "libcosmic"
|
name = "libcosmic"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.85"
|
rust-version = "1.90"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "cosmic"
|
name = "cosmic"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["multi-window", "a11y"]
|
default = [
|
||||||
|
"winit",
|
||||||
|
"tokio",
|
||||||
|
"a11y",
|
||||||
|
"dbus-config",
|
||||||
|
"x11",
|
||||||
|
"iced-wayland",
|
||||||
|
"multi-window",
|
||||||
|
]
|
||||||
|
advanced-shaping = ["iced/advanced-shaping"]
|
||||||
# Accessibility support
|
# Accessibility support
|
||||||
a11y = ["iced/a11y", "iced_accessibility"]
|
a11y = ["iced/a11y", "iced_accessibility"]
|
||||||
# Enable about widget
|
# Enable about widget
|
||||||
about = ["desktop", "dep:license"]
|
about = []
|
||||||
# Builds support for animated images
|
# Builds support for animated images
|
||||||
animated-image = ["dep:async-fs", "image/gif", "tokio?/io-util", "tokio?/fs"]
|
animated-image = [
|
||||||
|
"dep:async-fs",
|
||||||
|
"image/gif",
|
||||||
|
"image/webp",
|
||||||
|
"image/png",
|
||||||
|
"tokio?/io-util",
|
||||||
|
"tokio?/fs",
|
||||||
|
]
|
||||||
# XXX autosize should not be used on winit windows unless dialogs
|
# XXX autosize should not be used on winit windows unless dialogs
|
||||||
autosize = []
|
autosize = []
|
||||||
applet = [
|
applet = [
|
||||||
|
|
@ -27,8 +43,8 @@ applet = [
|
||||||
"multi-window",
|
"multi-window",
|
||||||
]
|
]
|
||||||
applet-token = ["applet"]
|
applet-token = ["applet"]
|
||||||
# Use the cosmic-settings-daemon for config handling
|
# Use the cosmic-settings-daemon for config handling on Linux targets
|
||||||
dbus-config = ["cosmic-config/dbus", "dep:zbus", "cosmic-settings-daemon"]
|
dbus-config = []
|
||||||
# Debug features
|
# Debug features
|
||||||
debug = ["iced/debug"]
|
debug = ["iced/debug"]
|
||||||
# Enables pipewire support in ashpd, if ashpd is enabled
|
# Enables pipewire support in ashpd, if ashpd is enabled
|
||||||
|
|
@ -40,7 +56,9 @@ rfd = ["dep:rfd"]
|
||||||
# Enables desktop files helpers
|
# Enables desktop files helpers
|
||||||
desktop = [
|
desktop = [
|
||||||
"process",
|
"process",
|
||||||
|
"dep:cosmic-settings-config",
|
||||||
"dep:freedesktop-desktop-entry",
|
"dep:freedesktop-desktop-entry",
|
||||||
|
"dep:image-extras",
|
||||||
"dep:mime",
|
"dep:mime",
|
||||||
"dep:shlex",
|
"dep:shlex",
|
||||||
"tokio?/io-util",
|
"tokio?/io-util",
|
||||||
|
|
@ -64,18 +82,24 @@ tokio = [
|
||||||
]
|
]
|
||||||
# Tokio async runtime
|
# Tokio async runtime
|
||||||
# Wayland window support
|
# Wayland window support
|
||||||
wayland = [
|
iced-wayland = [
|
||||||
"ashpd?/wayland",
|
"ashpd?/wayland",
|
||||||
"autosize",
|
"autosize",
|
||||||
"iced_runtime/wayland",
|
|
||||||
"iced/wayland",
|
"iced/wayland",
|
||||||
"iced_winit/wayland",
|
"iced_winit/wayland",
|
||||||
"cctk",
|
|
||||||
"surface-message",
|
"surface-message",
|
||||||
]
|
]
|
||||||
|
wayland = [
|
||||||
|
"iced-wayland",
|
||||||
|
"iced_runtime/cctk",
|
||||||
|
"iced_winit/cctk",
|
||||||
|
"iced_wgpu/cctk",
|
||||||
|
"iced/cctk",
|
||||||
|
"dep:cctk",
|
||||||
|
]
|
||||||
surface-message = []
|
surface-message = []
|
||||||
# multi-window support
|
# multi-window support
|
||||||
multi-window = ["iced/multi-window"]
|
multi-window = []
|
||||||
# Render with wgpu
|
# Render with wgpu
|
||||||
wgpu = ["iced/wgpu", "iced_wgpu"]
|
wgpu = ["iced/wgpu", "iced_wgpu"]
|
||||||
# X11 window support via winit
|
# X11 window support via winit
|
||||||
|
|
@ -90,53 +114,77 @@ markdown = ["iced/markdown"]
|
||||||
highlighter = ["iced/highlighter"]
|
highlighter = ["iced/highlighter"]
|
||||||
async-std = [
|
async-std = [
|
||||||
"dep:async-std",
|
"dep:async-std",
|
||||||
"ashpd/async-std",
|
"ashpd?/async-std",
|
||||||
"rfd?/async-std",
|
"rfd?/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.11.0", default-features = false, optional = true }
|
ashpd = { version = "0.12.3", default-features = false, optional = true }
|
||||||
async-fs = { version = "2.1", optional = true }
|
async-fs = { version = "2.2", optional = true }
|
||||||
async-std = { version = "1.13", optional = true }
|
async-std = { version = "1.13", optional = true }
|
||||||
auto_enums = "0.8.7"
|
auto_enums = "0.8.8"
|
||||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true }
|
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true }
|
||||||
chrono = "0.4.40"
|
jiff = "0.2"
|
||||||
cosmic-config = { path = "cosmic-config" }
|
cosmic-config = { path = "cosmic-config" }
|
||||||
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
|
||||||
|
# Internationalization
|
||||||
|
i18n-embed = { version = "0.16.0", features = [
|
||||||
|
"fluent-system",
|
||||||
|
"desktop-requester",
|
||||||
|
] }
|
||||||
|
i18n-embed-fl = "0.10"
|
||||||
|
rust-embed = "8.11.0"
|
||||||
css-color = "0.2.8"
|
css-color = "0.2.8"
|
||||||
derive_setters = "0.1.6"
|
derive_setters = "0.1.9"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
image = { version = "0.25.5", default-features = false, features = [
|
image = { version = "0.25.10", default-features = false, features = [
|
||||||
|
"ico",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"png",
|
"png",
|
||||||
] }
|
] }
|
||||||
lazy_static = "1.5.0"
|
image-extras = { version = "0.1.0", default-features = false, features = [
|
||||||
libc = { version = "0.2.171", optional = true }
|
"xpm",
|
||||||
license = { version = "3.6.0", optional = true }
|
"xbm",
|
||||||
|
], optional = true }
|
||||||
|
libc = { version = "0.2.183", optional = true }
|
||||||
|
log = "0.4"
|
||||||
mime = { version = "0.3.17", optional = true }
|
mime = { version = "0.3.17", optional = true }
|
||||||
palette = "0.7.6"
|
palette = "0.7.6"
|
||||||
rfd = { version = "0.15.3", default-features = false, features = [
|
rfd = { version = "0.16.0", default-features = false, features = [
|
||||||
"xdg-portal",
|
"xdg-portal",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
rustix = { version = "1.0", features = ["pipe", "process"], optional = true }
|
rustix = { version = "1.1", features = ["pipe", "process"], optional = true }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
slotmap = "1.0.7"
|
slotmap = "1.1.1"
|
||||||
smol = { version = "2.0.2", optional = true }
|
smol = { version = "2.0.2", optional = true }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.18"
|
||||||
tokio = { version = "1.44.1", optional = true }
|
taffy = { version = "0.9.2", features = ["grid"] }
|
||||||
tracing = "0.1.41"
|
tokio = { version = "1.50.0", optional = true }
|
||||||
|
tracing = "0.1.44"
|
||||||
unicode-segmentation = "1.12"
|
unicode-segmentation = "1.12"
|
||||||
url = "2.5.4"
|
url = "2.5.8"
|
||||||
zbus = { version = "5.7.1", default-features = false, optional = true }
|
zbus = { version = "5.14.0", default-features = false, optional = true }
|
||||||
|
float-cmp = "0.10.0"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
# Enable DBus feature on Linux targets
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
cosmic-config = { path = "cosmic-config", features = ["dbus"] }
|
||||||
|
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||||
|
zbus = { version = "5.14.0", default-features = false }
|
||||||
|
|
||||||
|
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
|
||||||
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
|
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
|
||||||
freedesktop-desktop-entry = { version = "0.7.11", 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 }
|
||||||
|
|
||||||
|
[target.'cfg(any(not(unix), target_os = "macos"))'.dependencies]
|
||||||
|
# Used to embed bundled icons for non-unix platforms.
|
||||||
|
phf = { version = "0.13.1", features = ["macros"] }
|
||||||
|
|
||||||
[dependencies.cosmic-theme]
|
[dependencies.cosmic-theme]
|
||||||
path = "cosmic-theme"
|
path = "cosmic-theme"
|
||||||
|
|
||||||
|
|
@ -186,17 +234,13 @@ optional = true
|
||||||
|
|
||||||
[dependencies.cosmic-panel-config]
|
[dependencies.cosmic-panel-config]
|
||||||
git = "https://github.com/pop-os/cosmic-panel"
|
git = "https://github.com/pop-os/cosmic-panel"
|
||||||
|
# path = "../cosmic-panel/cosmic-panel-config"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.ron]
|
[dependencies.ron]
|
||||||
version = "0.9"
|
version = "0.12"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.taffy]
|
|
||||||
git = "https://github.com/DioxusLabs/taffy"
|
|
||||||
rev = "7781c70"
|
|
||||||
features = ["grid"]
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"cosmic-config",
|
"cosmic-config",
|
||||||
|
|
@ -209,12 +253,5 @@ exclude = ["iced"]
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
[patch."https://github.com/pop-os/libcosmic"]
|
tempfile = "3.27.0"
|
||||||
libcosmic = { path = "./" }
|
|
||||||
|
|
||||||
# FIXME update winit deps where necessary to use this
|
|
||||||
# [patch.crates-io]
|
|
||||||
# [patch."https://github.com/pop-os/winit.git"]
|
|
||||||
# winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" }
|
|
||||||
# winit = { path = "../../winit" }
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,11 @@ A platform toolkit based on iced for creating applets and applications for the C
|
||||||
## Templates
|
## Templates
|
||||||
|
|
||||||
- https://github.com/pop-os/cosmic-app-template: Application project template
|
- https://github.com/pop-os/cosmic-app-template: Application project template
|
||||||
|
- https://github.com/pop-os/cosmic-applet-template: Panel applet project template
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
While libcosmic is written entirely in Rust, some of its dependencies may require shared system library headers to be installed. On Pop!_OS, the following dependencies are all that's necessary compile a typical COSMIC project:
|
While libcosmic is written entirely in Rust, some of its dependencies may require shared system library headers to be installed. On Pop!_OS, the following dependencies are all that's necessary to compile a typical COSMIC project:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
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
|
||||||
|
|
|
||||||
63
build.rs
Normal file
63
build.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("cargo::rerun-if-changed=build.rs");
|
||||||
|
|
||||||
|
if env::var_os("CARGO_CFG_UNIX").is_none()
|
||||||
|
|| env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos")
|
||||||
|
{
|
||||||
|
generate_bundled_icons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_bundled_icons() {
|
||||||
|
println!("cargo::rerun-if-changed=cosmic-icons");
|
||||||
|
|
||||||
|
let manifest_dir = std::path::Path::new(std::env!("CARGO_MANIFEST_DIR"));
|
||||||
|
let icon_paths = [
|
||||||
|
"cosmic-icons/freedesktop/scalable",
|
||||||
|
"cosmic-icons/extra/scalable",
|
||||||
|
];
|
||||||
|
|
||||||
|
let key_value_assignments = icon_paths
|
||||||
|
.into_iter()
|
||||||
|
.map(|path| manifest_dir.join(path))
|
||||||
|
.inspect(|icon_path| assert!(icon_path.exists(), "path = {icon_path:?}"))
|
||||||
|
.map(|icon_path| std::fs::read_dir(icon_path).unwrap())
|
||||||
|
.flat_map(|dir| {
|
||||||
|
dir.flat_map(|entry| entry.unwrap().path().read_dir().unwrap())
|
||||||
|
.map(|entry| {
|
||||||
|
let entry = entry.unwrap();
|
||||||
|
let path = entry.path().canonicalize().unwrap();
|
||||||
|
let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned();
|
||||||
|
let path = path.into_os_string().into_string().unwrap();
|
||||||
|
(file_name, path)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.fold(
|
||||||
|
std::collections::BTreeMap::new(),
|
||||||
|
|mut set, (name, path)| {
|
||||||
|
set.insert(name, path);
|
||||||
|
set
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.fold(String::new(), |mut output, (name, path)| {
|
||||||
|
// This changes the escape character to the one used by Windows.
|
||||||
|
#[cfg(windows)]
|
||||||
|
let path = path.replace("\\", "/");
|
||||||
|
output.push_str(&format!(" \"{name}\" => include_bytes!(\"{path}\"),\n"));
|
||||||
|
output
|
||||||
|
});
|
||||||
|
|
||||||
|
let code = [
|
||||||
|
"static ICONS: phf::Map<&'static str, &'static [u8]> = phf::phf_map!(\n",
|
||||||
|
&key_value_assignments,
|
||||||
|
");",
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
|
||||||
|
let out_dir = std::env::var_os("OUT_DIR").unwrap();
|
||||||
|
let out_file = std::path::Path::new(&out_dir).join("bundled_icons.rs");
|
||||||
|
std::fs::write(&out_file, &code).unwrap();
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-config-derive"
|
name = "cosmic-config-derive"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# 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
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
syn = "1.0"
|
syn = "2.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,16 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
let version = attributes
|
let version = attributes
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|attr| {
|
.find_map(|attr| {
|
||||||
if attr.path.is_ident("version") {
|
if attr.path().is_ident("version") {
|
||||||
match attr.parse_meta() {
|
match attr.meta {
|
||||||
Ok(syn::Meta::NameValue(syn::MetaNameValue {
|
syn::Meta::NameValue(syn::MetaNameValue {
|
||||||
lit: syn::Lit::Int(lit_int),
|
value:
|
||||||
|
syn::Expr::Lit(syn::ExprLit {
|
||||||
|
lit: syn::Lit::Int(ref lit_int),
|
||||||
|
..
|
||||||
|
}),
|
||||||
..
|
..
|
||||||
})) => Some(lit_int.base10_parse::<u64>().unwrap()),
|
}) => Some(lit_int.base10_parse::<u64>().unwrap()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -102,7 +106,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let gen = quote! {
|
let generate = quote! {
|
||||||
impl CosmicConfigEntry for #name {
|
impl CosmicConfigEntry for #name {
|
||||||
const VERSION: u64 = #version;
|
const VERSION: u64 = #version;
|
||||||
|
|
||||||
|
|
@ -143,5 +147,5 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
gen.into()
|
generate.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-config"
|
name = "cosmic-config"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["macro", "subscription"]
|
default = ["macro", "subscription"]
|
||||||
|
|
@ -11,24 +11,23 @@ subscription = ["iced_futures"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||||
zbus = { version = "5.7.1", 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.2", optional = true }
|
calloop = { version = "0.14.4", optional = true }
|
||||||
notify = "8.0.0"
|
notify = "8.2.0"
|
||||||
ron = "0.9.0"
|
ron = "0.12.0"
|
||||||
serde = "1.0.219"
|
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 }
|
||||||
once_cell = "1.21.1"
|
|
||||||
futures-util = { version = "0.3", optional = true }
|
futures-util = { version = "0.3", optional = true }
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
tokio = { version = "1.44", optional = true, features = ["time"] }
|
tokio = { version = "1.50", optional = true, features = ["time"] }
|
||||||
async-std = { version = "1.13", optional = true }
|
async-std = { version = "1.13", optional = true }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
xdg = "2.5"
|
xdg = "3.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
known-folders = "1.2.0"
|
known-folders = "1.4.2"
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use std::ops::Deref;
|
use std::{any::TypeId, 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::{
|
use iced_futures::{
|
||||||
futures::{self, future::pending, Stream, StreamExt},
|
Subscription,
|
||||||
stream, Subscription,
|
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>> {
|
||||||
|
|
@ -56,6 +57,20 @@ impl Watcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Wrapper(
|
||||||
|
TypeId,
|
||||||
|
CosmicSettingsDaemonProxy<'static>,
|
||||||
|
&'static str,
|
||||||
|
bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl std::hash::Hash for Wrapper {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.0.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
|
pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
|
||||||
settings_daemon: CosmicSettingsDaemonProxy<'static>,
|
settings_daemon: CosmicSettingsDaemonProxy<'static>,
|
||||||
|
|
@ -63,158 +78,185 @@ pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'stat
|
||||||
is_state: bool,
|
is_state: bool,
|
||||||
) -> iced_futures::Subscription<Update<T>> {
|
) -> iced_futures::Subscription<Update<T>> {
|
||||||
let id = std::any::TypeId::of::<T>();
|
let id = std::any::TypeId::of::<T>();
|
||||||
Subscription::run_with_id(
|
Subscription::run_with(
|
||||||
(id, config_id),
|
Wrapper(id, settings_daemon, config_id, is_state),
|
||||||
watcher_stream(settings_daemon, config_id, is_state),
|
|&Wrapper(_, ref settings_daemon, ref config_id, ref is_state)| {
|
||||||
|
let is_state = *is_state;
|
||||||
|
let config_id = *config_id;
|
||||||
|
let settings_daemon = settings_daemon.clone();
|
||||||
|
enum Change {
|
||||||
|
Changes(Changed),
|
||||||
|
OwnerChanged(bool),
|
||||||
|
}
|
||||||
|
stream::channel(
|
||||||
|
5,
|
||||||
|
move |mut tx: futures::channel::mpsc::Sender<Update<T>>| async move {
|
||||||
|
let version = T::VERSION;
|
||||||
|
|
||||||
|
let Ok(cosmic_config) = (if is_state {
|
||||||
|
crate::Config::new_state(config_id, version)
|
||||||
|
} else {
|
||||||
|
crate::Config::new(config_id, version)
|
||||||
|
}) else {
|
||||||
|
pending::<()>().await;
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut attempts = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let watcher = if is_state {
|
||||||
|
Watcher::new_state(&settings_daemon, config_id, version).await
|
||||||
|
} else {
|
||||||
|
Watcher::new_config(&settings_daemon, config_id, version).await
|
||||||
|
};
|
||||||
|
let Ok(watcher) = watcher else {
|
||||||
|
tracing::error!("Failed to create watcher for {config_id}");
|
||||||
|
|
||||||
|
#[cfg(feature = "tokio")]
|
||||||
|
::tokio::time::sleep(::tokio::time::Duration::from_secs(
|
||||||
|
2_u64.pow(attempts),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
#[cfg(feature = "async-std")]
|
||||||
|
async_std::task::sleep(std::time::Duration::from_secs(
|
||||||
|
2_u64.pow(attempts),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
|
||||||
|
{
|
||||||
|
pending::<()>().await;
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
attempts += 1;
|
||||||
|
// The settings daemon has exited
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Ok(changes) = watcher.receive_changed().await else {
|
||||||
|
tracing::error!("Failed to listen for changes for {config_id}");
|
||||||
|
|
||||||
|
#[cfg(feature = "tokio")]
|
||||||
|
::tokio::time::sleep(::tokio::time::Duration::from_secs(
|
||||||
|
2_u64.pow(attempts),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
#[cfg(feature = "async-std")]
|
||||||
|
async_std::task::sleep(std::time::Duration::from_secs(
|
||||||
|
2_u64.pow(attempts),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
|
||||||
|
{
|
||||||
|
pending::<()>().await;
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
attempts += 1;
|
||||||
|
// The settings daemon has exited
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut changes = changes.map(Change::Changes).fuse();
|
||||||
|
|
||||||
|
let Ok(owner_changed) = watcher.inner().receive_owner_changed().await
|
||||||
|
else {
|
||||||
|
tracing::error!("Failed to listen for owner changes for {config_id}");
|
||||||
|
#[cfg(feature = "tokio")]
|
||||||
|
::tokio::time::sleep(::tokio::time::Duration::from_secs(
|
||||||
|
2_u64.pow(attempts),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
#[cfg(feature = "async-std")]
|
||||||
|
async_std::task::sleep(std::time::Duration::from_secs(
|
||||||
|
2_u64.pow(attempts),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
|
||||||
|
{
|
||||||
|
pending::<()>().await;
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
attempts += 1;
|
||||||
|
// The settings daemon has exited
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let mut owner_changed = owner_changed
|
||||||
|
.map(|c| Change::OwnerChanged(c.is_some()))
|
||||||
|
.fuse();
|
||||||
|
|
||||||
|
// update now, just in case we missed changes while setting up stream
|
||||||
|
let mut config = match T::get_entry(&cosmic_config) {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err((errors, default)) => {
|
||||||
|
for why in &errors {
|
||||||
|
if why.is_err() {
|
||||||
|
if let crate::Error::GetKey(_, err) = &why {
|
||||||
|
if err.kind() == std::io::ErrorKind::NotFound {
|
||||||
|
// No system default config installed; don't error
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracing::error!("error getting config: {config_id} {why}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = tx
|
||||||
|
.send(Update {
|
||||||
|
errors: Vec::new(),
|
||||||
|
keys: Vec::new(),
|
||||||
|
config: config.clone(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("Failed to send config: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let change: Changed = futures::select! {
|
||||||
|
c = changes.next() => {
|
||||||
|
let Some(Change::Changes(c)) = c else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
c
|
||||||
|
}
|
||||||
|
c = owner_changed.next() => {
|
||||||
|
let Some(Change::OwnerChanged(cont)) = c else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
if cont {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// The settings daemon has exited
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset the attempts counter if we received a change
|
||||||
|
attempts = 0;
|
||||||
|
let Ok(args) = change.args() else {
|
||||||
|
// The settings daemon has exited
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
let (errors, keys) = config.update_keys(&cosmic_config, &[args.key]);
|
||||||
|
if !keys.is_empty() {
|
||||||
|
if let Err(err) = tx
|
||||||
|
.send(Update {
|
||||||
|
errors,
|
||||||
|
keys,
|
||||||
|
config: config.clone(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("Failed to send config update: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn watcher_stream<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
|
|
||||||
settings_daemon: CosmicSettingsDaemonProxy<'static>,
|
|
||||||
config_id: &'static str,
|
|
||||||
is_state: bool,
|
|
||||||
) -> impl Stream<Item = Update<T>> {
|
|
||||||
enum Change {
|
|
||||||
Changes(Changed),
|
|
||||||
OwnerChanged(bool),
|
|
||||||
}
|
|
||||||
stream::channel(5, move |mut tx| async move {
|
|
||||||
let version = T::VERSION;
|
|
||||||
|
|
||||||
let Ok(cosmic_config) = (if is_state {
|
|
||||||
crate::Config::new_state(config_id, version)
|
|
||||||
} else {
|
|
||||||
crate::Config::new(config_id, version)
|
|
||||||
}) else {
|
|
||||||
pending::<()>().await;
|
|
||||||
unreachable!();
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut attempts = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let watcher = if is_state {
|
|
||||||
Watcher::new_state(&settings_daemon, config_id, version).await
|
|
||||||
} else {
|
|
||||||
Watcher::new_config(&settings_daemon, config_id, version).await
|
|
||||||
};
|
|
||||||
let Ok(watcher) = watcher else {
|
|
||||||
tracing::error!("Failed to create watcher for {config_id}");
|
|
||||||
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await;
|
|
||||||
#[cfg(feature = "async-std")]
|
|
||||||
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await;
|
|
||||||
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
|
|
||||||
{
|
|
||||||
pending::<()>().await;
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
attempts += 1;
|
|
||||||
// The settings daemon has exited
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Ok(changes) = watcher.receive_changed().await else {
|
|
||||||
tracing::error!("Failed to listen for changes for {config_id}");
|
|
||||||
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await;
|
|
||||||
#[cfg(feature = "async-std")]
|
|
||||||
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await;
|
|
||||||
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
|
|
||||||
{
|
|
||||||
pending::<()>().await;
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
attempts += 1;
|
|
||||||
// The settings daemon has exited
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut changes = changes.map(Change::Changes).fuse();
|
|
||||||
|
|
||||||
let Ok(owner_changed) = watcher.inner().receive_owner_changed().await else {
|
|
||||||
tracing::error!("Failed to listen for owner changes for {config_id}");
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await;
|
|
||||||
#[cfg(feature = "async-std")]
|
|
||||||
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await;
|
|
||||||
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
|
|
||||||
{
|
|
||||||
pending::<()>().await;
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
attempts += 1;
|
|
||||||
// The settings daemon has exited
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let mut owner_changed = owner_changed
|
|
||||||
.map(|c| Change::OwnerChanged(c.is_some()))
|
|
||||||
.fuse();
|
|
||||||
|
|
||||||
// update now, just in case we missed changes while setting up stream
|
|
||||||
let mut config = match T::get_entry(&cosmic_config) {
|
|
||||||
Ok(config) => config,
|
|
||||||
Err((errors, default)) => {
|
|
||||||
if !errors.is_empty() {
|
|
||||||
eprintln!("Error getting config: {config_id} {errors:?}");
|
|
||||||
}
|
|
||||||
default
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(err) = tx
|
|
||||||
.send(Update {
|
|
||||||
errors: Vec::new(),
|
|
||||||
keys: Vec::new(),
|
|
||||||
config: config.clone(),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
eprintln!("Failed to send config: {err}");
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let change: Changed = futures::select! {
|
|
||||||
c = changes.next() => {
|
|
||||||
let Some(Change::Changes(c)) = c else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
c
|
|
||||||
}
|
|
||||||
c = owner_changed.next() => {
|
|
||||||
let Some(Change::OwnerChanged(cont)) = c else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
if cont {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// The settings daemon has exited
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reset the attempts counter if we received a change
|
|
||||||
attempts = 0;
|
|
||||||
let Ok(args) = change.args() else {
|
|
||||||
// The settings daemon has exited
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
let (errors, keys) = config.update_keys(&cosmic_config, &[args.key]);
|
|
||||||
if !keys.is_empty() {
|
|
||||||
if let Err(err) = tx
|
|
||||||
.send(Update {
|
|
||||||
errors,
|
|
||||||
keys,
|
|
||||||
config: config.clone(),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
eprintln!("Failed to send config update: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,59 @@
|
||||||
//! Integrations for cosmic-config — the cosmic configuration system.
|
//! Integrations for cosmic-config — the cosmic configuration system.
|
||||||
|
|
||||||
use notify::{
|
use notify::{
|
||||||
event::{EventKind, ModifyKind},
|
RecommendedWatcher, Watcher,
|
||||||
Watcher,
|
event::{EventKind, ModifyKind, RenameMode},
|
||||||
};
|
};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
use std::{
|
use std::{
|
||||||
fmt, fs,
|
env, fmt, fs,
|
||||||
io::Write,
|
io::Write,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Get the config directory, with Flatpak sandbox support.
|
||||||
|
/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory,
|
||||||
|
/// allowing sandboxed apps to read host config files.
|
||||||
|
fn get_config_dir() -> Option<PathBuf> {
|
||||||
|
// Check if we're running in Flatpak
|
||||||
|
if let Some(flatpak_id) = env::var_os("FLATPAK_ID") {
|
||||||
|
tracing::debug!("Running in Flatpak: {:?}", flatpak_id);
|
||||||
|
// Try HOST_XDG_CONFIG_HOME first (requires --filesystem=xdg-config permission)
|
||||||
|
if let Some(host_config) = env::var_os("HOST_XDG_CONFIG_HOME") {
|
||||||
|
tracing::debug!("Using HOST_XDG_CONFIG_HOME: {:?}", host_config);
|
||||||
|
return Some(PathBuf::from(host_config));
|
||||||
|
}
|
||||||
|
// Fallback: try to construct from HOME (which points to real home in Flatpak)
|
||||||
|
if let Some(home) = env::var_os("HOME") {
|
||||||
|
let config_path = PathBuf::from(&home).join(".config");
|
||||||
|
tracing::debug!("Using HOME fallback for config: {:?}", config_path);
|
||||||
|
return Some(config_path);
|
||||||
|
}
|
||||||
|
tracing::warn!("Flatpak detected but no config directory found");
|
||||||
|
}
|
||||||
|
// Not in Flatpak or no host config available, use standard dirs
|
||||||
|
let config_dir = dirs::config_dir();
|
||||||
|
tracing::debug!("Using standard config dir: {:?}", config_dir);
|
||||||
|
config_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the state directory, with Flatpak sandbox support.
|
||||||
|
fn get_state_dir() -> Option<PathBuf> {
|
||||||
|
// Check if we're running in Flatpak
|
||||||
|
if env::var_os("FLATPAK_ID").is_some() {
|
||||||
|
// Try HOST_XDG_STATE_HOME first
|
||||||
|
if let Some(host_state) = env::var_os("HOST_XDG_STATE_HOME") {
|
||||||
|
return Some(PathBuf::from(host_state));
|
||||||
|
}
|
||||||
|
// Fallback: try to construct from HOME
|
||||||
|
if let Some(home) = env::var_os("HOME") {
|
||||||
|
return Some(PathBuf::from(home).join(".local").join("state"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dirs::state_dir()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "subscription")]
|
#[cfg(feature = "subscription")]
|
||||||
mod subscription;
|
mod subscription;
|
||||||
#[cfg(feature = "subscription")]
|
#[cfg(feature = "subscription")]
|
||||||
|
|
@ -140,9 +182,7 @@ impl Config {
|
||||||
pub fn system(name: &str, version: u64) -> Result<Self, Error> {
|
pub fn system(name: &str, version: u64) -> 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")
|
let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(path);
|
||||||
.map_err(std::io::Error::from)?
|
|
||||||
.find_data_file(path);
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let system_path =
|
let system_path =
|
||||||
|
|
@ -164,9 +204,7 @@ impl Config {
|
||||||
|
|
||||||
// Search data file, which provides default (e.g. /usr/share)
|
// Search data file, which provides default (e.g. /usr/share)
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let system_path = xdg::BaseDirectories::with_prefix("cosmic")
|
let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(&path);
|
||||||
.map_err(std::io::Error::from)?
|
|
||||||
.find_data_file(&path);
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let system_path =
|
let system_path =
|
||||||
|
|
@ -174,11 +212,10 @@ impl Config {
|
||||||
.map(|x| x.join("COSMIC").join(&path));
|
.map(|x| x.join("COSMIC").join(&path));
|
||||||
|
|
||||||
// Get libcosmic user configuration directory
|
// Get libcosmic user configuration directory
|
||||||
let cosmic_user_path = dirs::config_dir()
|
let mut user_path = get_config_dir().ok_or(Error::NoConfigDirectory)?;
|
||||||
.ok_or(Error::NoConfigDirectory)?
|
user_path.push("cosmic");
|
||||||
.join("cosmic");
|
user_path.push(path);
|
||||||
|
|
||||||
let user_path = cosmic_user_path.join(path);
|
|
||||||
// Create new configuration directory if not found.
|
// Create new configuration directory if not found.
|
||||||
fs::create_dir_all(&user_path)?;
|
fs::create_dir_all(&user_path)?;
|
||||||
|
|
||||||
|
|
@ -194,9 +231,9 @@ impl Config {
|
||||||
// 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 cosmic_user_path = custom_path.join("cosmic");
|
let mut user_path = custom_path;
|
||||||
|
user_path.push("cosmic");
|
||||||
let user_path = cosmic_user_path.join(path);
|
user_path.push(path);
|
||||||
// Create new configuration directory if not found.
|
// Create new configuration directory if not found.
|
||||||
fs::create_dir_all(&user_path)?;
|
fs::create_dir_all(&user_path)?;
|
||||||
|
|
||||||
|
|
@ -217,11 +254,9 @@ impl Config {
|
||||||
let path = sanitize_name(name)?.join(format!("v{}", version));
|
let path = sanitize_name(name)?.join(format!("v{}", version));
|
||||||
|
|
||||||
// Get libcosmic user state directory
|
// Get libcosmic user state directory
|
||||||
let cosmic_user_path = dirs::state_dir()
|
let mut user_path = get_state_dir().ok_or(Error::NoConfigDirectory)?;
|
||||||
.ok_or(Error::NoConfigDirectory)?
|
user_path.push("cosmic");
|
||||||
.join("cosmic");
|
user_path.push(path);
|
||||||
|
|
||||||
let user_path = cosmic_user_path.join(path);
|
|
||||||
// Create new state directory if not found.
|
// Create new state directory if not found.
|
||||||
fs::create_dir_all(&user_path)?;
|
fs::create_dir_all(&user_path)?;
|
||||||
|
|
||||||
|
|
@ -233,7 +268,7 @@ impl Config {
|
||||||
|
|
||||||
// Start a transaction (to set multiple configs at the same time)
|
// Start a transaction (to set multiple configs at the same time)
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn transaction(&self) -> ConfigTransaction {
|
pub fn transaction(&self) -> ConfigTransaction<'_> {
|
||||||
ConfigTransaction {
|
ConfigTransaction {
|
||||||
config: self,
|
config: self,
|
||||||
updates: Mutex::new(Vec::new()),
|
updates: Mutex::new(Vec::new()),
|
||||||
|
|
@ -244,7 +279,7 @@ impl Config {
|
||||||
// This may end up being an mpsc channel instead of a function
|
// This may end up being an mpsc channel instead of a function
|
||||||
// See EventHandler in the notify crate: https://docs.rs/notify/latest/notify/trait.EventHandler.html
|
// See EventHandler in the notify crate: https://docs.rs/notify/latest/notify/trait.EventHandler.html
|
||||||
// Having a callback allows for any application abstraction to be used
|
// Having a callback allows for any application abstraction to be used
|
||||||
pub fn watch<F>(&self, f: F) -> Result<notify::RecommendedWatcher, Error>
|
pub fn watch<F>(&self, f: F) -> Result<RecommendedWatcher, Error>
|
||||||
// Argument is an array of all keys that changed in that specific transaction
|
// Argument is an array of all keys that changed in that specific transaction
|
||||||
//TODO: simplify F requirements
|
//TODO: simplify F requirements
|
||||||
where
|
where
|
||||||
|
|
@ -257,10 +292,12 @@ impl Config {
|
||||||
let user_path_clone = user_path.clone();
|
let user_path_clone = user_path.clone();
|
||||||
let mut watcher =
|
let mut watcher =
|
||||||
notify::recommended_watcher(move |event_res: Result<notify::Event, notify::Error>| {
|
notify::recommended_watcher(move |event_res: Result<notify::Event, notify::Error>| {
|
||||||
match &event_res {
|
match event_res {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
match &event.kind {
|
match &event.kind {
|
||||||
EventKind::Access(_) | EventKind::Modify(ModifyKind::Metadata(_)) => {
|
EventKind::Access(_)
|
||||||
|
| EventKind::Modify(ModifyKind::Metadata(_))
|
||||||
|
| EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
|
||||||
// Data not mutated
|
// Data not mutated
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +330,7 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
watcher.watch(user_path, notify::RecursiveMode::NonRecursive)?;
|
watcher.watch(user_path, notify::RecursiveMode::Recursive)?;
|
||||||
Ok(watcher)
|
Ok(watcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,51 +18,66 @@ pub enum ConfigUpdate<T> {
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn config_subscription<
|
pub fn config_subscription<
|
||||||
I: 'static + Copy + Send + Sync + Hash,
|
I: 'static + Hash,
|
||||||
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
||||||
>(
|
>(
|
||||||
id: I,
|
id: I,
|
||||||
config_id: Cow<'static, str>,
|
config_id: Cow<'static, str>,
|
||||||
config_version: u64,
|
config_version: u64,
|
||||||
) -> iced_futures::Subscription<crate::Update<T>> {
|
) -> iced_futures::Subscription<crate::Update<T>> {
|
||||||
iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, false))
|
iced_futures::Subscription::run_with(
|
||||||
|
(id, config_id, config_version, false),
|
||||||
|
// FIXME there are type issues related to the 'static lifetime of the Cow if this is extracted to a named function...
|
||||||
|
|(_, config_id, config_version, is_state)| {
|
||||||
|
let config_id = config_id.clone();
|
||||||
|
let config_version = *config_version;
|
||||||
|
let is_state = *is_state;
|
||||||
|
|
||||||
|
stream::channel(100, move |mut output| async move {
|
||||||
|
let config_id = config_id.clone();
|
||||||
|
let mut state = ConfigState::Init(config_id, config_version, is_state);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
state = start_listening::<T>(state, &mut output).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn config_state_subscription<
|
pub fn config_state_subscription<
|
||||||
I: 'static + Copy + Send + Sync + Hash,
|
I: 'static + Hash,
|
||||||
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
||||||
>(
|
>(
|
||||||
id: I,
|
id: I,
|
||||||
config_id: Cow<'static, str>,
|
config_id: Cow<'static, str>,
|
||||||
config_version: u64,
|
config_version: u64,
|
||||||
) -> iced_futures::Subscription<crate::Update<T>> {
|
) -> iced_futures::Subscription<crate::Update<T>> {
|
||||||
iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, true))
|
iced_futures::Subscription::run_with(
|
||||||
}
|
(id, config_id, config_version, true),
|
||||||
|
|(_, config_id, config_version, is_state)| {
|
||||||
fn watcher_stream<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
|
|
||||||
config_id: Cow<'static, str>,
|
|
||||||
config_version: u64,
|
|
||||||
is_state: bool,
|
|
||||||
) -> impl Stream<Item = crate::Update<T>> {
|
|
||||||
stream::channel(100, move |mut output| {
|
|
||||||
let config_id = config_id.clone();
|
|
||||||
async move {
|
|
||||||
let config_id = config_id.clone();
|
let config_id = config_id.clone();
|
||||||
let mut state = ConfigState::Init(config_id, config_version, is_state);
|
let config_version = *config_version;
|
||||||
|
let is_state = *is_state;
|
||||||
|
|
||||||
loop {
|
stream::channel(100, move |mut output| async move {
|
||||||
state = start_listening::<T>(state, &mut output).await;
|
let config_id = config_id.clone();
|
||||||
}
|
let mut state = ConfigState::Init(config_id, config_version, is_state);
|
||||||
}
|
|
||||||
})
|
loop {
|
||||||
|
state = start_listening::<T>(state, &mut output).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
|
async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
|
||||||
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::{future::pending, StreamExt};
|
use iced_futures::futures::{StreamExt, future::pending};
|
||||||
|
|
||||||
match state {
|
match state {
|
||||||
ConfigState::Init(config_id, version, is_state) => {
|
ConfigState::Init(config_id, version, is_state) => {
|
||||||
|
|
@ -93,7 +108,7 @@ async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicCo
|
||||||
}
|
}
|
||||||
Err((errors, t)) => {
|
Err((errors, t)) => {
|
||||||
let update = crate::Update {
|
let update = crate::Update {
|
||||||
errors: errors,
|
errors,
|
||||||
keys: Vec::new(),
|
keys: Vec::new(),
|
||||||
config: t.clone(),
|
config: t.clone(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
1
cosmic-icons
Submodule
1
cosmic-icons
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5252095787cc96e2aed64604158f94e450703455
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-theme"
|
name = "cosmic-theme"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
|
|
@ -17,16 +17,23 @@ no-default = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
palette = { version = "0.7.6", features = ["serializing"] }
|
palette = { version = "0.7.6", features = ["serializing"] }
|
||||||
almost = "0.2"
|
almost = "0.2"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.140", optional = true, features = [
|
serde_json = { version = "1.0.149", optional = true, features = [
|
||||||
"preserve_order",
|
"preserve_order",
|
||||||
] }
|
] }
|
||||||
ron = "0.9.0"
|
ron = "0.12.0"
|
||||||
lazy_static = "1.5.0"
|
csscolorparser = { version = "0.8.3", features = ["serde"] }
|
||||||
csscolorparser = { version = "0.7.0", features = ["serde"] }
|
|
||||||
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
|
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
|
||||||
"subscription",
|
"subscription",
|
||||||
"macro",
|
"macro",
|
||||||
] }
|
] }
|
||||||
|
configparser = "3.1.0"
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.18"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
insta = "1.47.2"
|
||||||
|
|
||||||
|
[profile.dev.package]
|
||||||
|
insta.opt-level = 3
|
||||||
|
similar.opt-level = 3
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,10 @@ use palette::Srgba;
|
||||||
pub fn over<A: Into<Srgba>, B: Into<Srgba>>(a: A, b: B) -> Srgba {
|
pub fn over<A: Into<Srgba>, B: Into<Srgba>>(a: A, b: B) -> Srgba {
|
||||||
let a = a.into();
|
let a = a.into();
|
||||||
let b = b.into();
|
let b = b.into();
|
||||||
let o_a = (alpha_over(a.alpha, b.alpha)).max(0.0).min(1.0);
|
let o_a = (alpha_over(a.alpha, b.alpha)).clamp(0.0, 1.0);
|
||||||
let o_r = (c_over(a.red, b.red, a.alpha, b.alpha, o_a))
|
let o_r = (c_over(a.red, b.red, a.alpha, b.alpha, o_a)).clamp(0.0, 1.0);
|
||||||
.max(0.0)
|
let o_g = (c_over(a.green, b.green, a.alpha, b.alpha, o_a)).clamp(0.0, 1.0);
|
||||||
.min(1.0);
|
let o_b = (c_over(a.blue, b.blue, a.alpha, b.alpha, o_a)).clamp(0.0, 1.0);
|
||||||
let o_g = (c_over(a.green, b.green, a.alpha, b.alpha, o_a))
|
|
||||||
.max(0.0)
|
|
||||||
.min(1.0);
|
|
||||||
let o_b = (c_over(a.blue, b.blue, a.alpha, b.alpha, o_a))
|
|
||||||
.max(0.0)
|
|
||||||
.min(1.0);
|
|
||||||
|
|
||||||
Srgba::new(o_r, o_g, o_b, o_a)
|
Srgba::new(o_r, o_g, o_b, o_a)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use palette::Srgba;
|
use palette::Srgba;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
lazy_static! {
|
/// built-in light palette
|
||||||
/// built in light palette
|
pub static LIGHT_PALETTE: LazyLock<CosmicPalette> =
|
||||||
pub static ref LIGHT_PALETTE: CosmicPalette =
|
LazyLock::new(|| ron::from_str(include_str!("light.ron")).unwrap());
|
||||||
ron::from_str(include_str!("light.ron")).unwrap();
|
|
||||||
/// built in dark palette
|
/// built-in dark palette
|
||||||
pub static ref DARK_PALETTE: CosmicPalette =
|
pub static DARK_PALETTE: LazyLock<CosmicPalette> =
|
||||||
ron::from_str(include_str!("dark.ron")).unwrap();
|
LazyLock::new(|| ron::from_str(include_str!("dark.ron")).unwrap());
|
||||||
}
|
|
||||||
|
|
||||||
/// Palette type
|
/// Palette type
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use palette::Srgba;
|
use palette::{Srgba, WithAlpha};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::composite::over;
|
use crate::composite::over;
|
||||||
|
|
@ -27,9 +27,7 @@ impl Container {
|
||||||
mut small_widget: Srgba,
|
mut small_widget: Srgba,
|
||||||
is_high_contrast: bool,
|
is_high_contrast: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut divider_c = on;
|
let divider_c = on.with_alpha(if is_high_contrast { 0.5 } else { 0.2 });
|
||||||
divider_c.alpha = if is_high_contrast { 0.5 } else { 0.2 };
|
|
||||||
|
|
||||||
small_widget.alpha = 0.25;
|
small_widget.alpha = 0.25;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -115,13 +113,11 @@ impl Component {
|
||||||
hovered: Srgba,
|
hovered: Srgba,
|
||||||
pressed: Srgba,
|
pressed: Srgba,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let base: Srgba = base;
|
|
||||||
let mut base_50 = base;
|
let mut base_50 = base;
|
||||||
base_50.alpha *= 0.5;
|
base_50.alpha *= 0.5;
|
||||||
|
|
||||||
let on_20 = neutral;
|
let on_20 = neutral;
|
||||||
let mut on_50: Srgba = on_20;
|
let on_50 = on_20.with_alpha(0.5);
|
||||||
on_50.alpha = 0.5;
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
base,
|
base,
|
||||||
|
|
@ -151,8 +147,7 @@ impl Component {
|
||||||
let mut component = Component::colored_component(base, overlay, accent, hovered, pressed);
|
let mut component = Component::colored_component(base, overlay, accent, hovered, pressed);
|
||||||
component.on = on_button;
|
component.on = on_button;
|
||||||
|
|
||||||
let mut on_disabled = on_button;
|
let on_disabled = on_button.with_alpha(0.5);
|
||||||
on_disabled.alpha = 0.5;
|
|
||||||
component.on_disabled = on_disabled;
|
component.on_disabled = on_disabled;
|
||||||
|
|
||||||
component
|
component
|
||||||
|
|
@ -172,11 +167,8 @@ impl Component {
|
||||||
let mut base_50 = base;
|
let mut base_50 = base;
|
||||||
base_50.alpha *= 0.5;
|
base_50.alpha *= 0.5;
|
||||||
|
|
||||||
let mut on_20 = on_component;
|
let on_20 = on_component.with_alpha(0.2);
|
||||||
let mut on_50 = on_20;
|
let on_65 = on_20.with_alpha(0.65);
|
||||||
|
|
||||||
on_20.alpha = 0.2;
|
|
||||||
on_50.alpha = 0.5;
|
|
||||||
|
|
||||||
let mut disabled_border = border;
|
let mut disabled_border = border;
|
||||||
disabled_border.alpha *= 0.5;
|
disabled_border.alpha *= 0.5;
|
||||||
|
|
@ -200,10 +192,10 @@ impl Component {
|
||||||
},
|
},
|
||||||
selected_text: accent,
|
selected_text: accent,
|
||||||
focus: accent,
|
focus: accent,
|
||||||
divider: if is_high_contrast { on_50 } else { on_20 },
|
divider: if is_high_contrast { on_65 } else { on_20 },
|
||||||
on: on_component,
|
on: on_component,
|
||||||
disabled: over(base_50, base),
|
disabled: base_50,
|
||||||
on_disabled: over(on_50, base),
|
on_disabled: on_65,
|
||||||
border,
|
border,
|
||||||
disabled_border,
|
disabled_border,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE,
|
||||||
|
LIGHT_PALETTE, NAME, Spacing, ThemeMode,
|
||||||
composite::over,
|
composite::over,
|
||||||
steps::{color_index, get_index, get_small_widget_color, get_surface_color, get_text, steps},
|
steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps},
|
||||||
Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing, ThemeMode,
|
|
||||||
DARK_PALETTE, LIGHT_PALETTE, NAME,
|
|
||||||
};
|
};
|
||||||
use cosmic_config::{Config, CosmicConfigEntry};
|
use cosmic_config::{Config, CosmicConfigEntry};
|
||||||
use palette::{color_difference::Wcag21RelativeContrast, rgb::Rgb, IntoColor, Oklcha, Srgb, Srgba};
|
use palette::{
|
||||||
|
IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
|
@ -100,6 +102,10 @@ pub struct Theme {
|
||||||
/// 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.
|
||||||
pub accent_text: Option<Srgba>,
|
pub accent_text: Option<Srgba>,
|
||||||
|
/// control tint color
|
||||||
|
pub control_tint: Option<Srgb>,
|
||||||
|
/// text tint color
|
||||||
|
pub text_tint: Option<Srgb>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Theme {
|
impl Default for Theme {
|
||||||
|
|
@ -164,6 +170,109 @@ impl Theme {
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_0 color
|
||||||
|
pub fn control_0(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_1 color
|
||||||
|
pub fn control_1(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_2 color
|
||||||
|
pub fn control_2(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_2)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_3 color
|
||||||
|
pub fn control_3(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_3)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_3 color
|
||||||
|
pub fn control_4(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_4)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_3 color
|
||||||
|
pub fn control_5(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_5)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_3 color
|
||||||
|
pub fn control_6(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_6)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_3 color
|
||||||
|
pub fn control_7(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_7)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_3 color
|
||||||
|
pub fn control_8(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_3 color
|
||||||
|
pub fn control_9(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_9)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get control_3 color
|
||||||
|
pub fn control_10(&self) -> Srgba {
|
||||||
|
self.tint_neutral(self.palette.neutral_10)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[inline]
|
||||||
|
/// get @accent_color
|
||||||
|
fn tint_neutral(&self, neutral: Srgba) -> Srgba {
|
||||||
|
let Some(tint) = self.control_tint else {
|
||||||
|
return neutral;
|
||||||
|
};
|
||||||
|
let mut oklch_neutral: Oklcha = neutral.into_color();
|
||||||
|
let oklch_tint: Oklcha = tint.into_color();
|
||||||
|
oklch_neutral.hue = oklch_tint.hue;
|
||||||
|
oklch_neutral.chroma = oklch_tint.chroma;
|
||||||
|
oklch_neutral.into_color()
|
||||||
|
}
|
||||||
|
|
||||||
// TODO convenient getter functions for each named color variable
|
// TODO convenient getter functions for each named color variable
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[allow(clippy::doc_markdown)]
|
#[allow(clippy::doc_markdown)]
|
||||||
|
|
@ -202,9 +311,7 @@ impl Theme {
|
||||||
#[inline]
|
#[inline]
|
||||||
/// get @small_widget_divider
|
/// get @small_widget_divider
|
||||||
pub fn small_widget_divider(&self) -> Srgba {
|
pub fn small_widget_divider(&self) -> Srgba {
|
||||||
let mut neutral_9 = self.palette.neutral_9;
|
self.palette.neutral_9.with_alpha(0.2)
|
||||||
neutral_9.alpha = 0.2;
|
|
||||||
neutral_9
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Containers
|
// Containers
|
||||||
|
|
@ -578,18 +685,17 @@ impl Theme {
|
||||||
self.shade
|
self.shade
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the active theme
|
/// Get the active theme based on the current theme mode.
|
||||||
pub fn get_active() -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
|
pub fn get_active() -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
|
||||||
let config =
|
(|| {
|
||||||
Config::new(Self::id(), Self::VERSION).map_err(|e| (vec![e], Self::default()))?;
|
(if ThemeMode::is_dark(&Config::new(Self::id(), Self::VERSION)?)? {
|
||||||
let is_dark = ThemeMode::is_dark(&config).map_err(|e| (vec![e], Self::default()))?;
|
Self::dark_config
|
||||||
let config = if is_dark {
|
} else {
|
||||||
Self::dark_config()
|
Self::light_config
|
||||||
} else {
|
})()
|
||||||
Self::light_config()
|
})()
|
||||||
}
|
.map_err(|error| (vec![error], Self::default()))
|
||||||
.map_err(|e| (vec![e], Self::default()))?;
|
.and_then(|theme_config| Self::get_entry(&theme_config))
|
||||||
Self::get_entry(&config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -707,7 +813,7 @@ pub struct ThemeBuilder {
|
||||||
impl Default for ThemeBuilder {
|
impl Default for ThemeBuilder {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
palette: DARK_PALETTE.to_owned().into(),
|
palette: DARK_PALETTE.to_owned(),
|
||||||
spacing: Spacing::default(),
|
spacing: Spacing::default(),
|
||||||
corner_radii: CornerRadii::default(),
|
corner_radii: CornerRadii::default(),
|
||||||
neutral_tint: Default::default(),
|
neutral_tint: Default::default(),
|
||||||
|
|
@ -850,7 +956,7 @@ impl ThemeBuilder {
|
||||||
/// build the theme
|
/// build the theme
|
||||||
pub fn build(self) -> Theme {
|
pub fn build(self) -> Theme {
|
||||||
let Self {
|
let Self {
|
||||||
mut palette,
|
palette,
|
||||||
spacing,
|
spacing,
|
||||||
corner_radii,
|
corner_radii,
|
||||||
neutral_tint,
|
neutral_tint,
|
||||||
|
|
@ -880,41 +986,30 @@ impl ThemeBuilder {
|
||||||
let success = if let Some(success) = success {
|
let success = if let Some(success) = success {
|
||||||
success.into_color()
|
success.into_color()
|
||||||
} else {
|
} else {
|
||||||
palette.as_ref().accent_green
|
palette.as_ref().bright_green
|
||||||
};
|
};
|
||||||
|
|
||||||
let warning = if let Some(warning) = warning {
|
let warning = if let Some(warning) = warning {
|
||||||
warning.into_color()
|
warning.into_color()
|
||||||
} else {
|
} else {
|
||||||
palette.as_ref().accent_yellow
|
palette.as_ref().bright_orange
|
||||||
};
|
};
|
||||||
|
|
||||||
let destructive = if let Some(destructive) = destructive {
|
let destructive = if let Some(destructive) = destructive {
|
||||||
destructive.into_color()
|
destructive.into_color()
|
||||||
} else {
|
} else {
|
||||||
palette.as_ref().accent_red
|
palette.as_ref().bright_red
|
||||||
};
|
};
|
||||||
|
|
||||||
let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap()));
|
let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap()));
|
||||||
|
|
||||||
if let Some(neutral_tint) = neutral_tint {
|
let mut control_steps_array = if let Some(neutral_tint) = neutral_tint {
|
||||||
let mut neutral_steps_arr = steps(neutral_tint, NonZeroUsize::new(11).unwrap());
|
steps(neutral_tint, NonZeroUsize::new(11).unwrap())
|
||||||
if !is_dark {
|
} else {
|
||||||
neutral_steps_arr.reverse();
|
steps(palette.as_ref().neutral_2, NonZeroUsize::new(11).unwrap())
|
||||||
}
|
};
|
||||||
|
if !is_dark {
|
||||||
let p = palette.as_mut();
|
control_steps_array.reverse();
|
||||||
p.neutral_0 = neutral_steps_arr[0];
|
|
||||||
p.neutral_1 = neutral_steps_arr[1];
|
|
||||||
p.neutral_2 = neutral_steps_arr[2];
|
|
||||||
p.neutral_3 = neutral_steps_arr[3];
|
|
||||||
p.neutral_4 = neutral_steps_arr[4];
|
|
||||||
p.neutral_5 = neutral_steps_arr[5];
|
|
||||||
p.neutral_6 = neutral_steps_arr[6];
|
|
||||||
p.neutral_7 = neutral_steps_arr[7];
|
|
||||||
p.neutral_8 = neutral_steps_arr[8];
|
|
||||||
p.neutral_9 = neutral_steps_arr[9];
|
|
||||||
p.neutral_10 = neutral_steps_arr[10];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let p_ref = palette.as_ref();
|
let p_ref = palette.as_ref();
|
||||||
|
|
@ -934,9 +1029,9 @@ impl ThemeBuilder {
|
||||||
let bg_index = color_index(bg, step_array.len());
|
let bg_index = color_index(bg, step_array.len());
|
||||||
|
|
||||||
let mut component_hovered_overlay = if bg_index < 91 {
|
let mut component_hovered_overlay = if bg_index < 91 {
|
||||||
p_ref.neutral_10
|
control_steps_array[10]
|
||||||
} else {
|
} else {
|
||||||
p_ref.neutral_0
|
control_steps_array[0]
|
||||||
};
|
};
|
||||||
component_hovered_overlay.alpha = 0.1;
|
component_hovered_overlay.alpha = 0.1;
|
||||||
|
|
||||||
|
|
@ -944,22 +1039,18 @@ impl ThemeBuilder {
|
||||||
component_pressed_overlay.alpha = 0.2;
|
component_pressed_overlay.alpha = 0.2;
|
||||||
|
|
||||||
// Standard button background is neutral 7 with 25% opacity
|
// Standard button background is neutral 7 with 25% opacity
|
||||||
let button_bg = {
|
let button_bg = control_steps_array[7].with_alpha(0.25);
|
||||||
let mut color = p_ref.neutral_7;
|
|
||||||
color.alpha = 0.25;
|
|
||||||
color
|
|
||||||
};
|
|
||||||
|
|
||||||
let (mut button_hovered_overlay, mut button_pressed_overlay) =
|
let (button_hovered_overlay, button_pressed_overlay) = (
|
||||||
(p_ref.neutral_5, p_ref.neutral_2);
|
control_steps_array[5].with_alpha(0.2),
|
||||||
button_hovered_overlay.alpha = 0.2;
|
control_steps_array[2].with_alpha(0.5),
|
||||||
button_pressed_overlay.alpha = 0.5;
|
);
|
||||||
|
|
||||||
let bg_component = get_surface_color(bg_index, 8, &step_array, is_dark, &p_ref.neutral_2);
|
let bg_component = get_surface_color(bg_index, 8, &step_array, is_dark, &p_ref.neutral_2);
|
||||||
let on_bg_component = get_text(
|
let on_bg_component = get_text(
|
||||||
color_index(bg_component, step_array.len()),
|
color_index(bg_component, step_array.len()),
|
||||||
&step_array,
|
&step_array,
|
||||||
&p_ref.neutral_8,
|
&control_steps_array[8],
|
||||||
text_steps_array.as_deref(),
|
text_steps_array.as_deref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -967,50 +1058,49 @@ impl ThemeBuilder {
|
||||||
let 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, &p_ref.neutral_1)
|
get_surface_color(bg_index, 5, &step_array, is_dark, &control_steps_array[1])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap());
|
||||||
let base_index: usize = color_index(container_bg, step_array.len());
|
let base_index: usize = color_index(container_bg, step_array.len());
|
||||||
let component_base =
|
let component_base =
|
||||||
get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.neutral_3);
|
get_surface_color(base_index, 6, &step_array, is_dark, &control_steps_array[3]);
|
||||||
|
|
||||||
component_hovered_overlay = if base_index < 91 {
|
component_hovered_overlay = if base_index < 91 {
|
||||||
p_ref.neutral_10
|
control_steps_array[10]
|
||||||
} else {
|
} else {
|
||||||
p_ref.neutral_0
|
control_steps_array[0]
|
||||||
};
|
};
|
||||||
component_hovered_overlay.alpha = 0.1;
|
component_hovered_overlay.alpha = 0.1;
|
||||||
|
|
||||||
component_pressed_overlay = component_hovered_overlay;
|
component_pressed_overlay = component_hovered_overlay;
|
||||||
component_pressed_overlay.alpha = 0.2;
|
component_pressed_overlay.alpha = 0.2;
|
||||||
|
|
||||||
let container = Container::new(
|
Container::new(
|
||||||
Component::component(
|
Component::component(
|
||||||
component_base,
|
component_base,
|
||||||
accent,
|
accent,
|
||||||
get_text(
|
get_text(
|
||||||
color_index(component_base, step_array.len()),
|
color_index(component_base, step_array.len()),
|
||||||
&step_array,
|
&step_array,
|
||||||
&p_ref.neutral_8,
|
&control_steps_array[8],
|
||||||
text_steps_array.as_deref(),
|
text_steps_array.as_deref(),
|
||||||
),
|
),
|
||||||
component_hovered_overlay,
|
component_hovered_overlay,
|
||||||
component_pressed_overlay,
|
component_pressed_overlay,
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
p_ref.neutral_8,
|
control_steps_array[8],
|
||||||
),
|
),
|
||||||
container_bg,
|
container_bg,
|
||||||
get_text(
|
get_text(
|
||||||
base_index,
|
base_index,
|
||||||
&step_array,
|
&step_array,
|
||||||
&p_ref.neutral_8,
|
&control_steps_array[8],
|
||||||
text_steps_array.as_deref(),
|
text_steps_array.as_deref(),
|
||||||
),
|
),
|
||||||
get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6),
|
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
);
|
)
|
||||||
|
|
||||||
container
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let accent_text = if is_dark {
|
let accent_text = if is_dark {
|
||||||
|
|
@ -1072,16 +1162,16 @@ impl ThemeBuilder {
|
||||||
component_hovered_overlay,
|
component_hovered_overlay,
|
||||||
component_pressed_overlay,
|
component_pressed_overlay,
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
p_ref.neutral_8,
|
control_steps_array[8],
|
||||||
),
|
),
|
||||||
bg,
|
bg,
|
||||||
get_text(
|
get_text(
|
||||||
bg_index,
|
bg_index,
|
||||||
&step_array,
|
&step_array,
|
||||||
&p_ref.neutral_8,
|
&control_steps_array[8],
|
||||||
text_steps_array.as_deref(),
|
text_steps_array.as_deref(),
|
||||||
),
|
),
|
||||||
get_small_widget_color(bg_index, 5, &neutral_steps, &p_ref.neutral_6),
|
get_small_widget_color(bg_index, 5, &neutral_steps, &control_steps_array[6]),
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
),
|
),
|
||||||
primary,
|
primary,
|
||||||
|
|
@ -1089,17 +1179,18 @@ impl ThemeBuilder {
|
||||||
let container_bg = if let Some(secondary_container_bg) = secondary_container_bg {
|
let container_bg = if let Some(secondary_container_bg) = secondary_container_bg {
|
||||||
secondary_container_bg
|
secondary_container_bg
|
||||||
} else {
|
} else {
|
||||||
get_surface_color(bg_index, 10, &step_array, is_dark, &p_ref.neutral_2)
|
get_surface_color(bg_index, 10, &step_array, is_dark, &control_steps_array[2])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let step_array = steps(container_bg, NonZeroUsize::new(100).unwrap());
|
||||||
let base_index = color_index(container_bg, step_array.len());
|
let base_index = color_index(container_bg, step_array.len());
|
||||||
let secondary_component =
|
let secondary_component =
|
||||||
get_surface_color(base_index, 3, &step_array, is_dark, &p_ref.neutral_4);
|
get_surface_color(base_index, 3, &step_array, is_dark, &control_steps_array[4]);
|
||||||
|
|
||||||
component_hovered_overlay = if base_index < 91 {
|
component_hovered_overlay = if base_index < 91 {
|
||||||
p_ref.neutral_10
|
control_steps_array[10]
|
||||||
} else {
|
} else {
|
||||||
p_ref.neutral_0
|
control_steps_array[0]
|
||||||
};
|
};
|
||||||
component_hovered_overlay.alpha = 0.1;
|
component_hovered_overlay.alpha = 0.1;
|
||||||
|
|
||||||
|
|
@ -1113,36 +1204,36 @@ impl ThemeBuilder {
|
||||||
get_text(
|
get_text(
|
||||||
color_index(secondary_component, step_array.len()),
|
color_index(secondary_component, step_array.len()),
|
||||||
&step_array,
|
&step_array,
|
||||||
&p_ref.neutral_8,
|
&control_steps_array[8],
|
||||||
text_steps_array.as_deref(),
|
text_steps_array.as_deref(),
|
||||||
),
|
),
|
||||||
component_hovered_overlay,
|
component_hovered_overlay,
|
||||||
component_pressed_overlay,
|
component_pressed_overlay,
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
p_ref.neutral_8,
|
control_steps_array[8],
|
||||||
),
|
),
|
||||||
container_bg,
|
container_bg,
|
||||||
get_text(
|
get_text(
|
||||||
base_index,
|
base_index,
|
||||||
&step_array,
|
&step_array,
|
||||||
&p_ref.neutral_8,
|
&control_steps_array[8],
|
||||||
text_steps_array.as_deref(),
|
text_steps_array.as_deref(),
|
||||||
),
|
),
|
||||||
get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6),
|
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
accent: Component::colored_component(
|
accent: Component::colored_component(
|
||||||
accent,
|
accent,
|
||||||
p_ref.neutral_0,
|
control_steps_array[0],
|
||||||
accent,
|
accent,
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
),
|
),
|
||||||
accent_button: Component::colored_button(
|
accent_button: Component::colored_button(
|
||||||
accent,
|
accent,
|
||||||
p_ref.neutral_1,
|
control_steps_array[1],
|
||||||
p_ref.neutral_0,
|
control_steps_array[0],
|
||||||
accent,
|
accent,
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
|
|
@ -1154,19 +1245,19 @@ impl ThemeBuilder {
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
p_ref.neutral_8,
|
control_steps_array[8],
|
||||||
),
|
),
|
||||||
destructive: Component::colored_component(
|
destructive: Component::colored_component(
|
||||||
destructive,
|
destructive,
|
||||||
p_ref.neutral_0,
|
control_steps_array[0],
|
||||||
accent,
|
accent,
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
),
|
),
|
||||||
destructive_button: Component::colored_button(
|
destructive_button: Component::colored_button(
|
||||||
destructive,
|
destructive,
|
||||||
p_ref.neutral_1,
|
control_steps_array[1],
|
||||||
p_ref.neutral_0,
|
control_steps_array[0],
|
||||||
accent,
|
accent,
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
|
|
@ -1174,40 +1265,37 @@ impl ThemeBuilder {
|
||||||
icon_button: Component::component(
|
icon_button: Component::component(
|
||||||
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
||||||
accent,
|
accent,
|
||||||
p_ref.neutral_8,
|
control_steps_array[8],
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
p_ref.neutral_8,
|
control_steps_array[8],
|
||||||
),
|
),
|
||||||
link_button: {
|
link_button: {
|
||||||
let mut component = Component::component(
|
let mut component = Component::component(
|
||||||
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
||||||
accent,
|
accent,
|
||||||
accent,
|
accent_text.unwrap_or(accent),
|
||||||
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
||||||
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
p_ref.neutral_8,
|
control_steps_array[8],
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut on_50 = component.on;
|
component.on_disabled = over(component.on.with_alpha(0.5), component.base);
|
||||||
on_50.alpha = 0.5;
|
|
||||||
|
|
||||||
component.on_disabled = over(on_50, component.base);
|
|
||||||
component
|
component
|
||||||
},
|
},
|
||||||
success: Component::colored_component(
|
success: Component::colored_component(
|
||||||
success,
|
success,
|
||||||
p_ref.neutral_0,
|
control_steps_array[0],
|
||||||
accent,
|
accent,
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
),
|
),
|
||||||
success_button: Component::colored_button(
|
success_button: Component::colored_button(
|
||||||
success,
|
success,
|
||||||
p_ref.neutral_1,
|
control_steps_array[1],
|
||||||
p_ref.neutral_0,
|
control_steps_array[0],
|
||||||
accent,
|
accent,
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
|
|
@ -1215,23 +1303,23 @@ impl ThemeBuilder {
|
||||||
text_button: Component::component(
|
text_button: Component::component(
|
||||||
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
Srgba::new(0.0, 0.0, 0.0, 0.0),
|
||||||
accent,
|
accent,
|
||||||
accent,
|
accent_text.unwrap_or(accent),
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
is_high_contrast,
|
is_high_contrast,
|
||||||
p_ref.neutral_8,
|
control_steps_array[8],
|
||||||
),
|
),
|
||||||
warning: Component::colored_component(
|
warning: Component::colored_component(
|
||||||
warning,
|
warning,
|
||||||
p_ref.neutral_0,
|
control_steps_array[0],
|
||||||
accent,
|
accent,
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
),
|
),
|
||||||
warning_button: Component::colored_button(
|
warning_button: Component::colored_button(
|
||||||
warning,
|
warning,
|
||||||
p_ref.neutral_10,
|
control_steps_array[10],
|
||||||
p_ref.neutral_0,
|
control_steps_array[0],
|
||||||
accent,
|
accent,
|
||||||
button_hovered_overlay,
|
button_hovered_overlay,
|
||||||
button_pressed_overlay,
|
button_pressed_overlay,
|
||||||
|
|
@ -1246,6 +1334,8 @@ impl ThemeBuilder {
|
||||||
window_hint,
|
window_hint,
|
||||||
is_frosted,
|
is_frosted,
|
||||||
accent_text,
|
accent_text,
|
||||||
|
control_tint: neutral_tint,
|
||||||
|
text_tint,
|
||||||
};
|
};
|
||||||
theme.spacing = spacing;
|
theme.spacing = spacing;
|
||||||
theme.corner_radii = corner_radii;
|
theme.corner_radii = corner_radii;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{composite::over, steps::steps, Component, Theme};
|
use crate::{Component, Theme, composite::over, steps::steps};
|
||||||
use palette::{rgb::Rgba, Darken, IntoColor, Lighten, Srgba};
|
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},
|
||||||
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{to_rgba, OutputError};
|
use super::{OutputError, to_rgba};
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -75,8 +75,7 @@ impl Theme {
|
||||||
Rgba::new(0.0, 0.0, 0.0, 0.08)
|
Rgba::new(0.0, 0.0, 0.0, 0.08)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut inverted_bg_divider = background.base;
|
let inverted_bg_divider = background.base.with_alpha(0.5);
|
||||||
inverted_bg_divider.alpha = 0.5;
|
|
||||||
let scrollbar_outline = to_rgba(inverted_bg_divider);
|
let scrollbar_outline = to_rgba(inverted_bg_divider);
|
||||||
|
|
||||||
let mut css = format! {r#"/* GENERATED BY COSMIC */
|
let mut css = format! {r#"/* GENERATED BY COSMIC */
|
||||||
|
|
@ -149,7 +148,7 @@ impl Theme {
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn write_gtk4(&self) -> Result<(), OutputError> {
|
pub fn write_gtk4(&self) -> Result<(), OutputError> {
|
||||||
let css_str = self.as_gtk4();
|
let css_str = self.as_gtk4();
|
||||||
let Some(config_dir) = dirs::config_dir() else {
|
let Some(mut config_dir) = dirs::config_dir() else {
|
||||||
return Err(OutputError::MissingConfigDir);
|
return Err(OutputError::MissingConfigDir);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -159,14 +158,24 @@ impl Theme {
|
||||||
"light.css"
|
"light.css"
|
||||||
};
|
};
|
||||||
|
|
||||||
let config_dir = config_dir.join("gtk-4.0").join("cosmic");
|
config_dir.extend(["gtk-4.0", "cosmic"]);
|
||||||
if !config_dir.exists() {
|
if !config_dir.exists() {
|
||||||
std::fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
|
std::fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file = File::create(config_dir.join(name)).map_err(OutputError::Io)?;
|
let file_path = config_dir.join(name);
|
||||||
file.write_all(css_str.as_bytes())
|
let tmp_file_path = config_dir.join(name.to_owned() + "~");
|
||||||
.map_err(OutputError::Io)?;
|
|
||||||
|
// 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(css_str.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -182,23 +191,20 @@ impl Theme {
|
||||||
return Err(OutputError::MissingConfigDir);
|
return Err(OutputError::MissingConfigDir);
|
||||||
};
|
};
|
||||||
|
|
||||||
let gtk4 = config_dir.join("gtk-4.0");
|
let mut gtk4 = config_dir.join("gtk-4.0");
|
||||||
let gtk3 = config_dir.join("gtk-3.0");
|
let mut gtk3 = config_dir.join("gtk-3.0");
|
||||||
|
|
||||||
fs::create_dir_all(>k4).map_err(OutputError::Io)?;
|
fs::create_dir_all(>k4).map_err(OutputError::Io)?;
|
||||||
fs::create_dir_all(>k3).map_err(OutputError::Io)?;
|
fs::create_dir_all(>k3).map_err(OutputError::Io)?;
|
||||||
|
|
||||||
let cosmic_css_dir = gtk4.join("cosmic");
|
let cosmic_css_dir = gtk4.join("cosmic");
|
||||||
let cosmic_css =
|
let cosmic_css = cosmic_css_dir.join(if is_dark { "dark.css" } else { "light.css" });
|
||||||
cosmic_css_dir
|
|
||||||
.clone()
|
|
||||||
.join(if is_dark { "dark.css" } else { "light.css" });
|
|
||||||
|
|
||||||
let gtk4_dest = gtk4.join("gtk.css");
|
gtk4.push("gtk.css");
|
||||||
let gtk3_dest = gtk3.join("gtk.css");
|
gtk3.push("gtk.css");
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
for gtk_dest in [>k4_dest, >k3_dest] {
|
for gtk_dest in [>k4, >k3] {
|
||||||
use std::os::unix::fs::symlink;
|
use std::os::unix::fs::symlink;
|
||||||
Self::backup_non_cosmic_css(gtk_dest, &cosmic_css_dir).map_err(OutputError::Io)?;
|
Self::backup_non_cosmic_css(gtk_dest, &cosmic_css_dir).map_err(OutputError::Io)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use palette::{rgb::Rgba, Srgba};
|
use configparser::ini::WriteOptions;
|
||||||
|
use palette::{Srgba, rgb::Rgba};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
|
|
@ -6,6 +7,11 @@ use crate::Theme;
|
||||||
/// Module for outputting the Cosmic gtk4 theme type as CSS
|
/// Module for outputting the Cosmic gtk4 theme type as CSS
|
||||||
pub mod gtk4_output;
|
pub mod gtk4_output;
|
||||||
|
|
||||||
|
/// Module for configuring qt5ct and qt6ct to use our qt theme
|
||||||
|
pub mod qt56ct_output;
|
||||||
|
/// Module for outputting the Cosmic qt theme type as kdeglobals
|
||||||
|
pub mod qt_output;
|
||||||
|
|
||||||
pub mod vs_code;
|
pub mod vs_code;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
@ -14,33 +20,48 @@ pub enum OutputError {
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
#[error("Missing config directory")]
|
#[error("Missing config directory")]
|
||||||
MissingConfigDir,
|
MissingConfigDir,
|
||||||
|
#[error("Missing data directory")]
|
||||||
|
MissingDataDir,
|
||||||
#[error("Serde Error: {0}")]
|
#[error("Serde Error: {0}")]
|
||||||
Serde(#[from] serde_json::Error),
|
Serde(#[from] serde_json::Error),
|
||||||
|
#[error("Ini Error: {0}")]
|
||||||
|
Ini(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
/// 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 vs_res = self.clone().apply_vs_code();
|
let qt_res = Theme::apply_qt(self.is_dark);
|
||||||
|
let qt56ct_res = Theme::apply_qt56ct(self.is_dark);
|
||||||
gtk_res?;
|
gtk_res?;
|
||||||
vs_res?;
|
qt_res?;
|
||||||
|
qt56ct_res?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
/// 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();
|
||||||
gtk_res?;
|
gtk_res?;
|
||||||
|
qt_res?;
|
||||||
|
qt56ct_res?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
/// Un-export GTK and Qt theme configurations applied by us.
|
||||||
pub fn reset_exports() -> Result<(), OutputError> {
|
pub fn reset_exports() -> Result<(), OutputError> {
|
||||||
let gtk_res = Theme::reset_gtk();
|
let gtk_res = Theme::reset_gtk();
|
||||||
let vs_res = Theme::reset_vs_code();
|
let qt_res = Theme::reset_qt();
|
||||||
|
let qt56ct_res = Theme::reset_qt56ct();
|
||||||
gtk_res?;
|
gtk_res?;
|
||||||
vs_res?;
|
qt_res?;
|
||||||
|
qt56ct_res?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,3 +81,9 @@ pub fn to_rgba(c: Srgba) -> String {
|
||||||
c_u8.red, c_u8.green, c_u8.blue, c.alpha
|
c_u8.red, c_u8.green, c_u8.blue, c.alpha
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn qt_settings_ini_style() -> WriteOptions {
|
||||||
|
let mut write_options = WriteOptions::default();
|
||||||
|
write_options.blank_lines_between_sections = 1;
|
||||||
|
write_options
|
||||||
|
}
|
||||||
|
|
|
||||||
415
cosmic-theme/src/output/qt56ct_output.rs
Normal file
415
cosmic-theme/src/output/qt56ct_output.rs
Normal file
|
|
@ -0,0 +1,415 @@
|
||||||
|
use crate::Theme;
|
||||||
|
use configparser::ini::Ini;
|
||||||
|
use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba};
|
||||||
|
use std::{
|
||||||
|
fs::{self, File},
|
||||||
|
io::Write,
|
||||||
|
path::PathBuf,
|
||||||
|
vec,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{OutputError, qt_settings_ini_style};
|
||||||
|
|
||||||
|
impl Theme {
|
||||||
|
/// The "version" of this theme.
|
||||||
|
///
|
||||||
|
/// To avoid repeatedly overwriting the user's config, we use a version system.
|
||||||
|
///
|
||||||
|
/// Increment this value when changes to qt{5,6}ct.conf are needed.
|
||||||
|
/// If the config's version is outdated, we update several sections.
|
||||||
|
/// Otherwise, only the light/dark mode is updated.
|
||||||
|
const COSMIC_QT_VERSION: u64 = 2;
|
||||||
|
|
||||||
|
/// Produces a QPalette ini file for qt5ct and qt6ct.
|
||||||
|
///
|
||||||
|
/// Example file: https://github.com/trialuser02/qt6ct/blob/master/colors/airy.conf
|
||||||
|
#[must_use]
|
||||||
|
#[cold]
|
||||||
|
pub fn as_qpalette(&self) -> String {
|
||||||
|
let lightest = if self.is_dark {
|
||||||
|
self.background.on
|
||||||
|
} else {
|
||||||
|
self.background.base
|
||||||
|
};
|
||||||
|
let darkest = if self.is_dark {
|
||||||
|
self.background.base
|
||||||
|
} else {
|
||||||
|
self.background.on
|
||||||
|
};
|
||||||
|
let active = QPaletteGroup {
|
||||||
|
window_text: self.background.on,
|
||||||
|
button: self.button.base,
|
||||||
|
light: self.button.base.mix(lightest, 0.1),
|
||||||
|
midlight: self.button.base.mix(lightest, 0.05),
|
||||||
|
dark: self.button.base.mix(darkest, 0.1),
|
||||||
|
mid: self.button.base.mix(darkest, 0.05),
|
||||||
|
text: self.background.component.on,
|
||||||
|
bright_text: lightest,
|
||||||
|
button_text: self.button.on,
|
||||||
|
base: self.background.component.base,
|
||||||
|
window: self.background.base,
|
||||||
|
shadow: darkest,
|
||||||
|
// selection colors are swapped to fix menu bar contrast
|
||||||
|
highlight: self.background.component.selected_text,
|
||||||
|
highlighted_text: self.background.component.selected,
|
||||||
|
link: self.link_button.on,
|
||||||
|
link_visited: self.link_button.on.mix(self.secondary.component.base, 0.2),
|
||||||
|
alternate_base: self.background.base.mix(self.accent.base, 0.05),
|
||||||
|
no_role: self.background.component.disabled,
|
||||||
|
tool_tip_base: self.background.component.base,
|
||||||
|
tool_tip_text: self.background.component.on,
|
||||||
|
placeholder_text: self.background.component.on.with_alpha(0.5),
|
||||||
|
};
|
||||||
|
let inactive = QPaletteGroup {
|
||||||
|
window_text: active.window_text.with_alpha(0.8),
|
||||||
|
text: active.text.with_alpha(0.8),
|
||||||
|
highlighted_text: active.highlighted_text.with_alpha(0.8),
|
||||||
|
tool_tip_text: active.tool_tip_text.with_alpha(0.8),
|
||||||
|
..active
|
||||||
|
};
|
||||||
|
let disabled = QPaletteGroup {
|
||||||
|
button: self.button.disabled,
|
||||||
|
text: self.background.component.on_disabled,
|
||||||
|
button_text: self.button.on_disabled,
|
||||||
|
base: self.background.component.disabled,
|
||||||
|
highlighted_text: active.highlighted_text.with_alpha(0.5),
|
||||||
|
link: self.link_button.on_disabled,
|
||||||
|
link_visited: self
|
||||||
|
.link_button
|
||||||
|
.on_disabled
|
||||||
|
.mix(self.secondary.component.disabled, 0.2),
|
||||||
|
alternate_base: self.background.base.mix(self.accent.disabled, 0.05),
|
||||||
|
tool_tip_base: self.background.component.disabled,
|
||||||
|
tool_tip_text: self.background.component.on_disabled,
|
||||||
|
placeholder_text: self.background.component.on_disabled.with_alpha(0.5),
|
||||||
|
..inactive
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
r#"# GENERATED BY COSMIC
|
||||||
|
|
||||||
|
[ColorScheme]
|
||||||
|
active_colors={}
|
||||||
|
disabled_colors={}
|
||||||
|
inactive_colors={}
|
||||||
|
"#,
|
||||||
|
active.as_list(),
|
||||||
|
disabled.as_list(),
|
||||||
|
inactive.as_list(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the QPalette ini files to:
|
||||||
|
/// - `~/.config/qt6ct/colors/`
|
||||||
|
/// - `~/.config/qt5ct/colors/`
|
||||||
|
#[cold]
|
||||||
|
pub fn write_qt56ct(&self) -> Result<(), OutputError> {
|
||||||
|
let qpalette = self.as_qpalette();
|
||||||
|
let qt5ct_res = self.write_ct("qt5ct", &qpalette);
|
||||||
|
let qt6ct_res = self.write_ct("qt6ct", &qpalette);
|
||||||
|
qt5ct_res?;
|
||||||
|
qt6ct_res?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
#[cold]
|
||||||
|
fn write_ct(&self, ct: &str, qpalette: &str) -> Result<(), OutputError> {
|
||||||
|
let file_path = Self::get_qpalette_path(ct, self.is_dark)?;
|
||||||
|
let tmp_file_path = file_path.with_extension("conf.new");
|
||||||
|
|
||||||
|
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
|
||||||
|
let res = tmp_file
|
||||||
|
.write_all(qpalette.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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Edits qt{5,6}ct.conf to use COSMIC styles if needed.
|
||||||
|
#[cold]
|
||||||
|
pub fn apply_qt56ct(is_dark: bool) -> Result<(), OutputError> {
|
||||||
|
let qt5ct_res = Self::apply_ct("qt5ct", is_dark);
|
||||||
|
let qt6ct_res = Self::apply_ct("qt6ct", is_dark);
|
||||||
|
qt5ct_res?;
|
||||||
|
qt6ct_res?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
#[cold]
|
||||||
|
fn apply_ct(ct: &str, is_dark: bool) -> Result<(), OutputError> {
|
||||||
|
let path = Self::get_conf_path(ct)?;
|
||||||
|
let file_content = fs::read_to_string(&path).map_err(OutputError::Io)?;
|
||||||
|
let mut ini = Ini::new_cs();
|
||||||
|
ini.read(file_content).map_err(OutputError::Ini)?;
|
||||||
|
|
||||||
|
let old_version = ini
|
||||||
|
.getuint("Appearance", "cosmic_qt_version")
|
||||||
|
.map_err(OutputError::Ini)?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let color_scheme_path = Self::get_qpalette_path(ct, is_dark)?;
|
||||||
|
let icon_theme = if is_dark { "breeze-dark" } else { "breeze" };
|
||||||
|
|
||||||
|
ini.set(
|
||||||
|
"Appearance",
|
||||||
|
"cosmic_qt_version",
|
||||||
|
Some(Theme::COSMIC_QT_VERSION.to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if old_version < Theme::COSMIC_QT_VERSION {
|
||||||
|
// Config is outdated, update it unconditionally!
|
||||||
|
|
||||||
|
ini.setstr(
|
||||||
|
"Appearance",
|
||||||
|
"color_scheme_path",
|
||||||
|
color_scheme_path.to_str(),
|
||||||
|
);
|
||||||
|
// Enable the above color scheme, instead of using the default color scheme of e.g. Breeze
|
||||||
|
ini.setstr("Appearance", "custom_palette", Some("true"));
|
||||||
|
// COSMIC icons are stuck in light mode, so use breeze icons instead
|
||||||
|
ini.setstr("Appearance", "icon_theme", Some(icon_theme));
|
||||||
|
// Use COSMIC dialogs instead of KDE's
|
||||||
|
ini.setstr("Appearance", "standard_dialogs", Some("xdgdesktopportal"));
|
||||||
|
|
||||||
|
// TODO: Add fonts section to match COSMIC
|
||||||
|
} else {
|
||||||
|
// Config is not outdated, check before updating light/dark mode only!
|
||||||
|
|
||||||
|
let old_color_scheme_path = ini
|
||||||
|
.get("Appearance", "color_scheme_path")
|
||||||
|
.unwrap_or_else(|| "CosmicPlease".to_owned());
|
||||||
|
if old_color_scheme_path.contains("Cosmic") {
|
||||||
|
ini.setstr(
|
||||||
|
"Appearance",
|
||||||
|
"color_scheme_path",
|
||||||
|
color_scheme_path.to_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_icon_theme = ini
|
||||||
|
.get("Appearance", "icon_theme")
|
||||||
|
.unwrap_or_else(|| "breeze".to_owned());
|
||||||
|
if old_icon_theme.contains("breeze") {
|
||||||
|
ini.setstr("Appearance", "icon_theme", Some(icon_theme));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ini.pretty_write(path, &qt_settings_ini_style())
|
||||||
|
.map_err(OutputError::Io)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the applied qt56ct config by removing COSMIC-specific entries from the config file.
|
||||||
|
#[cold]
|
||||||
|
pub fn reset_qt56ct() -> Result<(), OutputError> {
|
||||||
|
let qt5ct_res = Self::reset_ct("qt5ct");
|
||||||
|
let qt6ct_res = Self::reset_ct("qt6ct");
|
||||||
|
qt5ct_res?;
|
||||||
|
qt6ct_res?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
#[cold]
|
||||||
|
fn reset_ct(ct: &str) -> Result<(), OutputError> {
|
||||||
|
let path = Self::get_conf_path(ct)?;
|
||||||
|
let file_content = fs::read_to_string(&path).map_err(OutputError::Io)?;
|
||||||
|
let mut ini = Ini::new_cs();
|
||||||
|
ini.read(file_content).map_err(OutputError::Ini)?;
|
||||||
|
|
||||||
|
let old_version = ini
|
||||||
|
.getuint("Appearance", "cosmic_qt_version")
|
||||||
|
.map_err(OutputError::Ini)?
|
||||||
|
.unwrap_or_default();
|
||||||
|
if old_version == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
ini.remove_key("Appearance", "cosmic_qt_version");
|
||||||
|
ini.remove_key("Appearance", "color_scheme_path");
|
||||||
|
ini.remove_key("Appearance", "icon_theme");
|
||||||
|
|
||||||
|
ini.pretty_write(path, &qt_settings_ini_style())
|
||||||
|
.map_err(OutputError::Io)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the file paths of the form `~/.config/ct/ct.conf`:
|
||||||
|
/// e.g. `~/.config/qt6ct/qt6ct.conf`.
|
||||||
|
///
|
||||||
|
/// The file and its parent directory are created if they don't exist.
|
||||||
|
#[cold]
|
||||||
|
fn get_conf_path(ct: &str) -> Result<PathBuf, OutputError> {
|
||||||
|
assert!(ct == "qt5ct" || ct == "qt6ct");
|
||||||
|
|
||||||
|
let Some(mut config_dir) = dirs::config_dir() else {
|
||||||
|
return Err(OutputError::MissingConfigDir);
|
||||||
|
};
|
||||||
|
config_dir.push(&ct);
|
||||||
|
if !config_dir.exists() {
|
||||||
|
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = config_dir.join(ct.to_owned() + ".conf");
|
||||||
|
if !file_path.exists() {
|
||||||
|
File::create_new(&file_path).map_err(OutputError::Io)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(file_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a path like `~/.config/qt6ct/colors/CosmicDark.conf`
|
||||||
|
///
|
||||||
|
/// Its parent directory is created if it doesn't exist.
|
||||||
|
#[cold]
|
||||||
|
fn get_qpalette_path(ct: &str, is_dark: bool) -> Result<PathBuf, OutputError> {
|
||||||
|
assert!(ct == "qt5ct" || ct == "qt6ct");
|
||||||
|
|
||||||
|
let Some(mut config_dir) = dirs::config_dir() else {
|
||||||
|
return Err(OutputError::MissingConfigDir);
|
||||||
|
};
|
||||||
|
config_dir.push(&ct);
|
||||||
|
config_dir.push("colors");
|
||||||
|
if !config_dir.exists() {
|
||||||
|
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_name = if is_dark {
|
||||||
|
"CosmicDark.conf"
|
||||||
|
} else {
|
||||||
|
"CosmicLight.conf"
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(config_dir.join(file_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the different symbolic color roles used in current GUIs.
|
||||||
|
///
|
||||||
|
/// qt5ct and qt6ct consume this as a list of colors, ordered by ColorRole:
|
||||||
|
/// - https://doc.qt.io/qt-6/qpalette.html#ColorRole-enum
|
||||||
|
/// - https://doc.qt.io/archives/qt-5.15/qpalette.html#ColorRole-enum
|
||||||
|
struct QPaletteGroup {
|
||||||
|
/// A general foreground color.
|
||||||
|
window_text: Srgba,
|
||||||
|
/// The general button background color.
|
||||||
|
button: Srgba,
|
||||||
|
/// Lighter than [button] color, used mostly for 3D bevel and shadow effects.
|
||||||
|
light: Srgba,
|
||||||
|
/// Between [button] and [light], used mostly for 3D bevel and shadow effects.
|
||||||
|
midlight: Srgba,
|
||||||
|
/// Darker than [button], used mostly for 3D bevel and shadow effects.
|
||||||
|
dark: Srgba,
|
||||||
|
/// Between [button] and [dark], used mostly for 3D bevel and shadow effects.
|
||||||
|
mid: Srgba,
|
||||||
|
/// The foreground color used with [base].
|
||||||
|
text: Srgba,
|
||||||
|
/// A text color that is very different from [window_text], and contrasts well with e.g. [dark].
|
||||||
|
/// Typically used for text that needs to be drawn where [text] or [window_text] would give poor contrast, such as on pressed push buttons.
|
||||||
|
bright_text: Srgba,
|
||||||
|
/// A foreground color used with the [button] color.
|
||||||
|
button_text: Srgba,
|
||||||
|
/// Used mostly as the background color for text entry widgets, but can also be used for other painting -
|
||||||
|
/// such as the background of combobox drop down lists and toolbar handles.
|
||||||
|
base: Srgba,
|
||||||
|
/// A general background color.
|
||||||
|
window: Srgba,
|
||||||
|
/// A very dark color, used mostly for 3D bevel and shadow effects.
|
||||||
|
/// Opaque black by default.
|
||||||
|
shadow: Srgba,
|
||||||
|
/// A color to indicate a selected item or the current item.
|
||||||
|
highlight: Srgba,
|
||||||
|
/// A text color that contrasts with [highlight].
|
||||||
|
highlighted_text: Srgba,
|
||||||
|
/// A text color used for unvisited hyperlinks.
|
||||||
|
link: Srgba,
|
||||||
|
/// A text color used for already visited hyperlinks.
|
||||||
|
link_visited: Srgba,
|
||||||
|
/// Used as the alternate background color in views with alternating row colors.
|
||||||
|
alternate_base: Srgba,
|
||||||
|
/// No role; this special role is often used to indicate that a role has not been assigned.
|
||||||
|
no_role: Srgba,
|
||||||
|
/// Used as the background color for QToolTip and QWhatsThis.
|
||||||
|
/// Tool tips use the inactive color group of QPalette, because tool tips are not active windows.
|
||||||
|
tool_tip_base: Srgba,
|
||||||
|
/// Used as the foreground color for QToolTip and QWhatsThis.
|
||||||
|
/// Tool tips use the inactive color group of QPalette, because tool tips are not active windows.
|
||||||
|
tool_tip_text: Srgba,
|
||||||
|
/// Used as the placeholder color for various text input widgets.
|
||||||
|
placeholder_text: Srgba,
|
||||||
|
// /// [accent] only exists since Qt 6.6. Including it here breaks qt5ct.
|
||||||
|
// /// When omitted, it defaults to [highlight].
|
||||||
|
// accent: Srgba,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QPaletteGroup {
|
||||||
|
/// Returns a comma-separated list of the colors as hex codes.
|
||||||
|
/// E.g. `#ff000000, #ffdcdcdc, ...`
|
||||||
|
///
|
||||||
|
/// Any transparent colors are flattened with [base] to avoid issues with
|
||||||
|
/// the Fusion style.
|
||||||
|
fn as_list(&self) -> String {
|
||||||
|
let colors = vec![
|
||||||
|
to_argb_hex(self.window_text.over(self.base)),
|
||||||
|
to_argb_hex(self.button.over(self.base)),
|
||||||
|
to_argb_hex(self.light.over(self.base)),
|
||||||
|
to_argb_hex(self.midlight.over(self.base)),
|
||||||
|
to_argb_hex(self.dark.over(self.base)),
|
||||||
|
to_argb_hex(self.mid.over(self.base)),
|
||||||
|
to_argb_hex(self.text.over(self.base)),
|
||||||
|
to_argb_hex(self.bright_text.over(self.base)),
|
||||||
|
to_argb_hex(self.button_text.over(self.base)),
|
||||||
|
to_argb_hex(self.base.over(self.base)),
|
||||||
|
to_argb_hex(self.window.over(self.base)),
|
||||||
|
to_argb_hex(self.shadow.over(self.base)),
|
||||||
|
to_argb_hex(self.highlight.over(self.base)),
|
||||||
|
to_argb_hex(self.highlighted_text.over(self.base)),
|
||||||
|
to_argb_hex(self.link.over(self.base)),
|
||||||
|
to_argb_hex(self.link_visited.over(self.base)),
|
||||||
|
to_argb_hex(self.alternate_base.over(self.base)),
|
||||||
|
to_argb_hex(self.no_role.over(self.base)),
|
||||||
|
to_argb_hex(self.tool_tip_base.over(self.base)),
|
||||||
|
to_argb_hex(self.tool_tip_text.over(self.base)),
|
||||||
|
to_argb_hex(self.placeholder_text.over(self.base)),
|
||||||
|
];
|
||||||
|
colors.join(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a color to a hex string in the format `#AARRGGBB`.
|
||||||
|
/// Do not use [to_hex] since that uses the format `RRGGBBAA`.
|
||||||
|
fn to_argb_hex(c: Srgba) -> String {
|
||||||
|
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
|
||||||
|
format!(
|
||||||
|
"#{:02x}{:02x}{:02x}{:02x}",
|
||||||
|
c_u8.alpha, c_u8.red, c_u8.green, c_u8.blue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_color_to_argb_hex() {
|
||||||
|
let color = Srgba::new(0x33, 0x55, 0x77, 0xff);
|
||||||
|
let argb = to_argb_hex(color.into());
|
||||||
|
assert_eq!(argb, "#ff335577");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_light_default_qpalette() {
|
||||||
|
let light_default_qpalette = Theme::light_default().as_qpalette();
|
||||||
|
insta::assert_snapshot!(light_default_qpalette);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dark_default_qpalette() {
|
||||||
|
let dark_default_qpalette = Theme::dark_default().as_qpalette();
|
||||||
|
insta::assert_snapshot!(dark_default_qpalette);
|
||||||
|
}
|
||||||
|
}
|
||||||
568
cosmic-theme/src/output/qt_output.rs
Normal file
568
cosmic-theme/src/output/qt_output.rs
Normal file
|
|
@ -0,0 +1,568 @@
|
||||||
|
use crate::Theme;
|
||||||
|
use configparser::ini::Ini;
|
||||||
|
use cosmic_config::CosmicConfigEntry;
|
||||||
|
use palette::{Mix, Srgba, blend::Compose};
|
||||||
|
use std::{
|
||||||
|
fs::{self, File},
|
||||||
|
io::{self, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{OutputError, qt_settings_ini_style};
|
||||||
|
|
||||||
|
impl Theme {
|
||||||
|
/// Produces a color scheme ini file for Qt.
|
||||||
|
///
|
||||||
|
/// Some high-level documentation for this file can be found at:
|
||||||
|
/// - https://api.kde.org/kcolorscheme.html
|
||||||
|
/// - https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/
|
||||||
|
#[must_use]
|
||||||
|
#[cold]
|
||||||
|
pub fn as_kcolorscheme(&self) -> String {
|
||||||
|
// Usually, disabled elements will have strongly reduced contrast and are often notably darker or lighter
|
||||||
|
let disabled_color_effects = IniColorEffects {
|
||||||
|
color: self.button.disabled,
|
||||||
|
color_amount: 0.0,
|
||||||
|
color_effect: ColorEffect::Desaturate,
|
||||||
|
contrast_amount: 0.65,
|
||||||
|
contrast_effect: ColorEffect::Fade,
|
||||||
|
intensity_amount: 0.1,
|
||||||
|
intensity_effect: IntensityEffect::Lighten,
|
||||||
|
};
|
||||||
|
// Usually, inactive elements will have reduced contrast (text fades slightly into the background) and may have slightly reduced intensity
|
||||||
|
let inactive_color_effects = IniColorEffects {
|
||||||
|
color: self.palette.gray_1,
|
||||||
|
color_amount: 0.025,
|
||||||
|
color_effect: ColorEffect::Tint,
|
||||||
|
contrast_amount: 0.1,
|
||||||
|
contrast_effect: ColorEffect::Tint,
|
||||||
|
intensity_amount: 0.0,
|
||||||
|
intensity_effect: IntensityEffect::Shade,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bg = self.background.base;
|
||||||
|
// the background container
|
||||||
|
let window_colors = IniColors {
|
||||||
|
background_alternate: bg.mix(self.accent.base, 0.05),
|
||||||
|
background_normal: bg,
|
||||||
|
decoration_focus: self.accent_text_color(),
|
||||||
|
decoration_hover: self.accent_text_color(),
|
||||||
|
foreground_active: self.accent_text_color(),
|
||||||
|
foreground_inactive: self.background.on.mix(bg, 0.1),
|
||||||
|
foreground_link: self.link_button.on,
|
||||||
|
foreground_negative: self.destructive_text_color(),
|
||||||
|
foreground_neutral: self.warning_text_color(),
|
||||||
|
foreground_normal: self.background.on,
|
||||||
|
foreground_positive: self.success_text_color(),
|
||||||
|
foreground_visited: self.accent_text_color(),
|
||||||
|
};
|
||||||
|
// components inside the background container
|
||||||
|
let view_colors = IniColors {
|
||||||
|
background_alternate: self.background.component.base.mix(self.accent.base, 0.05),
|
||||||
|
background_normal: self.background.component.base,
|
||||||
|
..window_colors
|
||||||
|
};
|
||||||
|
|
||||||
|
// selected text and items
|
||||||
|
let selection_colors = {
|
||||||
|
// selection colors are swapped to fix menu bar contrast
|
||||||
|
let selected = self.background.component.selected_text;
|
||||||
|
let selected_text = self.background.component.selected;
|
||||||
|
IniColors {
|
||||||
|
background_alternate: selected.mix(bg, 0.5),
|
||||||
|
background_normal: selected,
|
||||||
|
decoration_focus: selected,
|
||||||
|
decoration_hover: selected,
|
||||||
|
foreground_active: selected_text,
|
||||||
|
foreground_inactive: selected_text.mix(selected, 0.5),
|
||||||
|
foreground_link: self.link_button.base,
|
||||||
|
foreground_negative: self.destructive_color(),
|
||||||
|
foreground_neutral: self.warning_color(),
|
||||||
|
foreground_normal: selected_text,
|
||||||
|
foreground_positive: self.success_color(),
|
||||||
|
foreground_visited: self.accent_color(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let button_colors = IniColors {
|
||||||
|
background_alternate: self.accent_button.base,
|
||||||
|
background_normal: self.button.base,
|
||||||
|
..view_colors
|
||||||
|
};
|
||||||
|
|
||||||
|
// Complementary: Areas of applications with an alternative color scheme; usually with a dark background for light color schemes.
|
||||||
|
let complementary_colors = {
|
||||||
|
let dark = if self.is_dark {
|
||||||
|
self.clone()
|
||||||
|
} else if cfg!(test) {
|
||||||
|
// For reproducible results in tests, use the default dark theme
|
||||||
|
Theme::dark_default()
|
||||||
|
} else {
|
||||||
|
Theme::dark_config()
|
||||||
|
.ok()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|conf| Theme::get_entry(conf).ok())
|
||||||
|
.unwrap_or_else(|| self.clone())
|
||||||
|
};
|
||||||
|
IniColors {
|
||||||
|
background_alternate: dark.accent.base,
|
||||||
|
background_normal: dark.background.base,
|
||||||
|
decoration_focus: dark.accent_text_color(),
|
||||||
|
decoration_hover: dark.accent_text_color(),
|
||||||
|
foreground_active: dark.accent_text_color(),
|
||||||
|
foreground_inactive: dark.background.on.mix(dark.background.base, 0.1),
|
||||||
|
foreground_link: dark.link_button.on,
|
||||||
|
foreground_negative: dark.destructive_text_color(),
|
||||||
|
foreground_neutral: dark.warning_text_color(),
|
||||||
|
foreground_normal: dark.background.on,
|
||||||
|
foreground_positive: dark.success_text_color(),
|
||||||
|
foreground_visited: dark.accent_text_color(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// headers in cosmic don't have a background
|
||||||
|
let header_colors = &window_colors;
|
||||||
|
let header_colors_inactive = &window_colors;
|
||||||
|
// tool tips, "What's This" tips, and similar elements
|
||||||
|
let tooltip_colors = &view_colors;
|
||||||
|
|
||||||
|
let general_color_scheme = if self.is_dark {
|
||||||
|
"CosmicDark"
|
||||||
|
} else {
|
||||||
|
"CosmicLight"
|
||||||
|
};
|
||||||
|
let general_name = if self.is_dark {
|
||||||
|
"COSMIC Dark"
|
||||||
|
} else {
|
||||||
|
"COSMIC Light"
|
||||||
|
};
|
||||||
|
// COSMIC icons are stuck in light mode, so use breeze icons instead
|
||||||
|
let icons_theme = if self.is_dark {
|
||||||
|
"breeze-dark"
|
||||||
|
} else {
|
||||||
|
"breeze"
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
r#"# GENERATED BY COSMIC
|
||||||
|
|
||||||
|
[ColorEffects:Disabled]
|
||||||
|
{}
|
||||||
|
|
||||||
|
[ColorEffects:Inactive]
|
||||||
|
ChangeSelectionColor=false
|
||||||
|
Enable=false
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Colors:Button]
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Colors:Complementary]
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Colors:Header]
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Colors:Header][Inactive]
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Colors:Selection]
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Colors:Tooltip]
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Colors:View]
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Colors:Window]
|
||||||
|
{}
|
||||||
|
|
||||||
|
[General]
|
||||||
|
ColorScheme={general_color_scheme}
|
||||||
|
Name={general_name}
|
||||||
|
shadeSortColumn=true
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Theme={icons_theme}
|
||||||
|
|
||||||
|
[KDE]
|
||||||
|
contrast=4
|
||||||
|
widgetStyle=qt6ct-style
|
||||||
|
|
||||||
|
[WM]
|
||||||
|
{}
|
||||||
|
"#,
|
||||||
|
format_ini_color_effects(&disabled_color_effects, bg),
|
||||||
|
format_ini_color_effects(&inactive_color_effects, bg),
|
||||||
|
format_ini_colors(&button_colors, bg),
|
||||||
|
format_ini_colors(&complementary_colors, bg),
|
||||||
|
format_ini_colors(&header_colors, bg),
|
||||||
|
format_ini_colors(&header_colors_inactive, bg),
|
||||||
|
format_ini_colors(&selection_colors, bg),
|
||||||
|
format_ini_colors(&tooltip_colors, bg),
|
||||||
|
format_ini_colors(&view_colors, bg),
|
||||||
|
format_ini_colors(&window_colors, bg),
|
||||||
|
format_ini_wm_colors(&window_colors, self.is_dark),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the color scheme to the appropriate directory.
|
||||||
|
/// Should be written in `~/.local/share/color-schemes/`.
|
||||||
|
///
|
||||||
|
/// See the docs: https://develop.kde.org/docs/plasma/#color-scheme
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an `OutputError` if there is an error writing the colors file.
|
||||||
|
#[cold]
|
||||||
|
pub fn write_qt(&self) -> Result<(), OutputError> {
|
||||||
|
let kcolorscheme = self.as_kcolorscheme();
|
||||||
|
let file_path = Self::get_kcolorscheme_path(self.is_dark)?;
|
||||||
|
let tmp_file_path = file_path.with_extension("colors.new");
|
||||||
|
|
||||||
|
// Write to tmp_file_path first, then move it to file_path
|
||||||
|
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
|
||||||
|
let res = tmp_file
|
||||||
|
.write_all(kcolorscheme.as_bytes())
|
||||||
|
.and_then(|_| tmp_file.flush())
|
||||||
|
.and_then(|_| std::fs::rename(&tmp_file_path, file_path));
|
||||||
|
if let Err(e) = res {
|
||||||
|
_ = std::fs::remove_file(&tmp_file_path);
|
||||||
|
return Err(OutputError::Io(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply the color scheme by copying its values to `~/.config/kdeglobals`.
|
||||||
|
///
|
||||||
|
/// See the docs: https://develop.kde.org/docs/plasma/#color-scheme
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an `OutputError` if there is an error applying the color scheme.
|
||||||
|
#[cold]
|
||||||
|
pub fn apply_qt(is_dark: bool) -> Result<(), OutputError> {
|
||||||
|
let Some(config_dir) = dirs::config_dir() else {
|
||||||
|
return Err(OutputError::MissingConfigDir);
|
||||||
|
};
|
||||||
|
let kdeglobals_file = config_dir.join("kdeglobals");
|
||||||
|
let mut kdeglobals_ini = Self::read_ini(&kdeglobals_file)?;
|
||||||
|
|
||||||
|
let src_file = Self::get_kcolorscheme_path(is_dark)?;
|
||||||
|
let src_ini = Self::read_ini(&src_file)?;
|
||||||
|
|
||||||
|
Self::backup_non_cosmic_kdeglobals(&kdeglobals_ini, &kdeglobals_file)
|
||||||
|
.map_err(OutputError::Io)?;
|
||||||
|
|
||||||
|
for (section, key_value) in src_ini.get_map_ref() {
|
||||||
|
for (key, value) in key_value {
|
||||||
|
kdeglobals_ini.set(section, key, value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kdeglobals_ini
|
||||||
|
.pretty_write(kdeglobals_file, &qt_settings_ini_style())
|
||||||
|
.map_err(OutputError::Io)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the applied qt colors by removing color scheme values from the
|
||||||
|
/// `~/.config/kdeglobals` file.
|
||||||
|
///
|
||||||
|
/// This does not restore the backed up kdeglobals file.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an `OutputError` if there is an error resetting the CSS file.
|
||||||
|
#[cold]
|
||||||
|
pub fn reset_qt() -> 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)?;
|
||||||
|
|
||||||
|
if !Self::is_cosmic_kdeglobals(&kdeglobals_ini)
|
||||||
|
.map_err(OutputError::Io)?
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
// Not a cosmic kdeglobals file, do nothing
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_dark = false; // doesn't matter since we're only reading keys
|
||||||
|
let src_file = Self::get_kcolorscheme_path(is_dark)?;
|
||||||
|
let src_ini = Self::read_ini(&src_file)?;
|
||||||
|
|
||||||
|
for (section, key_value) in src_ini.get_map_ref() {
|
||||||
|
for (key, _) in key_value {
|
||||||
|
kdeglobals_ini.remove_key(section, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kdeglobals_ini
|
||||||
|
.write(kdeglobals_file)
|
||||||
|
.map_err(OutputError::Io)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a path like `~/.local/share/color-schemes/CosmicDark.colors`
|
||||||
|
fn get_kcolorscheme_path(is_dark: bool) -> Result<PathBuf, OutputError> {
|
||||||
|
let Some(mut data_dir) = dirs::data_dir() else {
|
||||||
|
return Err(OutputError::MissingDataDir);
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_name = if is_dark {
|
||||||
|
"CosmicDark.colors"
|
||||||
|
} else {
|
||||||
|
"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))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn read_ini(path: &PathBuf) -> Result<Ini, OutputError> {
|
||||||
|
let mut ini = Ini::new_cs();
|
||||||
|
if !path.exists() {
|
||||||
|
return Ok(ini);
|
||||||
|
}
|
||||||
|
let file_content = fs::read_to_string(path).map_err(OutputError::Io)?;
|
||||||
|
ini.read(file_content).map_err(OutputError::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]
|
||||||
|
fn is_cosmic_kdeglobals(ini: &Ini) -> io::Result<Option<bool>> {
|
||||||
|
let color_scheme = ini.get("General", "ColorScheme");
|
||||||
|
if let Some(color_scheme) = color_scheme {
|
||||||
|
Ok(Some(
|
||||||
|
color_scheme == "CosmicDark" || color_scheme == "CosmicLight",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a color in the form `r,g,b` e.g. `255,255,255`.
|
||||||
|
/// If the color has transparency, it is mixed with bg first.
|
||||||
|
fn to_rgb(c: Srgba, bg: Srgba) -> String {
|
||||||
|
let c_u8: Srgba<u8> = c.over(bg).into_format();
|
||||||
|
format!("{},{},{}", c_u8.red, c_u8.green, c_u8.blue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_ini_color_effects(color_effects: &IniColorEffects, bg: Srgba) -> String {
|
||||||
|
format!(
|
||||||
|
r#"Color={}
|
||||||
|
ColorAmount={}
|
||||||
|
ColorEffect={}
|
||||||
|
ContrastAmount={}
|
||||||
|
ContrastEffect={}
|
||||||
|
IntensityAmount={}
|
||||||
|
IntensityEffect={}"#,
|
||||||
|
to_rgb(color_effects.color, bg),
|
||||||
|
color_effects.color_amount,
|
||||||
|
color_effects.color_effect.as_u8(),
|
||||||
|
color_effects.contrast_amount,
|
||||||
|
color_effects.contrast_effect.as_u8(),
|
||||||
|
color_effects.intensity_amount,
|
||||||
|
color_effects.intensity_effect.as_u8(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_ini_colors(colors: &IniColors, bg: Srgba) -> String {
|
||||||
|
format!(
|
||||||
|
r#"BackgroundAlternate={}
|
||||||
|
BackgroundNormal={}
|
||||||
|
DecorationFocus={}
|
||||||
|
DecorationHover={}
|
||||||
|
ForegroundActive={}
|
||||||
|
ForegroundInactive={}
|
||||||
|
ForegroundLink={}
|
||||||
|
ForegroundNegative={}
|
||||||
|
ForegroundNeutral={}
|
||||||
|
ForegroundNormal={}
|
||||||
|
ForegroundPositive={}
|
||||||
|
ForegroundVisited={}"#,
|
||||||
|
to_rgb(colors.background_alternate, bg),
|
||||||
|
to_rgb(colors.background_normal, bg),
|
||||||
|
to_rgb(colors.decoration_focus, bg),
|
||||||
|
to_rgb(colors.decoration_hover, bg),
|
||||||
|
to_rgb(colors.foreground_active, bg),
|
||||||
|
to_rgb(colors.foreground_inactive, bg),
|
||||||
|
to_rgb(colors.foreground_link, bg),
|
||||||
|
to_rgb(colors.foreground_negative, bg),
|
||||||
|
to_rgb(colors.foreground_neutral, bg),
|
||||||
|
to_rgb(colors.foreground_normal, bg),
|
||||||
|
to_rgb(colors.foreground_positive, bg),
|
||||||
|
to_rgb(colors.foreground_visited, bg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the colors for the titlebars of active and inactive windows.
|
||||||
|
fn format_ini_wm_colors(view_colors: &IniColors, is_dark: bool) -> String {
|
||||||
|
let bg = view_colors.background_normal;
|
||||||
|
let fg = view_colors.foreground_active;
|
||||||
|
let blend = if is_dark { fg } else { bg };
|
||||||
|
|
||||||
|
format!(
|
||||||
|
r#"activeBackground={}
|
||||||
|
activeBlend={}
|
||||||
|
activeForeground={}
|
||||||
|
inactiveBackground={}
|
||||||
|
inactiveBlend={}
|
||||||
|
inactiveForeground={}"#,
|
||||||
|
to_rgb(bg, bg),
|
||||||
|
to_rgb(blend, bg),
|
||||||
|
to_rgb(fg, bg),
|
||||||
|
to_rgb(bg, bg),
|
||||||
|
to_rgb(blend, bg),
|
||||||
|
to_rgb(fg, bg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IniColorEffects {
|
||||||
|
color: Srgba,
|
||||||
|
color_amount: f32,
|
||||||
|
color_effect: ColorEffect,
|
||||||
|
contrast_amount: f32,
|
||||||
|
/// Applied to the text, using the background as the reference color.
|
||||||
|
contrast_effect: ColorEffect,
|
||||||
|
intensity_amount: f32,
|
||||||
|
intensity_effect: IntensityEffect,
|
||||||
|
}
|
||||||
|
/// Each color set is made up of a number of roles which are available in all other sets.
|
||||||
|
/// In addition, except for Inactive Text, there is a corresponding background role for each of the text roles. Currently (except for Normal and Alternate Background), these colors are not chosen here but are automatically determined based on Normal Background and the corresponding Text color.
|
||||||
|
struct IniColors {
|
||||||
|
/// used when there is a need to subtly change the background to aid in item association. This might be used e.g. as the background of a heading, but is mostly used for alternating rows in lists, especially multi-column lists, to aid in visually tracking rows.
|
||||||
|
background_alternate: Srgba,
|
||||||
|
/// Normal background
|
||||||
|
background_normal: Srgba,
|
||||||
|
/// Used for drawing lines or shading UI elements to indicate the item which has active input focus.
|
||||||
|
/// Typically the same as foreground_active.
|
||||||
|
decoration_focus: Srgba,
|
||||||
|
/// Used for drawing lines or shading UI elements for mouse-over effects, e.g. the "illumination" effects for buttons.
|
||||||
|
/// Typically the same as foreground_active.
|
||||||
|
decoration_hover: Srgba,
|
||||||
|
/// used to indicate an active element or attract attention, e.g. alerts, notifications; also for hovered hyperlinks
|
||||||
|
foreground_active: Srgba,
|
||||||
|
/// used for text which should be unobtrusive, e.g. comments, "subtitles", unimportant information, etc.
|
||||||
|
foreground_inactive: Srgba,
|
||||||
|
/// used for hyperlinks or to otherwise indicate "something which may be visited", or to show relationships
|
||||||
|
foreground_link: Srgba,
|
||||||
|
/// used for errors, failure notices, notifications that an action may be dangerous (e.g. unsafe web page or security context), etc.
|
||||||
|
foreground_negative: Srgba,
|
||||||
|
/// used to draw attention when another role is not appropriate; e.g. warnings, to indicate secure/encrypted content, etc.
|
||||||
|
foreground_neutral: Srgba,
|
||||||
|
/// Normal foreground
|
||||||
|
foreground_normal: Srgba,
|
||||||
|
/// used for success notices, to indicate trusted content, etc.
|
||||||
|
foreground_positive: Srgba,
|
||||||
|
/// used for "something (e.g. a hyperlink) that has been visited", or to indicate something that is "old".
|
||||||
|
foreground_visited: Srgba,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intensity allows the overall color to be lightened or darkened.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
enum IntensityEffect {
|
||||||
|
/// Makes everything lighter or darker in a controlled manner.
|
||||||
|
///
|
||||||
|
/// intensity_amount increases or decreases the overall intensity (i.e. perceived brightness) by an absolute amount.
|
||||||
|
Shade,
|
||||||
|
/// Changes the intensity to a percentage of the initial value.
|
||||||
|
Darken,
|
||||||
|
/// Conceptually the opposite of darken; lighten can be thought of as working with "distance from white", where darken works with "distance from black".
|
||||||
|
Lighten,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntensityEffect {
|
||||||
|
pub fn as_u8(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Shade => 0,
|
||||||
|
Self::Darken => 1,
|
||||||
|
Self::Lighten => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This also changes the overall color like [IntensityEffect],
|
||||||
|
/// but is not limited to intensity.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
enum ColorEffect {
|
||||||
|
/// changes the relative chroma
|
||||||
|
///
|
||||||
|
/// This is available for "ColorEffect" but not "ContrastEffect".
|
||||||
|
Desaturate,
|
||||||
|
/// smoothly blends the original color into a reference color
|
||||||
|
Fade,
|
||||||
|
/// similar to Fade, except that the color (hue and chroma) changes more quickly while the intensity changes more slowly as the amount is increased
|
||||||
|
Tint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorEffect {
|
||||||
|
pub fn as_u8(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Desaturate => 0,
|
||||||
|
Self::Fade => 1,
|
||||||
|
Self::Tint => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_opaque_color_to_rgb() {
|
||||||
|
let color = Srgba::new(30.0 / 255.0, 50.0 / 255.0, 70.0 / 255.0, 1.0);
|
||||||
|
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
|
||||||
|
let result = to_rgb(color, bg);
|
||||||
|
assert_eq!(result, "30,50,70");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transparent_color_to_rgb() {
|
||||||
|
let color = Srgba::new(0.0, 0.0, 0.0, 0.0);
|
||||||
|
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
|
||||||
|
let result = to_rgb(color, bg);
|
||||||
|
assert_eq!(result, "255,255,255");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_translucent_color_to_rgb() {
|
||||||
|
let color = Srgba::new(0.0, 0.0, 0.0, 0.9);
|
||||||
|
let bg = Srgba::new(1.0, 1.0, 1.0, 1.0);
|
||||||
|
let result = to_rgb(color, bg);
|
||||||
|
assert_eq!(result, "26,26,26");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_light_default_kcolorscheme() {
|
||||||
|
let light_default_kcolorscheme = Theme::light_default().as_kcolorscheme();
|
||||||
|
insta::assert_snapshot!(light_default_kcolorscheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dark_default_kcolorscheme() {
|
||||||
|
let dark_default_kcolorscheme = Theme::dark_default().as_kcolorscheme();
|
||||||
|
insta::assert_snapshot!(dark_default_kcolorscheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: cosmic-theme/src/output/qt56ct_output.rs
|
||||||
|
expression: dark_default_qpalette
|
||||||
|
---
|
||||||
|
# GENERATED BY COSMIC
|
||||||
|
|
||||||
|
[ColorScheme]
|
||||||
|
active_colors=#ffe7e7e7, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffc0c0c0, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff434343, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffc0c0c0, #ff777777
|
||||||
|
disabled_colors=#e6d3d3d3, #8f474747, #a9696969, #a4626262, #a95f5f5f, #a45d5d5d, #d2a1a1a1, #ffe7e7e7, #d2a1a1a1, #bf2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #bf3c3c3c, #bf30555a, #bf324f53, #ff1f2425, #bf2e2e2e, #bf2e2e2e, #d2a1a1a1, #bf909090
|
||||||
|
inactive_colors=#ffc2c2c2, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffa3a3a3, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff3f3f3f, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffa3a3a3, #ff777777
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: cosmic-theme/src/output/qt56ct_output.rs
|
||||||
|
expression: light_default_qpalette
|
||||||
|
---
|
||||||
|
# GENERATED BY COSMIC
|
||||||
|
|
||||||
|
[ColorScheme]
|
||||||
|
active_colors=#ff121212, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff272727, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff272727, #ff8e8e8e
|
||||||
|
disabled_colors=#e62b2b2b, #8fc9c9c9, #a99b9b9b, #a4a0a0a0, #a9929292, #a49b9b9b, #d2535353, #ffd7d7d7, #d2535353, #bff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #bff6f6f6, #bf526d70, #bf72888a, #ffccd0d1, #bff5f5f5, #bff5f5f5, #d2535353, #bf6c6c6c
|
||||||
|
inactive_colors=#ff3f3f3f, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff505050, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff505050, #ff8e8e8e
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
|
|
||||||
use super::{to_hex, OutputError};
|
use super::{OutputError, to_hex};
|
||||||
|
|
||||||
/// Represents the workbench.colorCustomizations section of a VS Code settings.json file
|
/// Represents the workbench.colorCustomizations section of a VS Code settings.json file
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
@ -269,8 +269,9 @@ impl Theme {
|
||||||
#[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);
|
||||||
let config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
|
let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
|
||||||
let vs_code_dir = config_dir.join("Code").join("User");
|
config_dir.extend(["Code", "User"]);
|
||||||
|
let vs_code_dir = config_dir;
|
||||||
if !vs_code_dir.exists() {
|
if !vs_code_dir.exists() {
|
||||||
std::fs::create_dir_all(&vs_code_dir).map_err(OutputError::Io)?;
|
std::fs::create_dir_all(&vs_code_dir).map_err(OutputError::Io)?;
|
||||||
}
|
}
|
||||||
|
|
@ -292,9 +293,9 @@ impl Theme {
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn reset_vs_code() -> Result<(), OutputError> {
|
pub fn reset_vs_code() -> Result<(), OutputError> {
|
||||||
let config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
|
let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
|
||||||
let vs_code_dir = config_dir.join("Code").join("User");
|
config_dir.extend(["Code", "User", "settings.json"]);
|
||||||
let settings_file = vs_code_dir.join("settings.json");
|
let settings_file = config_dir;
|
||||||
// just remove the json entry for workbench.colorCustomizations
|
// just remove the json entry for workbench.colorCustomizations
|
||||||
let settings = std::fs::read_to_string(&settings_file).unwrap_or_default();
|
let settings = std::fs::read_to_string(&settings_file).unwrap_or_default();
|
||||||
let mut settings: serde_json::Value = serde_json::from_str(&settings).unwrap_or_default();
|
let mut settings: serde_json::Value = serde_json::from_str(&settings).unwrap_or_default();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use almost::equal;
|
use almost::equal;
|
||||||
use palette::{convert::FromColorUnclamped, ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba};
|
use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba, convert::FromColorUnclamped};
|
||||||
|
|
||||||
/// Get an array of 100 colors with a specific hue and chroma
|
/// Get an array of 100 colors with a specific hue and chroma
|
||||||
/// over the full range of lightness.
|
/// over the full range of lightness.
|
||||||
|
|
@ -93,7 +93,7 @@ pub fn get_text(
|
||||||
|
|
||||||
let index = get_index(base_index, 70, step_array.len(), is_dark)
|
let index = get_index(base_index, 70, step_array.len(), is_dark)
|
||||||
.or_else(|| get_index(base_index, 50, step_array.len(), is_dark))
|
.or_else(|| get_index(base_index, 50, step_array.len(), is_dark))
|
||||||
.unwrap_or_else(|| if is_dark { 99 } else { 0 });
|
.unwrap_or(if is_dark { 99 } else { 0 });
|
||||||
|
|
||||||
*step_array.get(index).unwrap_or(fallback)
|
*step_array.get(index).unwrap_or(fallback)
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +145,6 @@ pub fn is_valid_srgb(c: Srgba) -> bool {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use almost::equal;
|
|
||||||
use palette::{OklabHue, Srgba};
|
use palette::{OklabHue, Srgba};
|
||||||
|
|
||||||
use super::{is_valid_srgb, oklch_to_srgba_nearest_chroma};
|
use super::{is_valid_srgb, oklch_to_srgba_nearest_chroma};
|
||||||
|
|
@ -173,57 +172,57 @@ mod tests {
|
||||||
fn test_conversion_boundaries() {
|
fn test_conversion_boundaries() {
|
||||||
let c1 = palette::Oklcha::new(0.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
|
let c1 = palette::Oklcha::new(0.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1);
|
let srgb = oklch_to_srgba_nearest_chroma(c1);
|
||||||
equal(srgb.red, 0.0);
|
almost::zero(srgb.red);
|
||||||
equal(srgb.blue, 0.0);
|
almost::zero(srgb.blue);
|
||||||
equal(srgb.green, 0.0);
|
almost::zero(srgb.green);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(1.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
|
let c1 = palette::Oklcha::new(1.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1);
|
let srgb = oklch_to_srgba_nearest_chroma(c1);
|
||||||
|
|
||||||
equal(srgb.red, 1.0);
|
almost::equal(srgb.red, 1.0);
|
||||||
equal(srgb.blue, 1.0);
|
almost::equal(srgb.blue, 1.0);
|
||||||
equal(srgb.green, 1.0);
|
almost::equal(srgb.green, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_conversion_colors() {
|
fn test_conversion_colors() {
|
||||||
let c1 = palette::Oklcha::new(0.4608, 0.11111, OklabHue::new(57.31), 1.0);
|
let c1 = palette::Oklcha::new(0.4608, 0.11111, OklabHue::new(57.31), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 133);
|
assert_eq!(srgb.red, 133);
|
||||||
assert!(srgb.green == 69);
|
assert_eq!(srgb.green, 69);
|
||||||
assert!(srgb.blue == 0);
|
assert_eq!(srgb.blue, 0);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(0.30, 0.08, OklabHue::new(35.0), 1.0);
|
let c1 = palette::Oklcha::new(0.30, 0.08, OklabHue::new(35.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 78);
|
assert_eq!(srgb.red, 78);
|
||||||
assert!(srgb.green == 27);
|
assert_eq!(srgb.green, 27);
|
||||||
assert!(srgb.blue == 15);
|
assert_eq!(srgb.blue, 15);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(0.757, 0.146, OklabHue::new(301.2), 1.0);
|
let c1 = palette::Oklcha::new(0.757, 0.146, OklabHue::new(301.2), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 192);
|
assert_eq!(srgb.red, 192);
|
||||||
assert!(srgb.green == 153);
|
assert_eq!(srgb.green, 153);
|
||||||
assert!(srgb.blue == 253);
|
assert_eq!(srgb.blue, 253);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_conversion_fallback_colors() {
|
fn test_conversion_fallback_colors() {
|
||||||
let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0);
|
let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 255);
|
assert_eq!(srgb.red, 255);
|
||||||
assert!(srgb.green == 103);
|
assert_eq!(srgb.green, 102);
|
||||||
assert!(srgb.blue == 65);
|
assert_eq!(srgb.blue, 65);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0);
|
let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 193);
|
assert_eq!(srgb.red, 193);
|
||||||
assert!(srgb.green == 152);
|
assert_eq!(srgb.green, 152);
|
||||||
assert!(srgb.blue == 255);
|
assert_eq!(srgb.blue, 255);
|
||||||
|
|
||||||
let c1 = palette::Oklcha::new(0.163, 0.333, OklabHue::new(141.0), 1.0);
|
let c1 = palette::Oklcha::new(0.163, 0.333, OklabHue::new(141.0), 1.0);
|
||||||
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
|
||||||
assert!(srgb.red == 1);
|
assert_eq!(srgb.red, 1);
|
||||||
assert!(srgb.green == 19);
|
assert_eq!(srgb.green, 19);
|
||||||
assert!(srgb.blue == 0);
|
assert_eq!(srgb.blue, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,19 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1.41"
|
open = "5.3.3"
|
||||||
tracing-subscriber = "0.3.19"
|
|
||||||
tracing-log = "0.2.0"
|
|
||||||
open = "5.3.2"
|
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
default-features = false
|
|
||||||
features = [
|
features = [
|
||||||
"debug",
|
"debug",
|
||||||
"winit",
|
"winit",
|
||||||
"tokio",
|
"tokio",
|
||||||
"xdg-portal",
|
"xdg-portal",
|
||||||
"dbus-config",
|
|
||||||
"desktop",
|
"desktop",
|
||||||
"a11y",
|
"a11y",
|
||||||
"wayland",
|
"wayland",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"single-instance",
|
"single-instance",
|
||||||
"multi-window",
|
|
||||||
"about",
|
"about",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,14 @@
|
||||||
|
|
||||||
use cosmic::app::context_drawer::{self, ContextDrawer};
|
use cosmic::app::context_drawer::{self, ContextDrawer};
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced::widget::column;
|
use cosmic::executor;
|
||||||
use cosmic::iced_core::Size;
|
use cosmic::iced::{alignment, Length, Size};
|
||||||
|
use cosmic::prelude::*;
|
||||||
use cosmic::widget::{self, about::About, nav_bar};
|
use cosmic::widget::{self, about::About, nav_bar};
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
|
||||||
|
|
||||||
/// 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>> {
|
||||||
tracing_subscriber::fmt::init();
|
|
||||||
let _ = tracing_log::LogTracer::init();
|
|
||||||
|
|
||||||
let settings = Settings::default()
|
let settings = Settings::default()
|
||||||
.size(Size::new(1024., 768.));
|
.size(Size::new(1024., 768.));
|
||||||
|
|
||||||
|
|
@ -67,11 +64,12 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
let about = About::default()
|
let about = About::default()
|
||||||
.name("About Demo")
|
.name("About Demo")
|
||||||
.icon(Self::APP_ID)
|
.icon(widget::icon::from_name(Self::APP_ID))
|
||||||
.version("0.1.0")
|
.version("0.1.0")
|
||||||
.author("System 76")
|
.author("System76")
|
||||||
.license("GPL-3.0-only")
|
.license("GPL-3.0-only")
|
||||||
.developers([("Michael Murphy", "mmstick@system76.com")])
|
.license_url("https://choosealicense.com/licenses/gpl-3.0/")
|
||||||
|
.developers([("Michael Murphy", "info@system76.com")])
|
||||||
.links([
|
.links([
|
||||||
("Website", "https://system76.com/cosmic"),
|
("Website", "https://system76.com/cosmic"),
|
||||||
("Repository", "https://github.com/pop-os/libcosmic"),
|
("Repository", "https://github.com/pop-os/libcosmic"),
|
||||||
|
|
@ -85,7 +83,11 @@ impl cosmic::Application for App {
|
||||||
show_about: false,
|
show_about: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let command = app.update_title();
|
app.set_header_title("COSMIC About Example".into());
|
||||||
|
let command = app.set_window_title(
|
||||||
|
"COSMIC About Example".into(),
|
||||||
|
app.core.main_window_id().unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
(app, command)
|
(app, command)
|
||||||
}
|
}
|
||||||
|
|
@ -98,12 +100,17 @@ impl cosmic::Application for App {
|
||||||
/// Called when a navigation item is selected.
|
/// Called when a navigation item is selected.
|
||||||
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
|
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
|
||||||
self.nav_model.activate(id);
|
self.nav_model.activate(id);
|
||||||
self.update_title()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn context_drawer(&self) -> Option<ContextDrawer<Self::Message>> {
|
fn context_drawer(&self) -> Option<ContextDrawer<'_, Self::Message>> {
|
||||||
self.show_about
|
self.show_about.then(|| {
|
||||||
.then(|| context_drawer::about(&self.about, Message::Open, Message::ToggleAbout))
|
context_drawer::about(
|
||||||
|
&self.about,
|
||||||
|
|url| Message::Open(url.to_owned()),
|
||||||
|
Message::ToggleAbout,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle application events here.
|
/// Handle application events here.
|
||||||
|
|
@ -115,47 +122,27 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
Message::Open(url) => match open::that_detached(url) {
|
Message::Open(url) => match open::that_detached(url) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => tracing::error!("Failed to open URL: {err}"),
|
Err(err) => eprintln!("Failed to open URL: {err}"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 show_about_button = widget::button::text("Show about").on_press(Message::ToggleAbout);
|
||||||
let centered = cosmic::widget::container(
|
let centered = cosmic::widget::container(
|
||||||
column![widget::button::text("Show about").on_press(Message::ToggleAbout)]
|
widget::column::with_capacity(1)
|
||||||
.width(iced::Length::Fill)
|
.push(show_about_button)
|
||||||
.height(iced::Length::Shrink)
|
.width(Length::Fill)
|
||||||
.align_x(iced::alignment::Horizontal::Center),
|
.height(Length::Shrink)
|
||||||
|
.align_x(alignment::Horizontal::Center),
|
||||||
)
|
)
|
||||||
.width(iced::Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(iced::Length::Shrink)
|
.height(Length::Shrink)
|
||||||
.align_x(iced::alignment::Horizontal::Center)
|
.align_x(alignment::Horizontal::Center)
|
||||||
.align_y(iced::alignment::Vertical::Center);
|
.align_y(alignment::Vertical::Center);
|
||||||
|
|
||||||
Element::from(centered)
|
Element::from(centered)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App
|
|
||||||
where
|
|
||||||
Self: cosmic::Application,
|
|
||||||
{
|
|
||||||
fn active_page_title(&mut self) -> &str {
|
|
||||||
self.nav_model
|
|
||||||
.text(self.nav_model.active())
|
|
||||||
.unwrap_or("Unknown Page")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_title(&mut self) -> Task<Message> {
|
|
||||||
let header_title = self.active_page_title().to_owned();
|
|
||||||
let window_title = format!("{header_title} — COSMIC AppDemo");
|
|
||||||
self.set_header_title(header_title);
|
|
||||||
if let Some(id) = self.core.main_window_id() {
|
|
||||||
self.set_window_title(window_title, id)
|
|
||||||
} else {
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
rust-embed = "8.6.0"
|
rust-embed = "8.11.0"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
env_logger = "0.10.2"
|
env_logger = "0.10.2"
|
||||||
log = "0.4.26"
|
log = "0.4.29"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
git = "https://github.com/pop-os/libcosmic"
|
path = "../../"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["applet-token"]
|
features = ["applet-token"]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use cosmic::app::{Core, Task};
|
use cosmic::app::{Core, Task};
|
||||||
|
|
||||||
|
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::iced_runtime::core::window;
|
|
||||||
use cosmic::surface::action::{app_popup, destroy_popup};
|
use cosmic::surface::action::{app_popup, destroy_popup};
|
||||||
use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler};
|
use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler};
|
||||||
use cosmic::Element;
|
use cosmic::Element;
|
||||||
|
|
@ -13,6 +13,7 @@ pub struct Window {
|
||||||
core: Core,
|
core: Core,
|
||||||
popup: Option<Id>,
|
popup: Option<Id>,
|
||||||
example_row: bool,
|
example_row: bool,
|
||||||
|
toggle: bool,
|
||||||
selected: Option<usize>,
|
selected: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,6 +23,7 @@ impl Default for Window {
|
||||||
core: Core::default(),
|
core: Core::default(),
|
||||||
popup: None,
|
popup: None,
|
||||||
example_row: false,
|
example_row: false,
|
||||||
|
toggle: false,
|
||||||
selected: None,
|
selected: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -33,6 +35,7 @@ pub enum Message {
|
||||||
ToggleExampleRow(bool),
|
ToggleExampleRow(bool),
|
||||||
Selected(usize),
|
Selected(usize),
|
||||||
Surface(cosmic::surface::Action),
|
Surface(cosmic::surface::Action),
|
||||||
|
Toggle(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl cosmic::Application for Window {
|
impl cosmic::Application for Window {
|
||||||
|
|
@ -71,7 +74,6 @@ impl cosmic::Application for Window {
|
||||||
Message::ToggleExampleRow(toggled) => {
|
Message::ToggleExampleRow(toggled) => {
|
||||||
self.example_row = toggled;
|
self.example_row = toggled;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Surface(a) => {
|
Message::Surface(a) => {
|
||||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||||
cosmic::app::Action::Surface(a),
|
cosmic::app::Action::Surface(a),
|
||||||
|
|
@ -80,6 +82,9 @@ impl cosmic::Application for Window {
|
||||||
Message::Selected(i) => {
|
Message::Selected(i) => {
|
||||||
self.selected = Some(i);
|
self.selected = Some(i);
|
||||||
}
|
}
|
||||||
|
Message::Toggle(v) => {
|
||||||
|
self.toggle = v;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -123,9 +128,8 @@ impl cosmic::Application for Window {
|
||||||
"Example row",
|
"Example row",
|
||||||
cosmic::widget::container(
|
cosmic::widget::container(
|
||||||
toggler(state.example_row)
|
toggler(state.example_row)
|
||||||
.on_toggle(|value| Message::ToggleExampleRow(value)),
|
.on_toggle(Message::ToggleExampleRow),
|
||||||
)
|
),
|
||||||
.height(Length::Fixed(50.)),
|
|
||||||
))
|
))
|
||||||
.add(popup_dropdown(
|
.add(popup_dropdown(
|
||||||
&["1", "asdf", "hello", "test"],
|
&["1", "asdf", "hello", "test"],
|
||||||
|
|
@ -155,7 +159,7 @@ impl cosmic::Application for Window {
|
||||||
"oops".into()
|
"oops".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
|
fn style(&self) -> Option<cosmic::iced::theme::Style> {
|
||||||
Some(cosmic::applet::style())
|
Some(cosmic::applet::style())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,18 @@ default = ["wayland"]
|
||||||
wayland = ["libcosmic/wayland"]
|
wayland = ["libcosmic/wayland"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1.41"
|
env_logger = "0.11"
|
||||||
tracing-subscriber = "0.3.19"
|
|
||||||
tracing-log = "0.2.0"
|
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
default-features = false
|
|
||||||
features = [
|
features = [
|
||||||
"debug",
|
"debug",
|
||||||
"winit",
|
"winit",
|
||||||
"tokio",
|
"tokio",
|
||||||
"xdg-portal",
|
"xdg-portal",
|
||||||
"dbus-config",
|
|
||||||
"a11y",
|
"a11y",
|
||||||
"wgpu",
|
|
||||||
"single-instance",
|
"single-instance",
|
||||||
|
"surface-message",
|
||||||
"multi-window",
|
"multi-window",
|
||||||
|
"wgpu",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,14 @@
|
||||||
|
|
||||||
//! Application API example
|
//! Application API example
|
||||||
|
|
||||||
|
use cosmic::app::Settings;
|
||||||
|
use cosmic::iced::{Alignment, Length, Size};
|
||||||
|
use cosmic::widget::menu::{self, KeyBind};
|
||||||
|
use cosmic::widget::nav_bar;
|
||||||
|
use cosmic::{executor, iced, prelude::*, widget, Core};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use cosmic::app::{Core, Settings, Task};
|
|
||||||
use cosmic::iced::alignment::{Horizontal, Vertical};
|
|
||||||
use cosmic::iced::widget::column;
|
|
||||||
use cosmic::iced::Length;
|
|
||||||
use cosmic::iced_core::Size;
|
|
||||||
use cosmic::widget::icon::{from_name, Handle};
|
|
||||||
use cosmic::widget::menu::KeyBind;
|
|
||||||
use cosmic::widget::{button, text};
|
|
||||||
use cosmic::widget::{
|
|
||||||
container,
|
|
||||||
menu::menu_button,
|
|
||||||
menu::{self, action::MenuAction},
|
|
||||||
nav_bar, responsive,
|
|
||||||
};
|
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
|
||||||
|
|
||||||
static MENU_ID: LazyLock<iced::id::Id> = LazyLock::new(|| iced::id::Id::new("menu_id"));
|
static MENU_ID: LazyLock<iced::id::Id> = LazyLock::new(|| iced::id::Id::new("menu_id"));
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
|
@ -46,21 +35,28 @@ impl Page {
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Hi,
|
Hi,
|
||||||
|
Hi2,
|
||||||
|
Hi3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuAction for Action {
|
impl widget::menu::Action for Action {
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
fn message(&self) -> Message {
|
fn message(&self) -> Message {
|
||||||
Message::Hi
|
match self {
|
||||||
|
Action::Hi => Message::Hi,
|
||||||
|
Action::Hi2 => Message::Hi2,
|
||||||
|
Action::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>> {
|
||||||
// tracing_subscriber::fmt::init();
|
|
||||||
// let _ = tracing_log::LogTracer::init();
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||||
|
|
||||||
|
|
||||||
let input = vec![
|
let input = vec![
|
||||||
(Page::Page1, "🖖 Hello from libcosmic.".into()),
|
(Page::Page1, "🖖 Hello from libcosmic.".into()),
|
||||||
|
|
@ -71,9 +67,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
let settings = Settings::default()
|
let settings = Settings::default()
|
||||||
.size(Size::new(1024., 768.));
|
.size(Size::new(1024., 768.));
|
||||||
|
cosmic::app::run::<App>(settings, input).unwrap();
|
||||||
cosmic::app::run::<App>(settings, input)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,6 +80,9 @@ pub enum Message {
|
||||||
ToggleHide,
|
ToggleHide,
|
||||||
Surface(cosmic::surface::Action),
|
Surface(cosmic::surface::Action),
|
||||||
Hi,
|
Hi,
|
||||||
|
Hi2,
|
||||||
|
Hi3,
|
||||||
|
Tick,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [`App`] stores application-specific state.
|
/// The [`App`] stores application-specific state.
|
||||||
|
|
@ -96,6 +93,7 @@ pub struct App {
|
||||||
input_2: String,
|
input_2: String,
|
||||||
hidden: bool,
|
hidden: bool,
|
||||||
keybinds: HashMap<KeyBind, Action>,
|
keybinds: HashMap<KeyBind, Action>,
|
||||||
|
progress: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||||
|
|
@ -121,7 +119,7 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the application, and optionally emits task on initialize.
|
/// Creates the application, and optionally emits task on initialize.
|
||||||
fn init(core: Core, input: Self::Flags) -> (Self, Task<Self::Message>) {
|
fn init(core: Core, input: Self::Flags) -> (Self, cosmic::app::Task<Self::Message>) {
|
||||||
let mut nav_model = nav_bar::Model::default();
|
let mut nav_model = nav_bar::Model::default();
|
||||||
|
|
||||||
for (title, content) in input {
|
for (title, content) in input {
|
||||||
|
|
@ -137,6 +135,7 @@ impl cosmic::Application for App {
|
||||||
input_2: String::new(),
|
input_2: String::new(),
|
||||||
hidden: true,
|
hidden: true,
|
||||||
keybinds: HashMap::new(),
|
keybinds: HashMap::new(),
|
||||||
|
progress: 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let command = app.update_title();
|
let command = app.update_title();
|
||||||
|
|
@ -150,13 +149,13 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a navigation item is selected.
|
/// Called when a navigation item is selected.
|
||||||
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
|
fn on_nav_select(&mut self, id: nav_bar::Id) -> cosmic::app::Task<Self::Message> {
|
||||||
self.nav_model.activate(id);
|
self.nav_model.activate(id);
|
||||||
self.update_title()
|
self.update_title()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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) -> cosmic::app::Task<Self::Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Input1(v) => {
|
Message::Input1(v) => {
|
||||||
self.input_1 = v;
|
self.input_1 = v;
|
||||||
|
|
@ -176,164 +175,182 @@ impl cosmic::Application for App {
|
||||||
Message::Hi => {
|
Message::Hi => {
|
||||||
dbg!("hi");
|
dbg!("hi");
|
||||||
}
|
}
|
||||||
|
Message::Hi2 => {
|
||||||
|
dbg!("hi 2");
|
||||||
|
}
|
||||||
|
Message::Hi3 => {
|
||||||
|
dbg!("hi 3");
|
||||||
|
}
|
||||||
|
Message::Tick => {
|
||||||
|
self.progress = (self.progress + 0.01) % 1.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> iced::Subscription<Self::Message> {
|
||||||
|
iced::time::every(std::time::Duration::from_millis(64)).map(|_| Message::Tick)
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 page_content = self
|
let page_content = self
|
||||||
.nav_model
|
.nav_model
|
||||||
.active_data::<String>()
|
.active_data::<String>()
|
||||||
.map_or("No page selected", String::as_str);
|
.map_or("No page selected", String::as_str);
|
||||||
|
|
||||||
let text = cosmic::widget::text(page_content);
|
let centered = widget::container(
|
||||||
|
widget::column::with_capacity(14)
|
||||||
let centered = cosmic::widget::container(
|
.push(widget::text::body(page_content))
|
||||||
column![
|
.push(
|
||||||
text,
|
widget::text_input::text_input("", &self.input_1)
|
||||||
cosmic::widget::text_input::text_input("", &self.input_1)
|
.on_input(Message::Input1)
|
||||||
.on_input(Message::Input1)
|
.on_clear(Message::Ignore),
|
||||||
.on_clear(Message::Ignore),
|
|
||||||
cosmic::widget::text_input::secure_input(
|
|
||||||
"",
|
|
||||||
&self.input_1,
|
|
||||||
Some(Message::ToggleHide),
|
|
||||||
self.hidden
|
|
||||||
)
|
)
|
||||||
.on_input(Message::Input1),
|
.push(
|
||||||
cosmic::widget::text_input::text_input("", &self.input_1).on_input(Message::Input1),
|
widget::text_input::secure_input(
|
||||||
cosmic::widget::text_input::search_input("", &self.input_2)
|
"",
|
||||||
.on_input(Message::Input2)
|
&self.input_1,
|
||||||
.on_clear(Message::Ignore),
|
Some(Message::ToggleHide),
|
||||||
]
|
self.hidden,
|
||||||
.spacing(cosmic::theme::spacing().space_s)
|
)
|
||||||
.width(iced::Length::Fill)
|
.on_input(Message::Input1),
|
||||||
.height(iced::Length::Shrink)
|
)
|
||||||
.align_x(iced::Alignment::Center),
|
.push(widget::text_input::text_input("", &self.input_2).on_input(Message::Input2))
|
||||||
|
.push(
|
||||||
|
widget::text_input::search_input("", &self.input_2)
|
||||||
|
.on_input(Message::Input2)
|
||||||
|
.on_clear(Message::Ignore),
|
||||||
|
)
|
||||||
|
.push(widget::progress_bar::circular::Circular::new().size(50.0))
|
||||||
|
.push(widget::progress_bar::circular::Circular::new().size(20.0))
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::linear::Linear::new()
|
||||||
|
.girth(10.0)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::circular::Circular::new()
|
||||||
|
.bar_height(10.0)
|
||||||
|
.size(50.0)
|
||||||
|
.progress(self.progress),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::linear::Linear::new()
|
||||||
|
.girth(10.0)
|
||||||
|
.progress(self.progress)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::circular::Circular::new()
|
||||||
|
.size(50.0)
|
||||||
|
.progress(0.0),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::linear::Linear::new()
|
||||||
|
.girth(10.0)
|
||||||
|
.progress(0.0)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::circular::Circular::new()
|
||||||
|
.size(50.0)
|
||||||
|
.progress(1.0),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::linear::Linear::new()
|
||||||
|
.girth(10.0)
|
||||||
|
.progress(1.0)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.spacing(cosmic::theme::spacing().space_s)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Shrink)
|
||||||
|
.align_x(Alignment::Center),
|
||||||
)
|
)
|
||||||
.width(iced::Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(iced::Length::Shrink)
|
.height(Length::Shrink)
|
||||||
.align_x(iced::Alignment::Center)
|
.align_x(Alignment::Center)
|
||||||
.align_y(iced::Alignment::Center);
|
.align_y(Alignment::Center);
|
||||||
|
|
||||||
Element::from(centered)
|
Element::from(centered)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
fn header_start(&self) -> Vec<Element<'_, Self::Message>> {
|
||||||
use cosmic::widget::menu::Tree;
|
vec![cosmic::widget::responsive_menu_bar().into_element(
|
||||||
#[cfg(not(feature = "wayland"))]
|
self.core(),
|
||||||
{
|
&self.keybinds,
|
||||||
vec![cosmic::widget::menu::bar(vec![
|
MENU_ID.clone(),
|
||||||
Tree::with_children(
|
Message::Surface,
|
||||||
menu::root("hiiiiiiiiiiiiiiiiiii 1"),
|
vec![
|
||||||
menu::items(
|
(
|
||||||
&self.keybinds,
|
"hi 1".into(),
|
||||||
vec![menu::Item::Button("hi", None, Action::Hi)],
|
vec![
|
||||||
),
|
menu::Item::Button("hi 12", None, Action::Hi),
|
||||||
|
menu::Item::Button("hi 13", None, Action::Hi2),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Tree::with_children(
|
(
|
||||||
menu::root("hiiiiiiiiiiiiiiiiii 2"),
|
"hi 2".into(),
|
||||||
menu::items(
|
vec![
|
||||||
&self.keybinds,
|
menu::Item::Button("hi 21", None, Action::Hi),
|
||||||
vec![menu::Item::Button("hi 2", None, Action::Hi)],
|
menu::Item::Button("hi 22", None, Action::Hi2),
|
||||||
),
|
menu::Item::Folder(
|
||||||
|
"nest 3 2 >".into(),
|
||||||
|
vec![
|
||||||
|
menu::Item::Button("21", None, Action::Hi),
|
||||||
|
menu::Item::Button("242", None, Action::Hi2),
|
||||||
|
menu::Item::Button("2443", None, Action::Hi3),
|
||||||
|
menu::Item::Folder(
|
||||||
|
"nest 4 2 >".into(),
|
||||||
|
vec![
|
||||||
|
menu::Item::Button("243", None, Action::Hi2),
|
||||||
|
menu::Item::Button("2444", None, Action::Hi),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Tree::with_children(
|
(
|
||||||
menu::root("hiiiiiiiiiiiiiiiiiiiii 3"),
|
"hi 3".into(),
|
||||||
menu::items(
|
vec![
|
||||||
&self.keybinds,
|
menu::Item::Button("hi 31", None, Action::Hi),
|
||||||
vec![
|
menu::Item::Button("hi 332", None, Action::Hi2),
|
||||||
menu::Item::Button("hi 3", None, Action::Hi),
|
menu::Item::Button("hi 3333", None, Action::Hi3),
|
||||||
menu::Item::Button("hi 3 #2", None, Action::Hi),
|
menu::Item::Button("hi 33334", None, Action::Hi3),
|
||||||
],
|
menu::Item::Button("hi 333335", None, Action::Hi3),
|
||||||
),
|
menu::Item::Button("hi 3333336", None, Action::Hi3),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Tree::with_children(
|
(
|
||||||
menu::root("hi 3"),
|
"hiiiiiiiiiiiiiiiiiii 4".into(),
|
||||||
menu::items(
|
vec![
|
||||||
&self.keybinds,
|
menu::Item::Button("hi 4", None, Action::Hi),
|
||||||
vec![
|
menu::Item::Button("hi 44", None, Action::Hi2),
|
||||||
menu::Item::Button("hi 3", None, Action::Hi),
|
menu::Item::Button("hi 444", None, Action::Hi3),
|
||||||
menu::Item::Button("hi 3 #2", None, Action::Hi),
|
menu::Item::Folder(
|
||||||
menu::Item::Button("hi 3 #3", None, Action::Hi),
|
"nest 4 >".into(),
|
||||||
],
|
vec![
|
||||||
),
|
menu::Item::Button("hi 41", None, Action::Hi),
|
||||||
|
menu::Item::Button("hi 442", None, Action::Hi2),
|
||||||
|
menu::Item::Folder(
|
||||||
|
"nest 3 4 >".into(),
|
||||||
|
vec![
|
||||||
|
menu::Item::Button("hi 443", None, Action::Hi2),
|
||||||
|
menu::Item::Button("hi 4444", None, Action::Hi),
|
||||||
|
menu::Item::Button("hi 44444", None, Action::Hi3),
|
||||||
|
menu::Item::Button("hi 444445", None, Action::Hi3),
|
||||||
|
menu::Item::Button("hi 4444446", None, Action::Hi3),
|
||||||
|
menu::Item::Button("hi 44444447", None, Action::Hi3),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Tree::with_children(
|
],
|
||||||
menu::root("hi 4"),
|
)]
|
||||||
menu::items(
|
|
||||||
&self.keybinds,
|
|
||||||
vec![
|
|
||||||
menu::Item::Folder(
|
|
||||||
"hi 41 extra root",
|
|
||||||
vec![menu::Item::Button("hi 3", None, Action::Hi)],
|
|
||||||
),
|
|
||||||
menu::Item::Button("hi 42", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 43", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 44", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 45", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 46", None, Action::Hi),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.into()]
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
{
|
|
||||||
vec![cosmic::widget::responsive_menu_bar().into_element(
|
|
||||||
self.core(),
|
|
||||||
&self.keybinds,
|
|
||||||
MENU_ID.clone(),
|
|
||||||
Message::Surface,
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
"hiiiiiiiiiiiiiiiiiii 1",
|
|
||||||
vec![menu::Item::Button("hi 1", None, Action::Hi)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"hiiiiiiiiiiiiiiiiiii 2".into(),
|
|
||||||
vec![
|
|
||||||
menu::Item::Button("hi 2", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 22", None, Action::Hi),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"hiiiiiiiiiiiiiiiiiii 3".into(),
|
|
||||||
vec![
|
|
||||||
menu::Item::Button("hi 3", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 33", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 333", None, Action::Hi),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"hiiiiiiiiiiiiiiiiiii 4".into(),
|
|
||||||
vec![
|
|
||||||
menu::Item::Button("hi 4", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 44", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 444", None, Action::Hi),
|
|
||||||
menu::Item::Folder(
|
|
||||||
"nest 4".into(),
|
|
||||||
vec![
|
|
||||||
menu::Item::Button("hi 4", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 44", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 444", None, Action::Hi),
|
|
||||||
menu::Item::Folder(
|
|
||||||
"nest 2 4".into(),
|
|
||||||
vec![
|
|
||||||
menu::Item::Button("hi 4", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 44", None, Action::Hi),
|
|
||||||
menu::Item::Button("hi 444", None, Action::Hi),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,7 +364,7 @@ where
|
||||||
.unwrap_or("Unknown Page")
|
.unwrap_or("Unknown Page")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_title(&mut self) -> Task<Message> {
|
fn update_title(&mut self) -> cosmic::app::Task<Message> {
|
||||||
let header_title = self.active_page_title().to_owned();
|
let header_title = self.active_page_title().to_owned();
|
||||||
let window_title = format!("{header_title} — COSMIC AppDemo");
|
let window_title = format!("{header_title} — COSMIC AppDemo");
|
||||||
self.set_header_title(header_title);
|
self.set_header_title(header_title);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
[package]
|
[package]
|
||||||
name = "calendar"
|
name = "calendar"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# 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]
|
||||||
chrono = "0.4.40"
|
jiff = "0.2"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
default-features = false
|
|
||||||
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]
|
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
|
|
||||||
//! Calendar widget example
|
//! Calendar widget example
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::widget::calendar::CalendarModel;
|
use cosmic::widget::calendar::CalendarModel;
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
use cosmic::{ApplicationExt, Element, executor, iced};
|
||||||
|
use jiff::civil::{Date, Weekday};
|
||||||
|
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
@ -19,7 +19,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// Messages that are used specifically by our [`App`].
|
/// Messages that are used specifically by our [`App`].
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
DateSelected(NaiveDate),
|
DateSelected(Date),
|
||||||
PrevMonth,
|
PrevMonth,
|
||||||
NextMonth,
|
NextMonth,
|
||||||
}
|
}
|
||||||
|
|
@ -84,19 +84,16 @@ 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 mut content = cosmic::widget::column().spacing(12);
|
|
||||||
|
|
||||||
let calendar = cosmic::widget::calendar(
|
let calendar = cosmic::widget::calendar(
|
||||||
&self.calendar_model,
|
&self.calendar_model,
|
||||||
|date| Message::DateSelected(date),
|
|date| Message::DateSelected(date),
|
||||||
|| Message::PrevMonth,
|
|| Message::PrevMonth,
|
||||||
|| Message::NextMonth,
|
|| Message::NextMonth,
|
||||||
|
Weekday::Sunday,
|
||||||
);
|
);
|
||||||
|
|
||||||
content = content.push(calendar);
|
let centered = cosmic::widget::container(calendar)
|
||||||
|
|
||||||
let centered = cosmic::widget::container(content)
|
|
||||||
.width(iced::Length::Fill)
|
.width(iced::Length::Fill)
|
||||||
.height(iced::Length::Shrink)
|
.height(iced::Length::Shrink)
|
||||||
.align_x(iced::Alignment::Center)
|
.align_x(iced::Alignment::Center)
|
||||||
|
|
@ -110,8 +107,11 @@ impl App
|
||||||
where
|
where
|
||||||
Self: cosmic::Application,
|
Self: cosmic::Application,
|
||||||
{
|
{
|
||||||
fn update_title(&mut self) -> Task<Message> {
|
fn update_title(&mut self) -> cosmic::app::Task<Message> {
|
||||||
self.set_header_title(String::from("Calendar Demo"));
|
self.set_header_title(String::from("Calendar Demo"));
|
||||||
self.set_window_title(String::from("Calendar Demo"))
|
self.set_window_title(
|
||||||
|
String::from("Calendar Demo"),
|
||||||
|
self.core.main_window_id().unwrap(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
use cosmic_config::{Config, ConfigGet, ConfigSet};
|
use cosmic_config::{Config, ConfigGet, ConfigSet};
|
||||||
|
|
||||||
fn test_config(config: Config) {
|
fn test_config(config: Config) {
|
||||||
let watcher = config
|
let _watcher = config
|
||||||
.watch(|config, keys| {
|
.watch(|config, keys| {
|
||||||
println!("Changed: {:?}", keys);
|
println!("Changed: {:?}", keys);
|
||||||
for key in keys.iter() {
|
for key in keys.iter() {
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,18 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.22"
|
||||||
tracing-log = "0.2.0"
|
tracing-log = "0.2.0"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
default-features = false
|
features = [
|
||||||
features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"]
|
"debug",
|
||||||
|
"winit",
|
||||||
|
"wgpu",
|
||||||
|
"tokio",
|
||||||
|
"xdg-portal",
|
||||||
|
"surface-message",
|
||||||
|
"wayland",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
//! Application API example
|
//! Application API example
|
||||||
|
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced_core::Size;
|
use cosmic::iced::Size;
|
||||||
use cosmic::widget::menu;
|
use cosmic::widget::menu;
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -27,9 +27,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Clicked,
|
Clicked,
|
||||||
ShowContext,
|
|
||||||
WindowClose,
|
WindowClose,
|
||||||
ShowWindowMenu,
|
Surface(cosmic::surface::Action),
|
||||||
ToggleHideContent,
|
ToggleHideContent,
|
||||||
WindowNew,
|
WindowNew,
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +37,6 @@ pub enum Message {
|
||||||
pub struct App {
|
pub struct App {
|
||||||
core: Core,
|
core: Core,
|
||||||
button_label: String,
|
button_label: String,
|
||||||
show_context: bool,
|
|
||||||
hide_content: bool,
|
hide_content: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +68,6 @@ impl cosmic::Application for App {
|
||||||
core,
|
core,
|
||||||
button_label: String::from("Right click me"),
|
button_label: String::from("Right click me"),
|
||||||
hide_content: false,
|
hide_content: false,
|
||||||
show_context: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
app.set_header_title("COSMIC Context Menu Demo".into());
|
app.set_header_title("COSMIC Context Menu Demo".into());
|
||||||
|
|
@ -85,17 +82,30 @@ 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> {
|
||||||
self.button_label = format!("Clicked {message:?}");
|
match message {
|
||||||
|
Message::Clicked => {
|
||||||
|
self.button_label = format!("Clicked {message:?}");
|
||||||
|
}
|
||||||
|
Message::Surface(action) => {
|
||||||
|
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||||
|
cosmic::app::Action::Surface(action),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Message::WindowClose => {}
|
||||||
|
Message::ToggleHideContent => {}
|
||||||
|
Message::WindowNew => {}
|
||||||
|
}
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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).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);
|
||||||
|
|
||||||
let centered = cosmic::widget::container(widget)
|
let centered = cosmic::widget::container(widget)
|
||||||
.width(iced::Length::Fill)
|
.width(iced::Length::Fill)
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ libcosmic = { path = "../..", features = [
|
||||||
"xdg-portal",
|
"xdg-portal",
|
||||||
] }
|
] }
|
||||||
once_cell = "1.21"
|
once_cell = "1.21"
|
||||||
slotmap = "1.0.7"
|
slotmap = "1.1.1"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
log = "0.4.26"
|
log = "0.4.29"
|
||||||
|
|
||||||
[dependencies.cosmic-time]
|
[dependencies.cosmic-time]
|
||||||
git = "https://github.com/pop-os/cosmic-time"
|
git = "https://github.com/pop-os/cosmic-time"
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,14 @@ impl State {
|
||||||
column!(
|
column!(
|
||||||
list_column().add(settings::item(
|
list_column().add(settings::item(
|
||||||
"Bluetooth",
|
"Bluetooth",
|
||||||
toggler(None, self.enabled, Message::Enable)
|
toggler(self.enabled).on_toggle(Message::Enable)
|
||||||
)),
|
)),
|
||||||
text("Now visible as \"TODO\", just kidding")
|
text("Now visible as \"TODO\", just kidding")
|
||||||
)
|
)
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Devices")
|
settings::section()
|
||||||
|
.title("Devices")
|
||||||
.add(settings::item("No devices found", text("")))
|
.add(settings::item("No devices found", text("")))
|
||||||
.into(),
|
.into(),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -258,12 +258,13 @@ impl State {
|
||||||
match self.tab_bar.active_data() {
|
match self.tab_bar.active_data() {
|
||||||
None => panic!("no tab is active"),
|
None => panic!("no tab is active"),
|
||||||
Some(DemoView::TabA) => settings::view_column(vec![
|
Some(DemoView::TabA) => settings::view_column(vec![
|
||||||
settings::view_section("Debug")
|
settings::section()
|
||||||
|
.title("Debug")
|
||||||
.add(settings::item("Debug theme", choose_theme))
|
.add(settings::item("Debug theme", choose_theme))
|
||||||
.add(settings::item("Debug icon theme", choose_icon_theme))
|
.add(settings::item("Debug icon theme", choose_icon_theme))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Debug layout",
|
"Debug layout",
|
||||||
toggler(None, window.debug, Message::Debug),
|
toggler(window.debug).on_toggle(Message::Debug),
|
||||||
))
|
))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Scaling Factor",
|
"Scaling Factor",
|
||||||
|
|
@ -276,10 +277,11 @@ impl State {
|
||||||
.into(),
|
.into(),
|
||||||
]))
|
]))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Controls")
|
settings::section()
|
||||||
|
.title("Controls")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Toggler",
|
"Toggler",
|
||||||
toggler(None, self.toggler_value, Message::TogglerToggled),
|
toggler(self.toggler_value).on_toggle(Message::TogglerToggled),
|
||||||
))
|
))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Pick List (TODO)",
|
"Pick List (TODO)",
|
||||||
|
|
@ -299,15 +301,13 @@ impl State {
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Progress",
|
"Progress",
|
||||||
progress_bar(0.0..=100.0, self.slider_value)
|
progress_bar(0.0..=100.0, self.slider_value)
|
||||||
.width(Length::Fixed(250.0))
|
.length(Length::Fixed(250.0))
|
||||||
.height(Length::Fixed(4.0)),
|
.girth(Length::Fixed(4.0)),
|
||||||
))
|
))
|
||||||
.add(settings::item_row(vec![checkbox(
|
.add(settings::item_row(vec![checkbox(self.checkbox_value)
|
||||||
"Checkbox",
|
.label("Checkbox")
|
||||||
self.checkbox_value,
|
.on_toggle(Message::CheckboxToggled)
|
||||||
Message::CheckboxToggled,
|
.into()]))
|
||||||
)
|
|
||||||
.into()]))
|
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
format!(
|
format!(
|
||||||
"Spin Button (Range {}:{})",
|
"Spin Button (Range {}:{})",
|
||||||
|
|
@ -354,8 +354,7 @@ impl State {
|
||||||
.width(Length::Shrink)
|
.width(Length::Shrink)
|
||||||
.on_activate(Message::MultiSelection)
|
.on_activate(Message::MultiSelection)
|
||||||
.apply(container)
|
.apply(container)
|
||||||
.center_x()
|
.center_x(Length::Fill)
|
||||||
.width(Length::Fill)
|
|
||||||
.into(),
|
.into(),
|
||||||
text("Vertical With Spacing").into(),
|
text("Vertical With Spacing").into(),
|
||||||
cosmic::iced::widget::row(vec![
|
cosmic::iced::widget::row(vec![
|
||||||
|
|
@ -424,13 +423,12 @@ impl State {
|
||||||
])
|
])
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.into(),
|
.into(),
|
||||||
Some(DemoView::TabC) => {
|
Some(DemoView::TabC) => settings::view_column(vec![settings::section()
|
||||||
settings::view_column(vec![settings::view_section("Tab C")
|
.title("Tab C")
|
||||||
.add(text("Nothing here yet").width(Length::Fill))
|
.add(text("Nothing here yet").width(Length::Fill))
|
||||||
.into()])
|
.into()])
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.into()
|
.into(),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
container(text("Background container with some text").size(24))
|
container(text("Background container with some text").size(24))
|
||||||
.layer(cosmic_theme::Layer::Background)
|
.layer(cosmic_theme::Layer::Background)
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,8 @@ impl State {
|
||||||
fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
window.parent_page_button(DesktopPage::DesktopOptions),
|
window.parent_page_button(DesktopPage::DesktopOptions),
|
||||||
settings::view_section("Super Key Action")
|
settings::section()
|
||||||
|
.title("Super Key Action")
|
||||||
.add(settings::item("Launcher", horizontal_space(Length::Fill)))
|
.add(settings::item("Launcher", horizontal_space(Length::Fill)))
|
||||||
.add(settings::item("Workspaces", horizontal_space(Length::Fill)))
|
.add(settings::item("Workspaces", horizontal_space(Length::Fill)))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
|
|
@ -155,38 +156,34 @@ impl State {
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Hot Corner")
|
settings::section()
|
||||||
|
.title("Hot Corner")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Enable top-left hot corner for Workspaces",
|
"Enable top-left hot corner for Workspaces",
|
||||||
toggler(None, self.top_left_hot_corner, Message::TopLeftHotCorner),
|
toggler(self.top_left_hot_corner).on_toggle(Message::TopLeftHotCorner),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Top Panel")
|
settings::section()
|
||||||
|
.title("Top Panel")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Show Workspaces Button",
|
"Show Workspaces Button",
|
||||||
toggler(
|
toggler(self.show_workspaces_button).on_toggle(Message::ShowWorkspacesButton),
|
||||||
None,
|
|
||||||
self.show_workspaces_button,
|
|
||||||
Message::ShowWorkspacesButton,
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Show Applications Button",
|
"Show Applications Button",
|
||||||
toggler(
|
toggler(self.show_applications_button)
|
||||||
None,
|
.on_toggle(Message::ShowApplicationsButton),
|
||||||
self.show_applications_button,
|
|
||||||
Message::ShowApplicationsButton,
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Window Controls")
|
settings::section()
|
||||||
|
.title("Window Controls")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Show Minimize Button",
|
"Show Minimize Button",
|
||||||
toggler(None, self.show_minimize_button, Message::ShowMinimizeButton),
|
toggler(self.show_minimize_button).on_toggle(Message::ShowMinimizeButton),
|
||||||
))
|
))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Show Maximize Button",
|
"Show Maximize Button",
|
||||||
toggler(None, self.show_maximize_button, Message::ShowMaximizeButton),
|
toggler(self.show_maximize_button).on_toggle(Message::ShowMaximizeButton),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
])
|
])
|
||||||
|
|
@ -245,12 +242,12 @@ impl State {
|
||||||
list_column()
|
list_column()
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Same background on all displays",
|
"Same background on all displays",
|
||||||
toggler(None, self.same_background, Message::SameBackground),
|
toggler(self.same_background).on_toggle(Message::SameBackground),
|
||||||
))
|
))
|
||||||
.add(settings::item("Background fit", text("TODO")))
|
.add(settings::item("Background fit", text("TODO")))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Slideshow",
|
"Slideshow",
|
||||||
toggler(None, self.slideshow, Message::Slideshow),
|
toggler(self.slideshow).on_toggle(Message::Slideshow),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
column(image_column).spacing(16).into(),
|
column(image_column).spacing(16).into(),
|
||||||
|
|
@ -261,7 +258,8 @@ impl State {
|
||||||
fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
window.parent_page_button(DesktopPage::Wallpaper),
|
window.parent_page_button(DesktopPage::Wallpaper),
|
||||||
settings::view_section("Workspace Behavior")
|
settings::section()
|
||||||
|
.title("Workspace Behavior")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Dynamic workspaces",
|
"Dynamic workspaces",
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
|
|
@ -271,7 +269,8 @@ impl State {
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
))
|
))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Multi-monitor Behavior")
|
settings::section()
|
||||||
|
.title("Multi-monitor Behavior")
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Workspaces Span Displays",
|
"Workspaces Span Displays",
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
|
|
|
||||||
|
|
@ -69,14 +69,16 @@ impl State {
|
||||||
list_column()
|
list_column()
|
||||||
.add(settings::item("Device name", text("TODO")))
|
.add(settings::item("Device name", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Hardware")
|
settings::section()
|
||||||
|
.title("Hardware")
|
||||||
.add(settings::item("Hardware model", text("TODO")))
|
.add(settings::item("Hardware model", text("TODO")))
|
||||||
.add(settings::item("Memory", text("TODO")))
|
.add(settings::item("Memory", text("TODO")))
|
||||||
.add(settings::item("Processor", text("TODO")))
|
.add(settings::item("Processor", text("TODO")))
|
||||||
.add(settings::item("Graphics", text("TODO")))
|
.add(settings::item("Graphics", text("TODO")))
|
||||||
.add(settings::item("Disk Capacity", text("TODO")))
|
.add(settings::item("Disk Capacity", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Operating System")
|
settings::section()
|
||||||
|
.title("Operating System")
|
||||||
.add(settings::item("Operating system", text("TODO")))
|
.add(settings::item("Operating system", text("TODO")))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Operating system architecture",
|
"Operating system architecture",
|
||||||
|
|
@ -85,7 +87,8 @@ impl State {
|
||||||
.add(settings::item("Desktop environment", text("TODO")))
|
.add(settings::item("Desktop environment", text("TODO")))
|
||||||
.add(settings::item("Windowing system", text("TODO")))
|
.add(settings::item("Windowing system", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
settings::view_section("Related settings")
|
settings::section()
|
||||||
|
.title("Related settings")
|
||||||
.add(settings::item("Get support", text("TODO")))
|
.add(settings::item("Get support", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,9 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.22"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
default-features = false
|
features = ["debug", "winit", "wgpu", "tokio"]
|
||||||
features = ["debug", "winit", "tokio"]
|
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ 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 mut content = cosmic::widget::column().spacing(12);
|
let mut content = cosmic::widget::column::with_capacity(self.images.len()).spacing(12);
|
||||||
|
|
||||||
for (id, image) in self.images.iter().enumerate() {
|
for (id, image) in self.images.iter().enumerate() {
|
||||||
content = content.push(
|
content = content.push(
|
||||||
|
|
@ -108,6 +108,9 @@ where
|
||||||
{
|
{
|
||||||
fn update_title(&mut self) -> Task<Message> {
|
fn update_title(&mut self) -> Task<Message> {
|
||||||
self.set_header_title(String::from("Image Button Demo"));
|
self.set_header_title(String::from("Image Button Demo"));
|
||||||
self.set_window_title(String::from("Image Button Demo"))
|
self.set_window_title(
|
||||||
|
String::from("Image Button Demo"),
|
||||||
|
self.core.main_window_id().unwrap(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.22"
|
||||||
tracing-log = "0.2.0"
|
tracing-log = "0.2.0"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
default-features = false
|
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]
|
||||||
features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"]
|
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,15 @@ use std::collections::HashMap;
|
||||||
use std::{env, process};
|
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::keyboard::Key;
|
||||||
use cosmic::iced::window;
|
use cosmic::iced::window;
|
||||||
use cosmic::iced_core::alignment::{Horizontal, Vertical};
|
use cosmic::iced::{Length, Size};
|
||||||
use cosmic::iced_core::keyboard::Key;
|
|
||||||
use cosmic::iced_core::{Length, Size};
|
|
||||||
use cosmic::widget::menu::action::MenuAction;
|
use cosmic::widget::menu::action::MenuAction;
|
||||||
use cosmic::widget::menu::key_bind::KeyBind;
|
use cosmic::widget::menu::key_bind::KeyBind;
|
||||||
use cosmic::widget::menu::key_bind::Modifier;
|
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::{executor, Element};
|
use cosmic::{executor, Element};
|
||||||
|
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
|
|
@ -109,7 +110,7 @@ impl cosmic::Application for App {
|
||||||
(app, Task::none())
|
(app, Task::none())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
fn header_start(&self) -> Vec<Element<'_, Self::Message>> {
|
||||||
vec![menu_bar(&self.config, &self.key_binds)]
|
vec![menu_bar(&self.config, &self.key_binds)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +137,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 text = if self.config.hide_content {
|
let text = if self.config.hide_content {
|
||||||
cosmic::widget::text("")
|
cosmic::widget::text("")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -155,7 +156,7 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
|
pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
|
||||||
menu::bar(vec![menu::Tree::with_children(
|
menu::bar(vec![menu::Tree::with_children(
|
||||||
menu::root("File"),
|
RcElementWrapper::new(Element::from(menu::root("File"))),
|
||||||
menu::items(
|
menu::items(
|
||||||
key_binds,
|
key_binds,
|
||||||
vec![
|
vec![
|
||||||
|
|
|
||||||
|
|
@ -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 = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "multi-window", "dbus-config", "wgpu"] }
|
libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "wgpu", "wayland"] }
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
app::Core,
|
app::Core,
|
||||||
iced::{self, event, window},
|
iced::core::{id, Alignment, Length, Point},
|
||||||
iced_core::{id, Alignment, Length, Point},
|
iced::widget::{column, container, scrollable, text},
|
||||||
iced_widget::{column, container, scrollable, text},
|
iced::{self, event, window, Subscription},
|
||||||
|
prelude::*,
|
||||||
widget::{button, header_bar},
|
widget::{button, header_bar},
|
||||||
ApplicationExt, Task,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
@ -57,7 +57,7 @@ impl cosmic::Application for MultiWindow {
|
||||||
(windows, cosmic::app::Task::none())
|
(windows, cosmic::app::Task::none())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> cosmic::iced_futures::Subscription<Self::Message> {
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
event::listen_with(|event, _, id| {
|
event::listen_with(|event, _, id| {
|
||||||
if let iced::Event::Window(window_event) = event {
|
if let iced::Event::Window(window_event) = event {
|
||||||
match window_event {
|
match window_event {
|
||||||
|
|
@ -74,7 +74,7 @@ impl cosmic::Application for MultiWindow {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Message) -> iced::Task<cosmic::Action<Self::Message>> {
|
fn update(&mut self, message: Self::Message) -> Task<cosmic::Action<Self::Message>> {
|
||||||
match message {
|
match message {
|
||||||
Message::CloseWindow(id) => window::close(id),
|
Message::CloseWindow(id) => window::close(id),
|
||||||
Message::WindowClosed(id) => {
|
Message::WindowClosed(id) => {
|
||||||
|
|
@ -119,7 +119,7 @@ impl cosmic::Application for MultiWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_window(&self, id: window::Id) -> cosmic::prelude::Element<Self::Message> {
|
fn view_window(&self, id: window::Id) -> Element<'_, Self::Message> {
|
||||||
let w = self.windows.get(&id).unwrap();
|
let w = self.windows.get(&id).unwrap();
|
||||||
|
|
||||||
let input_id = w.input_id.clone();
|
let input_id = w.input_id.clone();
|
||||||
|
|
@ -152,7 +152,7 @@ impl cosmic::Application for MultiWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> cosmic::prelude::Element<Self::Message> {
|
fn view(&self) -> Element<'_, Self::Message> {
|
||||||
self.view_window(self.core.main_window_id().unwrap())
|
self.view_window(self.core.main_window_id().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.22"
|
||||||
tracing-log = "0.2.0"
|
tracing-log = "0.2.0"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
default-features = false
|
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]
|
||||||
features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"]
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced_core::Size;
|
use cosmic::iced::Size;
|
||||||
use cosmic::widget::{menu, nav_bar};
|
use cosmic::widget::{menu, nav_bar};
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||||
|
|
||||||
|
|
@ -172,7 +172,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 page_content = self
|
let page_content = self
|
||||||
.nav_model
|
.nav_model
|
||||||
.active_data::<String>()
|
.active_data::<String>()
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,11 @@ xdg-portal = ["libcosmic/xdg-portal"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
apply = "0.3.0"
|
apply = "0.3.0"
|
||||||
tokio = { version = "1.44", features = ["full"] }
|
tokio = { version = "1.49", features = ["full"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.22"
|
||||||
url = "2.5.4"
|
url = "2.5.8"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
features = ["debug", "winit", "multi-window", "wayland", "tokio"]
|
features = ["debug", "winit", "wgpu", "wayland", "tokio"]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
default-features = false
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::dialog::file_chooser::{self, FileFilter};
|
use cosmic::dialog::file_chooser::{self, FileFilter};
|
||||||
use cosmic::iced_core::Length;
|
use cosmic::iced::Length;
|
||||||
use cosmic::widget::button;
|
use cosmic::widget::button;
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -82,7 +82,7 @@ impl cosmic::Application for App {
|
||||||
(app, cmd)
|
(app, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_end(&self) -> Vec<Element<Self::Message>> {
|
fn header_end(&self) -> Vec<Element<'_, Self::Message>> {
|
||||||
// Places a button the header to create open dialogs.
|
// Places a button the header to create open dialogs.
|
||||||
vec![button::suggested("Open").on_press(Message::OpenFile).into()]
|
vec![button::suggested("Open").on_press(Message::OpenFile).into()]
|
||||||
}
|
}
|
||||||
|
|
@ -186,13 +186,17 @@ impl cosmic::Application for App {
|
||||||
Message::CloseError => {
|
Message::CloseError => {
|
||||||
self.error_status = None;
|
self.error_status = None;
|
||||||
}
|
}
|
||||||
Message::Surface(surface) => {}
|
Message::Surface(action) => {
|
||||||
|
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||||
|
cosmic::app::Action::Surface(action),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Self::Message> {
|
fn view(&self) -> Element<'_, Self::Message> {
|
||||||
let mut content = Vec::new();
|
let mut content = Vec::new();
|
||||||
|
|
||||||
if let Some(error) = self.error_status.as_deref() {
|
if let Some(error) = self.error_status.as_deref() {
|
||||||
|
|
@ -203,7 +207,7 @@ impl cosmic::Application for App {
|
||||||
);
|
);
|
||||||
|
|
||||||
content.push(
|
content.push(
|
||||||
iced::widget::vertical_space()
|
iced::widget::space::vertical()
|
||||||
.height(Length::Fixed(12.0))
|
.height(Length::Fixed(12.0))
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ edition = "2021"
|
||||||
fraction = "0.15.3"
|
fraction = "0.15.3"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
features = ["debug", "multi-window", "wayland", "winit", "desktop", "tokio"]
|
features = ["debug", "wgpu", "winit", "desktop", "tokio"]
|
||||||
path = "../.."
|
path = "../.."
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ impl Application for SpinButtonExamplApp {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Self::Message> {
|
fn view(&'_ self) -> Element<'_, Self::Message> {
|
||||||
let space_xs = cosmic::theme::spacing().space_xs;
|
let space_xs = cosmic::theme::spacing().space_xs;
|
||||||
|
|
||||||
let vert_spinner_row = iced::widget::row![
|
let vert_spinner_row = iced::widget::row![
|
||||||
|
|
|
||||||
10
examples/subscriptions/Cargo.toml
Normal file
10
examples/subscriptions/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "subscriptions"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[dependencies.libcosmic]
|
||||||
|
path = "../../"
|
||||||
|
features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"]
|
||||||
80
examples/subscriptions/src/main.rs
Normal file
80
examples/subscriptions/src/main.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2025 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! Application API example
|
||||||
|
|
||||||
|
use cosmic::app::{Core, Settings, Task};
|
||||||
|
use cosmic::iced::Subscription;
|
||||||
|
use cosmic::{executor, prelude::*, widget};
|
||||||
|
|
||||||
|
/// Runs application with these settings
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
cosmic::app::run::<App>(Settings::default(), ())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Messages that are used specifically by our [`App`].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Message {}
|
||||||
|
|
||||||
|
/// The [`App`] stores application-specific state.
|
||||||
|
pub struct App {
|
||||||
|
core: Core,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||||
|
impl cosmic::Application for App {
|
||||||
|
/// Default async executor to use with the app.
|
||||||
|
type Executor = executor::Default;
|
||||||
|
|
||||||
|
/// Argument received [`cosmic::Application::new`].
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
/// Message type specific to our [`App`].
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
/// The unique application ID to supply to the window manager.
|
||||||
|
const APP_ID: &'static str = "org.cosmic.TextInputsDemo";
|
||||||
|
|
||||||
|
fn core(&self) -> &Core {
|
||||||
|
&self.core
|
||||||
|
}
|
||||||
|
|
||||||
|
fn core_mut(&mut self) -> &mut Core {
|
||||||
|
&mut self.core
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the application, and optionally emits task on initialize.
|
||||||
|
fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
|
||||||
|
let mut app = App { core };
|
||||||
|
|
||||||
|
let commands = Task::batch(vec![app.update_title()]);
|
||||||
|
|
||||||
|
(app, commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
|
Subscription::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle application events here.
|
||||||
|
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a view after each update.
|
||||||
|
fn view(&self) -> Element<'_, Self::Message> {
|
||||||
|
widget::Row::new().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App
|
||||||
|
where
|
||||||
|
Self: cosmic::Application,
|
||||||
|
{
|
||||||
|
fn update_title(&mut self) -> Task<Message> {
|
||||||
|
let window_title = format!("COSMIC Subscriptions Demo");
|
||||||
|
self.set_header_title(window_title.clone());
|
||||||
|
self.set_window_title(window_title, self.core.main_window_id().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,12 +4,11 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = "0.3.17"
|
tracing-subscriber = "0.3.22"
|
||||||
tracing-log = "0.2.0"
|
tracing-log = "0.2.0"
|
||||||
chrono = "*"
|
chrono = "*"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
features = ["debug", "multi-window", "wayland", "winit", "desktop", "tokio"]
|
features = ["debug", "wgpu", "winit", "desktop", "tokio"]
|
||||||
path = "../.."
|
path = "../.."
|
||||||
default-features = false
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced_core::Size;
|
use cosmic::iced::Size;
|
||||||
use cosmic::prelude::*;
|
use cosmic::prelude::*;
|
||||||
use cosmic::widget::table;
|
use cosmic::widget::table;
|
||||||
use cosmic::widget::{self, nav_bar};
|
use cosmic::widget::{self, nav_bar};
|
||||||
|
|
@ -204,16 +204,16 @@ 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> {
|
||||||
cosmic::widget::responsive(|size| {
|
cosmic::widget::responsive(|size| {
|
||||||
if size.width < 600.0 {
|
if size.width < 600.0 {
|
||||||
widget::compact_table(&self.table_model)
|
widget::compact_table(&self.table_model)
|
||||||
.on_item_left_click(Message::ItemSelect)
|
.on_item_left_click(Message::ItemSelect)
|
||||||
.item_context(|item| {
|
.item_context(move |item| {
|
||||||
Some(widget::menu::items(
|
Some(widget::menu::items(
|
||||||
&HashMap::new(),
|
&HashMap::new(),
|
||||||
vec![widget::menu::Item::Button(
|
vec![widget::menu::Item::Button(
|
||||||
format!("Action on {}", item.name),
|
format!("Action on {}", item.name.to_string()),
|
||||||
None,
|
None,
|
||||||
Action::None,
|
Action::None,
|
||||||
)],
|
)],
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.22"
|
||||||
tracing-log = "0.2.0"
|
tracing-log = "0.2.0"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
default-features = false
|
features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"]
|
||||||
features = ["debug", "winit", "tokio", "xdg-portal"]
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,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 editable = cosmic::widget::editable_input(
|
let editable = cosmic::widget::editable_input(
|
||||||
"Input text here",
|
"Input text here",
|
||||||
&self.input,
|
&self.input,
|
||||||
|
|
@ -99,7 +99,9 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
let inline = cosmic::widget::inline_input("", &self.input).on_input(Message::Input);
|
let inline = cosmic::widget::inline_input("", &self.input).on_input(Message::Input);
|
||||||
|
|
||||||
let column = cosmic::widget::column().push(editable).push(inline);
|
let column = cosmic::widget::column::with_capacity(2)
|
||||||
|
.push(editable)
|
||||||
|
.push(inline);
|
||||||
|
|
||||||
let centered = cosmic::widget::container(column.width(200))
|
let centered = cosmic::widget::container(column.width(200))
|
||||||
.width(iced::Length::Fill)
|
.width(iced::Length::Fill)
|
||||||
|
|
@ -118,6 +120,6 @@ where
|
||||||
fn update_title(&mut self) -> Task<Message> {
|
fn update_title(&mut self) -> Task<Message> {
|
||||||
let window_title = format!("COSMIC TextInputs Demo");
|
let window_title = format!("COSMIC TextInputs Demo");
|
||||||
self.set_header_title(window_title.clone());
|
self.set_header_title(window_title.clone());
|
||||||
self.set_window_title(window_title)
|
self.set_window_title(window_title, self.core.main_window_id().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
i18n.toml
Normal file
4
i18n.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
fallback_language = "en"
|
||||||
|
|
||||||
|
[fluent]
|
||||||
|
assets_dir = "i18n"
|
||||||
0
i18n/af/libcosmic.ftl
Normal file
0
i18n/af/libcosmic.ftl
Normal file
36
i18n/ar/libcosmic.ftl
Normal file
36
i18n/ar/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Context Drawer
|
||||||
|
close = أغلِق
|
||||||
|
# About
|
||||||
|
license = الترخيص
|
||||||
|
links = الروابط
|
||||||
|
developers = المطوِّرون
|
||||||
|
designers = المصمّمون
|
||||||
|
artists = الفنانون
|
||||||
|
translators = المترجمون
|
||||||
|
documenters = الموثقون
|
||||||
|
january = يناير { $year }
|
||||||
|
february = فبراير { $year }
|
||||||
|
march = مارس { $year }
|
||||||
|
april = ابريل { $year }
|
||||||
|
may = مايو { $year }
|
||||||
|
june = يونيو { $year }
|
||||||
|
july = يوليو { $year }
|
||||||
|
august = أغسطس { $year }
|
||||||
|
september = سبتمبر { $year }
|
||||||
|
october = أكتوبر { $year }
|
||||||
|
november = نوفمبر { $year }
|
||||||
|
december = ديسمبر { $year }
|
||||||
|
monday = الاثنين
|
||||||
|
tuesday = الثلاثاء
|
||||||
|
wednesday = الأربعاء
|
||||||
|
thursday = الخميس
|
||||||
|
friday = الجمعة
|
||||||
|
saturday = السبت
|
||||||
|
sunday = الأحد
|
||||||
|
mon = ن
|
||||||
|
tue = ث
|
||||||
|
wed = ر
|
||||||
|
thu = خ
|
||||||
|
fri = ج
|
||||||
|
sat = س
|
||||||
|
sun = ح
|
||||||
27
i18n/be/libcosmic.ftl
Normal file
27
i18n/be/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
close = Закрыць
|
||||||
|
license = Ліцэнзія
|
||||||
|
links = Спасылкі
|
||||||
|
developers = Распрацоўшчыкі
|
||||||
|
designers = Дызайнеры
|
||||||
|
artists = Мастакі
|
||||||
|
translators = Перакладчыкі
|
||||||
|
documenters = Дакументалісты
|
||||||
|
february = Люты { $year }
|
||||||
|
november = Лістапад { $year }
|
||||||
|
friday = Пт
|
||||||
|
tuesday = Аў
|
||||||
|
may = Май { $year }
|
||||||
|
wednesday = Ср
|
||||||
|
april = Красавік { $year }
|
||||||
|
monday = Пн
|
||||||
|
december = Снежань { $year }
|
||||||
|
sunday = Нд
|
||||||
|
march = Сакавік { $year }
|
||||||
|
june = Чэрвень { $year }
|
||||||
|
saturday = Сб
|
||||||
|
august = Жнівень { $year }
|
||||||
|
july = Ліпень { $year }
|
||||||
|
thursday = Чц
|
||||||
|
september = Верасень { $year }
|
||||||
|
october = Кастрычнік { $year }
|
||||||
|
january = Студзень { $year }
|
||||||
29
i18n/bg/libcosmic.ftl
Normal file
29
i18n/bg/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Context Drawer
|
||||||
|
close = Затваряне
|
||||||
|
# About
|
||||||
|
license = Лиценз
|
||||||
|
links = Връзки
|
||||||
|
developers = Разработчици
|
||||||
|
designers = Дизайнери
|
||||||
|
artists = Художници
|
||||||
|
translators = Преводачи
|
||||||
|
documenters = Документатори
|
||||||
|
january = Януари { $year }
|
||||||
|
february = Февруари { $year }
|
||||||
|
march = Март { $year }
|
||||||
|
april = Април { $year }
|
||||||
|
may = Май { $year }
|
||||||
|
june = Юни { $year }
|
||||||
|
july = Юли { $year }
|
||||||
|
august = Август { $year }
|
||||||
|
september = Септември { $year }
|
||||||
|
october = Октомври { $year }
|
||||||
|
november = Ноември { $year }
|
||||||
|
december = Декември { $year }
|
||||||
|
monday = Пн
|
||||||
|
tuesday = Вт
|
||||||
|
wednesday = Ср
|
||||||
|
thursday = Чт
|
||||||
|
friday = Пт
|
||||||
|
saturday = Сб
|
||||||
|
sunday = Нд
|
||||||
0
i18n/bn/libcosmic.ftl
Normal file
0
i18n/bn/libcosmic.ftl
Normal file
0
i18n/ca/libcosmic.ftl
Normal file
0
i18n/ca/libcosmic.ftl
Normal file
36
i18n/cs/libcosmic.ftl
Normal file
36
i18n/cs/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Context Drawer
|
||||||
|
close = Zavřít
|
||||||
|
# About
|
||||||
|
license = Licence
|
||||||
|
links = Odkazy
|
||||||
|
developers = Vývojáři
|
||||||
|
designers = Designéři
|
||||||
|
artists = Grafici
|
||||||
|
translators = Překladatelé
|
||||||
|
documenters = Tvůrci dokumentace
|
||||||
|
sunday = Neděle
|
||||||
|
january = Leden { $year }
|
||||||
|
february = Únor { $year }
|
||||||
|
march = Březen { $year }
|
||||||
|
april = Duben { $year }
|
||||||
|
may = Květen { $year }
|
||||||
|
june = Červen { $year }
|
||||||
|
july = Červenec { $year }
|
||||||
|
august = Srpen { $year }
|
||||||
|
september = Září { $year }
|
||||||
|
october = Říjen { $year }
|
||||||
|
november = Listopad { $year }
|
||||||
|
december = Prosinec { $year }
|
||||||
|
monday = Pondělí
|
||||||
|
tuesday = Úterý
|
||||||
|
wednesday = Středa
|
||||||
|
thursday = Čtvrtek
|
||||||
|
friday = Pátek
|
||||||
|
saturday = Sobota
|
||||||
|
mon = Po
|
||||||
|
tue = Út
|
||||||
|
wed = St
|
||||||
|
thu = Čt
|
||||||
|
fri = Pá
|
||||||
|
sat = So
|
||||||
|
sun = Ne
|
||||||
0
i18n/da/libcosmic.ftl
Normal file
0
i18n/da/libcosmic.ftl
Normal file
37
i18n/de/libcosmic.ftl
Normal file
37
i18n/de/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Context Drawer
|
||||||
|
close = Schließen
|
||||||
|
# About
|
||||||
|
license = Lizenz
|
||||||
|
links = Links
|
||||||
|
developers = Entwickler(innen)
|
||||||
|
designers = Designer(innen)
|
||||||
|
artists = Künstler(innen)
|
||||||
|
translators = Übersetzer(innen)
|
||||||
|
documenters = Dokumentierer(innen)
|
||||||
|
# Calendar
|
||||||
|
january = Januar { $year }
|
||||||
|
february = Februar { $year }
|
||||||
|
march = März { $year }
|
||||||
|
april = April { $year }
|
||||||
|
may = Mai { $year }
|
||||||
|
june = Juni { $year }
|
||||||
|
july = Juli { $year }
|
||||||
|
august = August { $year }
|
||||||
|
september = September { $year }
|
||||||
|
october = Oktober { $year }
|
||||||
|
november = November { $year }
|
||||||
|
december = Dezember { $year }
|
||||||
|
monday = Montag
|
||||||
|
tuesday = Dienstag
|
||||||
|
wednesday = Mittwoch
|
||||||
|
thursday = Donnerstag
|
||||||
|
friday = Freitag
|
||||||
|
saturday = Samstag
|
||||||
|
sunday = Sonntag
|
||||||
|
wed = Mi
|
||||||
|
thu = Do
|
||||||
|
fri = Fr
|
||||||
|
sat = Sa
|
||||||
|
sun = So
|
||||||
|
tue = Di
|
||||||
|
mon = Mo
|
||||||
0
i18n/el/libcosmic.ftl
Normal file
0
i18n/el/libcosmic.ftl
Normal file
0
i18n/en-GB/libcosmic.ftl
Normal file
0
i18n/en-GB/libcosmic.ftl
Normal file
39
i18n/en/libcosmic.ftl
Normal file
39
i18n/en/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Context Drawer
|
||||||
|
close = Close
|
||||||
|
|
||||||
|
# About
|
||||||
|
license = License
|
||||||
|
links = Links
|
||||||
|
developers = Developers
|
||||||
|
designers = Designers
|
||||||
|
artists = Artists
|
||||||
|
translators = Translators
|
||||||
|
documenters = Documenters
|
||||||
|
|
||||||
|
# Calendar
|
||||||
|
january = January { $year }
|
||||||
|
february = February { $year }
|
||||||
|
march = March { $year }
|
||||||
|
april = April { $year }
|
||||||
|
may = May { $year }
|
||||||
|
june = June { $year }
|
||||||
|
july = July { $year }
|
||||||
|
august = August { $year }
|
||||||
|
september = September { $year }
|
||||||
|
october = October { $year }
|
||||||
|
november = November { $year }
|
||||||
|
december = December { $year }
|
||||||
|
monday = Monday
|
||||||
|
mon = Mon
|
||||||
|
tuesday = Tuesday
|
||||||
|
tue = Tue
|
||||||
|
wednesday = Wednesday
|
||||||
|
wed = Wed
|
||||||
|
thursday = Thursday
|
||||||
|
thu = Thu
|
||||||
|
friday = Friday
|
||||||
|
fri = Fri
|
||||||
|
saturday = Saturday
|
||||||
|
sat = Sat
|
||||||
|
sunday = Sunday
|
||||||
|
sun = Sun
|
||||||
11
i18n/eo/libcosmic.ftl
Normal file
11
i18n/eo/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Context Drawer
|
||||||
|
close = Fermi
|
||||||
|
|
||||||
|
# About
|
||||||
|
license = Permesilo
|
||||||
|
links = Ligiloj
|
||||||
|
developers = Programistoj
|
||||||
|
designers = Grafikistoj
|
||||||
|
artists = Artistoj
|
||||||
|
translators = Tradukantoj
|
||||||
|
documenters = Dokumentantoj
|
||||||
8
i18n/es-419/libcosmic.ftl
Normal file
8
i18n/es-419/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
close = Cerrar
|
||||||
|
license = Licencia
|
||||||
|
links = Enlaces
|
||||||
|
developers = Desarrolladores
|
||||||
|
designers = Diseñadores
|
||||||
|
artists = Artistas
|
||||||
|
translators = Traductores
|
||||||
|
documenters = Documentalistas
|
||||||
0
i18n/es-MX/libcosmic.ftl
Normal file
0
i18n/es-MX/libcosmic.ftl
Normal file
8
i18n/es/libcosmic.ftl
Normal file
8
i18n/es/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
license = Licencia
|
||||||
|
links = Enlaces
|
||||||
|
developers = Desarrolladores
|
||||||
|
designers = Diseñadores
|
||||||
|
artists = Artistas
|
||||||
|
translators = Traductores
|
||||||
|
documenters = Documentadores
|
||||||
|
close = Cerrar
|
||||||
8
i18n/et/libcosmic.ftl
Normal file
8
i18n/et/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
close = Sulge
|
||||||
|
license = Litsents
|
||||||
|
links = Lingid
|
||||||
|
developers = Arendajad
|
||||||
|
artists = Kunstnikud
|
||||||
|
translators = Tõlkijad
|
||||||
|
documenters = Dokumenteerijad
|
||||||
|
designers = Kujundajad
|
||||||
0
i18n/eu/libcosmic.ftl
Normal file
0
i18n/eu/libcosmic.ftl
Normal file
0
i18n/fa/libcosmic.ftl
Normal file
0
i18n/fa/libcosmic.ftl
Normal file
34
i18n/fi/libcosmic.ftl
Normal file
34
i18n/fi/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
monday = Maanantai
|
||||||
|
mon = ma
|
||||||
|
tuesday = Tiistai
|
||||||
|
tue = ti
|
||||||
|
wednesday = Keskiviikko
|
||||||
|
wed = ke
|
||||||
|
thursday = Torstai
|
||||||
|
thu = to
|
||||||
|
friday = Perjantai
|
||||||
|
fri = pe
|
||||||
|
saturday = Lauantai
|
||||||
|
sat = la
|
||||||
|
sunday = Sunnuntai
|
||||||
|
sun = su
|
||||||
|
close = Sulje
|
||||||
|
license = Lisenssi
|
||||||
|
links = Linkit
|
||||||
|
developers = Kehittäjät
|
||||||
|
designers = Suunnittelijat
|
||||||
|
artists = Artistit
|
||||||
|
translators = Kääntäjät
|
||||||
|
documenters = Dokumentoijat
|
||||||
|
january = Tammikuu { $year }
|
||||||
|
february = Helmikuu { $year }
|
||||||
|
march = Maaliskuu { $year }
|
||||||
|
april = Huhtikuu { $year }
|
||||||
|
may = Toukokuu { $year }
|
||||||
|
june = Kesäkuu { $year }
|
||||||
|
july = Heinäkuu { $year }
|
||||||
|
august = Elokuu { $year }
|
||||||
|
september = Syyskuu { $year }
|
||||||
|
october = Lokakuu { $year }
|
||||||
|
november = Marraskuu { $year }
|
||||||
|
december = Joulukuu { $year }
|
||||||
34
i18n/fr/libcosmic.ftl
Normal file
34
i18n/fr/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
close = Fermer
|
||||||
|
documenters = Rédacteurs
|
||||||
|
translators = Traducteurs
|
||||||
|
artists = Artistes
|
||||||
|
license = Licence
|
||||||
|
links = Liens
|
||||||
|
developers = Développeurs
|
||||||
|
january = Janvier { $year }
|
||||||
|
february = Février { $year }
|
||||||
|
april = Avril { $year }
|
||||||
|
march = Mars { $year }
|
||||||
|
november = Novembre { $year }
|
||||||
|
friday = Vendredi
|
||||||
|
tuesday = Mardi
|
||||||
|
may = Mai { $year }
|
||||||
|
wednesday = Mercredi
|
||||||
|
monday = Lundi
|
||||||
|
december = Décembre { $year }
|
||||||
|
sunday = Dimanche
|
||||||
|
june = Juin { $year }
|
||||||
|
saturday = Samedi
|
||||||
|
august = Août { $year }
|
||||||
|
july = Juillet { $year }
|
||||||
|
thursday = Jeudi
|
||||||
|
september = Septembre { $year }
|
||||||
|
october = Octobre { $year }
|
||||||
|
designers = Designers
|
||||||
|
mon = Lun
|
||||||
|
tue = Mar
|
||||||
|
wed = Mer
|
||||||
|
thu = Jeu
|
||||||
|
fri = Ven
|
||||||
|
sat = Sam
|
||||||
|
sun = Dim
|
||||||
0
i18n/fy/libcosmic.ftl
Normal file
0
i18n/fy/libcosmic.ftl
Normal file
34
i18n/ga/libcosmic.ftl
Normal file
34
i18n/ga/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
close = Dún
|
||||||
|
license = Ceadúnas
|
||||||
|
links = Naisc
|
||||||
|
developers = Forbróirí
|
||||||
|
designers = Dearthóirí
|
||||||
|
artists = Ealaíontóirí
|
||||||
|
translators = Aistritheoirí
|
||||||
|
documenters = Doiciméadóirí
|
||||||
|
january = Eanáir { $year }
|
||||||
|
february = Feabhra { $year }
|
||||||
|
march = Márta { $year }
|
||||||
|
april = Aibreán { $year }
|
||||||
|
may = Bealtaine { $year }
|
||||||
|
june = Meitheamh { $year }
|
||||||
|
july = Iúil { $year }
|
||||||
|
august = Lúnasa { $year }
|
||||||
|
september = Meán Fómhair { $year }
|
||||||
|
october = Deireadh Fómhair { $year }
|
||||||
|
november = Samhain { $year }
|
||||||
|
december = Nollaig { $year }
|
||||||
|
monday = Dé Luain
|
||||||
|
tuesday = Dé Máirt
|
||||||
|
wednesday = Dé Céadaoin
|
||||||
|
thursday = Déardaoin
|
||||||
|
friday = Dé hAoine
|
||||||
|
saturday = Dé Sathairn
|
||||||
|
sunday = Dé Domhnaigh
|
||||||
|
mon = Lua
|
||||||
|
tue = Mái
|
||||||
|
wed = Céa
|
||||||
|
thu = Déa
|
||||||
|
fri = Aoi
|
||||||
|
sat = Sat
|
||||||
|
sun = Dom
|
||||||
0
i18n/gd/libcosmic.ftl
Normal file
0
i18n/gd/libcosmic.ftl
Normal file
0
i18n/gu/libcosmic.ftl
Normal file
0
i18n/gu/libcosmic.ftl
Normal file
0
i18n/he/libcosmic.ftl
Normal file
0
i18n/he/libcosmic.ftl
Normal file
12
i18n/hi/libcosmic.ftl
Normal file
12
i18n/hi/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
close = बंद करें
|
||||||
|
license = लाइसेंस
|
||||||
|
links = लिंक
|
||||||
|
developers = डेवलपर्स
|
||||||
|
designers = डिज़ाइनर
|
||||||
|
february = फ़रवरी { $year }
|
||||||
|
documenters = दस्तावेज़ बनाने वाले
|
||||||
|
april = अप्रैल { $year }
|
||||||
|
translators = अनुवादक
|
||||||
|
artists = कलाकार
|
||||||
|
march = मार्च { $year }
|
||||||
|
january = जनवरी { $year }
|
||||||
0
i18n/hr/libcosmic.ftl
Normal file
0
i18n/hr/libcosmic.ftl
Normal file
36
i18n/hu/libcosmic.ftl
Normal file
36
i18n/hu/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Context Drawer
|
||||||
|
close = Bezárás
|
||||||
|
# About
|
||||||
|
license = Licenc
|
||||||
|
links = Hivatkozások
|
||||||
|
developers = Fejlesztők
|
||||||
|
designers = Tervezők
|
||||||
|
artists = Művészek
|
||||||
|
translators = Fordítók
|
||||||
|
documenters = Dokumentálók
|
||||||
|
january = { $year } január
|
||||||
|
february = { $year } február
|
||||||
|
march = { $year } március
|
||||||
|
april = { $year } április
|
||||||
|
may = { $year } május
|
||||||
|
june = { $year } június
|
||||||
|
july = { $year } július
|
||||||
|
august = { $year } augusztus
|
||||||
|
september = { $year } szeptember
|
||||||
|
october = { $year } október
|
||||||
|
november = { $year } november
|
||||||
|
december = { $year } december
|
||||||
|
monday = Hétfő
|
||||||
|
tuesday = Kedd
|
||||||
|
wednesday = Szerda
|
||||||
|
thursday = Csütörtök
|
||||||
|
friday = Péntek
|
||||||
|
saturday = Szombat
|
||||||
|
sunday = Vasárnap
|
||||||
|
mon = H
|
||||||
|
tue = K
|
||||||
|
wed = Sze
|
||||||
|
thu = Cs
|
||||||
|
fri = P
|
||||||
|
sat = Szo
|
||||||
|
sun = V
|
||||||
34
i18n/id/libcosmic.ftl
Normal file
34
i18n/id/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
close = Tutup
|
||||||
|
license = Lisensi
|
||||||
|
links = Tautan
|
||||||
|
developers = Pengembang
|
||||||
|
designers = Perancang
|
||||||
|
artists = Artis
|
||||||
|
translators = Penerjemah
|
||||||
|
documenters = Dokumenter
|
||||||
|
january = Januari { $year }
|
||||||
|
february = Februari { $year }
|
||||||
|
march = Maret { $year }
|
||||||
|
april = April { $year }
|
||||||
|
may = Mei { $year }
|
||||||
|
june = Juni { $year }
|
||||||
|
july = Juli { $year }
|
||||||
|
august = Agustus { $year }
|
||||||
|
september = September { $year }
|
||||||
|
october = Oktober { $year }
|
||||||
|
november = November { $year }
|
||||||
|
december = Desember { $year }
|
||||||
|
monday = Senin
|
||||||
|
tuesday = Selasa
|
||||||
|
wednesday = Rabu
|
||||||
|
sunday = Minggu
|
||||||
|
saturday = Sabtu
|
||||||
|
friday = Jum'at
|
||||||
|
thursday = Kamis
|
||||||
|
mon = Sen
|
||||||
|
tue = Sel
|
||||||
|
wed = Rab
|
||||||
|
thu = Kam
|
||||||
|
fri = Jum
|
||||||
|
sat = Sab
|
||||||
|
sun = Min
|
||||||
0
i18n/ie/libcosmic.ftl
Normal file
0
i18n/ie/libcosmic.ftl
Normal file
8
i18n/is/libcosmic.ftl
Normal file
8
i18n/is/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
close = Loka
|
||||||
|
license = Notandaleyfi
|
||||||
|
links = Tenglar
|
||||||
|
developers = Forritarar
|
||||||
|
designers = Hönnuðir
|
||||||
|
artists = Listafólk
|
||||||
|
translators = Þýðendur
|
||||||
|
documenters = Skjölunarhöfundar
|
||||||
8
i18n/it/libcosmic.ftl
Normal file
8
i18n/it/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
close = Chiudi
|
||||||
|
license = Licenza
|
||||||
|
links = Link
|
||||||
|
developers = Sviluppatori
|
||||||
|
designers = Designer
|
||||||
|
artists = Artisti
|
||||||
|
translators = Traduttori
|
||||||
|
documenters = Documentatori
|
||||||
8
i18n/ja/libcosmic.ftl
Normal file
8
i18n/ja/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
close = 閉じる
|
||||||
|
license = ライセンス
|
||||||
|
links = リンク
|
||||||
|
developers = 開発者
|
||||||
|
designers = デザイナー
|
||||||
|
artists = アーティスト
|
||||||
|
translators = 翻訳者
|
||||||
|
documenters = ドキュメント作成者
|
||||||
0
i18n/jv/libcosmic.ftl
Normal file
0
i18n/jv/libcosmic.ftl
Normal file
0
i18n/ka/libcosmic.ftl
Normal file
0
i18n/ka/libcosmic.ftl
Normal file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue