Compare commits

..

195 commits

Author SHA1 Message Date
Vukašin Vojinović
95756b1a57 improv(circular): prevent caps from touching 2026-04-18 16:08:34 -04:00
Vukašin Vojinović
c423ad1bfc improv(about): use ListButton 2026-04-17 13:52:08 +02:00
Vukašin Vojinović
8d7bcab258 fix(list_column): add back divider_padding
Also matches previous behavior of both paddings being applied to subsequent items, rather than globally.
2026-04-17 13:17:23 +02:00
Hojjat
c162a1f24a fix(animated-image): update frames and fix compilation errors 2026-04-16 19:58:39 +02:00
Vukašin Vojinović
3f9e93067b fix(item builder): remove unnecessary lifetime bound for radio 2026-04-16 18:26:17 +02:00
Vukašin Vojinović
917af9fda2 feat(radio): internal method for radio without label
Also adds the related settings item builder.
2026-04-16 17:19:36 +02:00
Vukašin Vojinović
9b465a8b5c feat(list_column): button list items 2026-04-16 17:19:36 +02:00
Vukašin Vojinović
9cac422c24 fix(toggler): animate external changes 2026-04-16 17:19:36 +02:00
Hojjat
0fc4638af3 fix: register image_extras in run_single_instance too 2026-04-15 23:59:57 +02:00
Hojjat
3d8d8915be chore: enable ico and xpm image support for desktop feature 2026-04-15 13:10:25 +02:00
Ian Douglas Scott
46d9f0c344 widget/icon: Bundle icons on macOS, not just Windows 2026-04-14 21:46:05 +02:00
Jeremy Soller
0d69cd9183
i18n: translation update from Hosted Weblate (#1177)
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-14 09:52:02 -06:00
Hojjat
52116d2f36 chore: update iced 2026-04-13 22:26:33 +02:00
Hosted Weblate
0e72508dcc
i18n: translation updates from weblate
Co-authored-by: Amadɣas <massiin@proton.me>
Co-authored-by: Asier Saratsua Garmendia <asier.sarasua@gmail.com>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 麋麓 BigELK176 <BigELK176@gmail.com>
Co-authored-by: 김유빈 <k.sein1016@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/kab/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ko/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hant/
Translation: Pop OS/libcosmic
2026-04-12 18:50:19 +02:00
Jeremy Soller
1d7113a244
chore: update iced (#1240)
- [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-04-11 06:27:56 -06:00
Hojjat
e287a789c1 chore: update iced 2026-04-10 20:53:43 -06:00
Hojjat
6caccaba33 fix: icon color when window is maximized 2026-04-09 12:54:32 -04:00
Ashley Wulber
a44cff8011 fix(text_input): always clip input text with the text bounds
this issue seems unique to tiny-skia
2026-04-08 17:05:40 +02:00
Ashley Wulber
47ab72be50
fix!(progress_bar): remove unused generic Message type 2026-04-08 07:38:18 +02:00
Adam Cosner
c7093beca3 fix(ci): cargo now running properly 2026-04-08 07:34:13 +02:00
Adam Cosner
77b37f2246 fix(ci) removed the smithay and wayland protocol docs builds 2026-04-08 07:04:54 +02:00
Adam Cosner
6df3f76a33 ci: Added a few more enabled dependency docs 2026-04-08 07:04:54 +02:00
Adam Cosner
12d2233c6b fix(ci): Added an inline doc to cctk reexport 2026-04-08 07:04:54 +02:00
Adam Cosner
e5955b568d ci: Updated pages.yml workflow
Use nightly channel to enable docs generating feature badges, plus enabled more features in the docs build, and building the cctk docs also
2026-04-08 07:04:54 +02:00
Adam Cosner
5d1dfc4c54
refactor!: remove cosmic::iced_* re-exports 2026-04-08 03:12:10 +02:00
Ashley Wulber
d9121d6f0d refactor: better helpers for the progress_bar 2026-04-07 21:47:46 +02:00
Ashley Wulber
b963fbfea9
feat(widget): progress bars 2026-04-07 17:02:58 +02:00
Hojjat
724351727a feat: select until char and double click select delimiter
adds a feature to select from the start of the sentence until the last
occurrence of a character. This can be used to select until the
extension in cosmic-files save dialog or rename pop up.

Also, it adds a feature to select until the last occurrence of a
character on double-click.
2026-04-07 13:35:26 +02:00
Hojjat
1f87cbc883 fix: do not allow cursor or keyboard activity when popup is open
traps Tab from escaping, and won't allow elements in the background to
react to hover
2026-04-07 13:32:21 +02:00
Ashley Wulber
9aa87cd66b fix(segmented_button): active font for context menu & prioritize active font over hover 2026-04-06 18:57:27 -04:00
Hojjat
ab3eedd0f2 chore: update iced
This pulls in the fix in cosmic-text to fallback to the default
SansSerif if there are missing glyphs in basic shaping.

Also removes advanced-shaping from the default features list.
2026-04-06 15:15:30 -04:00
KENZ
8e3672a7dd fix: focus detecting in IME logic 2026-04-06 15:59:18 +02:00
Hojjat
1d01054993 chore: update iced
pulls in fixes for cycling focus
2026-04-03 19:23:40 -04:00
Vukašin Vojinović
fdf3369cea chore: re-export iced row and column
This removes the custom row and column implementations and uses the iced ones directly.
2026-04-03 20:39:31 +02:00
Vukašin Vojinović
a9e0671075 fix(segmented_button): hover text style 2026-04-03 20:36:23 +02:00
Ashley Wulber
34219d1fd4 chore: wgpu cctk feature for wayland 2026-04-03 20:15:31 +02:00
Ashley Wulber
cdd825b953 fix: update iced
softbuffer released version doesn't support transparency yet
2026-04-03 16:17:44 +02:00
Ashley Wulber
b0f4e931f2 fix: font issues
some fonts are not falling back when a glyph is missing for a selected font and weight
2026-04-03 16:17:44 +02:00
Hendrik Hamerlinck
97a805e5a1 feat(applets): add destroy tooltip popup action
This commit adds a new surface action to explicitly destroy the tooltip
popup on `TOOLTIP_WINDOW_ID`, allowing proper cleanup when minimizing
applets.
2026-04-03 08:26:29 -04:00
Hojjat
24464908f6 fix: buttons are focusable again 2026-04-03 02:28:00 +02:00
GroobleDierne
7a02c9a296 fix(color palette): avoid duplicates 2026-04-02 16:21:50 -04:00
Hojjat
61e5d882ae fix(ci): only document libcosmic, no dependency 2026-04-02 15:47:43 -04:00
Hojjat
12be83a8ef chore: update iced 2026-04-01 22:14:07 -04:00
KENZ
f6eb314606
feat(text_input): minimal IME support for COSMIC specific text widgets 2026-04-02 00:35:57 +02:00
TobyDig
0ba668eb52
fix(desktop): use -e argument for spawning desktop entries with a terminal 2026-04-01 23:32:36 +02:00
Hojjat
aef328238f fix(editable): the UX is closer to design now
This fixes the unresponsive trailing icon and changes the behavior to be
closer to the UI/UX design.
2026-04-01 23:29:26 +02:00
Hojjat
22661fd764 chore: udpate iced 2026-04-01 23:26:42 +02:00
Hojjat
e1738d2ea7 fix(text_input): keyboard shortcuts when keyboard is a different language
Matches what Iced does
2026-04-01 23:26:42 +02:00
Hojjat
2299fba69b fix(text_input): RTL text cursor and highlight fixes 2026-04-01 23:26:42 +02:00
Adil Hanney
c33455e9ad test: use default dark theme, not real system theme 2026-04-01 23:23:37 +02:00
Adil Hanney
9a72fe6c2d fix: complementary should be dark not light 2026-04-01 23:23:37 +02:00
Adil Hanney
39e8300d90 test: snapshots of kcolorscheme and qpalette
AI disclosure: I asked GitHub Copilot (Claude Haiku 4.5) "What's the best way to add tests for my recently merged qt theming contributions?" It suggested the insta crate for golden testing the output strings as well as some unit tests. I implemented it myself.
2026-04-01 23:23:37 +02:00
Adil Hanney
f734ccbbde test: fix expected color value 2026-04-01 23:23:37 +02:00
Adil Hanney
e86304cf3f ref: use assert_eq not assert
This way, the test log can show the expected and actual result if it fails.

thread 'steps::tests::test_conversion_fallback_colors' (61338) panicked at cosmic-theme/src/steps.rs:213:9:
assertion `left == right` failed
  left: 102
 right: 103
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
2026-04-01 23:23:37 +02:00
Adil Hanney
672f9047a2 test: use almost::zero instead of almost::equal as per documentation
"Do not use this to compare a value with a constant zero. Instead, for this you should use almost::zero."
2026-04-01 23:23:37 +02:00
Adil Hanney
8b52592f2d ci: test cosmic-theme 2026-04-01 23:23:37 +02:00
Ashley Wulber
d631f9d6d7
chore: update iced 2026-04-01 23:21:27 +02:00
Ashley Wulber
4541c6a275 fix: example deps 2026-03-31 21:34:26 +02:00
Ashley Wulber
1433b89e40 chore: update iced 2026-03-31 21:34:26 +02:00
Adil Hanney
f06d15ae35
feat(cosmic-theme): produce QPalette ini for more compatibility 2026-03-31 17:02:52 +02:00
Ashley Wulber
413e63f62a chore: update features and feature gates 2026-03-30 22:25:27 -04:00
Ítalo Dell Areti
380b341bdc feat(text_input): add select_range method and Task function 2026-03-28 00:09:34 -04:00
Ashley Wulber
254c13cfc4 fix: ellipsize text in menu items 2026-03-27 21:22:54 +01:00
Ashley Wulber
e63f3196e2 fix: MenuActive path highlight 2026-03-27 21:21:59 +01:00
Hojjat
a38a6f5d73 fix(ci): install dependencies 2026-03-27 01:22:25 +01:00
Ashley Wulber
763f0da64c
fix(iced): RTL text fix 2026-03-26 22:19:39 +01:00
Ashley Wulber
adb3e341fc fix(theme): bright colors for success, warn, destructive 2026-03-25 19:04:30 +01:00
Ashley Wulber
8e439c842c chore: update iced 2026-03-24 01:25:57 +01:00
Frederic Laing
d7fd880ac6 fix(toggler): add touch input support 2026-03-23 10:22:04 -04:00
Hojjat
141261b9bf chore: update iced 2026-03-23 10:21:15 -04:00
Hojjat
c804d3851d fix: don't ever draw glyphs outside of the bounds 2026-03-23 10:21:15 -04:00
Hojjat
dc3ebaa38e feat(segmented_button): add ellipsize support 2026-03-23 10:21:15 -04:00
Hojjat
7a56762422 fix: restore width and height fill for app content 2026-03-20 22:23:16 +01:00
Ashley Wulber
36cba695d2 chore: update iced 2026-03-20 16:04:48 +01:00
Hojjat
3da55e8074 fix(flex_row): calculate height based on nodes 2026-03-18 15:54:33 +01:00
Vukašin Vojinović
54bcb9ec12
chore: update dependencies and examples 2026-03-18 15:54:07 +01:00
Ashley Wulber
6c6d16d34a
fix(iced): scaling issue in the cosmic-greeter lock screen 2026-03-18 15:53:09 +01:00
Ashley Wulber
c7ac9cfd31 fix: if not in bounds, return default mouse interaction 2026-03-17 20:51:22 +01:00
Vukašin Vojinović
0bb006c5bb fix(header_bar): add vertical SSD padding
Prevents SSDs from having a gap after the rebase.
2026-03-17 17:28:23 +01:00
Vukašin Vojinović
adb6e30405
feat(header_bar): use custom widget for layout 2026-03-17 16:23:31 +01:00
Ashley Wulber
9602dfd2f1 chore: update iced 2026-03-16 16:37:18 -04:00
Ashley Wulber
12cc536cd5 chore: update iced
fix for tiny-skia rotation
2026-03-16 19:37:18 +01:00
Jonathan Wingrove
c52ef97650 fix(table): Use on_item_mb_double for double-click handler instead of on_item_mb_left 2026-03-15 00:30:16 +01:00
Ashley Wulber
01e5593741 chore: update iced 2026-03-12 11:05:32 -04:00
Dryadxon
1dc9aa37ed feat(flex_row): re-export JustifyItems 2026-03-11 17:52:24 +01:00
Dryadxon
ce9e8b5205 fix(flex_row): layout::resolve swap align_items with justify_items 2026-03-11 17:52:24 +01:00
Ashley Wulber
b4533e3a56 chore: update deps 2026-03-11 15:43:49 +01:00
Jeremy Soller
c66652df41
chore: udpate iced (#1162)
- [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-03-11 08:38:12 -06:00
Ashley Wulber
242fe6c4ac chore: update iced 2026-03-11 10:15:30 -04:00
Ashley Wulber
26f4086931
fix(iced): fix touch event handling 2026-03-10 17:33:00 +01:00
Ashley Wulber
4b92ee5f80 chore: update iced
includes fix for virtual offsets
2026-03-09 17:05:27 -04:00
Jeremy Soller
ff6454248f
i18n: translation update from Hosted Weblate (#1147)
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-03-09 09:50:33 -06:00
Hosted Weblate
5eec820615
i18n: translation updates from weblate
Co-authored-by: Aman Alam <aalam@users.noreply.hosted.weblate.org>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Vilius Paliokas <viliuspaliokas@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/lt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pa/
Translation: Pop OS/libcosmic
2026-03-08 09:09:56 +00:00
Ashley Wulber
03d0171bbe chore: update iced 2026-03-06 16:49:44 -05:00
Ashley Wulber
3d2c018cd1
fix(dnd_source): rely on current cursor position for hover state 2026-03-06 20:37:56 +01:00
Ashley Wulber
79f8337634
fix(iced): space key is now handled differently in iced-winit 2026-03-06 19:21:34 +01:00
Ashley Wulber
14a5d0c0ba
fix(iced): reversed scroll direction 2026-03-06 17:55:53 +01:00
Ashley Wulber
1970499459 fix: capture mouse motion and mouse interactions in overlay 2026-03-05 22:02:40 +01:00
Alex Marín
1810bedfa5
fix(navbar): fill height of panel instead of shrinking 2026-03-05 15:07:26 +01:00
Ashley Wulber
ad65416551 fix: resize border 2026-03-04 13:12:28 -05:00
Ashley Wulber
8795c506fa chore: update iced
should fix responsive widgets
2026-03-04 12:04:33 -05:00
Ashley Wulber
976e0e214f chore: update iced 2026-03-04 12:04:33 -05:00
Ashley Wulber
0bfda2e28c chore: update deps and test fixes 2026-03-04 12:04:33 -05:00
Ashley Wulber
5432fee112 chore: update iced 2026-03-04 12:04:33 -05:00
Ashley Wulber
925cc9a39f chore: update iced 2026-03-04 12:04:33 -05:00
Ashley Wulber
0e1a9d46eb chore: update iced & cleanup text input 2026-03-04 12:04:33 -05:00
Ashley Wulber
89d31e988d chore: update iced 2026-03-04 12:04:33 -05:00
Ashley Wulber
3d8596287c fix: missed event status after rebase 2026-03-04 12:04:33 -05:00
Ashley Wulber
0298487096 fix: overlay event handling and mouse interaction 2026-03-04 12:04:33 -05:00
Ashley Wulber
904133397b fix: toggler width fixes & cleanup 2026-03-04 12:04:32 -05:00
Ashley Wulber
bee2d591db chore: update iced 2026-03-04 12:04:32 -05:00
Ashley Wulber
442ce6ad0c fix: context-menu
when a popup is created and a focus event is received, we shouldn't close the popups, because it may be a focus event for a popup
2026-03-04 12:04:32 -05:00
Ashley Wulber
fb1a7d3640 fix: open-dialog example 2026-03-04 12:04:32 -05:00
Ashley Wulber
89ee66f251 fix: menu bar and flex row event handling 2026-03-04 12:04:32 -05:00
Ashley Wulber
7554540b78 fix: update for applet widgets and grid 2026-03-04 12:04:32 -05:00
Ashley Wulber
71e2c7c99e fix: responsive menu layout 2026-03-04 12:04:32 -05:00
Ashley Wulber
0d37dc69e3 fix: applet popup width 2026-03-04 12:04:32 -05:00
Ashley Wulber
e6fe1a6811 fix: ellipsize 2026-03-04 12:04:32 -05:00
Ashley Wulber
e8d53b14ea chore: various fixes and some cleanup 2026-03-04 12:04:32 -05:00
Ashley Wulber
e10459fb37 wip rebase updates 2026-03-04 12:04:32 -05:00
Michael Aaron Murphy
86dcf8af6c
feat(cosmic-icons): new icons for cosmic image viewer app 2026-03-03 23:32:00 +01:00
Michael Aaron Murphy
85c27a9960
fix(cosmic-theme): on reset of theme exports, do not remove VS code configs
Closes #1139
2026-03-03 21:18:45 +01:00
Hojjat Abdollahi
bd1d3d5a73
fix: ellipsize headerbar title instead of wrapping (#1140) 2026-03-02 12:01:19 -07:00
Michael Murphy
5b648ca03f
i18n: translation update from Hosted Weblate (#1126)
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-03-02 17:31:52 +01:00
Hosted Weblate
f2caa66f0f
i18n: translation updates from weblate
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Arve Eriksson <031299870@telia.com>
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: Benmak Kizuna <benmakworkshop@gmail.com>
Co-authored-by: David Carvalho <david.snt.carvalho@gmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Fedorov Alexei <aleksejfedorov963@gmail.com>
Co-authored-by: Feike Donia <feikedonia@proton.me>
Co-authored-by: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jiri Grönroos <jiri.gronroos@iki.fi>
Co-authored-by: Julien Brouillard <julienbrouillard1@gmail.com>
Co-authored-by: Marko X <duffsd@gmail.com>
Co-authored-by: Tommi Nieminen <translator@legisign.org>
Co-authored-by: VandaL <vandalhj@gmail.com>
Co-authored-by: Zahid Rizky Fakhri <zahidrizkyfakhri@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: yakup <mtopac2018@gmail.com>
Co-authored-by: Димко <Term0@ukr.net>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/cs/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/fi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/fr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ga/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/id/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/kk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ru/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sv/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/tr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hans/
Translation: Pop OS/libcosmic
2026-03-02 16:10:07 +00:00
Michael Aaron Murphy
a37be90e81 fix(single-instance): unminimize main window on dbus activate 2026-02-24 16:02:56 +01:00
Alex Marín
384e8f6e21
fix(segmented_button): clear bold button text on context menu close 2026-02-20 12:06:45 +01:00
Hojjat Abdollahi
b9bd773940
feat: ellipsize text (#1132) 2026-02-19 10:06:45 -07:00
Jeremy Soller
1f6086e5ea Update iced 2026-02-19 09:18:35 -07:00
mariinkys
c1c09624bd fix: right-clicking any sidebar item makes all sidebar items bold 2026-02-19 16:43:56 +01:00
Adil Hanney
754b064bff tweak(cosmic-theme): pretty write ini 2026-02-18 14:19:50 -07:00
Adil Hanney
3ed5c173fd fix(cosmic-theme): copy for backup, not rename
We're now merging the colors with kdeglobals, not replacing it with a symlink. So renaming the file gives us a missing file Io error:
 [2026-02-18T20:03:08Z ERROR cosmic_settings_daemon::theme] Failed to apply COSMIC theme exports. Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })
2026-02-18 22:03:57 +01:00
Adil Hanney
dc3c194f09
fix(cosmic-theme): inverted Qt link_button colors 2026-02-18 21:02:58 +01:00
Michael Aaron Murphy
e1dad541b2
chore(cosmic-theme): Theme::apply_exports should not apply VS Code theme currently 2026-02-18 15:00:41 +01:00
Michael Aaron Murphy
7c49a736ec
refactor(cosmic-theme): remove Theme::apply_exports_static
Recently-added method is redundant with `apply_exports`, and the dark
mode preference is already defined in the theme being applied.
2026-02-18 14:24:19 +01:00
Michael Aaron Murphy
be98b7dd6f
refactor(cosmic-theme): remove recently-added Theme::get_active_with_brightness
The added method was not necessary. Also improves the code in the get_active method.
2026-02-18 14:18:27 +01:00
Vukašin Vojinović
cb288070af chore: cargo fmt 2026-02-17 21:18:55 +01:00
Vukašin Vojinović
990e2e291b refactor(calendar): use jiff instead of chrono
This refactors the calendar widget to use `jiff` instead of `chrono`.
Also mostly matches the design of the widget to the time applet.
2026-02-17 21:18:55 +01:00
Hosted Weblate
b05f040e5f i18n: translation updates from weblate
Co-authored-by: Benmak Kizuna <benmakworkshop@gmail.com>
Co-authored-by: Fedorov Alexei <aleksejfedorov963@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ru/
Translation: Pop OS/libcosmic
2026-02-17 09:53:46 -07:00
Adil Hanney
a2e903ad94
feat(cosmic-theme): add color schemes for qt apps 2026-02-17 17:39:37 +01:00
Michael Aaron Murphy
6328c40ef7
chore: update iced 2026-02-16 16:51:02 +01:00
Frieder Hannenheim
21c5a4f34a
feat(dnd_destination): xdg file transfer portal support
Requires the `xdg-portal` feature to be enabled to use these features.

- Adds `DndDestination::on_file_transfer` method to handle `application/vnd.portal.filetransfer` drop requests
- Adds `command::file_transfer_receive` function to handle the file transfer request messages
- Adds `command::file_transfer_send` to initiate a file transfer from the application
2026-02-16 16:41:35 +01:00
Jeremy Soller
ae1f15f37e Add pull request template 2026-02-13 12:36:03 -07:00
Michael Aaron Murphy
031818c6b0
fix(font): explicitly drop read guard in on font family lookup 2026-02-13 18:30:14 +01:00
Michael Aaron Murphy
ae830ca21d
perf(font): use RwLock when getting fonts instead of Mutex 2026-02-12 15:52:40 +01:00
Michael Aaron Murphy
a3cf875793
fix(single-instance): unminimize main window on dbus activate 2026-02-09 22:04:13 +01:00
Hosted Weblate
30a02ec0bb i18n: translation updates from weblate
Co-authored-by: Aliaksandr Truš <evils.mail@gmail.com>
Co-authored-by: Drugi Sapog <dindrugi@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Quentin PAGÈS <quentinantonin@free.fr>
Co-authored-by: jickson john <jickson.john@gmail.com>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: Димко <Term0@ukr.net>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/be/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/
Translation: Pop OS/libcosmic
2026-02-09 15:24:37 +01:00
Hosted Weblate
3e78eb2381 i18n: translation updates from weblate
Co-authored-by: Hafidz Nasruddin <hafidz@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Zahid Rizky Fakhri <zahidrizkyfakhri@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/id/
Translation: Pop OS/libcosmic
2026-01-30 19:10:49 -07:00
Ashley Wulber
fdcba7d8ec fix(segmented_button): dnd hover 2026-01-29 00:07:00 +01:00
Vukašin Vojinović
cf19ac665f chore: update dependencies 2026-01-27 18:07:35 -07:00
Vukašin Vojinović
b71a7c9edf improv: remove double coloring of content_container windows
This sets the main content and the header bar to transparent when `content_container` is true, so that things aren't colored twice and overlayed on top of each other.
This ensures that modifying color alpha behaves as expected, especially for frosted glass.
2026-01-27 18:07:35 -07:00
Ashley Wulber
9fcd449611 fix(segmented_button): hover state handling
when hover state changes, paragraphs also need to be updated. I'll make a not to check this again after the rebase though.
2026-01-27 14:10:04 -05:00
Hosted Weblate
f1c43f79ab i18n: translation updates from weblate
Co-authored-by: Aman Alam <aalam@users.noreply.hosted.weblate.org>
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jun Hwi Ku <siguning@gmail.com>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Co-authored-by: gift983 <983649@my.leicestercollege.ac.uk>
Co-authored-by: summoner001 <summoner@disroot.org>
Co-authored-by: 김유빈 <k.sein1016@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/kk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ko/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hans/
Translation: Pop OS/libcosmic
2026-01-27 10:33:22 -07:00
Ashley Wulber
927035809f refactor(segmented button): only clear tab drag after source event cancel or finish 2026-01-24 00:53:30 +01:00
Ashley Wulber
beddbf1770 improv(segmented_button): dnd state handling 2026-01-22 10:26:58 -05:00
Ashley Wulber
d71c42102d fix(segmented button): tab dnd 2026-01-22 02:11:08 +01:00
vacenty
689f25be53
feat(spin_button): when value is min/maxed, disable decrease/increase button 2026-01-21 14:08:25 +01:00
Hosted Weblate
097c76f0e5 i18n: translation updates from weblate
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2026-01-16 10:12:11 -07:00
Jonatan Pettersson
3e6c9a6add feat: add optional placeholder text to dropdown 2026-01-16 08:50:58 -07:00
Michael Aaron Murphy
85709b5c29
fix(iced): fix for crash in cosmic-launcher 2026-01-15 15:23:51 +01:00
Michael Aaron Murphy
03c440b97a
chore(cargo): update all crate dependencies 2026-01-14 18:46:53 +01:00
Michael Aaron Murphy
b0cbb54bf2
chore(widget): remove unused RcWrapper method 2026-01-13 17:01:57 +01:00
Michael Aaron Murphy
f000433690
fix(spin_button): compiler error on build without a11y 2026-01-13 17:01:27 +01:00
Mateusz Mikuła
f453db2425 chore: update iced submodule
This pulls in the fix made in https://github.com/pop-os/iced/pull/253.
2026-01-12 21:17:52 +01:00
Michael Aaron Murphy
b9c24d2421 feat(a11y): screen reader name and description support for button widgets 2026-01-09 23:35:28 +01:00
Hosted Weblate
f6039597b7 i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hans/
Translation: Pop OS/libcosmic
2026-01-06 17:17:11 +01:00
Michael Aaron Murphy
421552dea1
fix!(desktop): IconSourceExt::as_cosmic_icon should return Handle with SVG preference 2026-01-06 02:25:46 +01:00
Michael Aaron Murphy
e9bb5ed97d
chore: update freedesktop-desktop-entry 2026-01-06 02:25:11 +01:00
Michael Murphy
a9f64c33ce i18n: removing translation for Frankish 2025-12-30 08:45:53 -07:00
Hosted Weblate
6f92465fcb i18n: translation updates from weblate
Co-authored-by: Amadɣas <massiin@proton.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pt_BR/
Translation: Pop OS/libcosmic
2025-12-30 08:45:53 -07:00
Ashley Wulber
dd3610b8ae fix(dnd_destination): layout for dnd rectangle children 2025-12-19 16:05:40 -05:00
Michael Aaron Murphy
fa26e0e241
docs: add link to cosmic-applet-template 2025-12-17 03:25:00 +01:00
Hosted Weblate
e4978693b9 i18n: translation updates from weblate
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Ekramul Reza <ekramulreza@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Temuri Doghonadze <temuri.doghonadze@gmail.com>
Co-authored-by: Vilius Paliokas <viliuspaliokas@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ga/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/lt/
Translation: Pop OS/libcosmic
2025-12-16 16:56:04 +01:00
Bryan Hyland
aabc8dcda5
build(windows): change icon path separator for native windows builds 2025-12-09 20:01:57 +01:00
Hosted Weblate
3b8ad45950 i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/
Translation: Pop OS/libcosmic
2025-12-09 16:13:34 +01:00
Ian Douglas Scott
2f0b333491 Add helper for accumulating scroll into discrete delta
This converts `ScrollDelta::Pixels` and `ScrollDelta::Lines` into
integer values, accumulating partial scrolls until a full integer is
reached.

It also has a configurable rate-limit, so discrete integer events can
occur at a certain maximum frequency. This may need tuning for different
use cases, though I haven't tried using it for things other than
changing workspaces so far.
2025-12-06 20:00:59 -08:00
Michael Aaron Murphy
05c6608842
examples: fix libcosmic features, warnings, etc. 2025-12-05 17:59:42 +01:00
Michael Aaron Murphy
f39ad728c9
examples(calendar): update and fix compile 2025-12-05 17:29:11 +01:00
Michael Aaron Murphy
cdf4eafc9e
fix(segmented_button): set icon to symbolic 2025-12-05 17:18:26 +01:00
Michael Aaron Murphy
6793950bbc
fix(icon): from_svg_bytes should not default to symbolic 2025-12-05 17:16:35 +01:00
Michael Aaron Murphy
2ffd1f32f4
examples(application): update and fix compile 2025-12-05 17:05:57 +01:00
Hosted Weblate
8a9cd0da32 i18n: translation updates from weblate
Co-authored-by: CYAXXX <cyaxxx@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2025-12-05 08:59:03 -07:00
Michael Aaron Murphy
e13ab24151
revert: "fix(popover): match popup styling to designs"
Some application popovers required the previous behavior

This reverts commit 882481e518.
2025-12-05 16:46:23 +01:00
Michael Aaron Murphy
866da0f94b
revert: "fix(popover): set default position to Bottom"
Causes popups to be misplaced in applications that required the previous
behavior.

This reverts commit 18182e5f97.
2025-12-05 16:44:39 +01:00
Michael Aaron Murphy
45fd683bc9
examples(about): update and fix compile 2025-12-05 16:42:29 +01:00
Frederic Laing
c2b7d7847a feat: add Flatpak sandbox support for config paths
Implement get_config_dir() and get_state_dir() helper functions that detect
Flatpak sandboxing via FLATPAK_ID and use HOST_XDG_CONFIG_HOME/HOST_XDG_STATE_HOME
environment variables or fallback to HOME-based paths.

This allows libcosmic apps running in Flatpak sandboxes to properly read
system-wide COSMIC configuration (themes, corner radii, etc.) from the host.
2025-12-04 11:30:03 -07:00
Kyle Scheuing
54934a961f fix: cross compiling for windows from linux
#[cfg(not(unix))] applies to the host machine (since that's where the
build script is running) rather than the compilation target. Instead,
environment variables are available to provide the information relevant
to the build target at the build script's runtime.
2025-12-04 11:28:39 -07:00
Kyle Scheuing
80875d5962
fix: compiling on windows requires cosmic-icons in project root
* fix: compiling on windows requires cosmic-icons in project root

crabtime provides crabtime::WORKSPACE_PATH to refer to the
CARGO_MANIFEST_DIR of the top level crate being built, which means when
building libcosmic directly, crabtime::WORKSPACE_PATH will work, but
when building it as a dependency of another crate,
crabtime::WORKSPACE_PATH will no longer refer to the path to libcosmic.

I don't think there's a good workaround, since when in the context of
crabtime, CARGO_MANIFEST_DIR refers to the path to the crate generated
by crabtime rather than to libcosmic.

This replaces crabtime with a simple build.rs script that generates a
file in OUT_DIR.

* fix: do not generate icon bundle for unix targets

---------

Co-authored-by: Michael Aaron Murphy <michael@mmurphy.dev>
2025-12-04 17:31:47 +01:00
Vukašin Vojinović
18182e5f97 fix(popover): set default position to Bottom
I didn't see this part in my previous PR (sorry!).
2025-12-02 18:03:05 +01:00
Vukašin Vojinović
14cbebbadc chore: update iced 2025-12-02 17:43:20 +01:00
Vukašin Vojinović
882481e518 fix(popover): match popup styling to designs 2025-12-02 16:37:20 +01:00
Kyle Scheuing
62f661e077 fix: compile errors on windows
calendar.rs had some left over icon! macro_rules macros referencing now
deleted files.

bundle::get was defined twice on non-unix platforms.

A known remaining issue is that projects using libcosmic need to have
cosmic-icons in their project root, since the crabtime macro uses
crabtime::WORKSPACE_PATH rather than the path to wherever cargo puts
libcosmic's git submodule.

See: 639326fcc3
2025-11-26 15:39:32 -05:00
Michael Aaron Murphy
639326fcc3 feat(icon): optimize & bundle icons with crabtime for non-unix platforms 2025-11-21 18:37:49 +01:00
Stephan Buys
ce0868582b tests: fix env guard and pipe read for tab dnd 2025-11-20 22:33:13 +01:00
Stephan Buys
7f321cb0a3 segmented button: support tab drag + drop 2025-11-20 22:33:13 +01:00
208 changed files with 9531 additions and 4157 deletions

8
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,8 @@
- [ ] I have disclosed use of any AI generated code in my commit messages.
- If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment.
- [ ] I understand these changes in full and will be able to respond to review comments.
- [ ] My change is accurately described in the commit message.
- [ ] My contribution is tested and working as described.
- [ ] I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.

View file

@ -33,16 +33,17 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
features: test_args:
- "" # for cosmic-comp, don't remove! - --no-default-features --features "" # for cosmic-comp, don't remove!
- 'winit_debug' - --no-default-features --features "winit_debug"
- 'winit_tokio' - --no-default-features --features "winit_tokio"
- winit - --no-default-features --features "winit"
- winit_wgpu - --no-default-features --features "winit_wgpu"
- wayland - --no-default-features --features "wayland"
- applet - --no-default-features --features "applet"
- desktop,smol - --no-default-features --features "desktop,smol"
- desktop,tokio - --no-default-features --features "desktop,tokio"
- -p cosmic-theme
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout sources - name: Checkout sources
@ -66,7 +67,7 @@ jobs:
- name: Rust toolchain - name: Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Test features - name: Test features
run: cargo test --no-default-features --features "${{ matrix.features }}" run: cargo test ${{ matrix.test_args }} -- --test-threads=1
env: env:
RUST_BACKTRACE: full RUST_BACKTRACE: full
@ -103,7 +104,7 @@ jobs:
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Rust toolchain - name: Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Test example - name: Check example
run: cargo check -p "${{ matrix.examples }}" run: cargo check -p "${{ matrix.examples }}"
env: env:
RUST_BACKTRACE: full RUST_BACKTRACE: full

View file

@ -7,7 +7,6 @@ on:
jobs: jobs:
pages: pages:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -15,8 +14,20 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2025-07-31
- name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Build documentation - name: Build documentation
run: cargo doc --verbose --features tokio,winit run: |
RUSTDOCFLAGS="--cfg docsrs" \
cargo +nightly-2025-07-31 doc --no-deps \
-p cosmic-client-toolkit \
-p cosmic-protocols \
-p libcosmic \
--verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window
- name: Deploy documentation - name: Deploy documentation
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v3
with: with:

3
.gitmodules vendored
View file

@ -2,3 +2,6 @@
path = iced path = iced
url = https://github.com/pop-os/iced.git url = https://github.com/pop-os/iced.git
branch = master branch = master
[submodule "icon-theme"]
path = cosmic-icons
url = https://github.com/pop-os/cosmic-icons

View file

@ -1,20 +1,36 @@
[package] [package]
name = "libcosmic" name = "libcosmic"
version = "0.1.0" version = "1.0.0"
edition = "2024" edition = "2024"
rust-version = "1.85" rust-version = "1.90"
[lib] [lib]
name = "cosmic" name = "cosmic"
[features] [features]
default = ["dbus-config", "multi-window", "a11y"] default = [
"winit",
"tokio",
"a11y",
"dbus-config",
"x11",
"iced-wayland",
"multi-window",
]
advanced-shaping = ["iced/advanced-shaping"]
# Accessibility support # Accessibility support
a11y = ["iced/a11y", "iced_accessibility"] a11y = ["iced/a11y", "iced_accessibility"]
# Enable about widget # Enable about widget
about = [] about = []
# Builds support for animated images # Builds support for animated images
animated-image = ["dep:async-fs", "image/gif", "tokio?/io-util", "tokio?/fs"] animated-image = [
"dep:async-fs",
"image/gif",
"image/webp",
"image/png",
"tokio?/io-util",
"tokio?/fs",
]
# XXX autosize should not be used on winit windows unless dialogs # XXX autosize should not be used on winit windows unless dialogs
autosize = [] autosize = []
applet = [ applet = [
@ -42,6 +58,7 @@ desktop = [
"process", "process",
"dep:cosmic-settings-config", "dep:cosmic-settings-config",
"dep:freedesktop-desktop-entry", "dep:freedesktop-desktop-entry",
"dep:image-extras",
"dep:mime", "dep:mime",
"dep:shlex", "dep:shlex",
"tokio?/io-util", "tokio?/io-util",
@ -65,18 +82,24 @@ tokio = [
] ]
# Tokio async runtime # Tokio async runtime
# Wayland window support # Wayland window support
wayland = [ iced-wayland = [
"ashpd?/wayland", "ashpd?/wayland",
"autosize", "autosize",
"iced_runtime/wayland",
"iced/wayland", "iced/wayland",
"iced_winit/wayland", "iced_winit/wayland",
"cctk",
"surface-message", "surface-message",
] ]
wayland = [
"iced-wayland",
"iced_runtime/cctk",
"iced_winit/cctk",
"iced_wgpu/cctk",
"iced/cctk",
"dep:cctk",
]
surface-message = [] surface-message = []
# multi-window support # multi-window support
multi-window = ["iced/multi-window"] multi-window = []
# Render with wgpu # Render with wgpu
wgpu = ["iced/wgpu", "iced_wgpu"] wgpu = ["iced/wgpu", "iced_wgpu"]
# X11 window support via winit # X11 window support via winit
@ -96,15 +119,16 @@ async-std = [
"zbus?/async-io", "zbus?/async-io",
"iced/async-std", "iced/async-std",
] ]
x11 = ["iced/x11", "iced_winit/x11"]
[dependencies] [dependencies]
apply = "0.3.0" apply = "0.3.0"
ashpd = { version = "0.12.0", default-features = false, optional = true } ashpd = { version = "0.12.3", default-features = false, optional = true }
async-fs = { version = "2.1", optional = true } async-fs = { version = "2.2", optional = true }
async-std = { version = "1.13", optional = true } async-std = { version = "1.13", optional = true }
auto_enums = "0.8.7" auto_enums = "0.8.8"
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true } cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true }
chrono = "0.4.42" jiff = "0.2"
cosmic-config = { path = "cosmic-config" } cosmic-config = { path = "cosmic-config" }
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
# Internationalization # Internationalization
@ -113,45 +137,54 @@ i18n-embed = { version = "0.16.0", features = [
"desktop-requester", "desktop-requester",
] } ] }
i18n-embed-fl = "0.10" i18n-embed-fl = "0.10"
rust-embed = "8.7.2" rust-embed = "8.11.0"
css-color = "0.2.8" css-color = "0.2.8"
derive_setters = "0.1.8" derive_setters = "0.1.9"
futures = "0.3" futures = "0.3"
image = { version = "0.25.8", default-features = false, features = [ image = { version = "0.25.10", default-features = false, features = [
"ico",
"jpeg", "jpeg",
"png", "png",
] } ] }
libc = { version = "0.2.175", optional = true } image-extras = { version = "0.1.0", default-features = false, features = [
"xpm",
"xbm",
], optional = true }
libc = { version = "0.2.183", optional = true }
log = "0.4" log = "0.4"
mime = { version = "0.3.17", optional = true } mime = { version = "0.3.17", optional = true }
palette = "0.7.6" palette = "0.7.6"
raw-window-handle = "0.6" rfd = { version = "0.16.0", default-features = false, features = [
rfd = { version = "0.15.4", default-features = false, features = [
"xdg-portal", "xdg-portal",
], optional = true } ], optional = true }
rustix = { version = "1.1", features = ["pipe", "process"], optional = true } rustix = { version = "1.1", features = ["pipe", "process"], optional = true }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
slotmap = "1.0.7" slotmap = "1.1.1"
smol = { version = "2.0.2", optional = true } smol = { version = "2.0.2", optional = true }
thiserror = "2.0.16" thiserror = "2.0.18"
taffy = { version = "0.9.1", features = ["grid"] } taffy = { version = "0.9.2", features = ["grid"] }
tokio = { version = "1.47.1", optional = true } tokio = { version = "1.50.0", optional = true }
tracing = "0.1.41" tracing = "0.1.44"
unicode-segmentation = "1.12" unicode-segmentation = "1.12"
url = "2.5.7" url = "2.5.8"
zbus = { version = "5.11.0", default-features = false, optional = true } zbus = { version = "5.14.0", default-features = false, optional = true }
float-cmp = "0.10.0"
# Enable DBus feature on Linux targets # Enable DBus feature on Linux targets
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
cosmic-config = { path = "cosmic-config", features = ["dbus"] } cosmic-config = { path = "cosmic-config", features = ["dbus"] }
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
zbus = { version = "5.11.0", default-features = false } zbus = { version = "5.14.0", default-features = false }
[target.'cfg(unix)'.dependencies] [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" } freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
freedesktop-desktop-entry = { version = "0.7.14", optional = true } freedesktop-desktop-entry = { version = "0.8.1", optional = true }
shlex = { version = "1.3.0", optional = true } shlex = { version = "1.3.0", optional = true }
[target.'cfg(any(not(unix), target_os = "macos"))'.dependencies]
# Used to embed bundled icons for non-unix platforms.
phf = { version = "0.13.1", features = ["macros"] }
[dependencies.cosmic-theme] [dependencies.cosmic-theme]
path = "cosmic-theme" path = "cosmic-theme"
@ -205,7 +238,7 @@ git = "https://github.com/pop-os/cosmic-panel"
optional = true optional = true
[dependencies.ron] [dependencies.ron]
version = "0.11" version = "0.12"
optional = true optional = true
[workspace] [workspace]
@ -221,5 +254,4 @@ exclude = ["iced"]
dirs = "6.0.0" dirs = "6.0.0"
[dev-dependencies] [dev-dependencies]
tempfile = "3.13.0" tempfile = "3.27.0"

View file

@ -10,6 +10,7 @@ A platform toolkit based on iced for creating applets and applications for the C
## Templates ## Templates
- https://github.com/pop-os/cosmic-app-template: Application project template - https://github.com/pop-os/cosmic-app-template: Application project template
- https://github.com/pop-os/cosmic-applet-template: Panel applet project template
## Dependencies ## Dependencies

63
build.rs Normal file
View file

@ -0,0 +1,63 @@
use std::env;
fn main() {
println!("cargo::rerun-if-changed=build.rs");
if env::var_os("CARGO_CFG_UNIX").is_none()
|| env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos")
{
generate_bundled_icons();
}
}
fn generate_bundled_icons() {
println!("cargo::rerun-if-changed=cosmic-icons");
let manifest_dir = std::path::Path::new(std::env!("CARGO_MANIFEST_DIR"));
let icon_paths = [
"cosmic-icons/freedesktop/scalable",
"cosmic-icons/extra/scalable",
];
let key_value_assignments = icon_paths
.into_iter()
.map(|path| manifest_dir.join(path))
.inspect(|icon_path| assert!(icon_path.exists(), "path = {icon_path:?}"))
.map(|icon_path| std::fs::read_dir(icon_path).unwrap())
.flat_map(|dir| {
dir.flat_map(|entry| entry.unwrap().path().read_dir().unwrap())
.map(|entry| {
let entry = entry.unwrap();
let path = entry.path().canonicalize().unwrap();
let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned();
let path = path.into_os_string().into_string().unwrap();
(file_name, path)
})
})
.fold(
std::collections::BTreeMap::new(),
|mut set, (name, path)| {
set.insert(name, path);
set
},
)
.into_iter()
.fold(String::new(), |mut output, (name, path)| {
// This changes the escape character to the one used by Windows.
#[cfg(windows)]
let path = path.replace("\\", "/");
output.push_str(&format!(" \"{name}\" => include_bytes!(\"{path}\"),\n"));
output
});
let code = [
"static ICONS: phf::Map<&'static str, &'static [u8]> = phf::phf_map!(\n",
&key_value_assignments,
");",
]
.concat();
let out_dir = std::env::var_os("OUT_DIR").unwrap();
let out_file = std::path::Path::new(&out_dir).join("bundled_icons.rs");
std::fs::write(&out_file, &code).unwrap();
}

View file

@ -1,7 +1,7 @@
[package] [package]
name = "cosmic-config-derive" name = "cosmic-config-derive"
version = "0.1.0" version = "1.0.0"
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib] [lib]

View file

@ -106,7 +106,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
}) })
}); });
let gen = quote! { let generate = quote! {
impl CosmicConfigEntry for #name { impl CosmicConfigEntry for #name {
const VERSION: u64 = #version; const VERSION: u64 = #version;
@ -147,5 +147,5 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
} }
}; };
gen.into() generate.into()
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "cosmic-config" name = "cosmic-config"
version = "0.1.0" version = "1.0.0"
edition = "2024" edition = "2024"
[features] [features]
@ -11,18 +11,18 @@ subscription = ["iced_futures"]
[dependencies] [dependencies]
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
zbus = { version = "5.11.0", default-features = false, optional = true } zbus = { version = "5.14.0", default-features = false, optional = true }
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
calloop = { version = "0.14.3", optional = true } calloop = { version = "0.14.4", optional = true }
notify = "8.2.0" notify = "8.2.0"
ron = "0.11.0" ron = "0.12.0"
serde = "1.0.219" serde = "1.0.228"
cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true }
iced = { path = "../iced/", default-features = false, optional = true } iced = { path = "../iced/", default-features = false, optional = true }
iced_futures = { path = "../iced/futures/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true }
futures-util = { version = "0.3", optional = true } futures-util = { version = "0.3", optional = true }
dirs.workspace = true dirs.workspace = true
tokio = { version = "1.47", optional = true, features = ["time"] } tokio = { version = "1.50", optional = true, features = ["time"] }
async-std = { version = "1.13", optional = true } async-std = { version = "1.13", optional = true }
tracing = "0.1" tracing = "0.1"
@ -30,4 +30,4 @@ tracing = "0.1"
xdg = "3.0" xdg = "3.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
known-folders = "1.3.1" known-folders = "1.4.2"

View file

@ -1,11 +1,11 @@
use std::ops::Deref; use std::{any::TypeId, ops::Deref};
use crate::{CosmicConfigEntry, Update}; use crate::{CosmicConfigEntry, Update};
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
use futures_util::SinkExt; use futures_util::SinkExt;
use iced_futures::{ use iced_futures::{
Subscription, Subscription,
futures::{self, Stream, StreamExt, future::pending}, futures::{self, StreamExt, future::pending},
stream, stream,
}; };
@ -57,6 +57,20 @@ impl Watcher {
} }
} }
#[derive(Clone)]
struct Wrapper(
TypeId,
CosmicSettingsDaemonProxy<'static>,
&'static str,
bool,
);
impl std::hash::Hash for Wrapper {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>( pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
settings_daemon: CosmicSettingsDaemonProxy<'static>, settings_daemon: CosmicSettingsDaemonProxy<'static>,
@ -64,22 +78,19 @@ pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'stat
is_state: bool, is_state: bool,
) -> iced_futures::Subscription<Update<T>> { ) -> iced_futures::Subscription<Update<T>> {
let id = std::any::TypeId::of::<T>(); let id = std::any::TypeId::of::<T>();
Subscription::run_with_id( Subscription::run_with(
(id, config_id), Wrapper(id, settings_daemon, config_id, is_state),
watcher_stream(settings_daemon, config_id, is_state), |&Wrapper(_, ref settings_daemon, ref config_id, ref is_state)| {
) let is_state = *is_state;
} let config_id = *config_id;
let settings_daemon = settings_daemon.clone();
fn watcher_stream<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
settings_daemon: CosmicSettingsDaemonProxy<'static>,
config_id: &'static str,
is_state: bool,
) -> impl Stream<Item = Update<T>> {
enum Change { enum Change {
Changes(Changed), Changes(Changed),
OwnerChanged(bool), OwnerChanged(bool),
} }
stream::channel(5, move |mut tx| async move { stream::channel(
5,
move |mut tx: futures::channel::mpsc::Sender<Update<T>>| async move {
let version = T::VERSION; let version = T::VERSION;
let Ok(cosmic_config) = (if is_state { let Ok(cosmic_config) = (if is_state {
@ -103,9 +114,15 @@ fn watcher_stream<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone
tracing::error!("Failed to create watcher for {config_id}"); tracing::error!("Failed to create watcher for {config_id}");
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await; ::tokio::time::sleep(::tokio::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(feature = "async-std")] #[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await; async_std::task::sleep(std::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))] #[cfg(not(any(feature = "tokio", feature = "async-std")))]
{ {
pending::<()>().await; pending::<()>().await;
@ -119,9 +136,15 @@ fn watcher_stream<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone
tracing::error!("Failed to listen for changes for {config_id}"); tracing::error!("Failed to listen for changes for {config_id}");
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await; ::tokio::time::sleep(::tokio::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(feature = "async-std")] #[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await; async_std::task::sleep(std::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))] #[cfg(not(any(feature = "tokio", feature = "async-std")))]
{ {
pending::<()>().await; pending::<()>().await;
@ -134,12 +157,19 @@ fn watcher_stream<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone
let mut changes = changes.map(Change::Changes).fuse(); let mut changes = changes.map(Change::Changes).fuse();
let Ok(owner_changed) = watcher.inner().receive_owner_changed().await else { let Ok(owner_changed) = watcher.inner().receive_owner_changed().await
else {
tracing::error!("Failed to listen for owner changes for {config_id}"); tracing::error!("Failed to listen for owner changes for {config_id}");
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await; ::tokio::time::sleep(::tokio::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(feature = "async-std")] #[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await; async_std::task::sleep(std::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))] #[cfg(not(any(feature = "tokio", feature = "async-std")))]
{ {
pending::<()>().await; pending::<()>().await;
@ -225,5 +255,8 @@ fn watcher_stream<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone
} }
} }
} }
}) },
)
},
)
} }

View file

@ -6,12 +6,54 @@ use notify::{
}; };
use serde::{Serialize, de::DeserializeOwned}; use serde::{Serialize, de::DeserializeOwned};
use std::{ use std::{
fmt, fs, env, fmt, fs,
io::Write, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Mutex, sync::Mutex,
}; };
/// Get the config directory, with Flatpak sandbox support.
/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory,
/// allowing sandboxed apps to read host config files.
fn get_config_dir() -> Option<PathBuf> {
// Check if we're running in Flatpak
if let Some(flatpak_id) = env::var_os("FLATPAK_ID") {
tracing::debug!("Running in Flatpak: {:?}", flatpak_id);
// Try HOST_XDG_CONFIG_HOME first (requires --filesystem=xdg-config permission)
if let Some(host_config) = env::var_os("HOST_XDG_CONFIG_HOME") {
tracing::debug!("Using HOST_XDG_CONFIG_HOME: {:?}", host_config);
return Some(PathBuf::from(host_config));
}
// Fallback: try to construct from HOME (which points to real home in Flatpak)
if let Some(home) = env::var_os("HOME") {
let config_path = PathBuf::from(&home).join(".config");
tracing::debug!("Using HOME fallback for config: {:?}", config_path);
return Some(config_path);
}
tracing::warn!("Flatpak detected but no config directory found");
}
// Not in Flatpak or no host config available, use standard dirs
let config_dir = dirs::config_dir();
tracing::debug!("Using standard config dir: {:?}", config_dir);
config_dir
}
/// Get the state directory, with Flatpak sandbox support.
fn get_state_dir() -> Option<PathBuf> {
// Check if we're running in Flatpak
if env::var_os("FLATPAK_ID").is_some() {
// Try HOST_XDG_STATE_HOME first
if let Some(host_state) = env::var_os("HOST_XDG_STATE_HOME") {
return Some(PathBuf::from(host_state));
}
// Fallback: try to construct from HOME
if let Some(home) = env::var_os("HOME") {
return Some(PathBuf::from(home).join(".local").join("state"));
}
}
dirs::state_dir()
}
#[cfg(feature = "subscription")] #[cfg(feature = "subscription")]
mod subscription; mod subscription;
#[cfg(feature = "subscription")] #[cfg(feature = "subscription")]
@ -170,7 +212,7 @@ impl Config {
.map(|x| x.join("COSMIC").join(&path)); .map(|x| x.join("COSMIC").join(&path));
// Get libcosmic user configuration directory // Get libcosmic user configuration directory
let mut user_path = dirs::config_dir().ok_or(Error::NoConfigDirectory)?; let mut user_path = get_config_dir().ok_or(Error::NoConfigDirectory)?;
user_path.push("cosmic"); user_path.push("cosmic");
user_path.push(path); user_path.push(path);
@ -212,7 +254,7 @@ impl Config {
let path = sanitize_name(name)?.join(format!("v{}", version)); let path = sanitize_name(name)?.join(format!("v{}", version));
// Get libcosmic user state directory // Get libcosmic user state directory
let mut user_path = dirs::state_dir().ok_or(Error::NoConfigDirectory)?; let mut user_path = get_state_dir().ok_or(Error::NoConfigDirectory)?;
user_path.push("cosmic"); user_path.push("cosmic");
user_path.push(path); user_path.push(path);
// Create new state directory if not found. // Create new state directory if not found.

View file

@ -25,7 +25,24 @@ pub fn config_subscription<
config_id: Cow<'static, str>, config_id: Cow<'static, str>,
config_version: u64, config_version: u64,
) -> iced_futures::Subscription<crate::Update<T>> { ) -> iced_futures::Subscription<crate::Update<T>> {
iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, false)) iced_futures::Subscription::run_with(
(id, config_id, config_version, false),
// FIXME there are type issues related to the 'static lifetime of the Cow if this is extracted to a named function...
|(_, config_id, config_version, is_state)| {
let config_id = config_id.clone();
let config_version = *config_version;
let is_state = *is_state;
stream::channel(100, move |mut output| async move {
let config_id = config_id.clone();
let mut state = ConfigState::Init(config_id, config_version, is_state);
loop {
state = start_listening::<T>(state, &mut output).await;
}
})
},
)
} }
#[cold] #[cold]
@ -37,25 +54,23 @@ pub fn config_state_subscription<
config_id: Cow<'static, str>, config_id: Cow<'static, str>,
config_version: u64, config_version: u64,
) -> iced_futures::Subscription<crate::Update<T>> { ) -> iced_futures::Subscription<crate::Update<T>> {
iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, true)) iced_futures::Subscription::run_with(
} (id, config_id, config_version, true),
|(_, config_id, config_version, is_state)| {
fn watcher_stream<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
config_id: Cow<'static, str>,
config_version: u64,
is_state: bool,
) -> impl Stream<Item = crate::Update<T>> {
stream::channel(100, move |mut output| {
let config_id = config_id.clone(); let config_id = config_id.clone();
async move { let config_version = *config_version;
let is_state = *is_state;
stream::channel(100, move |mut output| async move {
let config_id = config_id.clone(); let config_id = config_id.clone();
let mut state = ConfigState::Init(config_id, config_version, is_state); let mut state = ConfigState::Init(config_id, config_version, is_state);
loop { loop {
state = start_listening::<T>(state, &mut output).await; state = start_listening::<T>(state, &mut output).await;
} }
}
}) })
},
)
} }
async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>( async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(

1
cosmic-icons Submodule

@ -0,0 +1 @@
Subproject commit 5252095787cc96e2aed64604158f94e450703455

View file

@ -1,6 +1,6 @@
[package] [package]
name = "cosmic-theme" name = "cosmic-theme"
version = "0.1.0" version = "1.0.0"
edition = "2024" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -17,15 +17,23 @@ no-default = []
[dependencies] [dependencies]
palette = { version = "0.7.6", features = ["serializing"] } palette = { version = "0.7.6", features = ["serializing"] }
almost = "0.2" almost = "0.2"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.143", optional = true, features = [ serde_json = { version = "1.0.149", optional = true, features = [
"preserve_order", "preserve_order",
] } ] }
ron = "0.11.0" ron = "0.12.0"
csscolorparser = { version = "0.7.2", features = ["serde"] } csscolorparser = { version = "0.8.3", features = ["serde"] }
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [ cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
"subscription", "subscription",
"macro", "macro",
] } ] }
configparser = "3.1.0"
dirs.workspace = true dirs.workspace = true
thiserror = "2.0.16" thiserror = "2.0.18"
[dev-dependencies]
insta = "1.47.2"
[profile.dev.package]
insta.opt-level = 3
similar.opt-level = 3

