Compare commits

..

222 commits

Author SHA1 Message Date
04848eacce merge: upstream/libcosmic a l'etat pinne par epoch-1.8.0 (d4d71fd53)
# Conflicts:
#	iced
#	src/widget/toaster/widget.rs
2026-09-10 18:09:10 +02:00
Hojjat
d4d71fd53e improv: better downsampler for small target images 2026-09-04 21:55:43 +02:00
Ashley Wulber
b728852754 chore: update iced
popup creation/cleanup fixes
2026-09-03 19:45:45 +02:00
Hojjat
6347ba2390 fix: keep content's overlays visible while showing toaster 2026-09-03 00:27:13 +02:00
Hojjat
9cc82abd9c fix: don't pass keyboard events to content if popover modal is open 2026-09-02 13:04:46 +02:00
Michael Aaron Murphy
c3ced76094
feat: update cosmic-icons 2026-08-31 23:58:52 +02:00
Hojjat Abdollahi
c003a58fff
feat(iced): hide-cursor and multi-popup features 2026-08-29 02:29:36 +02:00
173d6ebd32 Merge remote-tracking branch 'upstream/master'
# Conflicts:
#	iced
2026-08-27 07:49:01 +02:00
Eric Le Bihan
caec74c255 fix(menu example): fix key binds not being dispatched
The menu example advertised key binds (Ctrl+W / Ctrl+Shift+N) that were
never dispatched.

So add a subscription to take them into account.
2026-08-25 21:17:21 +02:00
Eric Le Bihan
3b62210d27 fix(menu): request redraw when menu bar opens
When using a menu bar without Wayland popup support, the menu items are
unclickable, as redrawing is never requested.