View file

@ -685,18 +685,17 @@ impl Theme {
self.shade self.shade
} }
/// get the active theme /// Get the active theme based on the current theme mode.
pub fn get_active() -> Result<Self, (Vec<cosmic_config::Error>, Self)> { pub fn get_active() -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
let config = (|| {
Config::new(Self::id(), Self::VERSION).map_err(|e| (vec![e], Self::default()))?; (if ThemeMode::is_dark(&Config::new(Self::id(), Self::VERSION)?)? {
let is_dark = ThemeMode::is_dark(&config).map_err(|e| (vec![e], Self::default()))?; Self::dark_config
let config = if is_dark {
Self::dark_config()
} else { } else {
Self::light_config() Self::light_config
} })()
.map_err(|e| (vec![e], Self::default()))?; })()
Self::get_entry(&config) .map_err(|error| (vec![error], Self::default()))
.and_then(|theme_config| Self::get_entry(&theme_config))
} }
#[must_use] #[must_use]
@ -987,19 +986,19 @@ impl ThemeBuilder {
let success = if let Some(success) = success { let success = if let Some(success) = success {
success.into_color() success.into_color()
} else { } else {
palette.as_ref().accent_green palette.as_ref().bright_green
}; };
let warning = if let Some(warning) = warning { let warning = if let Some(warning) = warning {
warning.into_color() warning.into_color()
} else { } else {
palette.as_ref().accent_yellow palette.as_ref().bright_orange
}; };
let destructive = if let Some(destructive) = destructive { let destructive = if let Some(destructive) = destructive {
destructive.into_color() destructive.into_color()
} else { } else {
palette.as_ref().accent_red palette.as_ref().bright_red
}; };
let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap())); let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap()));

View file

@ -163,9 +163,19 @@ impl Theme {
std::fs::create_dir_all(&config_dir).map_err(OutputError::Io)?; std::fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
} }
let mut file = File::create(config_dir.join(name)).map_err(OutputError::Io)?; let file_path = config_dir.join(name);
file.write_all(css_str.as_bytes()) let tmp_file_path = config_dir.join(name.to_owned() + "~");
.map_err(OutputError::Io)?;
// Write to tmp_file_path first, then move it to file_path
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
let res = tmp_file
.write_all(css_str.as_bytes())
.and_then(|_| tmp_file.flush())
.and_then(|_| std::fs::rename(&tmp_file_path, file_path));
if let Err(e) = res {
_ = std::fs::remove_file(&tmp_file_path);
return Err(OutputError::Io(e));
}
Ok(()) Ok(())
} }

View file

@ -1,3 +1,4 @@
use configparser::ini::WriteOptions;
use palette::{Srgba, rgb::Rgba}; use palette::{Srgba, rgb::Rgba};
use thiserror::Error; use thiserror::Error;
@ -6,6 +7,11 @@ use crate::Theme;
/// Module for outputting the Cosmic gtk4 theme type as CSS /// Module for outputting the Cosmic gtk4 theme type as CSS
pub mod gtk4_output; pub mod gtk4_output;
/// Module for configuring qt5ct and qt6ct to use our qt theme
pub mod qt56ct_output;
/// Module for outputting the Cosmic qt theme type as kdeglobals
pub mod qt_output;
pub mod vs_code; pub mod vs_code;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -14,33 +20,48 @@ pub enum OutputError {
Io(std::io::Error), Io(std::io::Error),
#[error("Missing config directory")] #[error("Missing config directory")]
MissingConfigDir, MissingConfigDir,
#[error("Missing data directory")]
MissingDataDir,
#[error("Serde Error: {0}")] #[error("Serde Error: {0}")]
Serde(#[from] serde_json::Error), Serde(#[from] serde_json::Error),
#[error("Ini Error: {0}")]
Ini(String),
} }
impl Theme { impl Theme {
#[inline] #[inline]
/// Apply COSMIC theme exports for GTK and Qt applications.
pub fn apply_exports(&self) -> Result<(), OutputError> { pub fn apply_exports(&self) -> Result<(), OutputError> {
let gtk_res = Theme::apply_gtk(self.is_dark); let gtk_res = Theme::apply_gtk(self.is_dark);
let vs_res = self.clone().apply_vs_code(); let qt_res = Theme::apply_qt(self.is_dark);
let qt56ct_res = Theme::apply_qt56ct(self.is_dark);
gtk_res?; gtk_res?;
vs_res?; qt_res?;
qt56ct_res?;
Ok(()) Ok(())
} }
#[inline] #[inline]
/// Write COSMIC theme exports for GTK and Qt applications.
pub fn write_exports(&self) -> Result<(), OutputError> { pub fn write_exports(&self) -> Result<(), OutputError> {
let gtk_res = self.write_gtk4(); let gtk_res = self.write_gtk4();
let qt_res = self.write_qt();
let qt56ct_res = self.write_qt56ct();
gtk_res?; gtk_res?;
qt_res?;
qt56ct_res?;
Ok(()) Ok(())
} }
#[inline] #[inline]
/// Un-export GTK and Qt theme configurations applied by us.
pub fn reset_exports() -> Result<(), OutputError> { pub fn reset_exports() -> Result<(), OutputError> {
let gtk_res = Theme::reset_gtk(); let gtk_res = Theme::reset_gtk();
let vs_res = Theme::reset_vs_code(); let qt_res = Theme::reset_qt();
let qt56ct_res = Theme::reset_qt56ct();
gtk_res?; gtk_res?;
vs_res?; qt_res?;
qt56ct_res?;
Ok(()) Ok(())
} }
} }
@ -60,3 +81,9 @@ pub fn to_rgba(c: Srgba) -> String {
c_u8.red, c_u8.green, c_u8.blue, c.alpha c_u8.red, c_u8.green, c_u8.blue, c.alpha
) )
} }
pub fn qt_settings_ini_style() -> WriteOptions {
let mut write_options = WriteOptions::default();
write_options.blank_lines_between_sections = 1;
write_options
}