So, request a redraw after opening so the overlay menu is created and
drawn immediately.
2026-08-25 21:17:21 +02:00
Michael Aaron Murphy
2a73fbc0ed fix(segmented_button): request redraw on tab focus shift 2026-08-25 16:46:23 +02:00
Ashley Wulber
f8f5c7b5a6
feat: popover top position (#1403) 2026-08-25 02:28:46 +02:00
Jeremy Soller
ef490df50b
Revert "feat(cosmic-config): add new_data method." (#1399)
It was found that dirs::data_dir conflicts with dirs::config_dir on
macOS and Windows.

This reverts commit 631c81b170 from
https://github.com/pop-os/libcosmic/pull/1172

- [x] 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.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-08-21 09:07:12 -06:00
Jeremy Soller
fd34b923f7 Revert "feat(cosmic-config): add new_data method."
It was found that dirs::data_dir conflicts with dirs::config_dir on
macOS and Windows.

This reverts commit 631c81b170.
2026-08-21 08:56:00 -06:00
Michael Aaron Murphy
2ab0d4c570
fix(iced): allow scrolling when children capture touch presses 2026-08-20 21:40:09 +02:00
Jeremy Soller
b812c77438
On platforms without state_dir, fall back to ~/.local/state (#1398)
This works around issues with state config on macOS and Windows, where
there is no defined application state directory

---

- [x] 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.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-08-20 09:13:20 -06:00
Jeremy Soller
637c454af8 On platforms without state_dir, fall back to ~/.local/state 2026-08-20 09:06:44 -06:00
Alex Klepov
3a8ebf9197
fix(menu): panic when right-clicking a submenu 2026-08-20 00:18:04 +02:00
9535ada4d0 Merge upstream/master (COSMIC 1.6) into main
Aligne le fork sur epoch-1.6.0 (63 commits amont) en preservant les 19
commits yoda.

Resolutions notables :
- les cfg `all(feature = "wayland", target_os = "linux")` deviennent
  l alias `wayland_platform` introduit en amont ; il exclut winit comme
  le faisait le patch yoda 45eaaf16, l intention est donc preservee ;
- les features iced `cctk` ont ete renommees `wayland` en amont, la
  feature `wayland` locale est adaptee ; `winit` reste un marqueur vide
  et `iced/winit` est liste explicitement ;
- cctk/ashpd gardent leurs chemins locaux dans la section conditionnelle
  unix introduite en amont ;
- la garde anti-crash c0b653f5 (context_menu non assigne) est conservee
  par-dessus la nouvelle structure amont ;
- traductions ca/oc restaurees depuis amont.

Le submodule iced suit (merge + correctif du cfg cctk residuel).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 21:55:00 +02:00
5392d9d5ae segmented_button: honour WindowControlsPosition for the tab close button
The close button of closable tabs was pinned to the trailing edge, so
enabling macOS-style window controls left the window buttons on the
left and the tab crosses on the right.

Read crate::config::window_controls_position() and, when Start, place
the close button before the tab contents and shift them accordingly.
The hit-test, the text width and the drawn icon all derive from the
same position so they stay in sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 21:18:31 +02:00
Hojjat
a5267e623f fix: request redraw on hover state change in dropdown widget
This also add a missing request when dropdown opens
2026-08-19 14:31:31 +02:00
Paul
75e9ee7e65 fix: request redraw on dropdown open/close in the in-window overlay path
`Dropdown::new` (the plain constructor used by any non-applet consumer) leaves `window_id`/`on_surface_action` unset, which routes the widget's `overlay()` impl to the in-window overlay path rather than the surface-action-based popup path. On that path, `update()`'s `open` closure and its close counterparts only ever requested a redraw inside the `#[cfg(wayland_platform)] if let Some(...)` block gated on those fields being set — so for the common case, opening or closing a dropdown flipped `is_open` with no redraw requested at all. The stale frame stuck around until some unrelated later event (e.g. cursor motion landing on the now-open/closed menu's bounds) forced a repaint,
making the menu appear to not open until the mouse moved, and not close until the same thing happened again on the way out.

Add an unconditional `shell.request_redraw()` at each of the three `is_open` transitions that don't already publish a Message of their own (an actual option selection already triggers a normal Message-driven redraw through the app's own update cycle, so that path needed no change):

- `open`: right after `is_open` is set true, ahead of the wayland-only surface-action block.
- the `close_operation` branch of the open/close operation state machine (`Id`-based programmatic close).
- the click-outside-closes-it branch of the mouse/touch press handler.

Fixes #1395
2026-08-19 14:31:31 +02:00
Michael Murphy
c1897c01aa
i18n: translation update from Hosted Weblate (#1379) 2026-08-18 14:05:23 +02:00
Hojjat
1f8d878694 fix: request redraw when hover state changes in toggle widget 2026-08-10 21:56:23 +02:00
Hojjat
0633751633 fix: request redraw if pressed state changes in button widget 2026-08-10 21:56:23 +02:00
Hojjat
5da2e94337 fix: request redraw when menu item hover state changes 2026-08-10 21:56:23 +02:00
Hojjat
633e6ab83f fix: request redraw if hovered state changes in segmented_button 2026-08-10 18:22:08 +02:00
Hojjat
030a7dbe02 fix: request redraw in selectable text and context menu 2026-08-09 06:09:28 +02:00
Ashley Wulber
8a017a15ee fix: window id handling for multi-window 2026-08-07 20:20:54 +02:00
Ashley Wulber
324ef6e905 chore: update selectable text menu corner radius 2026-08-07 20:20:54 +02:00
Ashley Wulber
b0b9bf16f4 fix: multi-window example headerbar
the headerbar is transparent, but a wrapping container applies the right color
2026-08-07 20:20:54 +02:00
Ashley Wulber
03a66751ea chore: feature gates 2026-08-07 20:20:54 +02:00
Hojjat
654ae0bf5d chore: add typography presets for selectable_text 2026-08-07 20:20:54 +02:00
Hojjat
8a03f063c0 refactor: rename to SelectableText instead of Text
This avoids API breakage
2026-08-07 20:20:54 +02:00
Hojjat
1de6ce9460 fix: dismiss the pop up on click outside or esc 2026-08-07 20:20:54 +02:00
Hojjat
8b31e83d58 fix(test): --features applet only
fix(test): `--feature wayland` only
2026-08-07 20:20:54 +02:00
Hojjat
a70feab2bf feat: integrate text context menu popups into Cosmic framework view system 2026-08-07 20:20:54 +02:00
Hojjat
d751855c74 feat: add Wayland popup context menu to TextInput 2026-08-07 20:20:54 +02:00
Hojjat
c6cf3db29d feat: add text context menu (overlay and wayland popup) 2026-08-07 20:20:54 +02:00
Hojjat
2cd43a8b89 feat: add context menu wrapper for TextEditor 2026-08-07 20:20:54 +02:00
Hojjat
834cd366ee feat: add context menu support to selectable Text widget 2026-08-07 20:20:54 +02:00
Hojjat
43a50cb6f5 chore: Update iced 2026-08-05 16:02:04 -04:00
Hojjat
1421337831 fix: do not publish ResponsiveMenuBar messages in a loop 2026-08-05 16:02:04 -04:00
Hojjat
31ec448bf0 fix: request redraw in text input when needed 2026-08-05 16:02:04 -04:00
Hojjat
5fd1ca4220 fix: request redraw in menus 2026-08-05 16:02:04 -04:00
Hojjat
1ac21a9d99 fix: request redraw when hover state changes 2026-08-05 16:02:04 -04:00
Hosted Weblate
05be504174
i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Martin Meizoso <martinmeizoso@gmail.com>
Co-authored-by: Quentin PAGÈS <quentinantonin@free.fr>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/oc/
Translation: Pop OS/libcosmic
2026-08-05 17:02:15 +02:00
Ashley Wulber
dc1cf9f00c chore: update iced 2026-07-29 20:28:18 +02:00
Ashley Wulber
abaa06f10f fix(menu): path highlight corners flipped 2026-07-29 12:46:09 -04:00
Jeremy Soller
d71c25879e
i18n: translation update from Hosted Weblate (#1329)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop
OS/libcosmic](https://hosted.weblate.org/projects/pop-os/libcosmic/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/libcosmic/matrix-auto.svg)
2026-07-29 09:13:39 -06:00
vacenty
0ca1b784c9 feat: Add option for frozen glass in maximized windows 2026-07-28 11:02:31 -04:00
Aadil
1f6dc991ea
fix(slider): adjust handle bounds and size for iced widget slider update 2026-07-27 14:16:25 +02:00
Michael Aaron Murphy
bdb273389d
chore: update iced with slider handle improvements 2026-07-27 14:11:20 +02:00
28f7ab4651 fix(segmented_button): crash when enumerating context menu buttons
Cherry-pick of upstream fix (pop-os/libcosmic#1354).

The .enumerate().find_map() pattern counted ALL items (buttons +
dividers) but the resulting index was used to access a children
array containing only button entries. When a divider preceded the
target button (e.g. trash in nav bar), the index was off-by-N,
causing an out-of-bounds panic and crashing cosmic-files.

Fix: filter_map first to keep only buttons, then enumerate.
2026-07-25 22:20:01 +02:00
Hojjat
7e777077b8 fix: gate frosted transparency on auto_blur 2026-07-24 11:52:07 +02:00
Hosted Weblate
c994f94ea9
i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: Nara Díaz Viñolas <nara.diaz.vinolas@gmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ca/
Translation: Pop OS/libcosmic
2026-07-23 18:51:12 +02:00
Michael Aaron Murphy
ef162b8e16
feat(icon): add opacity method 2026-07-19 04:48:42 +02:00
Adil Hanney
0eef591171
fix(cosmic-theme): remove kdeglobals theme generation support 2026-07-17 16:58:48 +02:00
Vukašin Vojinović
66263a76d3 fix(toggler): toggle on release 2026-07-16 19:25:42 +02:00
Vukašin Vojinović
ac74ed51d9
fix(circular): calculate bar height based on the size of the circular
This calculates the `bar_height` based on the size of the Circular by default, so that ones with a small size don't have visual issues caused by a fixed height.

Also fixes the `track_radius` calculation, which caused the widget to appear smaller than it should be.
2026-07-16 19:25:08 +02:00
Hojjat
16d148807c fix: clear paste cache after shift+insert 2026-07-16 19:23:41 +02:00
Ashley Wulber
6359a9465b chore: update iced 2026-07-15 16:54:18 -04:00
Ashley Wulber
63568a8d9c improv: feature gate for xdg_portal 2026-07-15 16:54:18 -04:00
Ashley Wulber
8e6aea650a fix: feature gates 2026-07-15 16:54:18 -04:00
Hojjat
511384f620 chore: update iced to pull in fixes for curved corners 2026-07-14 00:31:00 +02:00
Michael Aaron Murphy
96a82045d7 fix(segmented_button): crash when enumerating context menu buttons 2026-07-13 14:38:38 -04:00
Aadil
c53b43d41b
feat(color_picker): add mouse_interaction function (#1326) 2026-07-13 20:15:46 +02:00
Michael Aaron Murphy
fc5debccef fix: matching desktop entries by CRX ID
The existing test for this was broken, and the desktop entry
cache was being refreshed before a match is fully checked.
2026-07-13 17:04:17 +02:00
name-is-hai
18731e8392 fix(desktop): XDG desktop entry precedence
Resolve duplicate desktop IDs before applying desktop entry visibility rules

This allows user desktop entries to override matching system entries and ensures Hidden, NoDisplay, and NotShowIn overrides do not reveal lower-priority entries

Signed-off-by: name-is-hai <90458368+name-is-hai@users.noreply.github.com>
2026-07-13 16:49:31 +02:00
Eduardo Flores
5187dd6d1d fix(segmented_button): remove feature gate 2026-07-12 20:34:27 +02:00
Jeremy Soller
c44dbe793b
Update iced (#1341)
- [x] Depends on https://github.com/pop-os/iced/pull/365
___
- [x] 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.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-07-09 18:32:59 -06:00
Hojjat
a1003f24ec chore: update iced to fix corner radius 2026-07-09 18:24:36 -06:00
Ashley Wulber
8cb10bf6e2 fix: config watcher hash & blur 2026-07-09 17:45:24 -04:00
Ashley Wulber
dc11d6a11f fix: no text should use the steps array for transparent color 2026-07-09 11:04:46 -04:00
Ashley Wulber
74b2407342 fix: avoid blurring subsurfaces on creation 2026-07-08 21:21:33 +02:00
Ashley Wulber
5056fc6f9b improv: overlap corner radius strip regions
fixes handling of unfortunate scale values
2026-07-08 12:08:23 -04:00
Jeremy Soller
be41927859
refactor(rectange tracker): relax Copy to Clone (#1331)
- [x] 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.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-07-07 06:26:01 -06:00
Jeremy Soller
3b85b6e835
fix: update iced (#1335)
popup blur & corner cleanup

- [x] 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.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-07-06 16:56:50 -06:00
Ashley Wulber
d9b2b38bf4 fix: update iced
popup blur & corner cleanup
2026-07-06 18:49:36 -04:00
Ashley Wulber
2a325fa69a fix: update blur status after theme change 2026-07-06 14:54:58 -04:00
Ashley Wulber
9691870f72 refactor: option for customizing the border padding, instead of disabling the content container 2026-07-06 14:54:58 -04:00
3e4ab4576d fix(segmented_button): publish on_context without widget context_menu
Upstream's popup refactor gated the on_context callback behind
context_menu.is_some(), silencing consumers that provide their own
dialog (cosmic-term tab rename). Publish the message unconditionally;
keep menu state + popup creation gated on an actual context_menu.

Leyoda 2026 – GPLv3
2026-07-04 19:41:28 +02:00
Ashley Wulber
ee5d965983 fix: disabled menu buttons don't need to be transparent 2026-07-02 20:21:17 +02:00
Ashley Wulber
9d01c86f25 refactor(rectange tracker): relax Copy to Clone 2026-07-02 13:25:37 -04:00
2fe2d35286 yoda: cargo fix sweep post-rebase + iced submodule sweep bump
Leyoda 2026 – GPLv3
2026-07-02 19:11:38 +02:00
9e9ae942c4 yoda: absorb upstream theme-v2 integration debt post-rebase
- degate 'feature = "winit"' cfgs added by upstream (fork policy:
  winit toujours actif, le stub Cargo reste vide)
- cards.rs: restore 'id' param consumed by upstream's new struct init
- iced submodule rebased onto upstream c781ff61 (wgpu 28, cryoglyph)
- Cargo.lock regenerated against local path deps

Leyoda 2026 – GPLv3
2026-07-02 19:00:00 +02:00
Ashley Wulber
6cdb4c62ce refactor(rectange tracker): relax Copy to Clone 2026-07-02 12:50:27 -04:00
18c2326f34 yoda: rebase iced fork onto upstream c781ff61 (iced 0.14 bump)
Leyoda 2026 – GPLv3
2026-07-02 18:46:55 +02:00
37829fd899 chore: use local graphics dependencies 2026-07-02 18:36:28 +02:00
ca66c7c1b0 chore: use local COSMIC support crates 2026-07-02 18:36:28 +02:00
ce5e3b6eca fix: drop iced_winit/single-instance feature ref
Upstream dc84488c "fix(iced): double IME commit on GNOME desktop" wires
the libcosmic single-instance feature through iced_winit/single-instance,
but our pinned iced fork (yoda-wayland-only @ 81639935) does not carry
that feature yet. Drop the ref so libcosmic-yoda compiles after rebase;
the GNOME IME fix needs to be ported separately into the iced submodule
before this can be reinstated.

Leyoda 2026 – GPLv3
2026-07-02 18:35:50 +02:00
2c91d818c9 yoda: use local dbus settings bindings 2026-07-02 18:35:50 +02:00
9512f77a58 feat(flex_row): reorderable rows with shadow constant gated (squashed)
Squash of 2 yoda commits:
- 7dd0ee83 feat: reorderable flex row
- b1b8203a fix: gate reorderable flex row shadow constant
2026-07-02 18:35:50 +02:00
4e1080108c yoda: warning cleanup sweep (dead code + clippy --fix) (squashed)
Squash of 4 yoda commits:
- 84437e21 yoda: libcosmic-yoda dead-code purge (14->0 warnings)
- 999db0a4 yoda: cosmic-theme cleanup (4->0 warnings) — workspace at 0 warnings total
- 4743bb8e yoda: cargo clippy --fix on libcosmic-yoda (115->33 warnings)
- 675f3b59 chore: reduce local stack warnings
2026-07-02 18:35:50 +02:00
c85efc6180 yoda: bump iced submodule + cargo auto-fix sweeps (squashed)
Squash of 6 yoda commits:
- 282813c8 yoda: bump iced -> window_clipboard via public Forgejo fork
- cdf34938 yoda: cargo fix --lib (libcosmic-yoda) — drop 99 trivial warnings
- 38a988cb yoda: cargo fix on cosmic-config + bump iced auto-fix commit
- 301bbf6e yoda: bump iced submodule -> iced_winit warning cleanup (0 left)
- b94c03d9 yoda: bump iced submodule -> iced_widget cleanup (0 left)
- a9492d76 yoda: bump iced submodule -> all iced crates at 0 warnings
2026-07-02 18:35:21 +02:00
7c0b3ff1b7 feat(cosmic-theme): add apply_gtk_decoration_layout helper
Writes the gtk-decoration-layout key to ~/.config/gtk-{3,4}.0/settings.ini
and best-effort updates GNOME's button-layout GSettings key for apps that
still consult it. Factors out write_gtk_settings_key for reuse.

Leyoda 2026 – GPLv3
2026-07-02 18:35:00 +02:00
695c602ba1 yoda: bump iced submodule (Wayland-only + softbuffer cuts) (squashed)
Squash of 3 yoda commits:
- c118f5a2 yoda: bump iced submodule ref -> yoda-wayland-only HEAD 8f6be798
- 388e0655 yoda: bump iced submodule -> softbuffer + window_clipboard cuts
- 9b2a3643 Update iced warning fixes
2026-07-02 18:35:00 +02:00
45eaaf1605 yoda: fork pivot — Wayland-only + ungate winit + soft-fork libcosmic-yoda (squashed)
Squash of 7 yoda commits forming the fork pivot:
- 255cf7cc rename: libcosmic -> libcosmic-yoda (fork 0.1.0-yoda)
- 8701aa31 feat(yoda): Wayland-only cut — drop winit and x11 features
- 6736a596 yoda: soft-fork pivot — keep Cargo name 'libcosmic' for dep unification
- 3e23d087 yoda: re-apply hard rename — libcosmic -> libcosmic-yoda (0.1.0-yoda)
- aec3eb61 yoda: ungate remaining winit+wayland combined cfgs
- 8ab7b158 yoda-v2: color_picker Theme ref + context_menu/menu ungate winit
- 8d1d8739 yoda: drop x11 defaults on iced_winit + iced_tiny_skia
2026-07-02 18:34:50 +02:00
261a211647 header_bar: add WindowControlsPosition (macOS-style left controls)
Adds a new public enum `WindowControlsPosition { Start, End }` and a
matching field on `HeaderBar`, allowing window controls (close / minimize
/ maximize) to be packed on the start side of the headerbar (macOS
style, icon order close → minimize → maximize) instead of the default
end side (Linux / GNOME style, minimize → maximize → close).

Wiring:
- `crate::widget::WindowControlsPosition` re-exported alongside
  `HeaderBar`.
- `HeaderBar::controls_position(Option<WindowControlsPosition>)` setter;
  when left unset, falls back to `crate::config::window_controls_position()`
  (reads `CosmicTk.window_controls_position`), mirroring how `density`
  falls back to `header_size()`.
- New `CosmicTk.window_controls_position` field with default `End` for
  backwards compatibility; serde-friendly enum so existing configs keep
  working via `#[serde(default)]` semantics.

Tested with cosmic-yoterm, cosmic-settings, cosmic-edit, cosmic-files
rebuilt against this libcosmic via a local `[patch]` override. Config
changes picked up live through the existing cosmic-config subscription.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-02 18:28:43 +02:00
b7c81907ba feat(segmented_button): on_double_click + internal tab reorder (squashed)
Squash of 2 yoda commits:
- 108441ef segmented_button: add on_double_click callback
- a322516f segmented_button: fix internal tab reorder end-to-end
2026-07-02 18:28:43 +02:00
6219084eb6 perf: throttle malloc_trim + avoid VecDeque clones (squashed)
Squash of 2 yoda commits:
- 77262dd0 perf(malloc): throttle malloc_trim to 1 Hz in hot paths
- 1d98eee6 perf(widget): avoid VecDeque clone in segmented_button/table Model::clear
2026-07-02 18:28:43 +02:00
Ashley Wulber
20dfd7c236 cleanup: corner radius menu path logic 2026-07-01 15:10:28 -04:00
Ashley Wulber
be499d01af fix: test 2026-07-01 15:10:28 -04:00
Ashley Wulber
bb0145c566 chore: add the rounded rectangle strip helper 2026-07-01 15:10:28 -04:00
Ashley Wulber
a365d45091 fix: menu folder corners 2026-07-01 15:10:28 -04:00
Ashley Wulber
cad4be9401 fix: live settings override auto blur 2026-07-01 15:10:28 -04:00
Ashley Wulber
822d3b2a0f chore: update cosmic-protocols 2026-07-01 15:10:28 -04:00
Ashley Wulber
46b25d4218 refactor: allow for transparent header 2026-07-01 15:10:28 -04:00
Ashley Wulber
9855d8b6fd fix(theme-gen): typo, use overlay colors 2026-07-01 15:10:28 -04:00
Ashley Wulber
5ea8a86959 fix: card style 2026-07-01 15:10:28 -04:00
Ashley Wulber
e3a4e739e3 cleanup(segmented button): popup destruction on press 2026-07-01 15:10:28 -04:00
Ashley Wulber
46c717fd57 fix: corner radius setting match style 2026-07-01 15:10:28 -04:00
Ashley Wulber
94d2c18a23 chore: dialog default to overlay 2026-07-01 15:10:28 -04:00
Ashley Wulber
cde1989e78 refactor: popups for nav bar / segmented buttons 2026-07-01 15:10:28 -04:00
Ashley Wulber
f5fc392ba4 fix: opaque menu for overlay 2026-07-01 15:10:28 -04:00
Ashley Wulber
f04437cee5 chore: surface action for LiveSettings update 2026-07-01 15:10:28 -04:00
Ashley Wulber
e939d334f6 fix: on init, set blur value if theme is already marked transparent
useful for things like the files dialog
2026-07-01 15:10:28 -04:00
Ashley Wulber
217b4abe99 fix: guess offset 2026-07-01 15:10:28 -04:00
Ashley Wulber
0c336d77e4 chore(theme): helpers for alpha map 2026-07-01 15:10:28 -04:00
Ashley Wulber
00067dc5b8 fix: set corners and blur before surface creation
these are then saved and applied on creation
2026-07-01 15:10:28 -04:00
Ashley Wulber
19f4a289e5 fix: config 2026-07-01 15:10:28 -04:00
Ashley Wulber
43de9e3e77 cleanup and update iced 2026-07-01 15:10:28 -04:00
Ashley Wulber
033dfe6c57 cleanup 2026-07-01 15:10:28 -04:00
Ashley Wulber
76954a6524 fix: tooltip corner radius 2026-07-01 15:10:28 -04:00
Ashley Wulber
4a7ed015f6 feat: padding setting on surface creation 2026-07-01 15:10:28 -04:00
Ashley Wulber
991a8f2c78 feat: configurable blur alpha 2026-07-01 15:10:28 -04:00
Ashley Wulber
8d2dbb3f23 fix feature gate 2026-07-01 15:10:28 -04:00
Ashley Wulber
ac4a1ab259 improv: allow overrides for corner radius or blur that apply on creation as well 2026-07-01 15:10:28 -04:00
Ashley Wulber
f268bb86cf fix: allow loading v1 themes
fallback to default value for missing attributes
2026-07-01 15:10:28 -04:00
Ashley Wulber
58980231b9 refactor: allow opting out of auto corner radii tracking 2026-07-01 15:10:28 -04:00
Ashley Wulber
bd56f82ca0 fix(theme): color serialization 2026-07-01 15:10:28 -04:00
Ashley Wulber
90ac265ee0 chore: remove unnecessary bound 2026-07-01 15:10:28 -04:00
Ashley Wulber
40792f21de Revert "remove header container"
This reverts commit 8035ef9535fa87deb05d88594a6b6a412324c039.
2026-07-01 15:10:28 -04:00
Ashley Wulber
7d62b193f4 feat: improve handling of list items using data in theme 2026-07-01 15:10:28 -04:00
Ashley Wulber
408d37169a fix: feature gate 2026-07-01 15:10:28 -04:00
Ashley Wulber
5fca59df70 update blur alpha 2026-07-01 15:10:28 -04:00
Ashley Wulber
5938776c26 chore: surface task helper 2026-07-01 15:10:28 -04:00
Ashley Wulber
0ee5131670 fix: increase text contrast constraints for transparent surfaces 2026-07-01 15:10:28 -04:00
Ashley Wulber
fb4628e460 fix: winit feature gate 2026-07-01 15:10:28 -04:00
Ashley Wulber
1fafd57578 remove header container
i'm not sure what gaps it is referring to
2026-07-01 15:10:28 -04:00
Ashley Wulber
bf5ffcc2af fix: apply blur enabled to system theme 2026-07-01 15:10:28 -04:00
Ashley Wulber
2d7f66d528 refactor: remove extra opacity where it was added 2026-07-01 15:10:28 -04:00
Ashley Wulber
732785a50c track surfaces that use a normal view method 2026-07-01 15:10:28 -04:00
Ashley Wulber
cadb773377 wip refactor blur/corners 2026-07-01 15:10:28 -04:00
Ashley Wulber
9e77210740 fix: blur only after blur event 2026-07-01 15:10:28 -04:00
Ashley Wulber
cfd5220c1e wip: corner radius v2 2026-07-01 15:10:28 -04:00
Ashley Wulber
19142fd38e refactor opaque fallback 2026-07-01 15:10:28 -04:00
Ashley Wulber
d0b164d216 feat(blur): better align with designs and remove transparency from theme when not on wayland 2026-07-01 15:10:28 -04:00
Ashley Wulber
439109db04 wip: blurred transparency 2026-07-01 15:10:28 -04:00
Ashley Wulber
9395a23a0b chore: update qt light default kcolorscheme
this was likely off by one because of a rounding change from v1
2026-07-01 15:10:28 -04:00
Ashley Wulber
7ac8f74825 chore: update ron files 2026-07-01 15:10:28 -04:00
Ashley Wulber
72ddeaa5fb feat: hex_color serialization for the theme
can also can deserialize the previous version of the theme, so existing themes should not be affected
2026-07-01 15:10:28 -04:00
Ashley Wulber
7ecc6a32d6 feat(config): support for intermediate serialization/deserialization type via field attribute 2026-07-01 15:10:28 -04:00
Ashley Wulber
aa337c2a16 feat(config): support for fallback to previous config version 2026-07-01 15:10:28 -04:00
Vukašin Vojinović
18d76659f2 fix(progress_bar): prevent large animation time delta 2026-07-01 10:40:30 -04:00
Ashley Wulber
18a7a75e33 fix(key bind): space 2026-06-29 17:39:46 +02:00
Frederic Laing
e7f37f3e81 fix(popover): forward in-flight pointer events to modal popups so a selection drag started inside still receives its release 2026-06-26 17:26:34 +02:00
Vukašin Vojinović
13fb598517
chore(progress_bar): simplify linear code 2026-06-26 17:25:28 +02:00
Aadil
417923fbaf
feat(color_picker): apply gradient shifting to hue slider
This also removes vertical line gap in the center of slider handle by adding current hue color to handle.
2026-06-23 22:13:35 +02:00
Vukašin Vojinović
76bc13ff40
feat(progress_bar): improve determinate animations
Switches to using a critically damped spring animation, which prevents issues with rapid progress updates, and is generally smoother.
2026-06-23 18:30:28 +02:00
Weblate (bot)
6e116097c0
i18n: translation updates from weblate (#1318)
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/kmr/
Translation: Pop OS/libcosmic

Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
2026-06-23 17:09:47 +02:00
Hojjat Abdollahi
e7f278d17f
fix(text_input): add alt_clipboard bindings 2026-06-22 21:33:23 +02:00
黑嫖233
8df1858e5d
feat(list_column): add drag and drop support to widget API 2026-06-16 17:35:59 +02:00
Vukašin Vojinović
f79ccb8cba chore(ci): switch to actions-rust-lang 2026-06-16 17:33:39 +02:00
Aadil127
a991d7bb43 Fix: Removed unnecessary quad draw calls. 2026-06-15 10:47:14 -04:00
Aadil127
4d36a2ee5f Fix: progress bar overflow on low progress phase. 2026-06-15 10:47:14 -04:00
Jeremy Soller
cca48bc29e
FIx: progress bar containing markers overflow. (#1310)
@git-f0x fixed overflow issue for progress bar,
Thanks for pointing out the issue.

- [x] 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.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-06-10 13:08:51 -06:00
Aadil127
80b370d230 Fix bug in progress bar. 2026-06-10 23:35:29 +05:30
Jeremy Soller
bce86dc7c9 chore: update iced 2026-06-10 12:05:15 -06:00
Aadil127
904b7b95b1 Fix progress bar containing some width now does not overflow. 2026-06-10 22:56:56 +05:30
Jeremy Soller
ad5ea1621c
Fix progress bar showing incorrectly with small width. (#1307)
- [x] 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.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.



https://github.com/user-attachments/assets/0efa09fc-0b41-4e28-acfa-3967eee8e443

Fix for this pop-os/cosmic-monitor#8

Need feedback on UI if this is intended design for progress on low
value.
2026-06-10 09:16:43 -06:00
Aadil127
9b8c48a328 Fix progress bar now has flat egde. 2026-06-10 19:38:46 +05:30
Jeremy Soller
4657b6a725
chore: update iced (#1308)
- [x] 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.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-06-09 10:36:08 -06:00
Ashley Wulber
0d028c2dd2 chore: update iced 2026-06-09 10:39:58 -04:00
Aadil127
dc4e89596a Fix progress says consistant for different girth values. 2026-06-09 16:10:46 +05:30
Aadil127
87901c2d48 Fix progress bar showing incorrectly with small width. 2026-06-09 14:17:36 +05:30
Ashley Wulber
bb10afd849
chore(iced): ignore duplicate surfaces and log a warning 2026-06-08 18:26:35 +02:00
Ashley Wulber
f0f68933f1 improv(key_bind): allow fallback if not ascci graphic or space
check if the logical key is within the valid latin key range and use that if so
2026-06-01 14:40:44 -04:00
Michael Aaron Murphy
758c13723f
doc: add crate level documentation 2026-05-30 19:01:01 +02:00
Michael Aaron Murphy
9f71679cef
perf(iced): reuse cached cosmic-text fonts for svg renderer 2026-05-30 03:01:43 +02:00
Ashley Wulber
4178b149e6
fix(iced): revert swash cache change 2026-05-29 22:48:59 +02:00
Michael Aaron Murphy
fe7fd5b26a
perf(iced): reuse swash cache across draws 2026-05-29 20:00:45 +02:00
Vukašin Vojinović
c9255fe871
feat(progress_bar): draw circular line caps directly & fix gaps between the bar and caps 2026-05-29 17:43:55 +02:00
Vukašin Vojinović
04a93d304c chore(linear): cleanup 2026-05-29 00:07:19 +02:00
Ashley Wulber
59bebbdffe
fix(iced): context menu items are not highlighted on hover in Wayland popups 2026-05-28 00:23:26 +02:00
Ashley Wulber
8379ae7b0b chore: update iced 2026-05-26 17:57:34 +02:00
Hojjat
8fa6a01d04 feat: add ability to provide extra paths to search for icons 2026-05-22 01:26:34 +02:00
Michael Aaron Murphy
113e0ae1f9
chore(applet): gracefully exit with log when panel exits
This was causing confusion for users that thought this symptom may be the cause of a problem.
2026-05-22 01:21:03 +02:00
Michael Aaron Murphy
2f022280f2
docs: fix cargo doc generation 2026-05-20 17:04:07 +02:00
Michael Aaron Murphy
e2b172e0bc
feat(iced): X11 resize fix, f16 shader compat, & selectable text feature 2026-05-20 16:06:07 +02:00
Hojjat
b279d82240 fix: circular progressbar crashes on very small windows 2026-05-20 07:25:00 -04:00
Hojjat
147306d581 chore: update iced 2026-05-20 07:25:00 -04:00
Vukašin Vojinović
466e25d9f8 feat(progress_bar): linear progress markers 2026-05-19 20:00:14 +02:00
Michael Aaron Murphy
6f863f1905 chore: cargo +nightly fmt 2026-05-18 09:25:23 -04:00
Andrei Ivanou
5782cb927e fix(process): safely exit forked child after spawn
Co-Authored-By: Michael Murphy <michael@mmurphy.dev>
2026-05-18 09:25:23 -04:00
DanielKaleby
631c81b170
feat(cosmic-config): add new_data method. 2026-05-14 16:19:43 +02:00
Ilia Malanin
cab9d5aff2
feat!(menu): support physical key matches for non-Latin keyboard layouts 2026-05-14 16:10:59 +02:00
Michael Aaron Murphy
2aa9133f9d
docs(readme): add Made for COSMIC Flatpak section 2026-05-13 16:14:34 +02:00
Michael Murphy
4fab6c777d
i18n: translation update from Hosted Weblate (#1269)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop
OS/libcosmic](https://hosted.weblate.org/projects/pop-os/libcosmic/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/libcosmic/horizontal-auto.svg)
2026-05-12 17:12:40 +02:00
KENZ
b105e9a108
feat(iced): IME support improvements 2026-05-08 17:25:49 +02:00
Ashley Wulber
528513725d chore: update iced 2026-05-07 18:46:53 +02:00
Hojjat
0e0960f3c7 feat: reorderable flex row 2026-05-04 21:26:58 +02:00
Vukašin Vojinović
9c2a86a8f4 chore: add rustfmt config 2026-05-04 11:55:17 -04:00
Vukašin Vojinović
9abc4d483b chore: update dependencies 2026-05-04 11:55:17 -04:00
Vukašin Vojinović
34326b3925 chore(cosmic-theme): move Roundness to libcosmic 2026-05-04 11:55:17 -04:00
Hosted Weblate
15a8a535dd
i18n: translation updates from weblate
Co-authored-by: BoneNI <bounkirdni@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Марко М. Костић <marko.m.kostic@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sr/
Translation: Pop OS/libcosmic
2026-05-03 01:52:48 +02:00
Vukašin Vojinović
564ef834ce improv(theme): list_button style
This adds a new `list_button` field to theme, which makes ListItem buttons stay transparent when pressed. It's essentially a mix of `text_button` and `background.component`, without hover highlights (as per designs).
2026-04-28 13:59:25 +02:00
Michael Murphy
99c038a66a
i18n: translation update from Hosted Weblate (#1263)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop
OS/libcosmic](https://hosted.weblate.org/projects/pop-os/libcosmic/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/libcosmic/horizontal-auto.svg)
2026-04-27 16:24:57 +02:00
Hosted Weblate
cae05f3ffb
i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/el/
Translation: Pop OS/libcosmic
2026-04-27 16:10:11 +02:00
Vukašin Vojinović
8768bce2e2 fix(menu_bar): menu border radius 2026-04-27 15:28:03 +02:00
Michael Aaron Murphy
49846d9f28
fix: wgpu validation error on float16 shader with older GPUs 2026-04-27 15:05:05 +02:00
Rakshat
51be7dc42d
fix(menu_bar): use corner radius from theme for menu border 2026-04-27 15:00:52 +02:00
Will Sheehan
5825afb0da fix(segmented_button): reset menu state when reopening context menu
When a context menu is open and the user right-clicks a different item,
the overlay's close handler resets the menu state but no longer captures
the event (since e10459f). The right-click propagates to the widget,
which reopens the menu with stale `MenuBounds` from the previous context
menu. If the new menu has a different number of items, this causes an
assertion failure in `MenuState::layout`.

Call `data.reset()` before setting `data.open = true` in the right-click
handler to ensure stale `MenuBounds` are cleared before the next layout
pass rebuilds them.
2026-04-27 14:58:46 +02:00
Michael Aaron Murphy
f3fc763b5f
ci(pages): update Rust toolchain to latest nightly 2026-04-27 14:43:39 +02:00
Michael Aaron Murphy
21977ef617
feat(iced): update to wgpu 28 & cosmic-text 0.19 2026-04-27 14:39:52 +02:00
Michael Aaron Murphy
58b7532849
chore: update MSRV to Rust 1.93 2026-04-27 14:37:22 +02:00
Vukašin Vojinović
17291536a1 fix(header_bar): prevent double coloring 2026-04-23 11:15:32 -04:00
Vukašin Vojinović
dad5f1e273 fix(progress_bar): sync determinate animation clock on target change
This prevents the time delta from becoming very large when the widget hasn't animated for a bit, leading to snapping instead of a smooth animation.
Also prevents animating on widget creation, since it doesn't feel good in `cosmic-osd`.
2026-04-22 15:03:14 +02:00
Vukašin Vojinović
8b4c8adec8 feat(progress_bar): animate determinate progress 2026-04-21 13:39:43 -04:00
Vukašin Vojinović
4d39cf3d7b improv(progress_bar): unify indeterminate animations 2026-04-21 13:39:43 -04:00
KENZ
dc84488cd8
fix(iced): double IME commit on GNOME desktop 2026-04-19 17:03:20 +02:00
KENZ
6dba6a2561
fix(iced): make IME event forwarding to sctk optional
pulls in the change to make IME events forwarding to sctk optionally
2026-04-19 17:02:26 +02:00
260 changed files with 9987 additions and 3666 deletions

View file

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

View file

@ -11,25 +11,26 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@master
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly-2025-07-31
toolchain: nightly-2026-04-27
rustflags: ""
- name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Build documentation
run: |
RUSTDOCFLAGS="--cfg docsrs" \
cargo +nightly-2025-07-31 doc --no-deps \
cargo +nightly-2026-04-27 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
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc

15
.zed/settings.json Normal file
View file

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

View file

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

View file

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

View file

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

View file

@ -1,17 +1,32 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{self};
use syn;
#[proc_macro_derive(CosmicConfigEntry, attributes(version, id))]
#[proc_macro_derive(CosmicConfigEntry, attributes(version, id, cosmic_config_entry))]
pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_cosmic_config_entry_macro(&ast)
}
fn get_cosmic_config_attrs(field: &syn::Field) -> Result<Option<syn::Type>, syn::Error> {
let mut with = None;
for attr in &field.attrs {
if !attr.path().is_ident("cosmic_config_entry") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("with") {
let value = meta.value()?;
with = Some(value.parse()?);
}
Ok(())
})?;
}
Ok(with)
}
fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
let attributes = &ast.attrs;
let version = attributes
@ -48,19 +63,54 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
let write_each_config_field = fields.iter().map(|field| {
let field_name = &field.ident;
quote! {
cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?;
let with = match get_cosmic_config_attrs(field) {
Ok(attrs) => attrs,
Err(e) => {
return e.to_compile_error();
}
};
if let Some(with) = with {
quote! {
{
let conv = self.#field_name.clone().into();
cosmic_config::ConfigSet::set::<#with>(&tx, stringify!(#field_name), conv)?;
}
}
} else {
quote! {
cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?;
}
}
});
let get_each_config_field = fields.iter().map(|field| {
let field_name = &field.ident;
let field_type = &field.ty;
quote! {
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
Ok(#field_name) => default.#field_name = #field_name,
Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
Err(e) => errors.push(e),
let with = match get_cosmic_config_attrs(field) {
Ok(attrs) => attrs,
Err(e) => {
return e.to_compile_error();
}
};
if let Some(with) = with {
quote! {
match cosmic_config::ConfigGet::get::<#with>(config, stringify!(#field_name)) {
Ok(value) => {
default.#field_name = value.into();
}
Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
Err(e) => errors.push(e),
}
}
} else {
quote! {
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
Ok(#field_name) => default.#field_name = #field_name,
Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
Err(e) => errors.push(e),
}
}
}
});
@ -68,17 +118,39 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
let update_each_config_field = fields.iter().map(|field| {
let field_name = &field.ident;
let field_type = &field.ty;
quote! {
stringify!(#field_name) => {
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
Ok(value) => {
if self.#field_name != value {
keys.push(stringify!(#field_name));
}
self.#field_name = value;
},
Err(e) => {
errors.push(e);
let with = match get_cosmic_config_attrs(field) {
Ok(attrs) => attrs,
Err(e) => {
return e.to_compile_error();
}
};
if let Some(with) = with {
quote! {
stringify!(#field_name) => {
match cosmic_config::ConfigGet::get::<#with>(config, stringify!(#field_name)) {
Ok(value) => {
let value = value.into();
if self.#field_name != value {
keys.push(stringify!(#field_name));
}
self.#field_name = value;
},
Err(e) => errors.push(e),
}
}
}
} else {
quote! {
stringify!(#field_name) => {
match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
Ok(value) => {
if self.#field_name != value {
keys.push(stringify!(#field_name));
}
self.#field_name = value;
},
Err(e) => errors.push(e),
}
}
}

View file

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

View file

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

View file

@ -1,16 +1,13 @@
//! Integrations for cosmic-config — the cosmic configuration system.
use notify::{
RecommendedWatcher, Watcher,
event::{EventKind, ModifyKind, RenameMode},
};
use serde::{Serialize, de::DeserializeOwned};
use std::{
env, fmt, fs,
io::Write,
path::{Path, PathBuf},
sync::Mutex,
};
use notify::event::{EventKind, ModifyKind, RenameMode};
use notify::{RecommendedWatcher, Watcher};
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::{env, fmt, fs};
/// Get the config directory, with Flatpak sandbox support.
/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory,
@ -51,7 +48,12 @@ fn get_state_dir() -> Option<PathBuf> {
return Some(PathBuf::from(home).join(".local").join("state"));
}
}
dirs::state_dir()
if let Some(state_dir) = dirs::state_dir() {
return Some(state_dir);
}
// On platforms without state_dir, fall back to ~/.local/state
let home_dir = dirs::home_dir()?;
Some(home_dir.join(".local").join(".state"))
}
#[cfg(feature = "subscription")]
@ -162,6 +164,7 @@ pub trait ConfigSet {
pub struct Config {
system_path: Option<PathBuf>,
user_path: Option<PathBuf>,
previous: Option<Box<Config>>,
}
/// Check that the name is relative and doesn't contain . or ..
@ -180,9 +183,13 @@ fn sanitize_name(name: &str) -> Result<&Path, Error> {
impl Config {
/// Get a system config for the given name and config version
pub fn system(name: &str, version: u64) -> Result<Self, Error> {
Self::system_inner(name, version, true)
}
fn system_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
let path = sanitize_name(name)?.join(format!("v{version}"));
#[cfg(unix)]
let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(path);
let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(&path);
#[cfg(windows)]
let system_path =
@ -192,6 +199,13 @@ impl Config {
Ok(Self {
system_path,
user_path: None,
previous: if version > 1 && look_for_previous {
Self::system_inner(name, version - 1, false)
.ok()
.map(Box::new)
} else {
None
},
})
}
@ -199,6 +213,10 @@ impl Config {
// Use folder at XDG config/name for config storage, return Config if successful
//TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy)
pub fn new(name: &str, version: u64) -> Result<Self, Error> {
Self::new_inner(name, version, true)
}
fn new_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version));
@ -223,15 +241,29 @@ impl Config {
Ok(Self {
system_path,
user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::new_inner(name, version - 1, false).ok().map(Box::new)
} else {
None
},
})
}
/// Get config for the given application name and config version and custom path.
pub fn with_custom_path(name: &str, version: u64, custom_path: PathBuf) -> Result<Self, Error> {
Self::with_custom_path_inner(name, version, custom_path, true)
}
fn with_custom_path_inner(
name: &str,
version: u64,
custom_path: PathBuf,
look_for_previous: bool,
) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{version}"));
let mut user_path = custom_path;
let mut user_path = custom_path.clone();
user_path.push("cosmic");
user_path.push(path);
// Create new configuration directory if not found.
@ -241,6 +273,13 @@ impl Config {
Ok(Self {
system_path: None,
user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::with_custom_path_inner(name, version - 1, custom_path.clone(), false)
.ok()
.map(Box::new)
} else {
None
},
})
}
@ -250,6 +289,10 @@ impl Config {
// Use folder at XDG config/name for config storage, return Config if successful
//TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy)
pub fn new_state(name: &str, version: u64) -> Result<Self, Error> {
Self::new_state_inner(name, version, true)
}
fn new_state_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version));
@ -263,6 +306,13 @@ impl Config {
Ok(Self {
system_path: None,
user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::new_state_inner(name, version - 1, false)
.ok()
.map(Box::new)
} else {
None
},
})
}
@ -373,7 +423,13 @@ impl ConfigGet for Config {
Ok(ron::from_str(&data)?)
}
_ => Err(Error::NotFound),
_ => {
if let Some(previous) = self.previous.as_ref() {
previous.get_local(key)
} else {
Err(Error::NotFound)
}
}
}
}

View file

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

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

View file

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

View file

@ -0,0 +1,173 @@
//! Color representation and serde helpers for the Cosmic theme
use hex_color::HexColor;
use palette::{Srgb, Srgba};
use serde::{Deserialize, Serialize};
/// A color in the Cosmic theme for serialization and deserialization
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ColorRepr {
/// A color represented as a hex string
#[serde(with = "hex_color::rgba")]
Hex(HexColor),
/// A color represented as an RGBA value
Rgba(Srgba),
/// A color represented as an RGB value
Rgb(Srgb),
}
/// An optional color in the Cosmic theme for serialization and deserialization
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
#[serde(transparent)]
pub struct ColorReprOption(Option<ColorRepr>);
impl From<Srgb> for ColorRepr {
fn from(color: Srgb) -> Self {
let rgb_u8: Srgb<u8> = color.into_format();
ColorRepr::Hex(HexColor {
r: rgb_u8.red,
g: rgb_u8.green,
b: rgb_u8.blue,
a: 255,
})
}
}
impl From<Srgba> for ColorRepr {
fn from(color: Srgba) -> Self {
let rgba_u8: Srgba<u8> = color.into_format();
ColorRepr::Hex(HexColor {
r: rgba_u8.red,
g: rgba_u8.green,
b: rgba_u8.blue,
a: rgba_u8.alpha,
})
}
}
impl From<ColorRepr> for Srgb {
fn from(value: ColorRepr) -> Self {
match value {
ColorRepr::Hex(hex) => Srgb::<u8>::new(hex.r, hex.g, hex.b).into_format(),
ColorRepr::Rgb(rgb) => rgb,
ColorRepr::Rgba(rgba) => Srgb::new(rgba.red, rgba.green, rgba.blue),
}
}
}
impl From<ColorRepr> for Srgba {
fn from(value: ColorRepr) -> Self {
match value {
ColorRepr::Hex(hex) => Srgba::<u8>::new(hex.r, hex.g, hex.b, hex.a).into_format(),
ColorRepr::Rgb(rgb) => Srgba::new(rgb.red, rgb.green, rgb.blue, 1.0),
ColorRepr::Rgba(rgba) => rgba,
}
}
}
impl From<ColorReprOption> for Option<Srgb> {
fn from(value: ColorReprOption) -> Self {
value.0.map(std::convert::Into::into)
}
}
impl From<ColorReprOption> for Option<Srgba> {
fn from(value: ColorReprOption) -> Self {
value.0.map(std::convert::Into::into)
}
}
impl From<Option<Srgb>> for ColorReprOption {
fn from(value: Option<Srgb>) -> Self {
ColorReprOption(value.map(std::convert::Into::into))
}
}
impl From<Option<Srgba>> for ColorReprOption {
fn from(value: Option<Srgba>) -> Self {
ColorReprOption(value.map(std::convert::Into::into))
}
}
/// A trait for converting between a color type and its representation for serialization and deserialization
pub trait ConvColorRepr: Sized {
/// Convert from a color representation to the color type
fn from_repr(repr: ColorRepr) -> Self;
/// Convert from the color type to its representation for serialization
fn to_repr(&self) -> ColorRepr;
}
impl ConvColorRepr for Srgba {
fn from_repr(repr: ColorRepr) -> Self {
repr.into()
}
fn to_repr(&self) -> ColorRepr {
(*self).into()
}
}
impl ConvColorRepr for Srgb {
fn from_repr(repr: ColorRepr) -> Self {
repr.into()
}
fn to_repr(&self) -> ColorRepr {
(*self).into()
}
}
/// Serde helpers for serializing and deserializing colors in the Cosmic theme
pub mod color_serde {
use super::*;
use serde::{Deserialize, Deserializer, Serializer};
/// Serialize a color to a hex string
pub fn serialize<T, S>(color: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: ConvColorRepr,
S: Serializer,
{
let repr = color.to_repr();
repr.serialize(serializer)
}
/// Deserialize a color from a hex string or RGB/RGBA
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: ConvColorRepr,
D: Deserializer<'de>,
{
let repr = ColorRepr::deserialize(deserializer)?;
Ok(T::from_repr(repr))
}
/// Serde helpers for serializing and deserializing optional colors in the Cosmic theme
pub mod option {
use super::*;
/// Serialize an optional color
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: ConvColorRepr,
S: Serializer,
{
match value {
Some(v) => Some(v.to_repr()).serialize(serializer),
None => serializer.serialize_none(),
}
}
/// Deserialize an optional color
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: ConvColorRepr,
D: Deserializer<'de>,
{
let opt = Option::<ColorRepr>::deserialize(deserializer)?;
Ok(opt.map(T::from_repr))
}
}
}

View file

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

View file

@ -1,3 +1,4 @@
use crate::color::color_serde;
use palette::Srgba;
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
@ -95,75 +96,107 @@ pub struct CosmicPaletteInner {
/// Utility Colors
/// Colors used for various points of emphasis in the UI.
#[serde(with = "color_serde")]
pub bright_red: Srgba,
/// Colors used for various points of emphasis in the UI.
#[serde(with = "color_serde")]
pub bright_green: Srgba,
/// Colors used for various points of emphasis in the UI.
#[serde(with = "color_serde")]
pub bright_orange: Srgba,
/// Surface Grays
/// Colors used for three levels of surfaces in the UI.
#[serde(with = "color_serde")]
pub gray_1: Srgba,
/// Colors used for three levels of surfaces in the UI.
#[serde(with = "color_serde")]
pub gray_2: Srgba,
/// System Neutrals
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_0: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_1: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_2: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_3: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_4: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_5: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_6: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_7: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_8: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_9: Srgba,
/// A wider spread of dark colors for more general use.
#[serde(with = "color_serde")]
pub neutral_10: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_blue: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_indigo: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_purple: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_pink: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_red: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_orange: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_yellow: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_green: Srgba,
/// Potential Accent Color Combos
#[serde(with = "color_serde")]
pub accent_warm_grey: Srgba,
/// Extended Color Palette
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_warm_grey: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_orange: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_yellow: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_blue: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_purple: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_pink: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes.
#[serde(with = "color_serde")]
pub ext_indigo: Srgba,
}

View file

@ -1 +1 @@
Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0)))
Dark((name: "cosmic-dark",bright_red: "#FFA09AFF",bright_green: "#5EDB8CFF",bright_orange: "#FFA37DFF",gray_1: "#1B1B1BFF",gray_2: "#262626FF",neutral_0: "#000000FF",neutral_1: "#030303FF",neutral_2: "#161616FF",neutral_3: "#2E2E2EFF",neutral_4: "#484848FF",neutral_5: "#636363FF",neutral_6: "#808080FF",neutral_7: "#9E9E9EFF",neutral_8: "#BEBEBEFF",neutral_9: "#DEDEDEFF",neutral_10: "#FFFFFFFF",accent_blue: "#63D0DFFF",accent_indigo: "#A1C0EBFF",accent_purple: "#E79CFEFF",accent_pink: "#FF9CB1FF",accent_red: "#FDA1A0FF",accent_orange: "#FFAD00FF",accent_yellow: "#F7E062FF",accent_green: "#92CF9CFF",accent_warm_grey: "#CABAB4FF",ext_warm_grey: "#9B8E8AFF",ext_orange: "#FFAD00FF",ext_yellow: "#FEDB40FF",ext_blue: "#48B9C7FF",ext_purple: "#CF7DFFFF",ext_pink: "#F93A83FF",ext_indigo: "#3E88FFFF",))

View file

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

View file

@ -1,3 +1,4 @@
use crate::color::color_serde;
use palette::{Srgba, WithAlpha};
use serde::{Deserialize, Serialize};
@ -8,14 +9,18 @@ use crate::composite::over;
#[must_use]
pub struct Container {
/// the color of the container
#[serde(with = "color_serde")]
pub base: Srgba,
/// the color of components in the container
pub component: Component,
/// the color of dividers in the container
#[serde(with = "color_serde")]
pub divider: Srgba,
/// the color of text in the container
#[serde(with = "color_serde")]
pub on: Srgba,
/// the color of @small_widget_container
#[serde(with = "color_serde")]
pub small_widget: Srgba,
}
@ -45,30 +50,42 @@ impl Container {
#[must_use]
pub struct Component {
/// The base color of the widget
#[serde(with = "color_serde")]
pub base: Srgba,
/// The color of the widget when it is hovered
#[serde(with = "color_serde")]
pub hover: Srgba,
/// the color of the widget when it is pressed
#[serde(with = "color_serde")]
pub pressed: Srgba,
/// the color of the widget when it is selected
#[serde(with = "color_serde")]
pub selected: Srgba,
/// the color of the widget when it is selected
#[serde(with = "color_serde")]
pub selected_text: Srgba,
/// the color of the widget when it is focused
#[serde(with = "color_serde")]
pub focus: Srgba,
/// the color of dividers for this widget
#[serde(with = "color_serde")]
pub divider: Srgba,
/// the color of text for this widget
#[serde(with = "color_serde")]
pub on: Srgba,
// the color of text with opacity 80 for this widget
// pub text_opacity_80: Srgba,
/// the color of the widget when it is disabled
#[serde(with = "color_serde")]
pub disabled: Srgba,
/// the color of text in the widget when it is disabled
#[serde(with = "color_serde")]
pub on_disabled: Srgba,
/// the color of the border for the widget
#[serde(with = "color_serde")]
pub border: Srgba,
/// the color of the border for the widget when it is disabled
#[serde(with = "color_serde")]
pub disabled_border: Srgba,
}

View file

@ -1 +1 @@
Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0)))
Light((name: "cosmic-light",bright_red: "#890418FF",bright_green: "#00572CFF",bright_orange: "#792C00FF",gray_1: "#D7D7D7FF",gray_2: "#E4E4E4FF",neutral_0: "#FFFFFFFF",neutral_1: "#DEDEDEFF",neutral_2: "#BEBEBEFF",neutral_3: "#9E9E9EFF",neutral_4: "#808080FF",neutral_5: "#636363FF",neutral_6: "#484848FF",neutral_7: "#2E2E2EFF",neutral_8: "#161616FF",neutral_9: "#030303FF",neutral_10: "#000000FF",accent_blue: "#00525AFF",accent_indigo: "#2E496DFF",accent_purple: "#68217CFF",accent_pink: "#86043AFF",accent_red: "#78292EFF",accent_orange: "#624000FF",accent_yellow: "#534800FF",accent_green: "#185529FF",accent_warm_grey: "#554742FF",ext_warm_grey: "#9B8E8AFF",ext_orange: "#FBB86CFF",ext_yellow: "#F7E062FF",ext_blue: "#6ACAD8FF",ext_purple: "#D58CFFFF",ext_pink: "#FF9CDDFF",ext_indigo: "#95C4FCFF",))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,281 +1,21 @@
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 std::path::PathBuf;
use std::{fs, io};
use super::{OutputError, qt_settings_ini_style};
use super::OutputError;
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.
/// Reset the applied qt colors:
/// - Remove relevant keys from the `~/.config/kdeglobals` file
/// - Delete `~/.local/share/color-schemes/CosmicDark.colors` and `CosmicLight.colors`
///
/// This does not restore the backed up kdeglobals file.
///
/// # Errors
///
/// Returns an `OutputError` if there is an error resetting the CSS file.
/// Returns an `OutputError` if there is an error resetting the kdeglobals
/// file or deleting the color schemes.
#[cold]
pub fn reset_qt() -> Result<(), OutputError> {
let Some(config_dir) = dirs::config_dir() else {
@ -292,19 +32,29 @@ widgetStyle=qt6ct-style
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)?;
let light_scheme = Self::get_kcolorscheme_path(false)?;
let dark_scheme = Self::get_kcolorscheme_path(true)?;
if light_scheme.exists() {
let src_ini = Self::read_ini(&light_scheme)?;
for (section, key_value) in src_ini.get_map_ref() {
for (key, _) in key_value {
kdeglobals_ini.remove_key(section, key);
// Remove color scheme keys from kdeglobals
for (section, key_value) in src_ini.get_map_ref() {
for key in key_value.keys() {
kdeglobals_ini.remove_key(section, key);
}
}
kdeglobals_ini
.write(kdeglobals_file)
.map_err(OutputError::Io)?;
}
kdeglobals_ini
.write(kdeglobals_file)
.map_err(OutputError::Io)?;
// Delete now-unused kcolorscheme files
let delete_light_res = fs::remove_file(&light_scheme).map_err(OutputError::Io);
let delete_dark_res = fs::remove_file(&dark_scheme).map_err(OutputError::Io);
delete_light_res?;
delete_dark_res?;
Ok(())
}
@ -313,6 +63,7 @@ widgetStyle=qt6ct-style
let Some(mut data_dir) = dirs::data_dir() else {
return Err(OutputError::MissingDataDir);
};
data_dir.push("color-schemes");
let file_name = if is_dark {
"CosmicDark.colors"
@ -320,11 +71,6 @@ widgetStyle=qt6ct-style
"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))
}
@ -339,15 +85,6 @@ widgetStyle=qt6ct-style
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");
@ -361,208 +98,43 @@ widgetStyle=qt6ct-style
}
}
/// 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");
fn test_is_cosmic_kdeglobals_dark() -> io::Result<()> {
let mut ini = Ini::new_cs();
ini.setstr("General", "ColorScheme", Some("CosmicDark"));
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?;
assert_eq!(is_cosmic, Some(true));
Ok(())
}
#[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");
fn test_is_cosmic_kdeglobals_light() -> io::Result<()> {
let mut ini = Ini::new_cs();
ini.setstr("General", "ColorScheme", Some("CosmicLight"));
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?;
assert_eq!(is_cosmic, Some(true));
Ok(())
}
#[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");
fn test_is_cosmic_kdeglobals_breeze() -> io::Result<()> {
let mut ini = Ini::new_cs();
ini.setstr("General", "ColorScheme", Some("BreezeDark"));
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?;
assert_eq!(is_cosmic, Some(false));
Ok(())
}
#[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);
fn test_is_cosmic_kdeglobals_blank() -> io::Result<()> {
let mut ini = Ini::new_cs();
ini.setstr("General", "ColorScheme", None);
let is_cosmic = Theme::is_cosmic_kdeglobals(&ini)?;
assert_eq!(is_cosmic, None);
Ok(())
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

34
i18n/gl/libcosmic.ftl Normal file
View file

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

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