View file

@ -0,0 +1,415 @@
use crate::Theme;
use configparser::ini::Ini;
use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba};
use std::{
fs::{self, File},
io::Write,
path::PathBuf,
vec,
};
use super::{OutputError, qt_settings_ini_style};
impl Theme {
/// The "version" of this theme.
///
/// To avoid repeatedly overwriting the user's config, we use a version system.
///
/// Increment this value when changes to qt{5,6}ct.conf are needed.
/// If the config's version is outdated, we update several sections.
/// Otherwise, only the light/dark mode is updated.
const COSMIC_QT_VERSION: u64 = 2;
/// Produces a QPalette ini file for qt5ct and qt6ct.
///
/// Example file: https://github.com/trialuser02/qt6ct/blob/master/colors/airy.conf
#[must_use]
#[cold]
pub fn as_qpalette(&self) -> String {
let lightest = if self.is_dark {
self.background.on
} else {
self.background.base
};
let darkest = if self.is_dark {
self.background.base
} else {
self.background.on
};
let active = QPaletteGroup {
window_text: self.background.on,
button: self.button.base,
light: self.button.base.mix(lightest, 0.1),
midlight: self.button.base.mix(lightest, 0.05),
dark: self.button.base.mix(darkest, 0.1),
mid: self.button.base.mix(darkest, 0.05),
text: self.background.component.on,
bright_text: lightest,
button_text: self.button.on,
base: self.background.component.base,
window: self.background.base,
shadow: darkest,
// selection colors are swapped to fix menu bar contrast
highlight: self.background.component.selected_text,
highlighted_text: self.background.component.selected,
link: self.link_button.on,
link_visited: self.link_button.on.mix(self.secondary.component.base, 0.2),
alternate_base: self.background.base.mix(self.accent.base, 0.05),
no_role: self.background.component.disabled,
tool_tip_base: self.background.component.base,
tool_tip_text: self.background.component.on,
placeholder_text: self.background.component.on.with_alpha(0.5),
};
let inactive = QPaletteGroup {
window_text: active.window_text.with_alpha(0.8),
text: active.text.with_alpha(0.8),
highlighted_text: active.highlighted_text.with_alpha(0.8),
tool_tip_text: active.tool_tip_text.with_alpha(0.8),
..active
};
let disabled = QPaletteGroup {
button: self.button.disabled,
text: self.background.component.on_disabled,
button_text: self.button.on_disabled,
base: self.background.component.disabled,
highlighted_text: active.highlighted_text.with_alpha(0.5),
link: self.link_button.on_disabled,
link_visited: self
.link_button
.on_disabled
.mix(self.secondary.component.disabled, 0.2),
alternate_base: self.background.base.mix(self.accent.disabled, 0.05),
tool_tip_base: self.background.component.disabled,
tool_tip_text: self.background.component.on_disabled,
placeholder_text: self.background.component.on_disabled.with_alpha(0.5),
..inactive
};
format!(
r#"# GENERATED BY COSMIC
[ColorScheme]
active_colors={}
disabled_colors={}
inactive_colors={}
"#,
active.as_list(),
disabled.as_list(),
inactive.as_list(),
)
}
/// Writes the QPalette ini files to:
/// - `~/.config/qt6ct/colors/`
/// - `~/.config/qt5ct/colors/`
#[cold]
pub fn write_qt56ct(&self) -> Result<(), OutputError> {
let qpalette = self.as_qpalette();
let qt5ct_res = self.write_ct("qt5ct", &qpalette);
let qt6ct_res = self.write_ct("qt6ct", &qpalette);
qt5ct_res?;
qt6ct_res?;
Ok(())
}
#[must_use]
#[cold]
fn write_ct(&self, ct: &str, qpalette: &str) -> Result<(), OutputError> {
let file_path = Self::get_qpalette_path(ct, self.is_dark)?;
let tmp_file_path = file_path.with_extension("conf.new");
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
let res = tmp_file
.write_all(qpalette.as_bytes())
.and_then(|_| tmp_file.flush())
.and_then(|_| std::fs::rename(&tmp_file_path, file_path));
if let Err(e) = res {
_ = std::fs::remove_file(&tmp_file_path);
return Err(OutputError::Io(e));
}
Ok(())
}
/// Edits qt{5,6}ct.conf to use COSMIC styles if needed.
#[cold]
pub fn apply_qt56ct(is_dark: bool) -> Result<(), OutputError> {
let qt5ct_res = Self::apply_ct("qt5ct", is_dark);
let qt6ct_res = Self::apply_ct("qt6ct", is_dark);
qt5ct_res?;
qt6ct_res?;
Ok(())
}
#[must_use]
#[cold]
fn apply_ct(ct: &str, is_dark: bool) -> Result<(), OutputError> {
let path = Self::get_conf_path(ct)?;
let file_content = fs::read_to_string(&path).map_err(OutputError::Io)?;
let mut ini = Ini::new_cs();
ini.read(file_content).map_err(OutputError::Ini)?;
let old_version = ini
.getuint("Appearance", "cosmic_qt_version")
.map_err(OutputError::Ini)?
.unwrap_or_default();
let color_scheme_path = Self::get_qpalette_path(ct, is_dark)?;
let icon_theme = if is_dark { "breeze-dark" } else { "breeze" };
ini.set(
"Appearance",
"cosmic_qt_version",
Some(Theme::COSMIC_QT_VERSION.to_string()),
);
if old_version < Theme::COSMIC_QT_VERSION {
// Config is outdated, update it unconditionally!
ini.setstr(
"Appearance",
"color_scheme_path",
color_scheme_path.to_str(),
);
// Enable the above color scheme, instead of using the default color scheme of e.g. Breeze
ini.setstr("Appearance", "custom_palette", Some("true"));
// COSMIC icons are stuck in light mode, so use breeze icons instead
ini.setstr("Appearance", "icon_theme", Some(icon_theme));
// Use COSMIC dialogs instead of KDE's
ini.setstr("Appearance", "standard_dialogs", Some("xdgdesktopportal"));
// TODO: Add fonts section to match COSMIC
} else {
// Config is not outdated, check before updating light/dark mode only!
let old_color_scheme_path = ini
.get("Appearance", "color_scheme_path")
.unwrap_or_else(|| "CosmicPlease".to_owned());
if old_color_scheme_path.contains("Cosmic") {
ini.setstr(
"Appearance",
"color_scheme_path",
color_scheme_path.to_str(),
);
}
let old_icon_theme = ini
.get("Appearance", "icon_theme")
.unwrap_or_else(|| "breeze".to_owned());
if old_icon_theme.contains("breeze") {
ini.setstr("Appearance", "icon_theme", Some(icon_theme));
}
}
ini.pretty_write(path, &qt_settings_ini_style())
.map_err(OutputError::Io)?;
Ok(())
}
/// Reset the applied qt56ct config by removing COSMIC-specific entries from the config file.
#[cold]
pub fn reset_qt56ct() -> Result<(), OutputError> {
let qt5ct_res = Self::reset_ct("qt5ct");
let qt6ct_res = Self::reset_ct("qt6ct");
qt5ct_res?;
qt6ct_res?;
Ok(())
}
#[must_use]
#[cold]
fn reset_ct(ct: &str) -> Result<(), OutputError> {
let path = Self::get_conf_path(ct)?;
let file_content = fs::read_to_string(&path).map_err(OutputError::Io)?;
let mut ini = Ini::new_cs();
ini.read(file_content).map_err(OutputError::Ini)?;
let old_version = ini
.getuint("Appearance", "cosmic_qt_version")
.map_err(OutputError::Ini)?
.unwrap_or_default();
if old_version == 0 {
return Ok(());
}
ini.remove_key("Appearance", "cosmic_qt_version");
ini.remove_key("Appearance", "color_scheme_path");
ini.remove_key("Appearance", "icon_theme");
ini.pretty_write(path, &qt_settings_ini_style())
.map_err(OutputError::Io)?;
Ok(())
}
/// Returns the file paths of the form `~/.config/ct/ct.conf`:
/// e.g. `~/.config/qt6ct/qt6ct.conf`.
///
/// The file and its parent directory are created if they don't exist.
#[cold]
fn get_conf_path(ct: &str) -> Result<PathBuf, OutputError> {
assert!(ct == "qt5ct" || ct == "qt6ct");
let Some(mut config_dir) = dirs::config_dir() else {
return Err(OutputError::MissingConfigDir);
};
config_dir.push(&ct);
if !config_dir.exists() {
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
}
let file_path = config_dir.join(ct.to_owned() + ".conf");
if !file_path.exists() {
File::create_new(&file_path).map_err(OutputError::Io)?;
}
Ok(file_path)
}
/// Gets a path like `~/.config/qt6ct/colors/CosmicDark.conf`
///
/// Its parent directory is created if it doesn't exist.
#[cold]
fn get_qpalette_path(ct: &str, is_dark: bool) -> Result<PathBuf, OutputError> {
assert!(ct == "qt5ct" || ct == "qt6ct");
let Some(mut config_dir) = dirs::config_dir() else {
return Err(OutputError::MissingConfigDir);
};
config_dir.push(&ct);
config_dir.push("colors");
if !config_dir.exists() {
fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
}
let file_name = if is_dark {
"CosmicDark.conf"
} else {
"CosmicLight.conf"
};
Ok(config_dir.join(file_name))
}
}
/// Defines the different symbolic color roles used in current GUIs.
///
/// qt5ct and qt6ct consume this as a list of colors, ordered by ColorRole:
/// - https://doc.qt.io/qt-6/qpalette.html#ColorRole-enum
/// - https://doc.qt.io/archives/qt-5.15/qpalette.html#ColorRole-enum
struct QPaletteGroup {
/// A general foreground color.
window_text: Srgba,
/// The general button background color.
button: Srgba,
/// Lighter than [button] color, used mostly for 3D bevel and shadow effects.
light: Srgba,
/// Between [button] and [light], used mostly for 3D bevel and shadow effects.
midlight: Srgba,
/// Darker than [button], used mostly for 3D bevel and shadow effects.
dark: Srgba,
/// Between [button] and [dark], used mostly for 3D bevel and shadow effects.
mid: Srgba,
/// The foreground color used with [base].
text: Srgba,
/// A text color that is very different from [window_text], and contrasts well with e.g. [dark].
/// Typically used for text that needs to be drawn where [text] or [window_text] would give poor contrast, such as on pressed push buttons.
bright_text: Srgba,
/// A foreground color used with the [button] color.
button_text: Srgba,
/// Used mostly as the background color for text entry widgets, but can also be used for other painting -
/// such as the background of combobox drop down lists and toolbar handles.
base: Srgba,
/// A general background color.
window: Srgba,
/// A very dark color, used mostly for 3D bevel and shadow effects.
/// Opaque black by default.
shadow: Srgba,
/// A color to indicate a selected item or the current item.
highlight: Srgba,
/// A text color that contrasts with [highlight].
highlighted_text: Srgba,
/// A text color used for unvisited hyperlinks.
link: Srgba,
/// A text color used for already visited hyperlinks.
link_visited: Srgba,
/// Used as the alternate background color in views with alternating row colors.
alternate_base: Srgba,
/// No role; this special role is often used to indicate that a role has not been assigned.
no_role: Srgba,
/// Used as the background color for QToolTip and QWhatsThis.
/// Tool tips use the inactive color group of QPalette, because tool tips are not active windows.
tool_tip_base: Srgba,
/// Used as the foreground color for QToolTip and QWhatsThis.
/// Tool tips use the inactive color group of QPalette, because tool tips are not active windows.
tool_tip_text: Srgba,
/// Used as the placeholder color for various text input widgets.
placeholder_text: Srgba,
// /// [accent] only exists since Qt 6.6. Including it here breaks qt5ct.
// /// When omitted, it defaults to [highlight].
// accent: Srgba,
}
impl QPaletteGroup {
/// Returns a comma-separated list of the colors as hex codes.
/// E.g. `#ff000000, #ffdcdcdc, ...`
///
/// Any transparent colors are flattened with [base] to avoid issues with
/// the Fusion style.
fn as_list(&self) -> String {
let colors = vec![
to_argb_hex(self.window_text.over(self.base)),
to_argb_hex(self.button.over(self.base)),
to_argb_hex(self.light.over(self.base)),
to_argb_hex(self.midlight.over(self.base)),
to_argb_hex(self.dark.over(self.base)),
to_argb_hex(self.mid.over(self.base)),
to_argb_hex(self.text.over(self.base)),
to_argb_hex(self.bright_text.over(self.base)),
to_argb_hex(self.button_text.over(self.base)),
to_argb_hex(self.base.over(self.base)),
to_argb_hex(self.window.over(self.base)),
to_argb_hex(self.shadow.over(self.base)),
to_argb_hex(self.highlight.over(self.base)),
to_argb_hex(self.highlighted_text.over(self.base)),
to_argb_hex(self.link.over(self.base)),
to_argb_hex(self.link_visited.over(self.base)),
to_argb_hex(self.alternate_base.over(self.base)),
to_argb_hex(self.no_role.over(self.base)),
to_argb_hex(self.tool_tip_base.over(self.base)),
to_argb_hex(self.tool_tip_text.over(self.base)),
to_argb_hex(self.placeholder_text.over(self.base)),
];
colors.join(", ")
}
}
/// Converts a color to a hex string in the format `#AARRGGBB`.
/// Do not use [to_hex] since that uses the format `RRGGBBAA`.
fn to_argb_hex(c: Srgba) -> String {
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
format!(
"#{:02x}{:02x}{:02x}{:02x}",
c_u8.alpha, c_u8.red, c_u8.green, c_u8.blue
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_to_argb_hex() {
let color = Srgba::new(0x33, 0x55, 0x77, 0xff);
let argb = to_argb_hex(color.into());
assert_eq!(argb, "#ff335577");
}
#[test]
fn test_light_default_qpalette() {
let light_default_qpalette = Theme::light_default().as_qpalette();
insta::assert_snapshot!(light_default_qpalette);
}
#[test]
fn test_dark_default_qpalette() {
let dark_default_qpalette = Theme::dark_default().as_qpalette();
insta::assert_snapshot!(dark_default_qpalette);
}
}

View file

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

View file

@ -0,0 +1,10 @@
---
source: cosmic-theme/src/output/qt56ct_output.rs
expression: dark_default_qpalette
---
# GENERATED BY COSMIC
[ColorScheme]
active_colors=#ffe7e7e7, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffc0c0c0, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff434343, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffc0c0c0, #ff777777
disabled_colors=#e6d3d3d3, #8f474747, #a9696969, #a4626262, #a95f5f5f, #a45d5d5d, #d2a1a1a1, #ffe7e7e7, #d2a1a1a1, #bf2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #bf3c3c3c, #bf30555a, #bf324f53, #ff1f2425, #bf2e2e2e, #bf2e2e2e, #d2a1a1a1, #bf909090
inactive_colors=#ffc2c2c2, #ff4a4a4a, #ff555555, #ff505050, #ff4f4f4f, #ff4d4d4d, #ffa3a3a3, #ffe7e7e7, #ffc0c0c0, #ff2e2e2e, #ff1b1b1b, #ff1b1b1b, #ff63d0df, #ff3f3f3f, #ff63d0df, #ff5bb2be, #ff1f2425, #ff2e2e2e, #ff2e2e2e, #ffa3a3a3, #ff777777

View file

@ -0,0 +1,10 @@
---
source: cosmic-theme/src/output/qt56ct_output.rs
expression: light_default_qpalette
---
# GENERATED BY COSMIC
[ColorScheme]
active_colors=#ff121212, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff272727, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff272727, #ff8e8e8e
disabled_colors=#e62b2b2b, #8fc9c9c9, #a99b9b9b, #a4a0a0a0, #a9929292, #a49b9b9b, #d2535353, #ffd7d7d7, #d2535353, #bff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #bff6f6f6, #bf526d70, #bf72888a, #ffccd0d1, #bff5f5f5, #bff5f5f5, #d2535353, #bf6c6c6c
inactive_colors=#ff3f3f3f, #ffc3c3c3, #ffbababa, #ffbebebe, #ffb3b3b3, #ffbbbbbb, #ff505050, #ffd7d7d7, #ff272727, #fff5f5f5, #ffd7d7d7, #ff121212, #ff00525a, #fff6f6f6, #ff00525a, #ff317379, #ffccd0d1, #fff5f5f5, #fff5f5f5, #ff505050, #ff8e8e8e

View file

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

View file

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

View file

@ -145,7 +145,6 @@ pub fn is_valid_srgb(c: Srgba) -> bool {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use almost::equal;
use palette::{OklabHue, Srgba}; use palette::{OklabHue, Srgba};
use super::{is_valid_srgb, oklch_to_srgba_nearest_chroma}; use super::{is_valid_srgb, oklch_to_srgba_nearest_chroma};
@ -173,57 +172,57 @@ mod tests {
fn test_conversion_boundaries() { fn test_conversion_boundaries() {
let c1 = palette::Oklcha::new(0.0, 0.288, OklabHue::from_degrees(0.0), 1.0); let c1 = palette::Oklcha::new(0.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1); let srgb = oklch_to_srgba_nearest_chroma(c1);
equal(srgb.red, 0.0); almost::zero(srgb.red);
equal(srgb.blue, 0.0); almost::zero(srgb.blue);
equal(srgb.green, 0.0); almost::zero(srgb.green);
let c1 = palette::Oklcha::new(1.0, 0.288, OklabHue::from_degrees(0.0), 1.0); let c1 = palette::Oklcha::new(1.0, 0.288, OklabHue::from_degrees(0.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1); let srgb = oklch_to_srgba_nearest_chroma(c1);
equal(srgb.red, 1.0); almost::equal(srgb.red, 1.0);
equal(srgb.blue, 1.0); almost::equal(srgb.blue, 1.0);
equal(srgb.green, 1.0); almost::equal(srgb.green, 1.0);
} }
#[test] #[test]
fn test_conversion_colors() { fn test_conversion_colors() {
let c1 = palette::Oklcha::new(0.4608, 0.11111, OklabHue::new(57.31), 1.0); let c1 = palette::Oklcha::new(0.4608, 0.11111, OklabHue::new(57.31), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert!(srgb.red == 133); assert_eq!(srgb.red, 133);
assert!(srgb.green == 69); assert_eq!(srgb.green, 69);
assert!(srgb.blue == 0); assert_eq!(srgb.blue, 0);
let c1 = palette::Oklcha::new(0.30, 0.08, OklabHue::new(35.0), 1.0); let c1 = palette::Oklcha::new(0.30, 0.08, OklabHue::new(35.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert!(srgb.red == 78); assert_eq!(srgb.red, 78);
assert!(srgb.green == 27); assert_eq!(srgb.green, 27);
assert!(srgb.blue == 15); assert_eq!(srgb.blue, 15);
let c1 = palette::Oklcha::new(0.757, 0.146, OklabHue::new(301.2), 1.0); let c1 = palette::Oklcha::new(0.757, 0.146, OklabHue::new(301.2), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert!(srgb.red == 192); assert_eq!(srgb.red, 192);
assert!(srgb.green == 153); assert_eq!(srgb.green, 153);
assert!(srgb.blue == 253); assert_eq!(srgb.blue, 253);
} }
#[test] #[test]
fn test_conversion_fallback_colors() { fn test_conversion_fallback_colors() {
let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0); let c1 = palette::Oklcha::new(0.70, 0.284, OklabHue::new(35.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert!(srgb.red == 255); assert_eq!(srgb.red, 255);
assert!(srgb.green == 103); assert_eq!(srgb.green, 102);
assert!(srgb.blue == 65); assert_eq!(srgb.blue, 65);
let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0); let c1 = palette::Oklcha::new(0.757, 0.239, OklabHue::new(301.2), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert!(srgb.red == 193); assert_eq!(srgb.red, 193);
assert!(srgb.green == 152); assert_eq!(srgb.green, 152);
assert!(srgb.blue == 255); assert_eq!(srgb.blue, 255);
let c1 = palette::Oklcha::new(0.163, 0.333, OklabHue::new(141.0), 1.0); let c1 = palette::Oklcha::new(0.163, 0.333, OklabHue::new(141.0), 1.0);
let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>(); let srgb = oklch_to_srgba_nearest_chroma(c1).into_format::<u8, u8>();
assert!(srgb.red == 1); assert_eq!(srgb.red, 1);
assert!(srgb.green == 19); assert_eq!(srgb.green, 19);
assert!(srgb.blue == 0); assert_eq!(srgb.blue, 0);
} }
} }

View file

@ -4,25 +4,19 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tracing = "0.1.41" open = "5.3.3"
tracing-subscriber = "0.3.19"
tracing-log = "0.2.0"
open = "5.3.2"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false
features = [ features = [
"debug", "debug",
"winit", "winit",
"tokio", "tokio",
"xdg-portal", "xdg-portal",
"dbus-config",
"desktop", "desktop",
"a11y", "a11y",
"wayland", "wayland",
"wgpu", "wgpu",
"single-instance", "single-instance",
"multi-window",
"about", "about",
] ]

View file

@ -5,17 +5,14 @@
use cosmic::app::context_drawer::{self, ContextDrawer}; use cosmic::app::context_drawer::{self, ContextDrawer};
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::widget::column; use cosmic::executor;
use cosmic::iced_core::Size; use cosmic::iced::{alignment, Length, Size};
use cosmic::prelude::*;
use cosmic::widget::{self, about::About, nav_bar}; use cosmic::widget::{self, about::About, nav_bar};
use cosmic::{executor, iced, ApplicationExt, Element};
/// Runs application with these settings /// Runs application with these settings
#[rustfmt::skip] #[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let _ = tracing_log::LogTracer::init();
let settings = Settings::default() let settings = Settings::default()
.size(Size::new(1024., 768.)); .size(Size::new(1024., 768.));
@ -67,12 +64,12 @@ impl cosmic::Application for App {
let about = About::default() let about = About::default()
.name("About Demo") .name("About Demo")
.icon(Self::APP_ID) .icon(widget::icon::from_name(Self::APP_ID))
.version("0.1.0") .version("0.1.0")
.author("System 76") .author("System76")
.license("GPL-3.0-only") .license("GPL-3.0-only")
//.license_url("https://www.some-custom-license-url.com") .license_url("https://choosealicense.com/licenses/gpl-3.0/")
.developers([("Michael Murphy", "mmstick@system76.com")]) .developers([("Michael Murphy", "info@system76.com")])
.links([ .links([
("Website", "https://system76.com/cosmic"), ("Website", "https://system76.com/cosmic"),
("Repository", "https://github.com/pop-os/libcosmic"), ("Repository", "https://github.com/pop-os/libcosmic"),
@ -86,7 +83,11 @@ impl cosmic::Application for App {
show_about: false, show_about: false,
}; };
let command = app.update_title(); app.set_header_title("COSMIC About Example".into());
let command = app.set_window_title(
"COSMIC About Example".into(),
app.core.main_window_id().unwrap(),
);
(app, command) (app, command)
} }
@ -99,12 +100,17 @@ impl cosmic::Application for App {
/// Called when a navigation item is selected. /// Called when a navigation item is selected.
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> { fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
self.nav_model.activate(id); self.nav_model.activate(id);
self.update_title() Task::none()
} }
fn context_drawer(&self) -> Option<ContextDrawer<Self::Message>> { fn context_drawer(&self) -> Option<ContextDrawer<'_, Self::Message>> {
self.show_about self.show_about.then(|| {
.then(|| context_drawer::about(&self.about, Message::Open, Message::ToggleAbout)) context_drawer::about(
&self.about,
|url| Message::Open(url.to_owned()),
Message::ToggleAbout,
)
})
} }
/// Handle application events here. /// Handle application events here.
@ -116,47 +122,27 @@ impl cosmic::Application for App {
} }
Message::Open(url) => match open::that_detached(url) { Message::Open(url) => match open::that_detached(url) {
Ok(_) => (), Ok(_) => (),
Err(err) => tracing::error!("Failed to open URL: {err}"), Err(err) => eprintln!("Failed to open URL: {err}"),
}, },
} }
Task::none() Task::none()
} }
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let show_about_button = widget::button::text("Show about").on_press(Message::ToggleAbout);
let centered = cosmic::widget::container( let centered = cosmic::widget::container(
column![widget::button::text("Show about").on_press(Message::ToggleAbout)] widget::column::with_capacity(1)
.width(iced::Length::Fill) .push(show_about_button)
.height(iced::Length::Shrink) .width(Length::Fill)
.align_x(iced::alignment::Horizontal::Center), .height(Length::Shrink)
.align_x(alignment::Horizontal::Center),
) )
.width(iced::Length::Fill) .width(Length::Fill)
.height(iced::Length::Shrink) .height(Length::Shrink)
.align_x(iced::alignment::Horizontal::Center) .align_x(alignment::Horizontal::Center)
.align_y(iced::alignment::Vertical::Center); .align_y(alignment::Vertical::Center);
Element::from(centered) Element::from(centered)
} }
} }
impl App
where
Self: cosmic::Application,
{
fn active_page_title(&mut self) -> &str {
self.nav_model
.text(self.nav_model.active())
.unwrap_or("Unknown Page")
}
fn update_title(&mut self) -> Task<Message> {
let header_title = self.active_page_title().to_owned();
let window_title = format!("{header_title} — COSMIC AppDemo");
self.set_header_title(header_title);
if let Some(id) = self.core.main_window_id() {
self.set_window_title(window_title, id)
} else {
Task::none()
}
}
}

View file

@ -7,12 +7,12 @@ edition = "2021"
[dependencies] [dependencies]
once_cell = "1" once_cell = "1"
rust-embed = "8.6.0" rust-embed = "8.11.0"
tracing = "0.1" tracing = "0.1"
env_logger = "0.10.2" env_logger = "0.10.2"
log = "0.4.26" log = "0.4.29"
[dependencies.libcosmic] [dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic" path = "../../"
default-features = false default-features = false
features = ["applet-token"] features = ["applet-token"]

View file

@ -1,8 +1,8 @@
use cosmic::app::{Core, Task}; use cosmic::app::{Core, Task};
use cosmic::iced::core::window;
use cosmic::iced::window::Id; use cosmic::iced::window::Id;
use cosmic::iced::{Length, Rectangle}; use cosmic::iced::{Length, Rectangle};
use cosmic::iced_runtime::core::window;
use cosmic::surface::action::{app_popup, destroy_popup}; use cosmic::surface::action::{app_popup, destroy_popup};
use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler}; use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler};
use cosmic::Element; use cosmic::Element;
@ -13,6 +13,7 @@ pub struct Window {
core: Core, core: Core,
popup: Option<Id>, popup: Option<Id>,
example_row: bool, example_row: bool,
toggle: bool,
selected: Option<usize>, selected: Option<usize>,
} }
@ -22,6 +23,7 @@ impl Default for Window {
core: Core::default(), core: Core::default(),
popup: None, popup: None,
example_row: false, example_row: false,
toggle: false,
selected: None, selected: None,
} }
} }
@ -33,6 +35,7 @@ pub enum Message {
ToggleExampleRow(bool), ToggleExampleRow(bool),
Selected(usize), Selected(usize),
Surface(cosmic::surface::Action), Surface(cosmic::surface::Action),
Toggle(bool),
} }
impl cosmic::Application for Window { impl cosmic::Application for Window {
@ -71,7 +74,6 @@ impl cosmic::Application for Window {
Message::ToggleExampleRow(toggled) => { Message::ToggleExampleRow(toggled) => {
self.example_row = toggled; self.example_row = toggled;
} }
Message::Surface(a) => { Message::Surface(a) => {
return cosmic::task::message(cosmic::Action::Cosmic( return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(a), cosmic::app::Action::Surface(a),
@ -80,6 +82,9 @@ impl cosmic::Application for Window {
Message::Selected(i) => { Message::Selected(i) => {
self.selected = Some(i); self.selected = Some(i);
} }
Message::Toggle(v) => {
self.toggle = v;
}
}; };
Task::none() Task::none()
} }
@ -123,9 +128,8 @@ impl cosmic::Application for Window {
"Example row", "Example row",
cosmic::widget::container( cosmic::widget::container(
toggler(state.example_row) toggler(state.example_row)
.on_toggle(|value| Message::ToggleExampleRow(value)), .on_toggle(Message::ToggleExampleRow),
) ),
.height(Length::Fixed(50.)),
)) ))
.add(popup_dropdown( .add(popup_dropdown(
&["1", "asdf", "hello", "test"], &["1", "asdf", "hello", "test"],
@ -155,7 +159,7 @@ impl cosmic::Application for Window {
"oops".into() "oops".into()
} }
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -8,22 +8,18 @@ default = ["wayland"]
wayland = ["libcosmic/wayland"] wayland = ["libcosmic/wayland"]
[dependencies] [dependencies]
tracing = "0.1.41" env_logger = "0.11"
tracing-subscriber = "0.3.19"
tracing-log = "0.2.0"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false
features = [ features = [
"debug", "debug",
"winit", "winit",
"tokio", "tokio",
"xdg-portal", "xdg-portal",
"dbus-config",
"a11y", "a11y",
"wgpu",
"single-instance", "single-instance",
"multi-window",
"surface-message", "surface-message",
"multi-window",
"wgpu",
] ]

View file

@ -3,25 +3,14 @@
//! Application API example //! Application API example
use cosmic::app::Settings;
use cosmic::iced::{Alignment, Length, Size};
use cosmic::widget::menu::{self, KeyBind};
use cosmic::widget::nav_bar;
use cosmic::{executor, iced, prelude::*, widget, Core};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::widget::column;
use cosmic::iced::Length;
use cosmic::iced_core::Size;
use cosmic::widget::icon::{from_name, Handle};
use cosmic::widget::menu::KeyBind;
use cosmic::widget::{button, text};
use cosmic::widget::{
container,
menu::menu_button,
menu::{self, action::MenuAction},
nav_bar, responsive,
};
use cosmic::{executor, iced, ApplicationExt, Element};
static MENU_ID: LazyLock<iced::id::Id> = LazyLock::new(|| iced::id::Id::new("menu_id")); static MENU_ID: LazyLock<iced::id::Id> = LazyLock::new(|| iced::id::Id::new("menu_id"));
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -50,7 +39,7 @@ pub enum Action {
Hi3, Hi3,
} }
impl MenuAction for Action { impl widget::menu::Action for Action {
type Message = Message; type Message = Message;
fn message(&self) -> Message { fn message(&self) -> Message {
@ -65,8 +54,9 @@ impl MenuAction for Action {
/// Runs application with these settings /// Runs application with these settings
#[rustfmt::skip] #[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
// tracing_subscriber::fmt::init();
// let _ = tracing_log::LogTracer::init(); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
let input = vec![ let input = vec![
(Page::Page1, "🖖 Hello from libcosmic.".into()), (Page::Page1, "🖖 Hello from libcosmic.".into()),
@ -77,9 +67,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let settings = Settings::default() let settings = Settings::default()
.size(Size::new(1024., 768.)); .size(Size::new(1024., 768.));
cosmic::app::run::<App>(settings, input).unwrap();
cosmic::app::run::<App>(settings, input)?;
Ok(()) Ok(())
} }
@ -94,6 +82,7 @@ pub enum Message {
Hi, Hi,
Hi2, Hi2,
Hi3, Hi3,
Tick,
} }
/// The [`App`] stores application-specific state. /// The [`App`] stores application-specific state.
@ -104,6 +93,7 @@ pub struct App {
input_2: String, input_2: String,
hidden: bool, hidden: bool,
keybinds: HashMap<KeyBind, Action>, keybinds: HashMap<KeyBind, Action>,
progress: f32,
} }
/// Implement [`cosmic::Application`] to integrate with COSMIC. /// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -129,7 +119,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits task on initialize. /// Creates the application, and optionally emits task on initialize.
fn init(core: Core, input: Self::Flags) -> (Self, Task<Self::Message>) { fn init(core: Core, input: Self::Flags) -> (Self, cosmic::app::Task<Self::Message>) {
let mut nav_model = nav_bar::Model::default(); let mut nav_model = nav_bar::Model::default();
for (title, content) in input { for (title, content) in input {
@ -145,6 +135,7 @@ impl cosmic::Application for App {
input_2: String::new(), input_2: String::new(),
hidden: true, hidden: true,
keybinds: HashMap::new(), keybinds: HashMap::new(),
progress: 0.0,
}; };
let command = app.update_title(); let command = app.update_title();
@ -158,13 +149,13 @@ impl cosmic::Application for App {
} }
/// Called when a navigation item is selected. /// Called when a navigation item is selected.
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> { fn on_nav_select(&mut self, id: nav_bar::Id) -> cosmic::app::Task<Self::Message> {
self.nav_model.activate(id); self.nav_model.activate(id);
self.update_title() self.update_title()
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> { fn update(&mut self, message: Self::Message) -> cosmic::app::Task<Self::Message> {
match message { match message {
Message::Input1(v) => { Message::Input1(v) => {
self.input_1 = v; self.input_1 = v;
@ -190,51 +181,102 @@ impl cosmic::Application for App {
Message::Hi3 => { Message::Hi3 => {
dbg!("hi 3"); dbg!("hi 3");
} }
Message::Tick => {
self.progress = (self.progress + 0.01) % 1.0;
}
} }
Task::none() Task::none()
} }
fn subscription(&self) -> iced::Subscription<Self::Message> {
iced::time::every(std::time::Duration::from_millis(64)).map(|_| Message::Tick)
}
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let page_content = self let page_content = self
.nav_model .nav_model
.active_data::<String>() .active_data::<String>()
.map_or("No page selected", String::as_str); .map_or("No page selected", String::as_str);
let text = cosmic::widget::text(page_content); let centered = widget::container(
widget::column::with_capacity(14)
let centered = cosmic::widget::container( .push(widget::text::body(page_content))
column![ .push(
text, widget::text_input::text_input("", &self.input_1)
cosmic::widget::text_input::text_input("", &self.input_1)
.on_input(Message::Input1) .on_input(Message::Input1)
.on_clear(Message::Ignore), .on_clear(Message::Ignore),
cosmic::widget::text_input::secure_input( )
.push(
widget::text_input::secure_input(
"", "",
&self.input_1, &self.input_1,
Some(Message::ToggleHide), Some(Message::ToggleHide),
self.hidden self.hidden,
) )
.on_input(Message::Input1), .on_input(Message::Input1),
cosmic::widget::text_input::text_input("", &self.input_1).on_input(Message::Input1), )
cosmic::widget::text_input::search_input("", &self.input_2) .push(widget::text_input::text_input("", &self.input_2).on_input(Message::Input2))
.push(
widget::text_input::search_input("", &self.input_2)
.on_input(Message::Input2) .on_input(Message::Input2)
.on_clear(Message::Ignore), .on_clear(Message::Ignore),
]
.spacing(cosmic::theme::spacing().space_s)
.width(iced::Length::Fill)
.height(iced::Length::Shrink)
.align_x(iced::Alignment::Center),
) )
.width(iced::Length::Fill) .push(widget::progress_bar::circular::Circular::new().size(50.0))
.height(iced::Length::Shrink) .push(widget::progress_bar::circular::Circular::new().size(20.0))
.align_x(iced::Alignment::Center) .push(
.align_y(iced::Alignment::Center); widget::progress_bar::linear::Linear::new()
.girth(10.0)
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()
.bar_height(10.0)
.size(50.0)
.progress(self.progress),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(self.progress)
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()
.size(50.0)
.progress(0.0),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(0.0)
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()
.size(50.0)
.progress(1.0),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(1.0)
.width(Length::Fill),
)
.spacing(cosmic::theme::spacing().space_s)
.width(Length::Fill)
.height(Length::Shrink)
.align_x(Alignment::Center),
)
.width(Length::Fill)
.height(Length::Shrink)
.align_x(Alignment::Center)
.align_y(Alignment::Center);
Element::from(centered) Element::from(centered)
} }
fn header_start(&self) -> Vec<Element<Self::Message>> { fn header_start(&self) -> Vec<Element<'_, Self::Message>> {
vec![cosmic::widget::responsive_menu_bar().into_element( vec![cosmic::widget::responsive_menu_bar().into_element(
self.core(), self.core(),
&self.keybinds, &self.keybinds,
@ -322,7 +364,7 @@ where
.unwrap_or("Unknown Page") .unwrap_or("Unknown Page")
} }
fn update_title(&mut self) -> Task<Message> { fn update_title(&mut self) -> cosmic::app::Task<Message> {
let header_title = self.active_page_title().to_owned(); let header_title = self.active_page_title().to_owned();
let window_title = format!("{header_title} — COSMIC AppDemo"); let window_title = format!("{header_title} — COSMIC AppDemo");
self.set_header_title(header_title); self.set_header_title(header_title);

View file

@ -1,14 +1,13 @@
[package] [package]
name = "calendar" name = "calendar"
version = "0.1.0" version = "1.0.0"
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
chrono = "0.4.40" jiff = "0.2"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false
features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"] features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]

View file

@ -3,10 +3,10 @@
//! Calendar widget example //! Calendar widget example
use chrono::NaiveDate;
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::widget::calendar::CalendarModel; use cosmic::widget::calendar::CalendarModel;
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{ApplicationExt, Element, executor, iced};
use jiff::civil::{Date, Weekday};
/// Runs application with these settings /// Runs application with these settings
#[rustfmt::skip] #[rustfmt::skip]
@ -19,7 +19,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
/// Messages that are used specifically by our [`App`]. /// Messages that are used specifically by our [`App`].
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
DateSelected(NaiveDate), DateSelected(Date),
PrevMonth, PrevMonth,
NextMonth, NextMonth,
} }
@ -84,20 +84,16 @@ impl cosmic::Application for App {
} }
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let mut content = cosmic::widget::column().spacing(12);
let calendar = cosmic::widget::calendar( let calendar = cosmic::widget::calendar(
&self.calendar_model, &self.calendar_model,
|date| Message::DateSelected(date), |date| Message::DateSelected(date),
|| Message::PrevMonth, || Message::PrevMonth,
|| Message::NextMonth, || Message::NextMonth,
chrono::Weekday::Sun, Weekday::Sunday,
); );
content = content.push(calendar); let centered = cosmic::widget::container(calendar)
let centered = cosmic::widget::container(content)
.width(iced::Length::Fill) .width(iced::Length::Fill)
.height(iced::Length::Shrink) .height(iced::Length::Shrink)
.align_x(iced::Alignment::Center) .align_x(iced::Alignment::Center)
@ -111,8 +107,11 @@ impl App
where where
Self: cosmic::Application, Self: cosmic::Application,
{ {
fn update_title(&mut self) -> Task<Message> { fn update_title(&mut self) -> cosmic::app::Task<Message> {
self.set_header_title(String::from("Calendar Demo")); self.set_header_title(String::from("Calendar Demo"));
self.set_window_title(String::from("Calendar Demo")) self.set_window_title(
String::from("Calendar Demo"),
self.core.main_window_id().unwrap(),
)
} }
} }

View file

@ -4,7 +4,7 @@
use cosmic_config::{Config, ConfigGet, ConfigSet}; use cosmic_config::{Config, ConfigGet, ConfigSet};
fn test_config(config: Config) { fn test_config(config: Config) {
let watcher = config let _watcher = config
.watch(|config, keys| { .watch(|config, keys| {
println!("Changed: {:?}", keys); println!("Changed: {:?}", keys);
for key in keys.iter() { for key in keys.iter() {

View file

@ -4,19 +4,18 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tracing = "0.1.41" tracing = "0.1.44"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.22"
tracing-log = "0.2.0" tracing-log = "0.2.0"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false
features = [ features = [
"debug", "debug",
"winit", "winit",
"wgpu",
"tokio", "tokio",
"xdg-portal", "xdg-portal",
"multi-window",
"surface-message", "surface-message",
"wayland", "wayland",
] ]

View file

@ -4,7 +4,7 @@
//! Application API example //! Application API example
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced_core::Size; use cosmic::iced::Size;
use cosmic::widget::menu; use cosmic::widget::menu;
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
use std::collections::HashMap; use std::collections::HashMap;
@ -37,7 +37,6 @@ pub enum Message {
pub struct App { pub struct App {
core: Core, core: Core,
button_label: String, button_label: String,
show_context: bool,
hide_content: bool, hide_content: bool,
} }
@ -69,7 +68,6 @@ impl cosmic::Application for App {
core, core,
button_label: String::from("Right click me"), button_label: String::from("Right click me"),
hide_content: false, hide_content: false,
show_context: false,
}; };
app.set_header_title("COSMIC Context Menu Demo".into()); app.set_header_title("COSMIC Context Menu Demo".into());
@ -102,7 +100,7 @@ impl cosmic::Application for App {
} }
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let widget = cosmic::widget::context_menu( let widget = cosmic::widget::context_menu(
cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked), cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked),
self.context_menu(), self.context_menu(),

View file

@ -19,9 +19,9 @@ libcosmic = { path = "../..", features = [
"xdg-portal", "xdg-portal",
] } ] }
once_cell = "1.21" once_cell = "1.21"
slotmap = "1.0.7" slotmap = "1.1.1"
env_logger = "0.10" env_logger = "0.10"
log = "0.4.26" log = "0.4.29"
[dependencies.cosmic-time] [dependencies.cosmic-time]
git = "https://github.com/pop-os/cosmic-time" git = "https://github.com/pop-os/cosmic-time"

View file

@ -28,13 +28,14 @@ impl State {
column!( column!(
list_column().add(settings::item( list_column().add(settings::item(
"Bluetooth", "Bluetooth",
toggler(None, self.enabled, Message::Enable) toggler(self.enabled).on_toggle(Message::Enable)
)), )),
text("Now visible as \"TODO\", just kidding") text("Now visible as \"TODO\", just kidding")
) )
.spacing(8) .spacing(8)
.into(), .into(),
settings::view_section("Devices") settings::section()
.title("Devices")
.add(settings::item("No devices found", text(""))) .add(settings::item("No devices found", text("")))
.into(), .into(),
]) ])

View file

@ -258,12 +258,13 @@ impl State {
match self.tab_bar.active_data() { match self.tab_bar.active_data() {
None => panic!("no tab is active"), None => panic!("no tab is active"),
Some(DemoView::TabA) => settings::view_column(vec![ Some(DemoView::TabA) => settings::view_column(vec![
settings::view_section("Debug") settings::section()
.title("Debug")
.add(settings::item("Debug theme", choose_theme)) .add(settings::item("Debug theme", choose_theme))
.add(settings::item("Debug icon theme", choose_icon_theme)) .add(settings::item("Debug icon theme", choose_icon_theme))
.add(settings::item( .add(settings::item(
"Debug layout", "Debug layout",
toggler(None, window.debug, Message::Debug), toggler(window.debug).on_toggle(Message::Debug),
)) ))
.add(settings::item( .add(settings::item(
"Scaling Factor", "Scaling Factor",
@ -276,10 +277,11 @@ impl State {
.into(), .into(),
])) ]))
.into(), .into(),
settings::view_section("Controls") settings::section()
.title("Controls")
.add(settings::item( .add(settings::item(
"Toggler", "Toggler",
toggler(None, self.toggler_value, Message::TogglerToggled), toggler(self.toggler_value).on_toggle(Message::TogglerToggled),
)) ))
.add(settings::item( .add(settings::item(
"Pick List (TODO)", "Pick List (TODO)",
@ -299,14 +301,12 @@ impl State {
.add(settings::item( .add(settings::item(
"Progress", "Progress",
progress_bar(0.0..=100.0, self.slider_value) progress_bar(0.0..=100.0, self.slider_value)
.width(Length::Fixed(250.0)) .length(Length::Fixed(250.0))
.height(Length::Fixed(4.0)), .girth(Length::Fixed(4.0)),
)) ))
.add(settings::item_row(vec![checkbox( .add(settings::item_row(vec![checkbox(self.checkbox_value)
"Checkbox", .label("Checkbox")
self.checkbox_value, .on_toggle(Message::CheckboxToggled)
Message::CheckboxToggled,
)
.into()])) .into()]))
.add(settings::item( .add(settings::item(
format!( format!(
@ -354,8 +354,7 @@ impl State {
.width(Length::Shrink) .width(Length::Shrink)
.on_activate(Message::MultiSelection) .on_activate(Message::MultiSelection)
.apply(container) .apply(container)
.center_x() .center_x(Length::Fill)
.width(Length::Fill)
.into(), .into(),
text("Vertical With Spacing").into(), text("Vertical With Spacing").into(),
cosmic::iced::widget::row(vec![ cosmic::iced::widget::row(vec![
@ -424,13 +423,12 @@ impl State {
]) ])
.padding(0) .padding(0)
.into(), .into(),
Some(DemoView::TabC) => { Some(DemoView::TabC) => settings::view_column(vec![settings::section()
settings::view_column(vec![settings::view_section("Tab C") .title("Tab C")
.add(text("Nothing here yet").width(Length::Fill)) .add(text("Nothing here yet").width(Length::Fill))
.into()]) .into()])
.padding(0) .padding(0)
.into() .into(),
}
}, },
container(text("Background container with some text").size(24)) container(text("Background container with some text").size(24))
.layer(cosmic_theme::Layer::Background) .layer(cosmic_theme::Layer::Background)

View file

@ -147,7 +147,8 @@ impl State {
fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> { fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
settings::view_column(vec![ settings::view_column(vec![
window.parent_page_button(DesktopPage::DesktopOptions), window.parent_page_button(DesktopPage::DesktopOptions),
settings::view_section("Super Key Action") settings::section()
.title("Super Key Action")
.add(settings::item("Launcher", horizontal_space(Length::Fill))) .add(settings::item("Launcher", horizontal_space(Length::Fill)))
.add(settings::item("Workspaces", horizontal_space(Length::Fill))) .add(settings::item("Workspaces", horizontal_space(Length::Fill)))
.add(settings::item( .add(settings::item(
@ -155,38 +156,34 @@ impl State {
horizontal_space(Length::Fill), horizontal_space(Length::Fill),
)) ))
.into(), .into(),
settings::view_section("Hot Corner") settings::section()
.title("Hot Corner")
.add(settings::item( .add(settings::item(
"Enable top-left hot corner for Workspaces", "Enable top-left hot corner for Workspaces",
toggler(None, self.top_left_hot_corner, Message::TopLeftHotCorner), toggler(self.top_left_hot_corner).on_toggle(Message::TopLeftHotCorner),
)) ))
.into(), .into(),
settings::view_section("Top Panel") settings::section()
.title("Top Panel")
.add(settings::item( .add(settings::item(
"Show Workspaces Button", "Show Workspaces Button",
toggler( toggler(self.show_workspaces_button).on_toggle(Message::ShowWorkspacesButton),
None,
self.show_workspaces_button,
Message::ShowWorkspacesButton,
),
)) ))
.add(settings::item( .add(settings::item(
"Show Applications Button", "Show Applications Button",
toggler( toggler(self.show_applications_button)
None, .on_toggle(Message::ShowApplicationsButton),
self.show_applications_button,
Message::ShowApplicationsButton,
),
)) ))
.into(), .into(),
settings::view_section("Window Controls") settings::section()
.title("Window Controls")
.add(settings::item( .add(settings::item(
"Show Minimize Button", "Show Minimize Button",
toggler(None, self.show_minimize_button, Message::ShowMinimizeButton), toggler(self.show_minimize_button).on_toggle(Message::ShowMinimizeButton),
)) ))
.add(settings::item( .add(settings::item(
"Show Maximize Button", "Show Maximize Button",
toggler(None, self.show_maximize_button, Message::ShowMaximizeButton), toggler(self.show_maximize_button).on_toggle(Message::ShowMaximizeButton),
)) ))
.into(), .into(),
]) ])
@ -245,12 +242,12 @@ impl State {
list_column() list_column()
.add(settings::item( .add(settings::item(
"Same background on all displays", "Same background on all displays",
toggler(None, self.same_background, Message::SameBackground), toggler(self.same_background).on_toggle(Message::SameBackground),
)) ))
.add(settings::item("Background fit", text("TODO"))) .add(settings::item("Background fit", text("TODO")))
.add(settings::item( .add(settings::item(
"Slideshow", "Slideshow",
toggler(None, self.slideshow, Message::Slideshow), toggler(self.slideshow).on_toggle(Message::Slideshow),
)) ))
.into(), .into(),
column(image_column).spacing(16).into(), column(image_column).spacing(16).into(),
@ -261,7 +258,8 @@ impl State {
fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> { fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
settings::view_column(vec![ settings::view_column(vec![
window.parent_page_button(DesktopPage::Wallpaper), window.parent_page_button(DesktopPage::Wallpaper),
settings::view_section("Workspace Behavior") settings::section()
.title("Workspace Behavior")
.add(settings::item( .add(settings::item(
"Dynamic workspaces", "Dynamic workspaces",
horizontal_space(Length::Fill), horizontal_space(Length::Fill),
@ -271,7 +269,8 @@ impl State {
horizontal_space(Length::Fill), horizontal_space(Length::Fill),
)) ))
.into(), .into(),
settings::view_section("Multi-monitor Behavior") settings::section()
.title("Multi-monitor Behavior")
.add(settings::item( .add(settings::item(
"Workspaces Span Displays", "Workspaces Span Displays",
horizontal_space(Length::Fill), horizontal_space(Length::Fill),

View file

@ -69,14 +69,16 @@ impl State {
list_column() list_column()
.add(settings::item("Device name", text("TODO"))) .add(settings::item("Device name", text("TODO")))
.into(), .into(),
settings::view_section("Hardware") settings::section()
.title("Hardware")
.add(settings::item("Hardware model", text("TODO"))) .add(settings::item("Hardware model", text("TODO")))
.add(settings::item("Memory", text("TODO"))) .add(settings::item("Memory", text("TODO")))
.add(settings::item("Processor", text("TODO"))) .add(settings::item("Processor", text("TODO")))
.add(settings::item("Graphics", text("TODO"))) .add(settings::item("Graphics", text("TODO")))
.add(settings::item("Disk Capacity", text("TODO"))) .add(settings::item("Disk Capacity", text("TODO")))
.into(), .into(),
settings::view_section("Operating System") settings::section()
.title("Operating System")
.add(settings::item("Operating system", text("TODO"))) .add(settings::item("Operating system", text("TODO")))
.add(settings::item( .add(settings::item(
"Operating system architecture", "Operating system architecture",
@ -85,7 +87,8 @@ impl State {
.add(settings::item("Desktop environment", text("TODO"))) .add(settings::item("Desktop environment", text("TODO")))
.add(settings::item("Windowing system", text("TODO"))) .add(settings::item("Windowing system", text("TODO")))
.into(), .into(),
settings::view_section("Related settings") settings::section()
.title("Related settings")
.add(settings::item("Get support", text("TODO"))) .add(settings::item("Get support", text("TODO")))
.into(), .into(),
]) ])

View file

@ -4,10 +4,9 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tracing = "0.1.41" tracing = "0.1.44"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.22"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false features = ["debug", "winit", "wgpu", "tokio"]
features = ["debug", "winit", "tokio"]

View file

@ -79,8 +79,8 @@ impl cosmic::Application for App {
} }
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let mut content = cosmic::widget::column().spacing(12); let mut content = cosmic::widget::column::with_capacity(self.images.len()).spacing(12);
for (id, image) in self.images.iter().enumerate() { for (id, image) in self.images.iter().enumerate() {
content = content.push( content = content.push(
@ -108,6 +108,9 @@ where
{ {
fn update_title(&mut self) -> Task<Message> { fn update_title(&mut self) -> Task<Message> {
self.set_header_title(String::from("Image Button Demo")); self.set_header_title(String::from("Image Button Demo"));
self.set_window_title(String::from("Image Button Demo")) self.set_window_title(
String::from("Image Button Demo"),
self.core.main_window_id().unwrap(),
)
} }
} }

View file

@ -4,11 +4,10 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tracing = "0.1.41" tracing = "0.1.44"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.22"
tracing-log = "0.2.0" tracing-log = "0.2.0"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]
features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"]

View file

@ -7,10 +7,10 @@ use std::collections::HashMap;
use std::{env, process}; use std::{env, process};
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::keyboard::Key;
use cosmic::iced::window; use cosmic::iced::window;
use cosmic::iced_core::alignment::{Horizontal, Vertical}; use cosmic::iced::{Length, Size};
use cosmic::iced_core::keyboard::Key;
use cosmic::iced_core::{Length, Size};
use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::key_bind::Modifier; use cosmic::widget::menu::key_bind::Modifier;
@ -110,7 +110,7 @@ impl cosmic::Application for App {
(app, Task::none()) (app, Task::none())
} }
fn header_start(&self) -> Vec<Element<Self::Message>> { fn header_start(&self) -> Vec<Element<'_, Self::Message>> {
vec![menu_bar(&self.config, &self.key_binds)] vec![menu_bar(&self.config, &self.key_binds)]
} }
@ -137,7 +137,7 @@ impl cosmic::Application for App {
} }
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let text = if self.config.hide_content { let text = if self.config.hide_content {
cosmic::widget::text("") cosmic::widget::text("")
} else { } else {

View file

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

View file

@ -2,11 +2,11 @@ use std::collections::HashMap;
use cosmic::{ use cosmic::{
app::Core, app::Core,
iced::{self, event, window}, iced::core::{id, Alignment, Length, Point},
iced_core::{id, Alignment, Length, Point}, iced::widget::{column, container, scrollable, text},
iced_widget::{column, container, scrollable, text}, iced::{self, event, window, Subscription},
prelude::*,
widget::{button, header_bar}, widget::{button, header_bar},
ApplicationExt, Task,
}; };
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -57,7 +57,7 @@ impl cosmic::Application for MultiWindow {
(windows, cosmic::app::Task::none()) (windows, cosmic::app::Task::none())
} }
fn subscription(&self) -> cosmic::iced_futures::Subscription<Self::Message> { fn subscription(&self) -> Subscription<Self::Message> {
event::listen_with(|event, _, id| { event::listen_with(|event, _, id| {
if let iced::Event::Window(window_event) = event { if let iced::Event::Window(window_event) = event {
match window_event { match window_event {
@ -74,7 +74,7 @@ impl cosmic::Application for MultiWindow {
}) })
} }
fn update(&mut self, message: Self::Message) -> iced::Task<cosmic::Action<Self::Message>> { fn update(&mut self, message: Self::Message) -> Task<cosmic::Action<Self::Message>> {
match message { match message {
Message::CloseWindow(id) => window::close(id), Message::CloseWindow(id) => window::close(id),
Message::WindowClosed(id) => { Message::WindowClosed(id) => {
@ -119,7 +119,7 @@ impl cosmic::Application for MultiWindow {
} }
} }
fn view_window(&self, id: window::Id) -> cosmic::prelude::Element<Self::Message> { fn view_window(&self, id: window::Id) -> Element<'_, Self::Message> {
let w = self.windows.get(&id).unwrap(); let w = self.windows.get(&id).unwrap();
let input_id = w.input_id.clone(); let input_id = w.input_id.clone();
@ -152,7 +152,7 @@ impl cosmic::Application for MultiWindow {
} }
} }
fn view(&self) -> cosmic::prelude::Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
self.view_window(self.core.main_window_id().unwrap()) self.view_window(self.core.main_window_id().unwrap())
} }
} }

View file

@ -4,11 +4,10 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tracing = "0.1.41" tracing = "0.1.44"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.22"
tracing-log = "0.2.0" tracing-log = "0.2.0"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]
features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"]

View file

@ -6,7 +6,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced_core::Size; use cosmic::iced::Size;
use cosmic::widget::{menu, nav_bar}; use cosmic::widget::{menu, nav_bar};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
@ -172,7 +172,7 @@ impl cosmic::Application for App {
} }
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let page_content = self let page_content = self
.nav_model .nav_model
.active_data::<String>() .active_data::<String>()

View file

@ -10,12 +10,11 @@ xdg-portal = ["libcosmic/xdg-portal"]
[dependencies] [dependencies]
apply = "0.3.0" apply = "0.3.0"
tokio = { version = "1.44", features = ["full"] } tokio = { version = "1.49", features = ["full"] }
tracing = "0.1.41" tracing = "0.1.44"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.22"
url = "2.5.4" url = "2.5.8"
[dependencies.libcosmic] [dependencies.libcosmic]
features = ["debug", "winit", "multi-window", "wayland", "tokio"] features = ["debug", "winit", "wgpu", "wayland", "tokio"]
path = "../../" path = "../../"
default-features = false

View file

@ -6,7 +6,7 @@
use apply::Apply; use apply::Apply;
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::dialog::file_chooser::{self, FileFilter}; use cosmic::dialog::file_chooser::{self, FileFilter};
use cosmic::iced_core::Length; use cosmic::iced::Length;
use cosmic::widget::button; use cosmic::widget::button;
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
use std::sync::Arc; use std::sync::Arc;
@ -82,7 +82,7 @@ impl cosmic::Application for App {
(app, cmd) (app, cmd)
} }
fn header_end(&self) -> Vec<Element<Self::Message>> { fn header_end(&self) -> Vec<Element<'_, Self::Message>> {
// Places a button the header to create open dialogs. // Places a button the header to create open dialogs.
vec![button::suggested("Open").on_press(Message::OpenFile).into()] vec![button::suggested("Open").on_press(Message::OpenFile).into()]
} }
@ -186,13 +186,17 @@ impl cosmic::Application for App {
Message::CloseError => { Message::CloseError => {
self.error_status = None; self.error_status = None;
} }
Message::Surface(surface) => {} Message::Surface(action) => {
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(action),
));
}
} }
Task::none() Task::none()
} }
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let mut content = Vec::new(); let mut content = Vec::new();
if let Some(error) = self.error_status.as_deref() { if let Some(error) = self.error_status.as_deref() {
@ -203,7 +207,7 @@ impl cosmic::Application for App {
); );
content.push( content.push(
iced::widget::vertical_space() iced::widget::space::vertical()
.height(Length::Fixed(12.0)) .height(Length::Fixed(12.0))
.into(), .into(),
); );

View file

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

View file

@ -130,7 +130,7 @@ impl Application for SpinButtonExamplApp {
Task::none() Task::none()
} }
fn view(&self) -> Element<Self::Message> { fn view(&'_ self) -> Element<'_, Self::Message> {
let space_xs = cosmic::theme::spacing().space_xs; let space_xs = cosmic::theme::spacing().space_xs;
let vert_spinner_row = iced::widget::row![ let vert_spinner_row = iced::widget::row![

View file

@ -0,0 +1,10 @@
[package]
name = "subscriptions"
version = "0.1.0"
edition = "2024"
[dependencies]
[dependencies.libcosmic]
path = "../../"
features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"]

View file

@ -0,0 +1,80 @@
// Copyright 2025 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Application API example
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Subscription;
use cosmic::{executor, prelude::*, widget};
/// Runs application with these settings
fn main() -> Result<(), Box<dyn std::error::Error>> {
cosmic::app::run::<App>(Settings::default(), ())?;
Ok(())
}
/// Messages that are used specifically by our [`App`].
#[derive(Clone, Debug)]
pub enum Message {}
/// The [`App`] stores application-specific state.
pub struct App {
core: Core,
}
/// Implement [`cosmic::Application`] to integrate with COSMIC.
impl cosmic::Application for App {
/// Default async executor to use with the app.
type Executor = executor::Default;
/// Argument received [`cosmic::Application::new`].
type Flags = ();
/// Message type specific to our [`App`].
type Message = Message;
/// The unique application ID to supply to the window manager.
const APP_ID: &'static str = "org.cosmic.TextInputsDemo";
fn core(&self) -> &Core {
&self.core
}
fn core_mut(&mut self) -> &mut Core {
&mut self.core
}
/// Creates the application, and optionally emits task on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut app = App { core };
let commands = Task::batch(vec![app.update_title()]);
(app, commands)
}
fn subscription(&self) -> Subscription<Self::Message> {
Subscription::none()
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
Task::none()
}
/// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> {
widget::Row::new().into()
}
}
impl App
where
Self: cosmic::Application,
{
fn update_title(&mut self) -> Task<Message> {
let window_title = format!("COSMIC Subscriptions Demo");
self.set_header_title(window_title.clone());
self.set_window_title(window_title, self.core.main_window_id().unwrap())
}
}

View file

@ -4,12 +4,11 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tracing = "0.1.37" tracing = "0.1.44"
tracing-subscriber = "0.3.17" tracing-subscriber = "0.3.22"
tracing-log = "0.2.0" tracing-log = "0.2.0"
chrono = "*" chrono = "*"
[dependencies.libcosmic] [dependencies.libcosmic]
features = ["debug", "multi-window", "wayland", "winit", "desktop", "tokio"] features = ["debug", "wgpu", "winit", "desktop", "tokio"]
path = "../.." path = "../.."
default-features = false

View file

@ -7,7 +7,7 @@ use std::collections::HashMap;
use chrono::Datelike; use chrono::Datelike;
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced_core::Size; use cosmic::iced::Size;
use cosmic::prelude::*; use cosmic::prelude::*;
use cosmic::widget::table; use cosmic::widget::table;
use cosmic::widget::{self, nav_bar}; use cosmic::widget::{self, nav_bar};
@ -204,7 +204,7 @@ impl cosmic::Application for App {
} }
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
cosmic::widget::responsive(|size| { cosmic::widget::responsive(|size| {
if size.width < 600.0 { if size.width < 600.0 {
widget::compact_table(&self.table_model) widget::compact_table(&self.table_model)

View file

@ -4,11 +4,10 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tracing = "0.1.41" tracing = "0.1.44"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.22"
tracing-log = "0.2.0" tracing-log = "0.2.0"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"]
features = ["debug", "winit", "tokio", "xdg-portal"]

View file

@ -87,7 +87,7 @@ impl cosmic::Application for App {
} }
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let editable = cosmic::widget::editable_input( let editable = cosmic::widget::editable_input(
"Input text here", "Input text here",
&self.input, &self.input,
@ -99,7 +99,9 @@ impl cosmic::Application for App {
let inline = cosmic::widget::inline_input("", &self.input).on_input(Message::Input); let inline = cosmic::widget::inline_input("", &self.input).on_input(Message::Input);
let column = cosmic::widget::column().push(editable).push(inline); let column = cosmic::widget::column::with_capacity(2)
.push(editable)
.push(inline);
let centered = cosmic::widget::container(column.width(200)) let centered = cosmic::widget::container(column.width(200))
.width(iced::Length::Fill) .width(iced::Length::Fill)
@ -118,6 +120,6 @@ where
fn update_title(&mut self) -> Task<Message> { fn update_title(&mut self) -> Task<Message> {
let window_title = format!("COSMIC TextInputs Demo"); let window_title = format!("COSMIC TextInputs Demo");
self.set_header_title(window_title.clone()); self.set_header_title(window_title.clone());
self.set_window_title(window_title) self.set_window_title(window_title, self.core.main_window_id().unwrap())
} }
} }

View file

@ -1,10 +1,36 @@
# Context Drawer # Context Drawer
close = أغلق close = أغلِق
# About # About
license = الترخيص license = الترخيص
links = الروابط links = الروابط
developers = المطورون developers = المطوِّرون
designers = المصممون designers = المصمّمون
artists = الفنانون artists = الفنانون
translators = المترجمون translators = المترجمون
documenters = الموثقون documenters = الموثقون
january = يناير { $year }
february = فبراير { $year }
march = مارس { $year }
april = ابريل { $year }
may = مايو { $year }
june = يونيو { $year }
july = يوليو { $year }
august = أغسطس { $year }
september = سبتمبر { $year }
october = أكتوبر { $year }
november = نوفمبر { $year }
december = ديسمبر { $year }
monday = الاثنين
tuesday = الثلاثاء
wednesday = الأربعاء
thursday = الخميس
friday = الجمعة
saturday = السبت
sunday = الأحد
mon = ن
tue = ث
wed = ر
thu = خ
fri = ج
sat = س
sun = ح

View file

@ -6,3 +6,22 @@ designers = Дызайнеры
artists = Мастакі artists = Мастакі
translators = Перакладчыкі translators = Перакладчыкі
documenters = Дакументалісты documenters = Дакументалісты
february = Люты { $year }
november = Лістапад { $year }
friday = Пт
tuesday = Аў
may = Май { $year }
wednesday = Ср
april = Красавік { $year }
monday = Пн
december = Снежань { $year }
sunday = Нд
march = Сакавік { $year }
june = Чэрвень { $year }
saturday = Сб
august = Жнівень { $year }
july = Ліпень { $year }
thursday = Чц
september = Верасень { $year }
october = Кастрычнік { $year }
january = Студзень { $year }

View file

@ -8,7 +8,7 @@ designers = Designéři
artists = Grafici artists = Grafici
translators = Překladatelé translators = Překladatelé
documenters = Tvůrci dokumentace documenters = Tvůrci dokumentace
sunday = Ne sunday = Neděle
january = Leden { $year } january = Leden { $year }
february = Únor { $year } february = Únor { $year }
march = Březen { $year } march = Březen { $year }
@ -21,9 +21,16 @@ september = Září { $year }
october = Říjen { $year } october = Říjen { $year }
november = Listopad { $year } november = Listopad { $year }
december = Prosinec { $year } december = Prosinec { $year }
monday = Po monday = Pondělí
tuesday = Út tuesday = Úterý
wednesday = St wednesday = Středa
thursday = Čt thursday = Čtvrtek
friday = Pá friday = Pátek
saturday = So saturday = Sobota
mon = Po
tue = Út
wed = St
thu = Čt
fri = Pá
sat = So
sun = Ne

View file

@ -3,11 +3,11 @@ close = Schließen
# About # About
license = Lizenz license = Lizenz
links = Links links = Links
developers = Entwickler*innen developers = Entwickler(innen)
designers = Designer*innen designers = Designer(innen)
artists = Künstler*innen artists = Künstler(innen)
translators = Übersetzer*innen translators = Übersetzer(innen)
documenters = Dokumentierer*innen documenters = Dokumentierer(innen)
# Calendar # Calendar
january = Januar { $year } january = Januar { $year }
february = Februar { $year } february = Februar { $year }
@ -21,10 +21,17 @@ september = September { $year }
october = Oktober { $year } october = Oktober { $year }
november = November { $year } november = November { $year }
december = Dezember { $year } december = Dezember { $year }
monday = Mo monday = Montag
tuesday = Di tuesday = Dienstag
wednesday = Mi wednesday = Mittwoch
thursday = Do thursday = Donnerstag
friday = Fr friday = Freitag
saturday = Sa saturday = Samstag
sunday = So sunday = Sonntag
wed = Mi
thu = Do
fri = Fr
sat = Sa
sun = So
tue = Di
mon = Mo

View file

@ -23,10 +23,17 @@ september = September { $year }
october = October { $year } october = October { $year }
november = November { $year } november = November { $year }
december = December { $year } december = December { $year }
monday = Mon monday = Monday
tuesday = Tue mon = Mon
wednesday = Wed tuesday = Tuesday
thursday = Thu tue = Tue
friday = Fri wednesday = Wednesday
saturday = Sat wed = Wed
sunday = Sun thursday = Thursday
thu = Thu
friday = Friday
fri = Fri
saturday = Saturday
sat = Sat
sunday = Sunday
sun = Sun

0
i18n/eu/libcosmic.ftl Normal file
View file

View file

@ -0,0 +1,34 @@
monday = Maanantai
mon = ma
tuesday = Tiistai
tue = ti
wednesday = Keskiviikko
wed = ke
thursday = Torstai
thu = to
friday = Perjantai
fri = pe
saturday = Lauantai
sat = la
sunday = Sunnuntai
sun = su
close = Sulje
license = Lisenssi
links = Linkit
developers = Kehittäjät
designers = Suunnittelijat
artists = Artistit
translators = Kääntäjät
documenters = Dokumentoijat
january = Tammikuu { $year }
february = Helmikuu { $year }
march = Maaliskuu { $year }
april = Huhtikuu { $year }
may = Toukokuu { $year }
june = Kesäkuu { $year }
july = Heinäkuu { $year }
august = Elokuu { $year }
september = Syyskuu { $year }
october = Lokakuu { $year }
november = Marraskuu { $year }
december = Joulukuu { $year }

View file

@ -10,18 +10,25 @@ february = Février { $year }
april = Avril { $year } april = Avril { $year }
march = Mars { $year } march = Mars { $year }
november = Novembre { $year } november = Novembre { $year }
friday = Ven friday = Vendredi
tuesday = Mar tuesday = Mardi
may = Mai { $year } may = Mai { $year }
wednesday = Mer wednesday = Mercredi
monday = Lun monday = Lundi
december = Décembre { $year } december = Décembre { $year }
sunday = Dim sunday = Dimanche
june = Juin { $year } june = Juin { $year }
saturday = Sam saturday = Samedi
august = Août { $year } august = Août { $year }
july = Juillet { $year } july = Juillet { $year }
thursday = Jeu thursday = Jeudi
september = Septembre { $year } september = Septembre { $year }
october = Octobre { $year } october = Octobre { $year }
designers = Designers designers = Designers
mon = Lun
tue = Mar
wed = Mer
thu = Jeu
fri = Ven
sat = Sam
sun = Dim

View file

@ -6,3 +6,29 @@ designers = Dearthóirí
artists = Ealaíontóirí artists = Ealaíontóirí
translators = Aistritheoirí translators = Aistritheoirí
documenters = Doiciméadóirí documenters = Doiciméadóirí
january = Eanáir { $year }
february = Feabhra { $year }
march = Márta { $year }
april = Aibreán { $year }
may = Bealtaine { $year }
june = Meitheamh { $year }
july = Iúil { $year }
august = Lúnasa { $year }
september = Meán Fómhair { $year }
october = Deireadh Fómhair { $year }
november = Samhain { $year }
december = Nollaig { $year }
monday = Dé Luain
tuesday = Dé Máirt
wednesday = Dé Céadaoin
thursday = Déardaoin
friday = Dé hAoine
saturday = Dé Sathairn
sunday = Dé Domhnaigh
mon = Lua
tue = Mái
wed = Céa
thu = Déa
fri = Aoi
sat = Sat
sun = Dom

View file

@ -2,7 +2,7 @@
close = Bezárás close = Bezárás
# About # About
license = Licenc license = Licenc
links = Linkek links = Hivatkozások
developers = Fejlesztők developers = Fejlesztők
designers = Tervezők designers = Tervezők
artists = Művészek artists = Művészek
@ -20,10 +20,17 @@ september = { $year } szeptember
october = { $year } október october = { $year } október
november = { $year } november november = { $year } november
december = { $year } december december = { $year } december
monday = H monday = Hétfő
tuesday = K tuesday = Kedd
wednesday = Sze wednesday = Szerda
thursday = Cs thursday = Csütörtök
friday = P friday = Péntek
saturday = Szo saturday = Szombat
sunday = V sunday = Vasárnap
mon = H
tue = K
wed = Sze
thu = Cs
fri = P
sat = Szo
sun = V

View file

@ -0,0 +1,34 @@
close = Tutup
license = Lisensi
links = Tautan
developers = Pengembang
designers = Perancang
artists = Artis
translators = Penerjemah
documenters = Dokumenter
january = Januari { $year }
february = Februari { $year }
march = Maret { $year }
april = April { $year }
may = Mei { $year }
june = Juni { $year }
july = Juli { $year }
august = Agustus { $year }
september = September { $year }
october = Oktober { $year }
november = November { $year }
december = Desember { $year }
monday = Senin
tuesday = Selasa
wednesday = Rabu
sunday = Minggu
saturday = Sabtu
friday = Jum'at
thursday = Kamis
mon = Sen
tue = Sel
wed = Rab
thu = Kam
fri = Jum
sat = Sab
sun = Min

0
i18n/ka/libcosmic.ftl Normal file
View file

33
i18n/kab/libcosmic.ftl Normal file
View file

@ -0,0 +1,33 @@
close = Mdel
license = Turagt
links = Iseɣwan
developers = Ineflayen
artists = Inaẓuren
translators = Imsuqlen
january = Yennayer { $year }
february = Fuṛar { $year }
march = Meɣres { $year }
april = Yebrir { $year }
may = Mayyu { $year }
june = Yunyu { $year }
july = Yulyu { $year }
august = Ɣuct { $year }
september = Ctembeṛ { $year }
october = Tubeṛ { $year }
november = Wambeṛ { $year }
december = Dujembeṛ { $year }
documenters = Imeskaren
monday = Arim
mon = Ari
tuesday = Aram
tue = Ara
wednesday = Ahad
wed = Aha
thursday = Amhad
thu = Amh
friday = Sem
fri = Sm
saturday = Sed
sat = Sd
sunday = Acer
sun = Ace

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

@ -0,0 +1,34 @@
close = Жабу
license = Лицензия
links = Сілтемелер
developers = Әзірлеушілер
designers = Дизайнерлер
artists = Суретшілер
translators = Аудармашылар
documenters = Құжаттаушылар
january = Қаңтар { $year }
february = Ақпан { $year }
march = Наурыз { $year }
april = Сәуір { $year }
may = Мамыр { $year }
june = Маусым { $year }
july = Шілде { $year }
august = Тамыз { $year }
september = Қыркүйек { $year }
october = Қазан { $year }
november = Қараша { $year }
december = Желтоқсан { $year }
monday = Дүйсенбі
tuesday = Сейсенбі
wednesday = Сәрсенбі
thursday = Бейсенбі
friday = Жұма
saturday = Сенбі
sunday = Жексенбі
mon = Дс
tue = Сс
wed = Ср
thu = Бс
fri = Жм
sat = Сн
sun = Жк

0
i18n/kmr/libcosmic.ftl Normal file
View file

View file

@ -0,0 +1,34 @@
february = { $year }년 2월
close = 닫기
documenters = 문서 작성자
november = { $year }년 11월
friday = 금요일
tuesday = 화요일
may = { $year }년 5월
wednesday = 수요일
april = { $year }년 4월
monday = 월요일
translators = 번역가
artists = 아티스트
license = 라이선스
december = { $year }년 12월
sunday = 일요일
links = 링크
march = { $year }년 3월
june = { $year }년 6월
saturday = 토요일
august = { $year }년 8월
developers = 개발자
july = { $year }년 7월
thursday = 목요일
september = { $year }년 9월
designers = 디자이너
october = { $year }년 10월
january = { $year }년 1월
mon = 월
tue = 화
wed = 수
thu = 목
fri = 금
sat = 토
sun = 일

View file

@ -0,0 +1,34 @@
february = Vasaris { $year }
close = Uždaryti
documenters = Dokumentuotojai
november = Lapkritis { $year }
friday = Penktadienis
tuesday = Antradienis
may = Gegužė { $year }
wednesday = Trečiadienis
april = Balandis { $year }
monday = Pirmadienis
translators = Vertėjai
artists = Menininkai
license = Licencija
december = Gruodis { $year }
sunday = Sekmadienis
links = Nuorodos
march = Kovas { $year }
june = Birželis { $year }
saturday = Šeštadienis
august = Rugpjūtis { $year }
developers = Kūrėjai
july = Liepa { $year }
thursday = Ketvirtadienis
september = Rugsėjis { $year }
designers = Dizaineriai
october = Spalis { $year }
january = Sausis { $year }
mon = Pirm
tue = Antr
wed = Treč
thu = Ketv
fri = Penkt
sat = Šešt
sun = Sekm

0
i18n/ml/libcosmic.ftl Normal file
View file

0
i18n/ms/libcosmic.ftl Normal file
View file

View file

@ -18,4 +18,10 @@ wednesday = Woe
thursday = Do thursday = Do
friday = Vrij friday = Vrij
saturday = Za saturday = Za
sunday = Zon sunday = Zo
links = Links
developers = Ontwikkeling
designers = Ontwerp
translators = Vertaling
documenters = Documentatie
artists = Vormgeving

0
i18n/oc/libcosmic.ftl Normal file
View file

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

@ -0,0 +1,34 @@
close = ਬੰਦ ਕਰੋ
license = ਲਸੰਸ
links = ਲਿੰਕ
developers = ਡਿਵੈਲਪਰ
designers = ਡਿਜ਼ਾਇਨਰ
artists = ਕਲਾਕਾਰ
translators = ਅਨੁਵਾਦਕ
documenters = ਦਸਤਾਵੇਜ਼ ਤਿਆਰ ਕਰਤਾ
january = ਜਨਵਰੀ { $year }
february = ਫਰਵਰੀ { $year }
march = ਮਾਰਚ { $year }
april = ਅਪਰੈਲ { $year }
may = ਮਈ { $year }
june = ਜੂਨ { $year }
july = ਜੁਲਾਈ { $year }
august = ਅਗਸਤ { $year }
september = ਸਤੰਬਰ { $year }
october = ਅਕਤੂਬਰ { $year }
november = ਨਵੰਬਰ { $year }
december = ਦਸੰਬਰ { $year }
monday = ਸੋਮਵਾਰ
mon = ਸੋਮ
tuesday = ਮੰਗਲਵਾਰ
tue = ਮੰਗਲ
wednesday = ਬੁੱਧਵਾਰ
wed = ਬੁੱਧ
thursday = ਵੀਰਵਾਰ
thu = ਵੀਰ
friday = ਸ਼ੁੱਕਰਵਾਰ
fri = ਸ਼ੁੱਕਰ
saturday = ਸ਼ਨਿੱਚਰਵਾਰ
sat = ਸ਼ਨਿੱਚਰ
sunday = ਐਤਵਾਰ
sun = ਐਤ

View file

@ -20,10 +20,17 @@ september = Wrzesień { $year }
october = Październik { $year } october = Październik { $year }
november = Listopad { $year } november = Listopad { $year }
december = Grudzień { $year } december = Grudzień { $year }
monday = Pon monday = Poniedziałek
tuesday = Wto tuesday = Wtorek
wednesday = Śro wednesday = Środa
thursday = Czw thursday = Czwartek
friday = Pią friday = Piątek
saturday = Sob saturday = Sobota
sunday = Nie sunday = Niedziela
mon = Pon
tue = Wto
wed = Śro
thu = Czw
fri = Pia
sat = Sob
sun = Nie

View file

@ -8,22 +8,29 @@ designers = Designers
artists = Artistas artists = Artistas
translators = Tradutores translators = Tradutores
documenters = Documentadores documenters = Documentadores
january = Janeiro { $year } january = Janeiro de { $year }
february = Fevereiro { $year } february = Fevereiro de { $year }
march = Março { $year } march = Março de { $year }
april = Abril { $year } april = Abril de { $year }
may = Maio { $year } may = Maio de { $year }
june = Junho { $year } june = Junho de { $year }
july = Julho { $year } july = Julho de { $year }
august = Agosto { $year } august = Agosto de { $year }
september = Setembro { $year } september = Setembro de { $year }
october = Outubro { $year } october = Outubro de { $year }
november = Novembro { $year } november = Novembro de { $year }
december = Dezembro { $year } december = Dezembro de { $year }
monday = Seg monday = Segunda-feira
tuesday = Ter tuesday = Terça-feira
wednesday = Qua wednesday = Quarta-feira
thursday = Qui thursday = Quinta-feira
friday = Sex friday = Sexta-feira
saturday = Sáb saturday = Sábado
sunday = Dom sunday = Domingo
mon = Seg
tue = Ter
wed = Qua
thu = Qui
fri = Sex
sat = Sáb
sun = Dom

View file

@ -6,3 +6,29 @@ designers = Дизайнеры
artists = Художники artists = Художники
translators = Переводчики translators = Переводчики
documenters = Авторы документации documenters = Авторы документации
january = Январь { $year }
february = Февраль { $year }
march = Март { $year }
april = Апрель { $year }
may = Май { $year }
june = Июнь { $year }
july = Июль { $year }
august = Август { $year }
september = Сентябрь { $year }
october = Октябрь { $year }
november = Ноябрь { $year }
december = Декабрь { $year }
monday = Понедельник
tuesday = Вторник
wednesday = Среда
thursday = Четверг
friday = Пятница
saturday = Суббота
sunday = Воскресенье
mon = Пн
tue = Вт
wed = Ср
thu = Чт
fri = Пт
sat = Сб
sun = Вс

0
i18n/sl/libcosmic.ftl Normal file
View file

View file

@ -18,10 +18,17 @@ september = September { $year }
october = Oktober { $year } october = Oktober { $year }
november = November { $year } november = November { $year }
december = December { $year } december = December { $year }
monday = Mån monday = Måndag
tuesday = Tis tuesday = Tisdag
wednesday = Ons wednesday = Onsdag
thursday = Tor thursday = Torsdag
friday = Fre friday = Fredag
saturday = Lör saturday = Lördag
sunday = Sön sunday = Söndag
sun = Sön
mon = Mån
tue = Tis
wed = Ons
thu = Tor
fri = Fre
sat = Lör

0
i18n/ti/libcosmic.ftl Normal file
View file

View file

@ -1,6 +1,5 @@
# Context Drawer # Context Drawer
close = Kapat close = Kapat
# About # About
license = Lisans license = Lisans
links = Bağlantılar links = Bağlantılar
@ -9,3 +8,29 @@ designers = Tasarımcılar
artists = Sanatçılar artists = Sanatçılar
translators = Çevirmenler translators = Çevirmenler
documenters = Belgelendiriciler documenters = Belgelendiriciler
january = Ocak { $year }
february = Şubat { $year }
march = Mart { $year }
april = Nisan { $year }
may = Mayıs { $year }
june = Haziran { $year }
july = Temmuz { $year }
august = Ağustos { $year }
september = Eylül { $year }
october = Ekim { $year }
november = Kasım { $year }
december = Aralık { $year }
monday = Pazartesi
mon = Pzt
tuesday = Salı
tue = Sal
wednesday = Çarşamba
wed = Çar
thursday = Perşembe
thu = Per
friday = Cuma
fri = Cum
saturday = Cumartesi
sat = Cmt
sunday = Pazar
sun = Paz

View file

@ -2,7 +2,7 @@
close = Закрити close = Закрити
# About # About
license = Ліцензія license = Ліцензія
links = Посилання links = Ланки
developers = Розробники developers = Розробники
designers = Дизайнери designers = Дизайнери
artists = Художники artists = Художники
@ -10,20 +10,27 @@ translators = Перекладачі
documenters = Документатори documenters = Документатори
february = Лютий { $year } february = Лютий { $year }
november = Листопад { $year } november = Листопад { $year }
friday = Пт friday = П'ятниця
tuesday = Вт tuesday = Вівторок
may = Травень { $year } may = Травень { $year }
wednesday = Ср wednesday = Середа
april = Квітень { $year } april = Квітень { $year }
monday = Пн monday = Понеділок
december = Грудень { $year } december = Грудень { $year }
sunday = Нд sunday = Неділя
march = Березень { $year } march = Березень { $year }
june = Червень { $year } june = Червень { $year }
saturday = Сб saturday = Субота
august = Серпень { $year } august = Серпень { $year }
july = Липень { $year } july = Липень { $year }
thursday = Чт thursday = Четвер
september = Вересень { $year } september = Вересень { $year }
october = Жовтень { $year } october = Жовтень { $year }
january = Січень { $year } january = Січень { $year }
mon = Пн
tue = Вт
wed = Ср
thu = Чт
fri = Пт
sat = Cб
sun = Нд

0
i18n/uz/libcosmic.ftl Normal file
View file

View file

View file

@ -4,3 +4,31 @@ links = 链接
developers = 开发者 developers = 开发者
designers = 设计师 designers = 设计师
translators = 译者 translators = 译者
january = { $year }年1月
february = { $year }年2月
march = { $year }年3月
april = { $year }年4月
may = { $year }年5月
june = { $year }年6月
july = { $year }年7月
august = { $year }年8月
september = { $year }年9月
october = { $year }年10月
november = { $year }年11月
december = { $year }年12月
monday = 星期一
tuesday = 星期二
wednesday = 星期三
thursday = 星期四
friday = 星期五
saturday = 星期六
sunday = 星期日
artists = 艺术家
documenters = 文档作者
mon = 周一
tue = 周二
wed = 周三
thu = 周四
fri = 周五
sat = 周六
sun = 周日

View file

@ -0,0 +1,34 @@
close = 關閉
developers = 開發人員
designers = 設計人員
artists = 美編設計
translators = 翻譯人員
documenters = 文件編輯人員
january = { $year } 年 1 月
monday = 星期一
tuesday = 星期二
wednesday = 星期三
thursday = 星期四
friday = 星期五
saturday = 星期六
sunday = 星期日
mon = 週一
tue = 週二
wed = 週三
thu = 週四
fri = 週五
sat = 週六
sun = 週日
license = 授權
links = 連結
february = { $year } 年 2 月
march = { $year } 年 3 月
april = { $year } 年 4 月
may = { $year } 年 5 月
june = { $year } 年 6 月
july = { $year } 年 7 月
august = { $year } 年 8 月
september = { $year } 年 9 月
october = { $year } 年 10 月
november = { $year } 年 11 月
december = { $year } 年 12 月

2
iced

@ -1 +1 @@
Subproject commit ac57731980039516f64834534297fe92b2043730 Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece

View file

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 4C10 3.446 9.554 3 9 3H4C3.446 3 3 3.446 3 4C3 4.554 3.446 5 4 5H9C9.554 5 10 4.554 10 4ZM7 8C7 7.446 6.554 7 6 7H4C3.446 7 3 7.446 3 8C3 8.554 3.446 9 4 9H6C6.554 9 7 8.554 7 8ZM10 12C10 11.446 9.554 11 9 11H4C3.446 11 3 11.446 3 12C3 12.554 3.446 13 4 13H9C9.554 13 10 12.554 10 12Z" fill="#232323"/>
<path d="M12 5L9 8L12 11" stroke="#232323" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 528 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8 8.61106C11.8 8.61106 12 8.37437 12 8.01163C12 7.64889 11.8 7.4122 11.8 7.4122L5.59947 1.21812C5.59947 1.21812 4.86007 0.63914 4.24935 1.36778C3.71912 1.96029 4.19936 2.61659 4.19936 2.61659L9.49979 8.01163L4.19936 13.4063C4.19936 13.4063 3.71913 14.0057 4.24936 14.6551C4.8405 15.3309 5.59947 14.8051 5.59947 14.8051L11.8 8.61106Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 467 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.20002 8.61106C4.20002 8.61106 4.00002 8.37437 4 8.01163C3.99998 7.64889 4.20002 7.4122 4.20002 7.4122L10.4005 1.21812C10.4005 1.21812 11.1399 0.63914 11.7507 1.36778C12.2809 1.96029 11.8006 2.61659 11.8006 2.61659L6.50021 8.01163L11.8006 13.4063C11.8006 13.4063 12.2809 14.0057 11.7506 14.6551C11.1595 15.3309 10.4005 14.8051 10.4005 14.8051L4.20002 8.61106Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 492 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3C7.73478 3 7.48043 3.10536 7.29289 3.29289C7.10536 3.48043 7 3.73478 7 4V6.996L4 7C3.73478 7 3.48043 7.10536 3.29289 7.29289C3.10536 7.48043 3 7.73478 3 8C3 8.26522 3.10536 8.51957 3.29289 8.70711C3.48043 8.89464 3.73478 9 4 9L7 8.996V12C7 12.2652 7.10536 12.5196 7.29289 12.7071C7.48043 12.8946 7.73478 13 8 13C8.26522 13 8.51957 12.8946 8.70711 12.7071C8.89464 12.5196 9 12.2652 9 12V8.996L12 9C12.2652 9 12.5196 8.89464 12.7071 8.70711C12.8946 8.51957 13 8.26522 13 8C13 7.73478 12.8946 7.48043 12.7071 7.29289C12.5196 7.10536 12.2652 7 12 7L9 6.996V4C9 3.73478 8.89464 3.48043 8.70711 3.29289C8.51957 3.10536 8.26522 3 8 3Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 762 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 7C3.73478 7 3.48043 7.10536 3.29289 7.29289C3.10536 7.48043 3 7.73478 3 8C3 8.26522 3.10536 8.51957 3.29289 8.70711C3.48043 8.89464 3.73478 9 4 9H12C12.2652 9 12.5196 8.89464 12.7071 8.70711C12.8946 8.51957 13 8.26522 13 8C13 7.73478 12.8946 7.48043 12.7071 7.29289C12.5196 7.10536 12.2652 7 12 7H4Z" fill="#232323"/>
</svg>

Before

Width:  |  Height:  |  Size: 433 B

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