Compare commits

...

561 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
Ashley Wulber
709044891e chore: update iced 2025-11-19 16:42:15 +01:00
Michael Aaron Murphy
fc85fcac3e fix(dropdown): refresh popup when selections change 2025-11-18 19:03:23 +01:00
Michael Aaron Murphy
7eecbe30d7 feat(dropdown): add Id support with custom close, open operations 2025-11-18 19:03:23 +01:00
Ashley Wulber
47cc6dbdbf chore: update iced 2025-11-18 18:50:42 +01:00
Hosted Weblate
8528477355 i18n: translation updates from weblate
Co-authored-by: Feike Donia <feikedonia@proton.me>
Co-authored-by: GerardWassink <gerard.wassink@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Julien Brouillard <julienbrouillard1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/fr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nl/
Translation: Pop OS/libcosmic
2025-11-17 15:11:57 -07:00
Ashley Wulber
16d095b2cd chore: update iced 2025-11-14 21:31:59 +01:00
Ashley Wulber
96a51be3e4 chore: update iced
image improvements
2025-11-14 17:48:43 +01:00
Hosted Weblate
d6b3720e1f i18n: translation updates from weblate
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hu/
Translation: Pop OS/libcosmic
2025-11-13 10:53:22 -07:00
Stephan Buys
690f1d331d
feat(desktop): add DesktopEntryCache and unit tests for known problematic entries 2025-11-13 16:02:12 +01:00
Ashley Wulber
2296e8e94d
feat(applets): configurable applet overlap and padding increases 2025-11-11 21:04:09 +01:00
Hosted Weblate
2c93a4094f i18n: translation updates from weblate
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Feike Donia <feikedonia@proton.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Yelysei <yelysei.matviienko@proton.me>
Co-authored-by: twlvnn kraftwerk <kraft_werk@tutanota.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/bg/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/
Translation: Pop OS/libcosmic
2025-11-11 17:18:58 +01:00
Cheong Lau
bc744bd4e3
fix(segmented_button): use less restrictive FnOnce for builder method over Fn 2025-11-11 17:18:38 +01:00
Ian Douglas Scott
bb6f6e9ac8 improv(cosmic-config): Remove unneeded trait bounds for subscriptions
It looks like these functions where previously implemented in a
different way that required these traits, but now it uses
`Subscription::run_with_id`, the `id` only needs to be `Hash + 'static`.
2025-11-10 11:17:53 -08:00
Michael Aaron Murphy
6439507aa2
fix(icon): default to prefer_svg if symbolic 2025-11-06 07:57:03 +01:00
Vukašin Vojinović
37ae722320 fix(context_drawer): match to designs 2025-11-06 07:33:31 +01:00
Hosted Weblate
d2f7fdea6d i18n: translation updates from weblate
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Guilherme Aiolfi <gradinf@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kartik Nayak <thisiskartiknayak@protonmail.com>
Co-authored-by: Torsten <keulehoschi@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pt_BR/
Translation: Pop OS/libcosmic
2025-11-04 16:58:56 +01:00
Vukašin Vojinović
b6c6d1cb7b improv(context_drawer): move title out of header row
This moves the title below the header row containing actions and the close button, allowing more room for the title and actions.
Also makes actions an `Element` instead of a `Vec<Element>`, providing more flexibility for developers.
2025-11-04 16:58:33 +01:00
Weblate (bot)
2299b46862
i18n: translation updates from weblate (#1034)
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hi/
Translation: Pop OS/libcosmic

Co-authored-by: Kartik Nayak <thisiskartiknayak@protonmail.com>
2025-10-30 10:35:27 -06:00
Weblate (bot)
b110b9ca3f
i18n: translation updates from weblate (#1033)
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/hi/
Translation: Pop OS/libcosmic

Co-authored-by: Kartik Nayak <thisiskartiknayak@protonmail.com>
2025-10-30 08:21:26 -06:00
Hosted Weblate
8e1d06e7da i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mattias Eriksson <snaggen@gmail.com>
Co-authored-by: Sachin Chaudhary <chaudharysachinasachin@gmail.com>
Co-authored-by: VandaL <vandalhj@gmail.com>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/cs/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sv/
Translation: Pop OS/libcosmic
2025-10-29 09:25:24 -06:00
Ashley Wulber
a1b64dde3e fix(input): handle ctrl shortcuts with caps lock 2025-10-27 13:22:05 -04:00
Ashley Wulber
0c6c85429e
chore: update iced (#1029) 2025-10-27 16:24:02 +01:00
Ashley Wulber
6204784f20 chore: update iced 2025-10-23 17:17:20 -04:00
UchiWerfer
380042396b added German translations to the localization of the calendar-widget 2025-10-22 04:31:38 +02:00
UchiWerfer
e49a30104b added localization for month and weekday to calendar-widget 2025-10-22 04:31:38 +02:00
Cheong Lau
1d6a43486e remove redundant clones, use mul_add on f32s 2025-10-22 04:30:57 +02:00
Cheong Lau
bd438a8581 perf: reduce memory allocations
This also changes `widget::column::with_children` and
`widget::row::with_children` to take an `impl IntoIterator` instead
of a `Vec`, like the `iced` variants of these functions do.

This shouldn't be a breaking change since passing in a `Vec` will still
compile and function exactly as before.

(Using `iced::widget::Column::from_vec` or
`iced::widget::Row::from_vec` isn't possible, since the elements of the
`Vec` aren't checked, so the size of the resulting `Column` or `Row`
won't adapt to the size of its children. Perhaps a new function could
be added to mirror `iced`'s?)
2025-10-22 04:30:57 +02:00
Ashley Wulber
840ef21e4d fix(dnd_destination): Don't capture leave events 2025-10-22 04:29:04 +02:00
Ashley Wulber
2e87bd7c41 fix(segmented_button): ensure modifier state exact match for tab 2025-10-21 19:29:48 +02:00
Eduardo Flores
f2e965c76c fix: dialog body overflows 2025-10-20 13:53:58 -06:00
Ashley Wulber
529eeebaeb fix: avoid focus effects if already focused 2025-10-20 11:58:40 -04:00
Ian Douglas Scott
76c1897d4d Update iced for input_zone change
https://github.com/pop-os/iced/pull/241
2025-10-17 12:06:47 -07:00
Michael Aaron Murphy
f44d82a7e8
fix(spin_buttton): change text style to body 2025-10-14 16:28:43 +02:00
Hosted Weblate
cd3e9c1493 i18n: translation updates from weblate
Co-authored-by: Aleksandar Anžel <44969003+AAnzel@users.noreply.github.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: sicKat <giuseppecatillo2003@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sr_Cyrl/
Translation: Pop OS/libcosmic
2025-10-14 06:56:10 -06:00
Hosted Weblate
483fb2cdd1 i18n: translation updates from weblate
Co-authored-by: Feike Donia <feikedonia@proton.me>
Co-authored-by: Guðmundur Erlingsson <gudmundure@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Stepan Denysenko <stepden04@gmail.com>
Co-authored-by: Ziad El-sayed <ziadelsayed1797@tutamail.com>
Co-authored-by: oddib <oddbjorn.mr@hotmail.no>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ar/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/is/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nn/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/
Translation: Pop OS/libcosmic
2025-10-12 10:31:24 -06:00
Ian Douglas Scott
d82e6a167c Update iced
Update iced with https://github.com/pop-os/iced/pull/244.
2025-10-09 12:17:12 -07:00
Hosted Weblate
804250af64 i18n: translation updates from weblate
Co-authored-by: Feike Donia <feikedonia@proton.me>
Co-authored-by: Guðmundur Erlingsson <gudmundure@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Stepan Denysenko <stepden04@gmail.com>
Co-authored-by: oddib <oddbjorn.mr@hotmail.no>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/is/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nn/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/uk/
Translation: Pop OS/libcosmic
2025-10-09 08:32:25 -06:00
Ashley Wulber
a929829521 fix(color picker): avoid 0 in color picker slider value 2025-10-09 05:28:10 +02:00
Ashley Wulber
f17cd2928a fix: forward events to trailing element regardless of cursor position 2025-10-09 05:28:10 +02:00
Weblate (bot)
2dda96c07f
i18n: translation update from Hosted Weblate (#1008)
* i18n: translation updates from weblate

Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Aliaksandr Truš <evils.mail@gmail.com>
Co-authored-by: Fedorov Alexei <aleksejfedorov963@gmail.com>
Co-authored-by: Guðmundur Erlingsson <gudmundure@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ignacio Viggiani <ign.v@hotmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Yago Raña Gayoso <yago.rana.gayoso@gmail.com>
Co-authored-by: mikenu <mikenu-jp@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/be/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/es/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/et/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ga/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ja/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ru/
Translation: Pop OS/libcosmic

* i18n: adding translation for Norwegian Nynorsk

---------

Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Aliaksandr Truš <evils.mail@gmail.com>
Co-authored-by: Fedorov Alexei <aleksejfedorov963@gmail.com>
Co-authored-by: Guðmundur Erlingsson <gudmundure@gmail.com>
Co-authored-by: Ignacio Viggiani <ign.v@hotmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Yago Raña Gayoso <yago.rana.gayoso@gmail.com>
Co-authored-by: mikenu <mikenu-jp@users.noreply.hosted.weblate.org>
Co-authored-by: oddib <oddbjorn.mr@hotmail.no>
Co-authored-by: Jeremy Soller <jackpot51@gmail.com>
2025-10-07 15:30:29 -06:00
Ashley Wulber
d40e9fa4e4 fix: support NotShowIn 2025-10-07 21:59:45 +02:00
Ashley Wulber
dc4e0edd73 fix(input): drag threshold 2025-10-07 19:43:26 +02:00
Hosted Weblate
4d4f754318 i18n: translation updates from weblate
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Aliaksandr Truš <evils.mail@gmail.com>
Co-authored-by: Fedorov Alexei <aleksejfedorov963@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Yago Raña Gayoso <yago.rana.gayoso@gmail.com>
Co-authored-by: mikenu <mikenu-jp@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/be/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/es/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/et/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ga/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ja/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ru/
Translation: Pop OS/libcosmic
2025-10-07 17:17:54 +02:00
Ashley Wulber
4c4eddb50c fix: use is_maximized 2025-10-06 12:58:46 -06:00
Cheong Lau
a27bb5e05d chore: apply clippy suggestions 2025-10-05 04:40:51 +02:00
Ashley Wulber
34f55d6720 fix: surface cleanup 2025-10-03 18:02:44 -04:00
Vukašin Vojinović
ad1672b881 fix: window corner handling 2025-10-03 14:28:34 -04:00
Vukašin Vojinović
5cd7742413 chore(about): styling fixes
Also reduces code duplication a bit.
2025-10-03 18:18:16 +02:00
Ashley Wulber
0059fe182b refactor: set sharp corner window radius to 0 instead of unsetting 2025-10-03 18:15:27 +02:00
Hosted Weblate
6c5b799b34 i18n: translation updates from weblate
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/ga/
Translation: Pop OS/libcosmic
2025-10-02 10:27:06 -06:00
grant-wilson
cc8e5ebdea Fix typo in README dependencies section 2025-10-02 09:00:45 -06:00
Hosted Weblate
1c83be9d1c i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hugo Carvalho <hugokarvalho@hotmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/es_419/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/et/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/pt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/zh_Hans/
Translation: Pop OS/libcosmic
2025-10-01 14:20:33 -06:00
Walter William Beckerleg Bruckman
aeafe447e3 i18n: adding translation for Chinese (Simplified Han script) 2025-10-01 08:33:43 -06:00
Walter William Beckerleg Bruckman
5092d19861 i18n(es-419): update translations from Weblate
Currently translated at 100.0% (8 of 8 strings)

Translation: Pop OS/libcosmic
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/es_419/
2025-10-01 08:33:43 -06:00
Walter William Beckerleg Bruckman
511fe02624 i18n: adding translation for Spanish (Latin America) 2025-10-01 08:33:43 -06:00
Priit Jõerüüt
432e43d258 i18n(et): update translations from Weblate
Currently translated at 87.5% (7 of 8 strings)

Translation: Pop OS/libcosmic
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/et/
2025-10-01 08:33:43 -06:00
Michael Aaron Murphy
6a0c06a368 chore: update taffy crate to crates.io release 2025-09-30 22:31:43 +02:00
Michael Aaron Murphy
df9df40963 chore(about): drop license dependency
Not needed since the application can already give URLs to their license
2025-09-30 22:31:32 +02:00
Priit Jõerüüt
ee84ad958f Added translation using Weblate (Estonian) 2025-09-30 16:15:17 -04:00
Mattias Eriksson
59e480a4c6 Translated using Weblate (Swedish)
Currently translated at 100.0% (8 of 8 strings)

Translation: Pop OS/libcosmic
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/sv/
2025-09-30 16:15:17 -04:00
Mattias Eriksson
f097b643b3 Added translation using Weblate (Swedish) 2025-09-30 16:15:17 -04:00
oddib
0ca7a99c9f Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (8 of 8 strings)

Translation: Pop OS/libcosmic
Translate-URL: https://hosted.weblate.org/projects/pop-os/libcosmic/nb_NO/
2025-09-30 16:15:17 -04:00
oddib
00b4a8a9f5 Added translation using Weblate (Norwegian Bokmål) 2025-09-30 16:15:17 -04:00
Ashley Wulber
4a71189d34 chore: update cosmic-protocols 2025-09-30 11:41:36 -04:00
Ashley Wulber
7015b8ace4 chore: update iced 2025-09-30 10:59:51 -04:00
Ashley Wulber
03f07d2f1e fix: sharp corners & window state handling 2025-09-30 10:59:51 -04:00
Ashley Wulber
27f591e5aa fix(corner-radius): fix radius from array to match iced and better respect sharp corners 2025-09-30 10:59:51 -04:00
Ashley Wulber
ab41b83cd8 cargo fmt 2025-09-30 10:59:51 -04:00
Ashley Wulber
9815d4d981 feat(wayland): corner-radius protocol support 2025-09-30 10:59:51 -04:00
rdsq
43314e3e6a add eo and uk 2025-09-29 12:36:20 -06:00
UchiWerfer
12014b683a i18n(de): add German translations 2025-09-29 12:35:19 -06:00
lorduskordus
ad70236a58 i18n(cs): Add Czech translation 2025-09-29 12:34:39 -06:00
twlvnn kraftwerk
9ccade723a i18n(bg): added bulgarian translation 2025-09-22 08:31:47 -06:00
VandaLHJ
47daaab610 Delete i18n/libcosmic.ftl PL
wrong location
2025-09-21 06:43:41 -06:00
VandaLHJ
f1998afff9 Create libcosmic.ftl PL initial translation
This time in correct location
2025-09-21 06:43:41 -06:00
VandaLHJ
4a29788199 Create libcosmic.ftl PL initial translation
Initial translation, i hope it's good enough.
2025-09-21 06:43:41 -06:00
therealmate
31fa09a92a
i18n(hu): add translation 2025-09-19 00:21:35 +02:00
David Carvalho
17fa2cd29a i18n(pt-BR): add translations 2025-09-19 00:21:06 +02:00
FurkanAdmin
66df10ad89
i18n(tr): add translation 2025-09-19 00:20:39 +02:00
jermanuts
19d273ed2e i18n(ar): add Arabic translation 2025-09-19 00:20:12 +02:00
Ashley Wulber
9ff208e9d7 fix: if editable input is focused by operation, emit a message 2025-09-17 22:27:37 +02:00
Ashley Wulber
c01254dd18 fix(menu): overlays should be used when multi-window is not active 2025-09-17 13:50:47 +02:00
Ashley Wulber
0e797b2440 improv(input): better initial handling of focus state 2025-09-16 00:27:25 +02:00
Matei Pralea
978bde5720 i18n(ro): Add Romanian translation 2025-09-14 07:10:08 -06:00
Vukašin Vojinović
b9a00c6e79 chore: update iced 2025-09-12 14:38:30 -04:00
Vukašin Vojinović
e568122083 fix(context_drawer): title alignment
Something caused text alignment to break, so this gets around it by wrapping the text in a container.
2025-09-11 10:49:26 -04:00
Ashley Wulber
31aa0bd3df chore: update iced 2025-09-10 17:34:01 +02:00
Vukašin Vojinović
e83e43bf1e fix(icon): always set size
Fixes an oversight in my previous commit 39a5607400.
2025-09-09 16:53:18 +02:00
Vukašin Vojinović
39a5607400 improv(icon): use correct size variant for Named
Update`Icon::size` method to correctly handle `Named` icons by using the provided size retroactively.
2025-09-09 15:57:11 +02:00
Jeremy Soller
ac18f009b4
Update iced 2025-09-07 19:17:59 -06:00
Vukašin Vojinović
066999586b
feat: add i18n support for libcosmic widgets 2025-09-05 18:50:25 +02:00
Vukašin Vojinović
ea349aca82 chore: use std::syncLazyLock
Also migrates workspace members to Rust 2024.
2025-09-03 21:54:46 +02:00
Vukašin Vojinović
b72b15d719 chore: update dependencies 2025-09-03 21:54:46 +02:00
UchiWerfer
c5df9dcf88
fix(calendar): show button icons on non-Linux targets 2025-09-03 19:35:37 +02:00
Ashley Wulber
f5f7c14f03 chore: update cctk 2025-09-03 19:33:34 +02:00
Tony4dev
2dd6dce053
improv(about): support custom license URLs 2025-09-03 14:49:35 +02:00
Ashley Wulber
4d06524f2c chore: update iced 2025-09-02 18:03:05 +02:00
Ashley Wulber
364c0b9381 refactor(theme): .65 opacity for disabled button text 2025-08-26 15:22:22 -06:00
Ashley Wulber
94ee4e1915 theme: fix disabled button 2025-08-26 13:18:23 -06:00
Ashley Wulber
6f1fe2a28b chore: update libcosmic 2025-08-25 17:35:21 +02:00
Ian Douglas Scott
66a2632e2e fix(cosmic-config): Fixes for error printing
* Use `tracing::error!` in places where `eprintln!` was used
* Loop over errors and print seperately
* Print errors with `Display` rather than `Debug`
* Don't print errors that should be ignored
  - Matches https://github.com/pop-os/libcosmic/pull/949, for same
    reasons.

With this, and the previous change, cosmic-panel no longer spams a bunch
of config errors from different applets on start.
2025-08-23 00:49:31 +02:00
Ian Douglas Scott
2d62503fdf
fix: don't error when default config for toolkit settings is not present 2025-08-22 22:41:12 +02:00
Jeremy Soller
8415d77b0a
feat(settings/section): support custom header widgets 2025-08-21 10:51:36 -06:00
Soso
29f38f83a3
fix(about): wrong icon size in about widget 2025-08-20 17:33:55 +02:00
Ashley Wulber
ba2f4b193a fix(theme): control tint colors need to be reversed for light theme 2025-08-20 17:32:49 +02:00
Michael Aaron Murphy
e7b7c3a126 improv: enable dbus-config by default, but only for Linux targets 2025-08-20 17:31:26 +02:00
Michael Aaron Murphy
6e7a634398
fix(segmented_button): draw all indent levels 2025-08-19 16:31:19 +02:00
Michael Aaron Murphy
c10695600b
feat(segmented_button): add FileNav style with related widget improvements 2025-08-19 11:13:28 +02:00
Michael Aaron Murphy
8412dd5939
fix(image_button): improve rendering of selected image buttons 2025-08-13 21:39:29 +02:00
Michael Aaron Murphy
7712ec0021
fix(context_drawer): adjust fill portion when max_width < 392 2025-08-13 20:06:06 +02:00
Michael Aaron Murphy
95ebabf149
improv(segmented_button): hide focus state until tabbed 2025-08-13 12:20:22 +02:00
Michael Aaron Murphy
5434dc95d5
feat(segmented_button): pressed state style 2025-08-13 12:13:05 +02:00
Michael Aaron Murphy
4f423349a2
fix(segmented_button): duplicate focus fix 2025-08-13 11:18:58 +02:00
Michael Aaron Murphy
6a5076ecb7
fix(context_drawer): adjust header to avoid text wrapping 2025-08-12 22:20:28 +02:00
Ashley Wulber
989fcad99e fix(input): reset cursor and last click state on unfocus 2025-08-12 17:54:47 +02:00
Michael Aaron Murphy
8badf73383
improv(segmented_button): nav bar, tab, and segmented control theme improvements 2025-08-12 17:52:59 +02:00
Friedrich
562e885872 Make ashpd optional for async-std feature 2025-08-06 09:15:00 -06:00
Ashley Wulber
b58d864e85 chore: update iced 2025-07-31 20:26:26 +02:00
Ashley Wulber
05874e8ea2 fix: theme updates 2025-07-29 16:18:50 -04:00
wiiznokes
5e136f9499
fix!(windows): remove desktop dependency for the about feature
BREAKING CHANGE: Icon must be provided as a handle instead of a string.
2025-07-28 16:33:22 +02:00
Jeremy Soller
2099dc45cb fix(header_bar): increase title portion based on maximum left or right portion 2025-07-24 15:45:03 -06:00
Ashley Wulber
1b988ed1e9 fix(theme change): make sure that all theme variables are in sync after a change 2025-07-24 12:59:36 -06:00
Ashley Wulber
c40eb87611 fix(context-menu): close menu if pressed out of bounds and open 2025-07-24 14:37:24 -04:00
Ashley Wulber
3c13669865 fix: close context menu on escape press 2025-07-24 14:37:24 -04:00
Jeremy Soller
5aa025af7d context-menu: allow borrowed content 2025-07-24 14:37:24 -04:00
Ashley Wulber
8ebd06bed0 chore: more style updates 2025-07-24 17:03:35 +02:00
Ashley Wulber
8c4cb2e54f chore: theme color updates 2025-07-21 22:59:30 +02:00
Ian Douglas Scott
b8eaad2a7e
feat: add dbus_connection() method to app::Application trait 2025-07-21 22:59:09 +02:00
Vukašin Vojinović
38dde24f96 chore(applet): add spacing field 2025-07-21 10:52:22 -04:00
Vukašin Vojinović
ec7a531539 chore: use with_alpha() where applicable 2025-07-21 10:52:22 -04:00
Ashley Wulber
7748e59ae6 refactor: better method of implementing tinted control colors 2025-07-17 17:04:04 -04:00
Ashley Wulber
0041fc2d12 Revert "refactor: introduce new palette colors for control tint"
This reverts commit b8f9dc6cb0.
2025-07-17 17:04:04 -04:00
Ashley Wulber
364af2bcdf refactor: introduce new palette colors for control tint
neutral colors will not be tinted anymore
2025-07-17 09:54:46 -04:00
Ashley Wulber
0943f131c2 refactor: track focus chain 2025-07-16 22:23:13 +02:00
Ashley Wulber
50367b96e3 fix(headerbar): handle zero length segments 2025-07-08 23:07:54 +02:00
Jeremy Soller
aaa4b83577
Fix bundling of header bar icons 2025-07-01 09:30:27 -06:00
8roken
52b802a11a fix(cosmic-config): Avoid dual notifications in transaction commits
When a transaction gets committed, the files gets written to a file in
the .atomicwrite[0-9a-Z] folder and then gets moved to their final
location. The watcher will emit two events:

- Modify(Name(To))
- Modify(Name(Both)

The last one will include both the source and the destination and is
essentially a duplicate of the first event. By discarding this event,
behavior seems to be the same, and all consumers of those events get
only notified once instead of twice when a configuration changes.
2025-06-27 19:57:35 +00:00
Jeremy Soller
dfdca0ef81
fix(menu): make shortcut text 75 percent opacity 2025-06-27 09:53:20 -06:00
Joshua Megnauth
46cbce033b
fix(header_bar): Windows build fix 2025-06-26 06:36:52 +02:00
Ashley Wulber
7555d9dfd1 cargo fmt 2025-06-25 19:37:52 -04:00
Ashley Wulber
7fb514bfac fix(menu): only draw within intersection of viewport bounds 2025-06-25 19:37:52 -04:00
Jeremy Soller
a85b369399 Fix config watching 2025-06-23 11:20:48 -06:00
Michael Aaron Murphy
1af2f4ffe5
chore: format 2025-06-23 17:50:28 +02:00
Michael Aaron Murphy
90ad3e9e1b
improv(cosmic-config): use notifier debouncer on inotify watchers 2025-06-23 17:13:58 +02:00
Ashley Wulber
5be9611c8a fix(segmented-button): context menu state management 2025-06-19 08:02:54 -06:00
Ashley Wulber
6be5403852 improv(header): remove title if condensed and better handle large fixed size elements 2025-06-18 18:02:21 -04:00
Ashley Wulber
b0f62a5109 improv: use full root menu width when using wayland popups 2025-06-18 16:04:44 -04:00
Ashley Wulber
bf9fc4c29f fix(menu): disable slide_x for repositioned nested popups 2025-06-18 16:04:44 -04:00
Adrian Geipert
90ed634b06
chore: update syn to v2 2025-06-18 09:11:22 +02:00
Ashley Wulber
7d7274b801 fix(header-bar): allocate space that accounts for window controls 2025-06-18 09:10:37 +02:00
Ashley Wulber
12317d8103 chore: update iced 2025-06-12 15:27:30 -04:00
Ashley Wulber
00ba16fe01 refactor(menu): fallback behavior for non wayland windowing system 2025-06-12 10:21:45 -06:00
Ashley Wulber
ba72aed6fb feat: context menu popups 2025-06-11 16:46:31 -04:00
Ashley Wulber
4c6061d40a fix(menu inner): avoid unnecessary panic in debug builds. 2025-06-11 12:15:12 -04:00
Michael Aaron Murphy
3f4a50ee2c
chore: remove eprintln logs 2025-06-11 11:49:32 +02:00
Michael Aaron Murphy
8edbbec1e8
fix!(desktop): support launching terminal-based desktop entries 2025-06-11 09:26:18 +02:00
Michael Aaron Murphy
f835afa59c
fix(segmented_button): unfocus when clicking out of bounds 2025-06-11 09:26:18 +02:00
Ashley Wulber
96416c2a3f fix(button): return from draw if there is no content layout 2025-06-10 18:57:06 -04:00
Ashley Wulber
7f2d34ead4 fix(menu-bar): exit early from popup creation if no root is hovered 2025-06-10 18:57:06 -04:00
Ashley Wulber
92ec78ba29
feat: menu bar popups 2025-06-10 18:22:07 +02:00
Eduardo Flores
5b77f37fde feat: allow dialog resize 2025-06-02 04:17:57 +02:00
Josh Megnauth
944c6761f7 fix(windows): Mingw doesn't support trim
Closes: #872
2025-05-29 07:28:38 +02:00
Ryan Brue
6fb4a4a43e Use size thresholds for panel sizing in libcosmic
Signed-off-by: Ryan Brue <ryanbrue.dev@gmail.com>
2025-05-28 15:10:00 -04:00
Ryan Brue
b73532f62f Handle Custom panel size in libcosmic
The logic here is set up to mimic the current text sizes for the hardcoded panel sizes.

Signed-off-by: Ryan Brue <ryanbrue.dev@gmail.com>
2025-05-28 12:54:03 -04:00
Soso
a55ed23ba8
improv!(dropdown): accept impl Into<Cow<'_, str> (#881) 2025-05-26 22:53:35 +02:00
Ashley Wulber
1fce5df160 refactor: add optional parameter for layout offset and bounds for button handlers
Buttons are often used for toggling popups, so something allowing more straightforward positioning is important.
2025-05-23 19:19:42 +02:00
Ian Douglas Scott
b61a7ebd5f
chore: update rfd and ashpd 2025-05-23 01:48:57 +02:00
Ian Douglas Scott
25322e0263
feat!: update zbus from 4.x to 5.x 2025-05-23 00:02:23 +02:00
Ashley Wulber
ce56237ab9 fix(dnd): leave event handlers should expect None as the drag Id 2025-05-22 17:18:56 +02:00
Vukašin Vojinović
a46483f161 fix(header_bar): add is_ssd field 2025-05-19 19:07:25 +02:00
Vukašin Vojinović
147fc5a2a4 improv(header_bar): reduce chance of end elements being pushed out 2025-05-19 19:07:25 +02:00
Ashley Wulber
2a1af3a24f
chore: update iced 2025-05-19 19:06:09 +02:00
UchiWerfer
b7aed0e4d6
feat(calendar): add first day of week parameter 2025-05-16 17:02:16 +02:00
Mia McMahill
d4a87bd394
fix: embed spin button and warning icons on non-linux systems 2025-05-16 17:00:35 +02:00
Jeremy Soller
c42b8ff5a5
Update iced 2025-05-15 14:15:52 -06:00
Ashley Wulber
c8c650c041 fix: text input RTL panic
RTL text will want to alight to the right, and will try to use all of an unbounded width in the input. It also tends to be offset in a negative direction.
2025-05-06 14:21:19 -04:00
Ashley Wulber
7151638f51 fix(theme mode subscription): avoid checking the keys because this interferes with the first value from the subscription
the check should be redundant, because we also later check whether the value has changed or not anyway
2025-05-01 21:40:35 -06:00
Jeremy Soller
e838616813
Update iced with support for infinite list 2025-04-30 16:32:05 -06:00
Ashley Wulber
8fa4b1cae2 fix: card style 2025-04-30 21:36:41 +02:00
Ashley Wulber
264b9d8367 chore: update iced 2025-04-30 18:40:36 +02:00
Ashley Wulber
76c7100ef4 refactor: add only_show_in filter for loaded apps 2025-04-30 12:26:08 -04:00
Michael Aaron Murphy
6763abec41
chore(cargo): update fde to 0.7.11 2025-04-30 17:13:59 +02:00
Juniper
e5802b535b
chore: add highlighter feature for iced 2025-04-24 17:26:33 +02:00
Horu
9b9d373e89
chore(task): add none function 2025-04-22 14:37:58 +02:00
Michael Aaron Murphy
7aadfe6ba6
improv(context-drawer): import definitions and add map method 2025-04-21 18:12:41 +02:00
Ashley Wulber
1abd6d7578 refactor(responsive menu): define a custom width for the collapsed menu
This can be much smaller than a regular menu to save space
2025-04-16 11:21:36 -04:00
Ashley Wulber
bbcd874d9c refactor: responsive headers should allow some options 2025-04-15 17:02:32 -04:00
Ashley Wulber
99d2478d98 cargo fmt 2025-04-14 14:16:17 +02:00
Ashley Wulber
cc3ca6ed14 refactor: applet tooltips may be for an overflow window 2025-04-14 14:16:17 +02:00
Vadim Khitrin
c2a7d63060 fix: Calculate Icons For Header Bar Individually
Based on the existing non Linux macro, we are required to calculate
icons individually.
2025-04-14 14:16:00 +02:00
Johann Tuffe
67df54f383
perf(table): sort optimizations 2025-04-11 18:19:43 +02:00
Ashley Wulber
924a9ff371 fix(input): don't ignore typing for managed inputs 2025-04-11 18:18:37 +02:00
Ashley Wulber
164f8ce155 chore: update iced 2025-04-11 08:46:08 -06:00
Dryadxon
0ddde755ee fix(about): use fde::IconSource following commit 9b9600a5d6 2025-04-07 14:33:01 -06:00
Vukašin Vojinović
3df0e5c2fe fix(app): remove context padding without content_container 2025-04-05 07:23:09 -06:00
Vukašin Vojinović
dd1b16a353 feat(scrollable): add helper for horizontal scrollables
Adds a helper function to get horizontal scrollables with COSMIC styling.
2025-04-04 18:34:55 +02:00
Vukašin Vojinović
1509163230 fix(app): match padding to designs
Also makes the maximize button change to restore when the window is maximized.
2025-04-04 18:34:55 +02:00
Michael Aaron Murphy
f7d22446c1
fix: update imports for fde and fd-icons 2025-04-04 02:37:35 +02:00
Michael Aaron Murphy
9b9600a5d6
fix(desktop): matching the wrong desktop enrties and not getting icons 2025-04-04 01:56:19 +02:00
Vukašin Vojinović
1486569481 chore: use theme::spacing() where applicable 2025-04-02 00:33:21 +02:00
Vukašin Vojinović
227f7a76b1 fix(about): match section spacing to designs
Also makes the buttons use the `Button::Link` class.
2025-04-02 00:33:21 +02:00
Jeremy Soller
e6326a28d7
Update iced 2025-03-31 08:50:47 -06:00
Michael Aaron Murphy
f16bc4a764
fix(text_input): Backspace key ignored 2025-03-27 16:38:28 +01:00
Michael Aaron Murphy
0eb86850ce fix(text_input): support numpad keys 2025-03-27 16:33:23 +01:00
Michael Aaron Murphy
c0b0b817e8 fix(text_input): send on_unfocus message where they were missing 2025-03-26 16:42:50 +01:00
Michael Aaron Murphy
8bce57ed83 chore(table): format code 2025-03-26 16:42:50 +01:00
Michael Aaron Murphy
61c0fc7543 fix(popover): modal popover handling broken 2025-03-26 16:42:50 +01:00
Adam Cosner
2753941aad
feat(widget): add table widget 2025-03-24 17:48:20 +01:00
Michael Aaron Murphy
c955c8400f
fix(segmented_button): fix widget focus not being applied on click 2025-03-24 04:36:53 +01:00
Michael Aaron Murphy
d011be6feb
fix(button): unfocus on click to prevent multiple focused buttons 2025-03-24 04:27:41 +01:00
Michael Aaron Murphy
ae5bb40d6e
fix(text_input): conflicts with keyboard focus navigation 2025-03-24 03:37:14 +01:00
Ashley Wulber
cb682be3c8
fix(applet): fixes for recent updates 2025-03-21 17:23:46 +01:00
Michael Aaron Murphy
0aa518984e
chore: format for 2024 edition 2025-03-21 13:33:07 +01:00
Michael Aaron Murphy
8cf372c9b9
perf: inline public getters/setters, and use non-generic inner functions
To reduce compile-times and avoid some overhead to binary size, this will modify some of our
generic functions to use non-generic inner functions where possible. The inner functions are
marked carefully with `#[inline(never)]` to prevent being inlined by LLVM at their callsites

While looking for generic functions to optimize, I have also taken the opportunity to annotate
public non-generic getters and setters with `#[inline]` to ensure that LLVM will inline them
across crate boundaries. By default, only generic functions are automatically inlined, and
only when enabling fat LTO are constant functions reliably inlined across crate boundaries.
2025-03-21 13:31:34 +01:00
Michael Aaron Murphy
c538d672df
improv(text_input): optimize, fix, and improve the text inputs 2025-03-21 13:21:57 +01:00
Michael Aaron Murphy
92b2756e26
chore: update dependencies; including ron 0.9 2025-03-21 13:20:45 +01:00
Michael Aaron Murphy
ab887aeeac
feat: add cosmic::theme::spacing() 2025-03-21 13:20:45 +01:00
Michael Aaron Murphy
2afa192573
fix: Rust 2024 errors 2025-03-21 13:20:45 +01:00
Michael Aaron Murphy
77c3a8ed90
feat(task): add stream function 2025-03-21 13:20:45 +01:00
Ashley Wulber
cb6ea4c7c3 chore: update iced 2025-03-21 07:50:04 +01:00
Ashley Wulber
a3525ef56e refactor: track virtual offset in the layout 2025-03-21 03:45:30 +01:00
Jeremy Soller
91eae67dd5
Update iced 2025-03-19 14:36:53 -06:00
Ashley Wulber
202684d021
chore: update fde and iced 2025-03-19 20:46:12 +01:00
Jeremy Soller
e29ce0d4c1
Fix compilation with process feature on other Unixes 2025-03-19 12:27:21 -06:00
Michael Aaron Murphy
b2b6d90fb6 fix(text_input): fixing multiple issues 2025-03-18 16:56:01 +01:00
Ashley Wulber
337b80d4ca
feat: Tooltips and Better Surface Management 2025-03-14 16:56:21 +01:00
Ashley Wulber
c7edd37b03 cargo fmt 2025-03-12 23:40:31 +01:00
Ashley Wulber
26ea70a6bd chore: update fde 2025-03-12 23:40:31 +01:00
Vukašin Vojinović
617de4dc95 improv(app): visually align window corners to content 2025-03-12 22:41:32 +01:00
Jeremy Soller
872e7dd65e
Remove unused cctk import from autosize widget 2025-03-12 08:35:17 -06:00
Vadim Khitrin
64ddcb3bf2 fix: Support Spawning Processes on macOS
Allow using pipe on macOS when attempting to spawn a process.
2025-03-10 16:22:23 +01:00
ellieplayswow
a234a45ea6
fix(segmented_button): ignore button release / finger lifted events when dragged out of target 2025-03-10 16:21:34 +01:00
Ashley Wulber
508753ae69 feat: high contrast theme updates 2025-03-10 16:10:32 +01:00
Ashley Wulber
50d2104485 fix: include config id in subscription id 2025-03-09 06:05:01 +01:00
Tyler Hardin
ae14aade11 fixup README 2025-03-05 10:04:18 -07:00
Ashley Wulber
238603640c fix: filter out tab events with control modifier 2025-03-04 23:03:55 +01:00
Michael Aaron Murphy
8a0e74b189
perf(iced): improve window resize performance 2025-02-20 13:36:04 +01:00
Michael Aaron Murphy
1f826e38b9
improv: call malloc_trim after view and update calls 2025-02-19 18:13:24 +01:00
Michael Aaron Murphy
76348bb985
chore: handle more sources of excess cosmic-config logs 2025-02-19 16:57:28 +01:00
Michael Aaron Murphy
ab6de5304b
chore(theme): avoid logging errors for unset config parameters 2025-02-19 15:37:59 +01:00
Michael Aaron Murphy
7d84d21129 improv: switch to Open Sans and Noto Sans Mono, with tweaked text styles 2025-02-18 23:57:58 +01:00
Ashley Wulber
3f25af87a3 refactor: small widget container colors 2025-02-17 15:13:16 +01:00
Ashley Wulber
25bf8f60cc feat: improve accent_text for low contrast accent colors. 2025-02-17 15:13:16 +01:00
Michael Aaron Murphy
580db26868
fix(cosmic_config): is_err method conditions reversed 2025-02-14 22:42:50 +01:00
Michael Aaron Murphy
cd8f4ee859
fix(cosmic_config): treat errors getting key_path in get_local as NotFound 2025-02-14 22:39:09 +01:00
Michael Aaron Murphy
ccc1068d9f
feat(cosmic_config): add ConfigGet::get_{local,system_default}
Required by https://github.com/pop-os/cosmic-settings/pull/975 to a modify a config
containing a HashMap which is used to partially-override the system default config
in the compositor.
2025-02-14 21:44:08 +01:00
Tony4dev
0b7e23444a
feat(segmented_button): add len method 2025-02-13 16:22:42 +01:00
Ian Douglas Scott
cba28b1372 Update cctk and iced 2025-02-13 06:59:27 -08:00
Michael Aaron Murphy
f59eb77252 perf: set static mmap threshold on gnu target env by default 2025-02-12 10:59:46 -07:00
Ian Douglas Scott
9426a985c6 Update iced and cctk 2025-02-04 11:04:12 -08:00
Victoria Brekenfeld
fdfd80f8b1 chore: Update ron to 0.9 2025-01-22 15:50:44 +01:00
ellieplayswow
900fc34444
feat(icon): add rotation property 2025-01-20 11:08:36 +01:00
Tony4dev
def11c6c96
improv(calendar): make visible public 2025-01-19 15:37:07 +01:00
Eduardo Flores
00a4042c40
chore(calendar): add derive attributes 2025-01-19 12:58:57 +01:00
Michael Aaron Murphy
1914006cdd
fix(text_input): compiler errors after Cow change 2025-01-16 06:38:08 +01:00
Tony4dev
8211fb68bd
improv!(calendar): do not select date when navigating prev/next months 2025-01-16 06:30:21 +01:00
Soso
90c5c84cce
improv(text_input): use Cow<str> for label, helper, and error text 2025-01-16 06:29:03 +01:00
netraptor
b244970a18
fix: disable async-std default dependency in rfd dependency when using tokio 2025-01-16 06:28:20 +01:00
Michael Aaron Murphy
4a97b3ddd2
chore: enable image/gif when using animated-image feature 2025-01-14 21:45:10 +01:00
Michael Aaron Murphy
99b729faff
chore: update image-rs and set defaults to only png + jpeg 2025-01-14 21:41:16 +01:00
Dryadxon
bd8347f7fc fix: impl combo_box::Catalog 2025-01-13 15:45:27 -07:00
Ian Douglas Scott
af9e353f50 dnd_source: Add suppport for surface offset
The `drag_icon` callback is passed the offset of the cursor within the
widget at the start of the drag, and can return an offset the drag
surface should be placed relative to the cursor.
2025-01-13 10:44:33 -07:00
Jason Rodney Hansen
aaa2ba3ad4 Fix entering text with compose key
Previously entering text in text inputs with the compose key would insert one
or more NUL bytes before the inserted character.
2025-01-05 17:51:44 -07:00
Michael Aaron Murphy
e162c59160
perf: reduce memory usage by dropping ustr dependency
The string cache used by ustr pre-allocates 12 MB, even if we're
only using it for a few font family names. We can therefore
manage our own set of leaked strings to reduce memory usage by 12 MB.
2025-01-03 21:58:04 +01:00
Michael Aaron Murphy
fdefc5860b
perf: avoid duplicate Ustr for default font names 2025-01-03 01:18:47 +01:00
Jason Hansen
51ede4bce6
fix(segmented_button): close context menu when clicked outside 2025-01-01 00:37:04 +01:00
Ashley Wulber
3f2ba11d56 refactor: send initial config after watching for changes 2024-12-31 11:23:28 -05:00
Eduardo Flores
b2ce4ccea2 fix: add cfg for unix only packages 2024-12-26 15:31:54 +01:00
Vukašin Vojinović
58fc034459 fix(dropdown): styling 2024-12-25 04:42:24 +01:00
Vukašin Vojinović
2d06ec4226 fix(style): use radius_s for nav bar toggle 2024-12-25 04:42:24 +01:00
Ian Douglas Scott
75a11b3c84 Don't require 'static child in dnd_destination_for_data
Matches `dnd_destination`.
2024-12-20 08:30:18 -07:00
Ashley Wulber
aeb87f8886 refactor: backup non-cosmic gtk css files 2024-12-13 15:34:31 -05:00
Ashley Wulber
2c29a7b158 update iced 2024-12-12 16:44:06 +01:00
Michael Aaron Murphy
5422ab3130
feat(list_column): configurable list item and divider padding 2024-12-10 16:35:57 +01:00
Michael Aaron Murphy
a02fa21d36
feat(button): add ListItem style 2024-12-10 16:35:24 +01:00
Michael Aaron Murphy
43e7213b70 fix: switch to cosmic fork of freedesktop-icons
Switch to a fork that we maintain, which containss a few fixes
that haven't been merged upstream yet.
2024-12-05 14:41:16 +01:00
Ashley Wulber
ff0ba4860c fix: autosize layout limits 2024-12-03 16:59:04 -05:00
Ashley Wulber
b524ccb0a4 chore: update cctk 2024-12-03 05:35:56 +01:00
Tony4dev
d536341234
fix(segmented_button): model index out of bounds when setting position 2024-12-03 05:14:33 +01:00
wiiznokes
b80d90e5ce
fix: compile for markdown feature 2024-12-03 05:13:54 +01:00
Ashley Wulber
931165050d
chore: update iced 2024-12-03 05:13:27 +01:00
Vukašin Vojinović
de0c1921f7 fix(list_column): match padding/spacing to designs 2024-12-02 17:43:19 +01:00
Vukašin Vojinović
a6c08d68f9 fix(toggler): remove extra padding 2024-12-02 17:43:19 +01:00
Vukašin Vojinović
a6db807c1b fix 2024-11-26 15:24:25 -05:00
wiiznokes
96c67e29a4
fix(iced): correct event types for Windows 2024-11-26 16:30:56 +01:00
Vukašin Vojinović
8e823f622f fix(header_bar): match spacing to designs 2024-11-26 16:29:53 +01:00
Vukašin Vojinović
af7157b45a improv: window border corner appearance 2024-11-26 16:29:53 +01:00
Ashley Wulber
a9c7c3cdbf
fix(iced): a11y tree focus 2024-11-25 06:52:32 +01:00
Michael Aaron Murphy
9a8a56952d
feat(dropdown): optional icons for dropdowns 2024-11-25 06:51:16 +01:00
Michael Aaron Murphy
52ab37c1eb
feat(icon): add draw method for renderer 2024-11-25 06:50:59 +01:00
Michael Aaron Murphy
637d932669
fix: revert change of cosmic::Task to cosmic::app::Task
This change caused all uses of `cosmic::Task` to be coerced into a
message type specific to `cosmic::app`. Thus, users were forced to
create messages that are wrapped in `cosmic::app::Message` enums.
2024-11-25 06:47:06 +01:00
Jeremy Soller
0ef3cced6a
fix(text): make title 1 text use light weight 2024-11-22 12:48:06 -07:00
Adam Cosner
2ca8961b9f Made app::Task a winit-only feature
Not exactly sure why, but the wayland feature disables cosmic::app so this is required
2024-11-21 10:45:21 -05:00
Adam Cosner
a64529af17 Changed cosmic::command module to cosmic::task and changed cosmic::Task to reexport cosmic::app::Task instead of iced::Task 2024-11-21 10:45:21 -05:00
Adam Cosner
525a14cfb1 Fixed issue #700 where not using multi-window caused the main window to not be able to be interacted with 2024-11-19 23:49:47 -05:00
Bryan Hyland
b14fde9033 feat!(spin_button): refactor and support vertical widget variant 2024-11-20 05:03:29 +01:00
Ashley Wulber
bc89a8aede fix: don't apply drawer position as overlay translation 2024-11-20 05:02:59 +01:00
wiiznokes
d79faab789
fix(widget): impl Catalog for qr_code 2024-11-19 20:03:31 +01:00
Ashley Wulber
a58c73334e
fix(iced): autosize fixes 2024-11-19 20:02:25 +01:00
Vukašin Vojinović
2b7d171fed update iced 2024-11-18 19:59:59 -05:00
Vukašin Vojinović
dd79e900da fix(footer): corner radius 2024-11-18 19:59:59 -05:00
Eduardo Flores
62b0c8a401 fix: icon spacing and size 2024-11-18 08:39:37 -05:00
Eduardo Flores
f3d9e4c0d3 fix: examples 2024-11-18 08:39:37 -05:00
Eduardo Flores
e7b9c6493a improv: icon support for menus 2024-11-18 08:39:37 -05:00
wiiznokes
a355a049d9
feat!(app): ContextDrawer return type for context_drawer method 2024-11-16 03:38:29 +01:00
Ashley Wulber
aaadf7199e refactor: add is_daemon setting
The app can request to be treated by iced as a daemon so it can perform cleanup when its main window is closed.
2024-11-14 11:01:54 -05:00
Ashley Wulber
e3fabf7d12
fix: revert change for overlay layout 2024-11-13 16:55:30 +01:00
Ashley Wulber
4a97030f52
fix(iced): a11y_nodes fix 2024-11-12 20:56:59 +01:00
Ashley Wulber
2ecef3c7b2 feat: better a11y support 2024-11-11 15:02:04 -07:00
Jeremy Soller
be4c0a0848 Move about widget to new about feature 2024-11-11 11:17:02 -07:00
Michael Aaron Murphy
01bd999d28 fix(list_column): broken sliders and flex row in cosmic-settings 2024-11-11 10:56:23 -05:00
Vukašin Vojinović
3dcc47d6a7 improv(context_drawer): add optional header and footer element 2024-11-10 19:58:42 +01:00
Vukašin Vojinović
2909d37b58 fix(context_drawer): remove content padding
This is so that the scrollbar can be at the edge of the context drawer.
Apps will need to specify this padding for everything that goes below the header (if using a scrollable, it should be applied before the scrollable).
2024-11-10 15:49:25 +01:00
Vukašin Vojinović
c310f4ca24 feat(context_drawer): add actions to header 2024-11-10 15:49:25 +01:00
Eduardo Flores
d8357d0ea3
refactor: about page as a widget 2024-11-10 02:42:16 +01:00
Eduardo Flores
6f53b68be5 fix: add scrollable to about view 2024-11-09 14:48:47 +01:00
Michael Aaron Murphy
8c69491f2a fix(context_drawer): remove scrollable 2024-11-09 03:38:03 +01:00
wiiznokes
5fdc2df9cd feat: optional title on dialog 2024-11-08 16:00:40 +01:00
Vukašin Vojinović
707f2115eb fix(context_drawer): center header elements 2024-11-08 15:59:33 +01:00
Ashley Wulber
2704a77aa3 update iced 2024-11-07 17:16:03 -07:00
wiiznokes
568ff097d6 feat: export markdown iced feature 2024-11-07 01:22:32 +01:00
Vukašin Vojinović
3c5a2d9340 fix(app): conditionally set context drawer padding
This fixes the cosmic-term terminal_box becoming cropped when opening a context drawer.
2024-11-06 18:24:50 +01:00
Eduardo Flores
6dfd9d7dd5 fix: right padding for nav bar 2024-11-06 17:39:11 +01:00
Eduardo Flores
8d4afb90da
feat(app): add context view method for creating About views 2024-11-06 03:36:33 +01:00
Vukašin Vojinović
a4c1909fc2 fix(content_col): remove spacing
Removes spacing between the footer and content, until the `resize_border` is moved.
2024-11-06 02:48:23 +01:00
Vukašin Vojinović
9825e730bc update iced 2024-11-05 15:36:02 -07:00
Vukašin Vojinović
2ce3e95cf6 fix(app): border radius
Increases the border radius by 1.0 to prevent some corner artifacts.
2024-11-05 15:36:02 -07:00
Ashley Wulber
bd28ae6581 fix: update iced
should improve iced diffing and avoid bad interactions with custom id elements having more than 1 child element
2024-11-05 09:24:06 -07:00
Vukašin Vojinović
d34fd5dc2b fix: apply content padding only when content_container is true 2024-11-05 09:18:45 -07:00
Vukašin Vojinović
127ce17b85 improv: add window border 2024-11-05 10:02:19 +01:00
Ashley Wulber
36b3cfa13a fix: color picker 2024-11-01 22:39:52 -04:00
wiiznokes
0dfaa4d158 Update section.rs 2024-11-01 13:47:27 -04:00
Eduardo Flores
4b562867ec chore: update iced 2024-10-30 13:24:51 -04:00
Ashley Wulber
842cd2ec94
fix: specify width and height when importing raster images 2024-10-30 11:00:30 +01:00
Ashley Wulber
d84447aaad fix: apply App::Executor to multi-window instance 2024-10-29 13:32:08 -06:00
Ashley Wulber
fde0516484 chore: update iced 2024-10-23 21:52:35 -06:00
Ashley Wulber
3414f62367 refactor: allow a11y to be disabled with applet feature 2024-10-23 21:52:35 -06:00
Ashley Wulber
d1aabecc16 fix: card button can't be transparent when disabled 2024-10-23 21:52:35 -06:00
Ashley Wulber
5dfac9748d update iced 2024-10-23 21:52:35 -06:00
Ashley Wulber
795654610a fix: button 2024-10-22 19:11:44 -06:00
Ashley Wulber
722f30c724 fix: bounds for button 2024-10-22 15:46:35 -06:00
Ashley Wulber
59407552b6 fix: apply translation to dropdown position 2024-10-22 16:11:46 -04:00
Ashley Wulber
fcc0c91dd6 chore: update iced 2024-10-22 13:00:11 -06:00
Jeremy Soller
47ba123f2f
Update iced 2024-10-22 12:29:51 -06:00
Jeremy Soller
f1e52b3aaa
Update iced 2024-10-22 08:17:11 -06:00
Ashley Wulber
07763aca8e refactor: allow resetting main window id to None 2024-10-21 17:33:10 -06:00
Ashley Wulber
953685a882 fix(input): fallback on text layout for dnd 2024-10-21 15:59:32 -06:00
Ashley Wulber
cf3ba4ca07 refactor: allow resetting the main window id 2024-10-21 17:37:51 -04:00
Ashley Wulber
b44fe2c0b6 update iced 2024-10-21 14:47:57 -06:00
Jeremy Soller
e380b2bd81
fix(app): only set application_id on Linux 2024-10-21 09:47:38 -06:00
Ashley Wulber
6f5d096be2 fix: docs 2024-10-21 08:58:34 -06:00
Ashley Wulber
533e099cf6 fix: text input icon render 2024-10-21 15:46:55 +02:00
Ashley Wulber
accb65b7ec fix(applet): toggler 2024-10-21 07:46:37 -06:00
netengy-dakotaraptor
d718a4a61c fix: remove non-existent cosmic-time feature from cosmic example
Removes the "libcosmic" feature of cosmic-time from the cosmic example. This feature appears to no longer exist.
2024-10-20 07:08:44 -06:00
Jeremy Soller
c70e536b38
Update iced 2024-10-18 17:43:09 -06:00
Ashley Wulber
c287445ecc chore: update iced
small scrollable fix
2024-10-18 16:29:26 -04:00
Ashley Wulber
a73e91a12b refactor(toggler): remove required on_toggle function 2024-10-18 14:04:39 -04:00
Ashley Wulber
f0a38aa1ad cleanup 2024-10-18 14:04:39 -04:00
Ashley Wulber
45f3999f9c refactor: include winit in the applet feature 2024-10-18 14:04:39 -04:00
Ashley Wulber
684d33115c fix: open-dialog example 2024-10-18 14:04:39 -04:00
Ashley Wulber
1c1840a7c2 fix: radio example 2024-10-18 14:04:39 -04:00
Ashley Wulber
69079b94e9 update iced 2024-10-18 14:04:39 -04:00
Ashley Wulber
7b9cad4d2c cleanup 2024-10-18 14:04:39 -04:00
Vukašin Vojinović
478f3ead75 fix(palette): remove deprecated colors 2024-10-18 14:04:39 -04:00
Vukašin Vojinović
33c60ed87a fix(theme): update color palette 2024-10-18 14:04:39 -04:00
Ashley Wulber
0491c4baaa libcosmic updates 2024-10-18 14:04:39 -04:00
319 changed files with 27771 additions and 8669 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,19 +7,30 @@ on:
jobs: jobs:
pages: pages:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Build documentation - name: Install Rust nightly
run: cargo doc --verbose --features tokio,winit uses: dtolnay/rust-toolchain@master
- name: Deploy documentation with:
uses: peaceiris/actions-gh-pages@v3 toolchain: nightly-2025-07-31
with: - name: System dependencies
github_token: ${{ secrets.GITHUB_TOKEN }} run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
publish_dir: ./target/doc - name: Build documentation
force_orphan: true run: |
RUSTDOCFLAGS="--cfg docsrs" \
cargo +nightly-2025-07-31 doc --no-deps \
-p cosmic-client-toolkit \
-p cosmic-protocols \
-p libcosmic \
--verbose --features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window
- name: Deploy documentation
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc
force_orphan: true

3
.gitmodules vendored
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,24 +1,50 @@
[package] [package]
name = "libcosmic" name = "libcosmic"
version = "0.1.0" version = "1.0.0"
edition = "2021" edition = "2024"
rust-version = "1.80" rust-version = "1.90"
[lib] [lib]
name = "cosmic" name = "cosmic"
[features] [features]
default = ["clipboard"] 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
about = []
# Builds support for animated images # Builds support for animated images
animated-image = ["image", "dep:async-fs", "tokio?/io-util", "tokio?/fs"] animated-image = [
# XXX Use "a11y"; which is causing a panic currently "dep:async-fs",
applet = ["wayland", "tokio", "cosmic-panel-config", "ron"] "image/gif",
applet-token = [] "image/webp",
clipboard = ["iced_sctk?/clipboard"] "image/png",
# Use the cosmic-settings-daemon for config handling "tokio?/io-util",
dbus-config = ["cosmic-config/dbus", "dep:zbus", "cosmic-settings-daemon"] "tokio?/fs",
]
# XXX autosize should not be used on winit windows unless dialogs
autosize = []
applet = [
"autosize",
"winit",
"wayland",
"tokio",
"cosmic-panel-config",
"ron",
"multi-window",
]
applet-token = ["applet"]
# Use the cosmic-settings-daemon for config handling on Linux targets
dbus-config = []
# Debug features # Debug features
debug = ["iced/debug"] debug = ["iced/debug"]
# Enables pipewire support in ashpd, if ashpd is enabled # Enables pipewire support in ashpd, if ashpd is enabled
@ -30,23 +56,22 @@ rfd = ["dep:rfd"]
# Enables desktop files helpers # Enables desktop files helpers
desktop = [ desktop = [
"process", "process",
"dep:cosmic-settings-config",
"dep:freedesktop-desktop-entry", "dep:freedesktop-desktop-entry",
"dep:image-extras",
"dep:mime", "dep:mime",
"dep:shlex", "dep:shlex",
"tokio?/io-util", "tokio?/io-util",
"tokio?/net", "tokio?/net",
] ]
# Enables launching desktop files inside systemd scopes # Enables launching desktop files inside systemd scopes
desktop-systemd-scope = [ desktop-systemd-scope = ["desktop", "dep:zbus"]
"desktop",
"dep:zbus",
]
# Enables keycode serialization # Enables keycode serialization
serde-keycode = ["iced_core/serde"] serde-keycode = ["iced_core/serde"]
# Prevents multiple separate process instances. # Prevents multiple separate process instances.
single-instance = ["dep:zbus", "ron"] single-instance = ["zbus/blocking-api", "ron"]
# smol async runtime # smol async runtime
smol = ["dep:smol", "iced/smol", "zbus?/async-io"] smol = ["dep:smol", "iced/smol", "zbus?/async-io", "rfd?/async-std"]
tokio = [ tokio = [
"dep:tokio", "dep:tokio",
"ashpd?/tokio", "ashpd?/tokio",
@ -57,15 +82,24 @@ tokio = [
] ]
# Tokio async runtime # Tokio async runtime
# Wayland window support # Wayland window support
wayland = [ iced-wayland = [
"ashpd?/wayland", "ashpd?/wayland",
"iced_runtime/wayland", "autosize",
"iced/wayland", "iced/wayland",
"iced_sctk", "iced_winit/wayland",
"cctk", "surface-message",
] ]
wayland = [
"iced-wayland",
"iced_runtime/cctk",
"iced_winit/cctk",
"iced_wgpu/cctk",
"iced/cctk",
"dep:cctk",
]
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
@ -76,51 +110,95 @@ winit_wgpu = ["winit", "wgpu"]
# Enables XDG portal integrations # Enables XDG portal integrations
xdg-portal = ["ashpd"] xdg-portal = ["ashpd"]
qr_code = ["iced/qr_code"] qr_code = ["iced/qr_code"]
markdown = ["iced/markdown"]
highlighter = ["iced/highlighter"]
async-std = [
"dep:async-std",
"ashpd?/async-std",
"rfd?/async-std",
"zbus?/async-io",
"iced/async-std",
]
x11 = ["iced/x11", "iced_winit/x11"]
[dependencies] [dependencies]
apply = "0.3.0" apply = "0.3.0"
ashpd = { version = "0.9.1", 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 }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c8d3a1c", optional = true } async-std = { version = "1.13", optional = true }
chrono = "0.4.35" auto_enums = "0.8.8"
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true }
jiff = "0.2"
cosmic-config = { path = "cosmic-config" } cosmic-config = { path = "cosmic-config" }
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
css-color = "0.2.5" # Internationalization
derive_setters = "0.1.5" i18n-embed = { version = "0.16.0", features = [
fraction = "0.15.3" "fluent-system",
image = { version = "0.25.1", optional = true } "desktop-requester",
lazy_static = "1.4.0" ] }
libc = { version = "0.2.155", optional = true } i18n-embed-fl = "0.10"
mime = { version = "0.3.17", optional = true } rust-embed = "8.11.0"
palette = "0.7.3" css-color = "0.2.8"
rfd = { version = "0.14.0", optional = true } derive_setters = "0.1.9"
rustix = { version = "0.38.34", features = [ futures = "0.3"
"pipe", image = { version = "0.25.10", default-features = false, features = [
"process", "ico",
"jpeg",
"png",
] }
image-extras = { version = "0.1.0", default-features = false, features = [
"xpm",
"xbm",
], optional = true } ], optional = true }
serde = { version = "1.0.180", features = ["derive"] } libc = { version = "0.2.183", optional = true }
slotmap = "1.0.6" log = "0.4"
smol = { version = "2.0.0", optional = true } mime = { version = "0.3.17", optional = true }
thiserror = "1.0.44" palette = "0.7.6"
tokio = { version = "1.24.2", optional = true } rfd = { version = "0.16.0", default-features = false, features = [
tracing = "0.1" "xdg-portal",
unicode-segmentation = "1.6" ], optional = true }
url = "2.4.0" rustix = { version = "1.1", features = ["pipe", "process"], optional = true }
ustr = { version = "1.0.0", features = ["serde"] } serde = { version = "1.0.228", features = ["derive"] }
zbus = { version = "4.2.1", default-features = false, optional = true } slotmap = "1.1.1"
smol = { version = "2.0.2", optional = true }
thiserror = "2.0.18"
taffy = { version = "0.9.2", features = ["grid"] }
tokio = { version = "1.50.0", optional = true }
tracing = "0.1.44"
unicode-segmentation = "1.12"
url = "2.5.8"
zbus = { version = "5.14.0", default-features = false, optional = true }
float-cmp = "0.10.0"
[target.'cfg(unix)'.dependencies] # Enable DBus feature on Linux targets
freedesktop-icons = "0.2.5" [target.'cfg(target_os = "linux")'.dependencies]
freedesktop-desktop-entry = { version = "0.5.1", optional = true } cosmic-config = { path = "cosmic-config", features = ["dbus"] }
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
zbus = { version = "5.14.0", default-features = false }
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
freedesktop-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"
[dependencies.iced] [dependencies.iced]
path = "./iced" path = "./iced"
default-features = false default-features = false
features = ["advanced", "image", "lazy", "svg", "web-colors"] features = [
"advanced",
"image-without-codecs",
"lazy",
"svg",
"web-colors",
"tiny-skia",
]
[dependencies.iced_runtime] [dependencies.iced_runtime]
path = "./iced/runtime" path = "./iced/runtime"
@ -146,13 +224,6 @@ optional = true
[dependencies.iced_tiny_skia] [dependencies.iced_tiny_skia]
path = "./iced/tiny_skia" path = "./iced/tiny_skia"
[dependencies.iced_style]
path = "./iced/style"
[dependencies.iced_sctk]
path = "./iced/sctk"
optional = true
[dependencies.iced_winit] [dependencies.iced_winit]
path = "./iced/winit" path = "./iced/winit"
optional = true optional = true
@ -163,17 +234,13 @@ optional = true
[dependencies.cosmic-panel-config] [dependencies.cosmic-panel-config]
git = "https://github.com/pop-os/cosmic-panel" git = "https://github.com/pop-os/cosmic-panel"
# path = "../cosmic-panel/cosmic-panel-config"
optional = true optional = true
[dependencies.ron] [dependencies.ron]
version = "0.8" version = "0.12"
optional = true optional = true
[dependencies.taffy]
git = "https://github.com/DioxusLabs/taffy"
rev = "7781c70"
features = ["grid"]
[workspace] [workspace]
members = [ members = [
"cosmic-config", "cosmic-config",
@ -184,8 +251,7 @@ members = [
exclude = ["iced"] exclude = ["iced"]
[workspace.dependencies] [workspace.dependencies]
dirs = "5.0.1" dirs = "6.0.0"
[dev-dependencies]
[patch."https://github.com/pop-os/libcosmic"] tempfile = "3.27.0"
libcosmic = { path = "./" }

View file

@ -10,10 +10,11 @@ A platform toolkit based on iced for creating applets and applications for the C
## Templates ## Templates
- https://github.com/pop-os/cosmic-app-template: Application project template - https://github.com/pop-os/cosmic-app-template: Application project template
- https://github.com/pop-os/cosmic-applet-template: Panel applet project template
## Dependencies ## Dependencies
While libcosmic is written entirely in Rust, some of its dependencies may require shared system library headers to be installed. On Pop!_OS, the following dependencies are all that's necessary compile a typical COSMIC project: While libcosmic is written entirely in Rust, some of its dependencies may require shared system library headers to be installed. On Pop!_OS, the following dependencies are all that's necessary to compile a typical COSMIC project:
```sh ```sh
sudo apt install cargo cmake just libexpat1-dev libfontconfig-dev libfreetype-dev libxkbcommon-dev pkgconf sudo apt install cargo cmake just libexpat1-dev libfontconfig-dev libfreetype-dev libxkbcommon-dev pkgconf
@ -25,7 +26,7 @@ Some examples are included in the [examples](./examples) directory to to kicksta
COSMIC adventure. To run them, you need to clone the repository with the following commands: COSMIC adventure. To run them, you need to clone the repository with the following commands:
```sh ```sh
git clone https://github.com/pop-os/libcosmic git clone --recurse-submodules https://github.com/pop-os/libcosmic
cd libcosmic cd libcosmic
``` ```

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,12 +1,12 @@
[package] [package]
name = "cosmic-config-derive" name = "cosmic-config-derive"
version = "0.1.0" version = "1.0.0"
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib] [lib]
proc-macro = true proc-macro = true
[dependencies] [dependencies]
syn = "1.0" syn = "2.0"
quote = "1.0" quote = "1.0"

View file

@ -17,12 +17,16 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
let version = attributes let version = attributes
.iter() .iter()
.find_map(|attr| { .find_map(|attr| {
if attr.path.is_ident("version") { if attr.path().is_ident("version") {
match attr.parse_meta() { match attr.meta {
Ok(syn::Meta::NameValue(syn::MetaNameValue { syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Int(lit_int), value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Int(ref lit_int),
..
}),
.. ..
})) => Some(lit_int.base10_parse::<u64>().unwrap()), }) => Some(lit_int.base10_parse::<u64>().unwrap()),
_ => None, _ => None,
} }
} else { } else {
@ -102,7 +106,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
}) })
}); });
let gen = quote! { let generate = quote! {
impl CosmicConfigEntry for #name { impl CosmicConfigEntry for #name {
const VERSION: u64 = #version; const VERSION: u64 = #version;
@ -143,5 +147,5 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
} }
}; };
gen.into() generate.into()
} }

View file

@ -1,7 +1,7 @@
[package] [package]
name = "cosmic-config" name = "cosmic-config"
version = "0.1.0" version = "1.0.0"
edition = "2021" edition = "2024"
[features] [features]
default = ["macro", "subscription"] default = ["macro", "subscription"]
@ -11,24 +11,23 @@ subscription = ["iced_futures"]
[dependencies] [dependencies]
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
zbus = { version = "4.2.1", default-features = false, optional = true } zbus = { version = "5.14.0", default-features = false, optional = true }
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
calloop = { version = "0.14.0", optional = true } calloop = { version = "0.14.4", optional = true }
notify = "6.0.0" notify = "8.2.0"
ron = "0.8.0" ron = "0.12.0"
serde = "1.0.152" serde = "1.0.228"
cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true } cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true }
iced = { path = "../iced/", default-features = false, optional = true } iced = { path = "../iced/", default-features = false, optional = true }
iced_futures = { path = "../iced/futures/", default-features = false, optional = true } iced_futures = { path = "../iced/futures/", default-features = false, optional = true }
once_cell = "1.19.0"
futures-util = { version = "0.3", optional = true } futures-util = { version = "0.3", optional = true }
dirs.workspace = true dirs.workspace = true
tokio = { version = "1.0", optional = true, features = ["time"] } tokio = { version = "1.50", optional = true, features = ["time"] }
async-std = { version = "1.10", optional = true } async-std = { version = "1.13", optional = true }
tracing = "0.1" tracing = "0.1"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
xdg = "2.1" xdg = "3.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
known-folders = "1.1.0" known-folders = "1.4.2"

View file

@ -1,9 +1,13 @@
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::futures::{self, future::pending, StreamExt}; use iced_futures::{
Subscription,
futures::{self, StreamExt, future::pending},
stream,
};
pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> { pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> {
let conn = zbus::Connection::session().await?; let conn = zbus::Connection::session().await?;
@ -17,6 +21,7 @@ pub struct Watcher {
impl Deref for Watcher { impl Deref for Watcher {
type Target = ConfigProxy<'static>; type Target = ConfigProxy<'static>;
#[inline]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.proxy &self.proxy
} }
@ -52,153 +57,206 @@ 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>,
config_id: &'static str, config_id: &'static str,
is_state: bool, is_state: bool,
) -> iced_futures::Subscription<Update<T>> { ) -> iced_futures::Subscription<Update<T>> {
enum Change {
Changes(Changed),
OwnerChanged(bool),
}
let id = std::any::TypeId::of::<T>(); let id = std::any::TypeId::of::<T>();
iced_futures::subscription::channel((is_state, config_id, id), 5, move |mut tx| async move { Subscription::run_with(
let version = T::VERSION; Wrapper(id, settings_daemon, config_id, is_state),
|&Wrapper(_, ref settings_daemon, ref config_id, ref is_state)| {
let Ok(cosmic_config) = (if is_state { let is_state = *is_state;
crate::Config::new_state(config_id, version) let config_id = *config_id;
} else { let settings_daemon = settings_daemon.clone();
crate::Config::new(config_id, version) enum Change {
}) else { Changes(Changed),
pending::<()>().await; OwnerChanged(bool),
unreachable!();
};
let mut config = match T::get_entry(&cosmic_config) {
Ok(config) => config,
Err((errors, default)) => {
if !errors.is_empty() {
eprintln!("Error getting config: {config_id} {errors:?}");
}
default
} }
}; stream::channel(
5,
move |mut tx: futures::channel::mpsc::Sender<Update<T>>| async move {
let version = T::VERSION;
if let Err(err) = tx let Ok(cosmic_config) = (if is_state {
.send(Update { crate::Config::new_state(config_id, version)
errors: Vec::new(), } else {
keys: Vec::new(), crate::Config::new(config_id, version)
config: config.clone(), }) else {
}) pending::<()>().await;
.await unreachable!();
{ };
eprintln!("Failed to send config: {err}");
}
let mut attempts = 0; let mut attempts = 0;
loop { loop {
let watcher = if is_state { let watcher = if is_state {
Watcher::new_state(&settings_daemon, config_id, version).await Watcher::new_state(&settings_daemon, config_id, version).await
} else {
Watcher::new_config(&settings_daemon, config_id, version).await
};
let Ok(watcher) = watcher else {
tracing::error!("Failed to create watcher for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let Ok(changes) = watcher.receive_changed().await else {
tracing::error!("Failed to listen for changes for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let mut changes = changes.map(Change::Changes).fuse();
let Ok(owner_changed) = watcher.inner().receive_owner_changed().await else {
tracing::error!("Failed to listen for owner changes for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(2_u64.pow(attempts))).await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let mut owner_changed = owner_changed
.map(|c| Change::OwnerChanged(c.is_some()))
.fuse();
loop {
let change: Changed = futures::select! {
c = changes.next() => {
let Some(Change::Changes(c)) = c else {
break;
};
c
}
c = owner_changed.next() => {
let Some(Change::OwnerChanged(cont)) = c else {
break;
};
if cont {
continue;
} else { } else {
// The settings daemon has exited Watcher::new_config(&settings_daemon, config_id, version).await
break; };
} let Ok(watcher) = watcher else {
}, tracing::error!("Failed to create watcher for {config_id}");
};
// Reset the attempts counter if we received a change #[cfg(feature = "tokio")]
attempts = 0; ::tokio::time::sleep(::tokio::time::Duration::from_secs(
let Ok(args) = change.args() else { 2_u64.pow(attempts),
// The settings daemon has exited ))
break; .await;
}; #[cfg(feature = "async-std")]
let (errors, keys) = config.update_keys(&cosmic_config, &[args.key]); async_std::task::sleep(std::time::Duration::from_secs(
if !keys.is_empty() { 2_u64.pow(attempts),
if let Err(err) = tx ))
.send(Update { .await;
errors, #[cfg(not(any(feature = "tokio", feature = "async-std")))]
keys, {
config: config.clone(), pending::<()>().await;
}) unreachable!();
.await }
{ attempts += 1;
eprintln!("Failed to send config update: {err}"); // The settings daemon has exited
continue;
};
let Ok(changes) = watcher.receive_changed().await else {
tracing::error!("Failed to listen for changes for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let mut changes = changes.map(Change::Changes).fuse();
let Ok(owner_changed) = watcher.inner().receive_owner_changed().await
else {
tracing::error!("Failed to listen for owner changes for {config_id}");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
// The settings daemon has exited
continue;
};
let mut owner_changed = owner_changed
.map(|c| Change::OwnerChanged(c.is_some()))
.fuse();
// update now, just in case we missed changes while setting up stream
let mut config = match T::get_entry(&cosmic_config) {
Ok(config) => config,
Err((errors, default)) => {
for why in &errors {
if why.is_err() {
if let crate::Error::GetKey(_, err) = &why {
if err.kind() == std::io::ErrorKind::NotFound {
// No system default config installed; don't error
continue;
}
}
tracing::error!("error getting config: {config_id} {why}");
}
}
default
}
};
if let Err(err) = tx
.send(Update {
errors: Vec::new(),
keys: Vec::new(),
config: config.clone(),
})
.await
{
tracing::error!("Failed to send config: {err}");
}
loop {
let change: Changed = futures::select! {
c = changes.next() => {
let Some(Change::Changes(c)) = c else {
break;
};
c
}
c = owner_changed.next() => {
let Some(Change::OwnerChanged(cont)) = c else {
break;
};
if cont {
continue;
} else {
// The settings daemon has exited
break;
}
},
};
// Reset the attempts counter if we received a change
attempts = 0;
let Ok(args) = change.args() else {
// The settings daemon has exited
break;
};
let (errors, keys) = config.update_keys(&cosmic_config, &[args.key]);
if !keys.is_empty() {
if let Err(err) = tx
.send(Update {
errors,
keys,
config: config.clone(),
})
.await
{
tracing::error!("Failed to send config update: {err}");
}
}
}
} }
} },
} )
} },
}) )
} }

View file

@ -1,17 +1,59 @@
//! Integrations for cosmic-config — the cosmic configuration system. //! Integrations for cosmic-config — the cosmic configuration system.
use notify::{ use notify::{
event::{EventKind, ModifyKind}, RecommendedWatcher, Watcher,
Watcher, event::{EventKind, ModifyKind, RenameMode},
}; };
use serde::{de::DeserializeOwned, Serialize}; use serde::{Serialize, de::DeserializeOwned};
use std::{ use std::{
fmt, fs, env, fmt, fs,
io::Write, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Mutex, sync::Mutex,
}; };
/// Get the config directory, with Flatpak sandbox support.
/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory,
/// allowing sandboxed apps to read host config files.
fn get_config_dir() -> Option<PathBuf> {
// Check if we're running in Flatpak
if let Some(flatpak_id) = env::var_os("FLATPAK_ID") {
tracing::debug!("Running in Flatpak: {:?}", flatpak_id);
// Try HOST_XDG_CONFIG_HOME first (requires --filesystem=xdg-config permission)
if let Some(host_config) = env::var_os("HOST_XDG_CONFIG_HOME") {
tracing::debug!("Using HOST_XDG_CONFIG_HOME: {:?}", host_config);
return Some(PathBuf::from(host_config));
}
// Fallback: try to construct from HOME (which points to real home in Flatpak)
if let Some(home) = env::var_os("HOME") {
let config_path = PathBuf::from(&home).join(".config");
tracing::debug!("Using HOME fallback for config: {:?}", config_path);
return Some(config_path);
}
tracing::warn!("Flatpak detected but no config directory found");
}
// Not in Flatpak or no host config available, use standard dirs
let config_dir = dirs::config_dir();
tracing::debug!("Using standard config dir: {:?}", config_dir);
config_dir
}
/// Get the state directory, with Flatpak sandbox support.
fn get_state_dir() -> Option<PathBuf> {
// Check if we're running in Flatpak
if env::var_os("FLATPAK_ID").is_some() {
// Try HOST_XDG_STATE_HOME first
if let Some(host_state) = env::var_os("HOST_XDG_STATE_HOME") {
return Some(PathBuf::from(host_state));
}
// Fallback: try to construct from HOME
if let Some(home) = env::var_os("HOME") {
return Some(PathBuf::from(home).join(".local").join("state"));
}
}
dirs::state_dir()
}
#[cfg(feature = "subscription")] #[cfg(feature = "subscription")]
mod subscription; mod subscription;
#[cfg(feature = "subscription")] #[cfg(feature = "subscription")]
@ -33,12 +75,14 @@ pub enum Error {
Io(std::io::Error), Io(std::io::Error),
NoConfigDirectory, NoConfigDirectory,
Notify(notify::Error), Notify(notify::Error),
NotFound,
Ron(ron::Error), Ron(ron::Error),
RonSpanned(ron::error::SpannedError), RonSpanned(ron::error::SpannedError),
GetKey(String, std::io::Error), GetKey(String, std::io::Error),
} }
impl fmt::Display for Error { impl fmt::Display for Error {
#[cold]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::AtomicWrites(err) => err.fmt(f), Self::AtomicWrites(err) => err.fmt(f),
@ -46,6 +90,7 @@ impl fmt::Display for Error {
Self::Io(err) => err.fmt(f), Self::Io(err) => err.fmt(f),
Self::NoConfigDirectory => write!(f, "cosmic config directory not found"), Self::NoConfigDirectory => write!(f, "cosmic config directory not found"),
Self::Notify(err) => err.fmt(f), Self::Notify(err) => err.fmt(f),
Self::NotFound => write!(f, "cosmic config key not configured"),
Self::Ron(err) => err.fmt(f), Self::Ron(err) => err.fmt(f),
Self::RonSpanned(err) => err.fmt(f), Self::RonSpanned(err) => err.fmt(f),
Self::GetKey(key, err) => write!(f, "failed to get key '{}': {}", key, err), Self::GetKey(key, err) => write!(f, "failed to get key '{}': {}", key, err),
@ -55,6 +100,16 @@ impl fmt::Display for Error {
impl std::error::Error for Error {} impl std::error::Error for Error {}
impl Error {
/// Whether the reason for the missing config is caused by an error.
///
/// Useful for determining if it is appropriate to log as an error.
#[inline]
pub fn is_err(&self) -> bool {
!matches!(self, Self::NoConfigDirectory | Self::NotFound)
}
}
impl From<atomicwrites::Error<std::io::Error>> for Error { impl From<atomicwrites::Error<std::io::Error>> for Error {
fn from(f: atomicwrites::Error<std::io::Error>) -> Self { fn from(f: atomicwrites::Error<std::io::Error>) -> Self {
Self::AtomicWrites(f) Self::AtomicWrites(f)
@ -87,7 +142,15 @@ impl From<ron::error::SpannedError> for Error {
pub trait ConfigGet { pub trait ConfigGet {
/// Get a configuration value /// Get a configuration value
///
/// Fallback to the system default if a local user override is not defined.
fn get<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error>; fn get<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error>;
/// Get a locally-defined configuration value from the user's local config.
fn get_local<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error>;
/// Get the system-defined default configuration value.
fn get_system_default<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error>;
} }
pub trait ConfigSet { pub trait ConfigSet {
@ -115,18 +178,11 @@ fn sanitize_name(name: &str) -> Result<&Path, Error> {
} }
impl Config { impl Config {
/// Get the config for the libcosmic toolkit
pub fn libcosmic() -> Result<Self, Error> {
Self::new("com.system76.libcosmic", 1)
}
/// Get a system config for the given name and config version /// Get a system config for the given name and config version
pub fn system(name: &str, version: u64) -> Result<Self, Error> { pub fn system(name: &str, version: u64) -> Result<Self, Error> {
let path = sanitize_name(name)?.join(format!("v{version}")); let path = sanitize_name(name)?.join(format!("v{version}"));
#[cfg(unix)] #[cfg(unix)]
let system_path = xdg::BaseDirectories::with_prefix("cosmic") let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(path);
.map_err(std::io::Error::from)?
.find_data_file(path);
#[cfg(windows)] #[cfg(windows)]
let system_path = let system_path =
@ -148,9 +204,7 @@ impl Config {
// Search data file, which provides default (e.g. /usr/share) // Search data file, which provides default (e.g. /usr/share)
#[cfg(unix)] #[cfg(unix)]
let system_path = xdg::BaseDirectories::with_prefix("cosmic") let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(&path);
.map_err(std::io::Error::from)?
.find_data_file(&path);
#[cfg(windows)] #[cfg(windows)]
let system_path = let system_path =
@ -158,11 +212,10 @@ impl Config {
.map(|x| x.join("COSMIC").join(&path)); .map(|x| x.join("COSMIC").join(&path));
// Get libcosmic user configuration directory // Get libcosmic user configuration directory
let cosmic_user_path = dirs::config_dir() let mut user_path = get_config_dir().ok_or(Error::NoConfigDirectory)?;
.ok_or(Error::NoConfigDirectory)? user_path.push("cosmic");
.join("cosmic"); user_path.push(path);
let user_path = cosmic_user_path.join(path);
// Create new configuration directory if not found. // Create new configuration directory if not found.
fs::create_dir_all(&user_path)?; fs::create_dir_all(&user_path)?;
@ -178,9 +231,9 @@ impl Config {
// Look for [name]/v[version] // Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{version}")); let path = sanitize_name(name)?.join(format!("v{version}"));
let cosmic_user_path = custom_path.join("cosmic"); let mut user_path = custom_path;
user_path.push("cosmic");
let user_path = cosmic_user_path.join(path); user_path.push(path);
// Create new configuration directory if not found. // Create new configuration directory if not found.
fs::create_dir_all(&user_path)?; fs::create_dir_all(&user_path)?;
@ -201,11 +254,9 @@ impl Config {
let path = sanitize_name(name)?.join(format!("v{}", version)); let path = sanitize_name(name)?.join(format!("v{}", version));
// Get libcosmic user state directory // Get libcosmic user state directory
let cosmic_user_path = dirs::state_dir() let mut user_path = get_state_dir().ok_or(Error::NoConfigDirectory)?;
.ok_or(Error::NoConfigDirectory)? user_path.push("cosmic");
.join("cosmic"); user_path.push(path);
let user_path = cosmic_user_path.join(path);
// Create new state directory if not found. // Create new state directory if not found.
fs::create_dir_all(&user_path)?; fs::create_dir_all(&user_path)?;
@ -216,7 +267,8 @@ impl Config {
} }
// Start a transaction (to set multiple configs at the same time) // Start a transaction (to set multiple configs at the same time)
pub fn transaction<'a>(&'a self) -> ConfigTransaction<'a> { #[inline]
pub fn transaction(&self) -> ConfigTransaction<'_> {
ConfigTransaction { ConfigTransaction {
config: self, config: self,
updates: Mutex::new(Vec::new()), updates: Mutex::new(Vec::new()),
@ -227,7 +279,7 @@ impl Config {
// This may end up being an mpsc channel instead of a function // This may end up being an mpsc channel instead of a function
// See EventHandler in the notify crate: https://docs.rs/notify/latest/notify/trait.EventHandler.html // See EventHandler in the notify crate: https://docs.rs/notify/latest/notify/trait.EventHandler.html
// Having a callback allows for any application abstraction to be used // Having a callback allows for any application abstraction to be used
pub fn watch<F>(&self, f: F) -> Result<notify::RecommendedWatcher, Error> pub fn watch<F>(&self, f: F) -> Result<RecommendedWatcher, Error>
// Argument is an array of all keys that changed in that specific transaction // Argument is an array of all keys that changed in that specific transaction
//TODO: simplify F requirements //TODO: simplify F requirements
where where
@ -240,10 +292,12 @@ impl Config {
let user_path_clone = user_path.clone(); let user_path_clone = user_path.clone();
let mut watcher = let mut watcher =
notify::recommended_watcher(move |event_res: Result<notify::Event, notify::Error>| { notify::recommended_watcher(move |event_res: Result<notify::Event, notify::Error>| {
match &event_res { match event_res {
Ok(event) => { Ok(event) => {
match &event.kind { match &event.kind {
EventKind::Access(_) | EventKind::Modify(ModifyKind::Metadata(_)) => { EventKind::Access(_)
| EventKind::Modify(ModifyKind::Metadata(_))
| EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
// Data not mutated // Data not mutated
return; return;
} }
@ -276,7 +330,7 @@ impl Config {
} }
} }
})?; })?;
watcher.watch(user_path, notify::RecursiveMode::NonRecursive)?; watcher.watch(user_path, notify::RecursiveMode::Recursive)?;
Ok(watcher) Ok(watcher)
} }
@ -288,6 +342,7 @@ impl Config {
Ok(system_path.join(sanitize_name(key)?)) Ok(system_path.join(sanitize_name(key)?))
} }
/// Get the path of the key in the user's local config directory.
fn key_path(&self, key: &str) -> Result<PathBuf, Error> { fn key_path(&self, key: &str) -> Result<PathBuf, Error> {
let Some(user_path) = self.user_path.as_ref() else { let Some(user_path) = self.user_path.as_ref() else {
return Err(Error::NoConfigDirectory); return Err(Error::NoConfigDirectory);
@ -300,22 +355,34 @@ impl Config {
impl ConfigGet for Config { impl ConfigGet for Config {
//TODO: check for transaction //TODO: check for transaction
fn get<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error> { fn get<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error> {
match self.get_local(key) {
Ok(value) => Ok(value),
Err(Error::NotFound) => self.get_system_default(key),
Err(why) => Err(why),
}
}
fn get_local<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error> {
// If key path exists // If key path exists
let key_path = self.key_path(key); match self.key_path(key) {
let data = match key_path {
Ok(key_path) if key_path.is_file() => { Ok(key_path) if key_path.is_file() => {
// Load user override // Load user override
fs::read_to_string(key_path).map_err(|err| Error::GetKey(key.to_string(), err))? let data = fs::read_to_string(key_path)
.map_err(|err| Error::GetKey(key.to_string(), err))?;
Ok(ron::from_str(&data)?)
} }
_ => {
// Load system default _ => Err(Error::NotFound),
let default_path = self.default_path(key)?; }
fs::read_to_string(default_path) }
.map_err(|err| Error::GetKey(key.to_string(), err))?
} fn get_system_default<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error> {
}; // Load system default
let t = ron::from_str(&data)?; let default_path = self.default_path(key)?;
Ok(t) let data =
fs::read_to_string(default_path).map_err(|err| Error::GetKey(key.to_string(), err))?;
Ok(ron::from_str(&data)?)
} }
} }
@ -336,7 +403,7 @@ pub struct ConfigTransaction<'a> {
updates: Mutex<Vec<(PathBuf, String)>>, updates: Mutex<Vec<(PathBuf, String)>>,
} }
impl<'a> ConfigTransaction<'a> { impl ConfigTransaction<'_> {
/// Apply all pending changes from ConfigTransaction /// Apply all pending changes from ConfigTransaction
//TODO: apply all changes at once //TODO: apply all changes at once
pub fn commit(self) -> Result<(), Error> { pub fn commit(self) -> Result<(), Error> {
@ -354,7 +421,7 @@ impl<'a> ConfigTransaction<'a> {
// Setting any setting in this way will do one transaction for all settings // Setting any setting in this way will do one transaction for all settings
// when commit finishes that transaction // when commit finishes that transaction
impl<'a> ConfigSet for ConfigTransaction<'a> { impl ConfigSet for ConfigTransaction<'_> {
fn set<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> { fn set<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> {
//TODO: sanitize key (no slashes, cannot be . or ..) //TODO: sanitize key (no slashes, cannot be . or ..)
let key_path = self.config.key_path(key)?; let key_path = self.config.key_path(key)?;
@ -384,6 +451,7 @@ where
) -> (Vec<crate::Error>, Vec<&'static str>); ) -> (Vec<crate::Error>, Vec<&'static str>);
} }
#[derive(Debug)]
pub struct Update<T> { pub struct Update<T> {
pub errors: Vec<crate::Error>, pub errors: Vec<crate::Error>,
pub keys: Vec<&'static str>, pub keys: Vec<&'static str>,

View file

@ -1,5 +1,5 @@
use iced_futures::futures::SinkExt; use iced_futures::futures::{SinkExt, Stream};
use iced_futures::{futures::channel::mpsc, subscription}; use iced_futures::{futures::channel::mpsc, stream};
use notify::RecommendedWatcher; use notify::RecommendedWatcher;
use std::{borrow::Cow, hash::Hash}; use std::{borrow::Cow, hash::Hash};
@ -16,57 +16,68 @@ pub enum ConfigUpdate<T> {
Failed, Failed,
} }
#[cold]
pub fn config_subscription< pub fn config_subscription<
I: 'static + Copy + Send + Sync + Hash, I: 'static + Hash,
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
>( >(
id: I, id: I,
config_id: Cow<'static, str>, config_id: Cow<'static, str>,
config_version: u64, config_version: u64,
) -> iced_futures::Subscription<crate::Update<T>> { ) -> iced_futures::Subscription<crate::Update<T>> {
subscription::channel(id, 100, move |mut output| { iced_futures::Subscription::run_with(
let config_id = config_id.clone(); (id, config_id, config_version, false),
async move { // 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_id = config_id.clone();
let mut state = ConfigState::Init(config_id, config_version, false); let config_version = *config_version;
let is_state = *is_state;
loop { stream::channel(100, move |mut output| async move {
state = start_listening(state, &mut output, id).await; let config_id = config_id.clone();
} let mut state = ConfigState::Init(config_id, config_version, is_state);
}
}) loop {
state = start_listening::<T>(state, &mut output).await;
}
})
},
)
} }
#[cold]
pub fn config_state_subscription< pub fn config_state_subscription<
I: 'static + Copy + Send + Sync + Hash, I: 'static + Hash,
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
>( >(
id: I, id: I,
config_id: Cow<'static, str>, config_id: Cow<'static, str>,
config_version: u64, config_version: u64,
) -> iced_futures::Subscription<crate::Update<T>> { ) -> iced_futures::Subscription<crate::Update<T>> {
subscription::channel(id, 100, move |mut output| { iced_futures::Subscription::run_with(
let config_id = config_id.clone(); (id, config_id, config_version, true),
async move { |(_, config_id, config_version, is_state)| {
let config_id = config_id.clone(); let config_id = config_id.clone();
let mut state = ConfigState::Init(config_id, config_version, true); let config_version = *config_version;
let is_state = *is_state;
loop { stream::channel(100, move |mut output| async move {
state = start_listening(state, &mut output, id).await; let config_id = config_id.clone();
} let mut state = ConfigState::Init(config_id, config_version, is_state);
}
}) loop {
state = start_listening::<T>(state, &mut output).await;
}
})
},
)
} }
async fn start_listening< async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
I: Copy,
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
>(
state: ConfigState<T>, state: ConfigState<T>,
output: &mut mpsc::Sender<crate::Update<T>>, output: &mut mpsc::Sender<crate::Update<T>>,
id: I,
) -> ConfigState<T> { ) -> ConfigState<T> {
use iced_futures::futures::{future::pending, StreamExt}; use iced_futures::futures::{StreamExt, future::pending};
match state { match state {
ConfigState::Init(config_id, version, is_state) => { ConfigState::Init(config_id, version, is_state) => {
@ -97,7 +108,7 @@ async fn start_listening<
} }
Err((errors, t)) => { Err((errors, t)) => {
let update = crate::Update { let update = crate::Update {
errors: errors, errors,
keys: Vec::new(), keys: Vec::new(),
config: t.clone(), config: t.clone(),
}; };

1
cosmic-icons Submodule

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

View file

@ -1,7 +1,7 @@
[package] [package]
name = "cosmic-theme" name = "cosmic-theme"
version = "0.1.0" version = "1.0.0"
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -15,18 +15,25 @@ export = ["serde_json"]
no-default = [] no-default = []
[dependencies] [dependencies]
palette = { version = "0.7.3", features = ["serializing"] } palette = { version = "0.7.6", features = ["serializing"] }
almost = "0.2" almost = "0.2"
serde = { version = "1.0.129", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.64", optional = true, features = [ serde_json = { version = "1.0.149", optional = true, features = [
"preserve_order", "preserve_order",
] } ] }
ron = "0.8" ron = "0.12.0"
lazy_static = "1.4.0" csscolorparser = { version = "0.8.3", features = ["serde"] }
csscolorparser = { version = "0.6.2", 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 = "1.0.5" thiserror = "2.0.18"
[dev-dependencies]
insta = "1.47.2"
[profile.dev.package]
insta.opt-level = 3
similar.opt-level = 3

View file

@ -4,16 +4,10 @@ use palette::Srgba;
pub fn over<A: Into<Srgba>, B: Into<Srgba>>(a: A, b: B) -> Srgba { pub fn over<A: Into<Srgba>, B: Into<Srgba>>(a: A, b: B) -> Srgba {
let a = a.into(); let a = a.into();
let b = b.into(); let b = b.into();
let o_a = (alpha_over(a.alpha, b.alpha)).max(0.0).min(1.0); let o_a = (alpha_over(a.alpha, b.alpha)).clamp(0.0, 1.0);
let o_r = (c_over(a.red, b.red, a.alpha, b.alpha, o_a)) let o_r = (c_over(a.red, b.red, a.alpha, b.alpha, o_a)).clamp(0.0, 1.0);
.max(0.0) let o_g = (c_over(a.green, b.green, a.alpha, b.alpha, o_a)).clamp(0.0, 1.0);
.min(1.0); let o_b = (c_over(a.blue, b.blue, a.alpha, b.alpha, o_a)).clamp(0.0, 1.0);
let o_g = (c_over(a.green, b.green, a.alpha, b.alpha, o_a))
.max(0.0)
.min(1.0);
let o_b = (c_over(a.blue, b.blue, a.alpha, b.alpha, o_a))
.max(0.0)
.min(1.0);
Srgba::new(o_r, o_g, o_b, o_a) Srgba::new(o_r, o_g, o_b, o_a)
} }

View file

@ -19,6 +19,6 @@ pub mod composite;
pub mod steps; pub mod steps;
/// name of cosmic theme /// name of cosmic theme
pub const NAME: &'static str = "com.system76.CosmicTheme"; pub const NAME: &str = "com.system76.CosmicTheme";
pub use palette; pub use palette;

View file

@ -1,15 +1,14 @@
use lazy_static::lazy_static;
use palette::Srgba; use palette::Srgba;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
lazy_static! { /// built-in light palette
/// built in light palette pub static LIGHT_PALETTE: LazyLock<CosmicPalette> =
pub static ref LIGHT_PALETTE: CosmicPalette = LazyLock::new(|| ron::from_str(include_str!("light.ron")).unwrap());
ron::from_str(include_str!("light.ron")).unwrap();
/// built in dark palette /// built-in dark palette
pub static ref DARK_PALETTE: CosmicPalette = pub static DARK_PALETTE: LazyLock<CosmicPalette> =
ron::from_str(include_str!("dark.ron")).unwrap(); LazyLock::new(|| ron::from_str(include_str!("dark.ron")).unwrap());
}
/// Palette type /// Palette type
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
@ -26,6 +25,7 @@ pub enum CosmicPalette {
impl CosmicPalette { impl CosmicPalette {
/// extract the inner palette /// extract the inner palette
#[inline]
pub fn inner(self) -> CosmicPaletteInner { pub fn inner(self) -> CosmicPaletteInner {
match self { match self {
CosmicPalette::Dark(p) => p, CosmicPalette::Dark(p) => p,
@ -37,6 +37,7 @@ impl CosmicPalette {
} }
impl AsMut<CosmicPaletteInner> for CosmicPalette { impl AsMut<CosmicPaletteInner> for CosmicPalette {
#[inline]
fn as_mut(&mut self) -> &mut CosmicPaletteInner { fn as_mut(&mut self) -> &mut CosmicPaletteInner {
match self { match self {
CosmicPalette::Dark(p) => p, CosmicPalette::Dark(p) => p,
@ -48,6 +49,7 @@ impl AsMut<CosmicPaletteInner> for CosmicPalette {
} }
impl AsRef<CosmicPaletteInner> for CosmicPalette { impl AsRef<CosmicPaletteInner> for CosmicPalette {
#[inline]
fn as_ref(&self) -> &CosmicPaletteInner { fn as_ref(&self) -> &CosmicPaletteInner {
match self { match self {
CosmicPalette::Dark(p) => p, CosmicPalette::Dark(p) => p,
@ -60,6 +62,7 @@ impl AsRef<CosmicPaletteInner> for CosmicPalette {
impl CosmicPalette { impl CosmicPalette {
/// check if the palette is dark /// check if the palette is dark
#[inline]
pub fn is_dark(&self) -> bool { pub fn is_dark(&self) -> bool {
match self { match self {
CosmicPalette::Dark(_) | CosmicPalette::HighContrastDark(_) => true, CosmicPalette::Dark(_) | CosmicPalette::HighContrastDark(_) => true,
@ -68,6 +71,7 @@ impl CosmicPalette {
} }
/// check if the palette is high_contrast /// check if the palette is high_contrast
#[inline]
pub fn is_high_contrast(&self) -> bool { pub fn is_high_contrast(&self) -> bool {
match self { match self {
CosmicPalette::HighContrastLight(_) | CosmicPalette::HighContrastDark(_) => true, CosmicPalette::HighContrastLight(_) | CosmicPalette::HighContrastDark(_) => true,
@ -77,6 +81,7 @@ impl CosmicPalette {
} }
impl Default for CosmicPalette { impl Default for CosmicPalette {
#[inline]
fn default() -> Self { fn default() -> Self {
CosmicPalette::Dark(Default::default()) CosmicPalette::Dark(Default::default())
} }
@ -88,23 +93,19 @@ pub struct CosmicPaletteInner {
/// name of the palette /// name of the palette
pub name: String, pub name: String,
/// basic palette /// Utility Colors
/// blue: colors used for various points of emphasis in the UI /// Colors used for various points of emphasis in the UI.
pub blue: Srgba, pub bright_red: Srgba,
/// red: colors used for various points of emphasis in the UI /// Colors used for various points of emphasis in the UI.
pub red: Srgba, pub bright_green: Srgba,
/// green: colors used for various points of emphasis in the UI /// Colors used for various points of emphasis in the UI.
pub green: Srgba, pub bright_orange: Srgba,
/// yellow: colors used for various points of emphasis in the UI
pub yellow: Srgba,
/// surface grays /// Surface Grays
/// colors used for three levels of surfaces in the UI /// Colors used for three levels of surfaces in the UI.
pub gray_1: Srgba, pub gray_1: Srgba,
/// colors used for three levels of surfaces in the UI /// Colors used for three levels of surfaces in the UI.
pub gray_2: Srgba, pub gray_2: Srgba,
/// colors used for three levels of surfaces in the UI
pub gray_3: Srgba,
/// System Neutrals /// System Neutrals
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
@ -130,13 +131,24 @@ pub struct CosmicPaletteInner {
/// A wider spread of dark colors for more general use. /// A wider spread of dark colors for more general use.
pub neutral_10: Srgba, pub neutral_10: Srgba,
// Utility Colors /// Potential Accent Color Combos
/// Utility bright green pub accent_blue: Srgba,
pub bright_green: Srgba, /// Potential Accent Color Combos
/// Utility bright red pub accent_indigo: Srgba,
pub bright_red: Srgba, /// Potential Accent Color Combos
/// Utility bright orange pub accent_purple: Srgba,
pub bright_orange: Srgba, /// Potential Accent Color Combos
pub accent_pink: Srgba,
/// Potential Accent Color Combos
pub accent_red: Srgba,
/// Potential Accent Color Combos
pub accent_orange: Srgba,
/// Potential Accent Color Combos
pub accent_yellow: Srgba,
/// Potential Accent Color Combos
pub accent_green: Srgba,
/// Potential Accent Color Combos
pub accent_warm_grey: Srgba,
/// Extended Color Palette /// Extended Color Palette
/// Colors used for themes, app icons, illustrations, and other brand purposes. /// Colors used for themes, app icons, illustrations, and other brand purposes.
@ -153,29 +165,11 @@ pub struct CosmicPaletteInner {
pub ext_pink: Srgba, pub ext_pink: Srgba,
/// Colors used for themes, app icons, illustrations, and other brand purposes. /// Colors used for themes, app icons, illustrations, and other brand purposes.
pub ext_indigo: Srgba, pub ext_indigo: Srgba,
/// Potential Accent Color Combos
pub accent_blue: Srgba,
/// Potential Accent Color Combos
pub accent_red: Srgba,
/// Potential Accent Color Combos
pub accent_green: Srgba,
/// Potential Accent Color Combos
pub accent_warm_grey: Srgba,
/// Potential Accent Color Combos
pub accent_orange: Srgba,
/// Potential Accent Color Combos
pub accent_yellow: Srgba,
/// Potential Accent Color Combos
pub accent_purple: Srgba,
/// Potential Accent Color Combos
pub accent_pink: Srgba,
/// Potential Accent Color Combos
pub accent_indigo: Srgba,
} }
impl CosmicPalette { impl CosmicPalette {
/// name of the palette /// name of the palette
#[inline]
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
match &self { match &self {
CosmicPalette::Dark(p) => &p.name, CosmicPalette::Dark(p) => &p.name,

View file

@ -1 +1 @@
Dark((name:"cosmic-dark",blue:(red:0.5803922,green:0.92156863,blue:0.92156863,alpha:1.0),red:(red:1.0,green:0.70980394,blue:0.70980394,alpha:1.0),green:(red:0.6745098,green:0.96862745,blue:0.8235294,alpha:1.0),yellow:(red:1.0,green:0.94509804,blue:0.61960787,alpha:1.0),gray_1:(red:0.105882354,green:0.105882354,blue:0.105882354,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),gray_3:(red:0.1882353,green:0.1882353,blue:0.1882353,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.105882354,green:0.105882354,blue:0.105882354,alpha:1.0),neutral_2:(red:0.1882353,green:0.1882353,blue:0.1882353,alpha:1.0),neutral_3:(red:0.2784314,green:0.2784314,blue:0.2784314,alpha:1.0),neutral_4:(red:0.36862746,green:0.36862746,blue:0.36862746,alpha:1.0),neutral_5:(red:0.46666667,green:0.46666667,blue:0.46666667,alpha:1.0),neutral_6:(red:0.5686275,green:0.5686275,blue:0.5686275,alpha:1.0),neutral_7:(red:0.67058825,green:0.67058825,blue:0.67058825,alpha:1.0),neutral_8:(red:0.7764706,green:0.7764706,blue:0.7764706,alpha:1.0),neutral_9:(red:0.8862745,green:0.8862745,blue:0.8862745,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),bright_green:(red:0.36862746,green:0.85882354,blue:0.54901963,alpha:1.0),bright_red:(red:1.0,green:0.627451,blue:0.5647059,alpha:1.0),bright_orange:(red:1.0,green:0.6392157,blue:0.49019608,alpha:1.0),ext_warm_grey:(red:0.60784316,green:0.5568628,blue:0.5411765,alpha:1.0),ext_orange:(red:1.0,green:0.6784314,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882354,blue:0.2509804,alpha:1.0),ext_blue:(red:0.28235295,green:0.7254902,blue:0.78039217,alpha:1.0),ext_purple:(red:0.8117647,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.9764706,green:0.22745098,blue:0.5137255,alpha:1.0),ext_indigo:(red:0.24313726,green:0.53333336,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.8156863,blue:0.8745098,alpha:1.0),accent_red:(red:0.99215686,green:0.6313726,blue:0.627451,alpha:1.0),accent_green:(red:0.57254905,green:0.8117647,blue:0.6117647,alpha:1.0),accent_warm_grey:(red:0.7921569,green:0.7294118,blue:0.7058824,alpha:1.0),accent_orange:(red:1.0,green:0.6784314,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.8784314,blue:0.38431373,alpha:1.0),accent_purple:(red:0.90588236,green:0.6117647,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.6117647,blue:0.69411767,alpha:1.0),accent_indigo:(red:0.6313726,green:0.7529412,blue:0.92156863,alpha:1.0))) Dark((name:"cosmic-dark",bright_red:(red:1.0,green:0.62745098,blue:0.60392157,alpha:1.0),bright_green:(red:0.36862745,green:0.85882352,blue:0.54901960,alpha:1.0),bright_orange:(red:1.0,green:0.63921569,blue:0.49019608,alpha:1.0),gray_1:(red:0.10588235,green:0.10588235,blue:0.10588235,alpha:1.0),gray_2:(red:0.14901961,green:0.14901961,blue:0.14901961,alpha:1.0),neutral_0:(red:0.0,green:0.0,blue:0.0,alpha:1.0),neutral_1:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_2:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_3:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_4:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_7:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_8:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_9:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_10:(red:1.0,green:1.0,blue:1.0,alpha:1.0),accent_blue:(red:0.3882353,green:0.81568627,blue:0.87450981,alpha:1.0),accent_indigo:(red:0.63137255,green:0.75294118,blue:0.92156863,alpha:1.0),accent_purple:(red:0.90588235,green:0.61176471,blue:0.99607843,alpha:1.0),accent_pink:(red:1.0,green:0.61176471,blue:0.69411765,alpha:1.0),accent_red:(red:0.99215686,green:0.63137255,blue:0.62745098,alpha:1.0),accent_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),accent_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),accent_green:(red:0.57254902,green:0.81176471,blue:0.61176471,alpha:1.0),accent_warm_grey:(red:0.79215686,green:0.72941176,blue:0.70588235,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:1.0,green:0.67843137,blue:0.0,alpha:1.0),ext_yellow:(red:0.99607843,green:0.85882353,blue:0.25098039,alpha:1.0),ext_blue:(red:0.28235294,green:0.72549020,blue:0.78039216,alpha:1.0),ext_purple:(red:0.81176471,green:0.49019608,blue:1.0,alpha:1.0),ext_pink:(red:0.97647059,green:0.22745098,blue:0.51372549,alpha:1.0),ext_indigo:(red:0.24313725,green:0.53333333,blue:1.0,alpha:1.0)))

View file

@ -1,4 +1,4 @@
use palette::Srgba; use palette::{Srgba, WithAlpha};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::composite::over; use crate::composite::over;
@ -25,10 +25,9 @@ impl Container {
base: Srgba, base: Srgba,
on: Srgba, on: Srgba,
mut small_widget: Srgba, mut small_widget: Srgba,
is_high_contrast: bool,
) -> Self { ) -> Self {
let mut divider_c = on; let divider_c = on.with_alpha(if is_high_contrast { 0.5 } else { 0.2 });
divider_c.alpha = 0.2;
small_widget.alpha = 0.25; small_widget.alpha = 0.25;
Self { Self {
@ -76,26 +75,31 @@ pub struct Component {
#[allow(clippy::must_use_candidate)] #[allow(clippy::must_use_candidate)]
#[allow(clippy::doc_markdown)] #[allow(clippy::doc_markdown)]
impl Component { impl Component {
#[inline]
/// get @hover_state_color /// get @hover_state_color
pub fn hover_state_color(&self) -> Srgba { pub fn hover_state_color(&self) -> Srgba {
self.hover self.hover
} }
#[inline]
/// get @pressed_state_color /// get @pressed_state_color
pub fn pressed_state_color(&self) -> Srgba { pub fn pressed_state_color(&self) -> Srgba {
self.pressed self.pressed
} }
#[inline]
/// get @selected_state_color /// get @selected_state_color
pub fn selected_state_color(&self) -> Srgba { pub fn selected_state_color(&self) -> Srgba {
self.selected self.selected
} }
#[inline]
/// get @selected_state_text_color /// get @selected_state_text_color
pub fn selected_state_text_color(&self) -> Srgba { pub fn selected_state_text_color(&self) -> Srgba {
self.selected_text self.selected_text
} }
#[inline]
/// get @focus_color /// get @focus_color
pub fn focus_color(&self) -> Srgba { pub fn focus_color(&self) -> Srgba {
self.focus self.focus
@ -109,13 +113,11 @@ impl Component {
hovered: Srgba, hovered: Srgba,
pressed: Srgba, pressed: Srgba,
) -> Self { ) -> Self {
let base: Srgba = base;
let mut base_50 = base; let mut base_50 = base;
base_50.alpha *= 0.5; base_50.alpha *= 0.5;
let on_20 = neutral; let on_20 = neutral;
let mut on_50: Srgba = on_20; let on_50 = on_20.with_alpha(0.5);
on_50.alpha = 0.5;
Component { Component {
base, base,
@ -145,8 +147,7 @@ impl Component {
let mut component = Component::colored_component(base, overlay, accent, hovered, pressed); let mut component = Component::colored_component(base, overlay, accent, hovered, pressed);
component.on = on_button; component.on = on_button;
let mut on_disabled = on_button; let on_disabled = on_button.with_alpha(0.5);
on_disabled.alpha = 0.5;
component.on_disabled = on_disabled; component.on_disabled = on_disabled;
component component
@ -166,11 +167,8 @@ impl Component {
let mut base_50 = base; let mut base_50 = base;
base_50.alpha *= 0.5; base_50.alpha *= 0.5;
let mut on_20 = on_component; let on_20 = on_component.with_alpha(0.2);
let mut on_50 = on_20; let on_65 = on_20.with_alpha(0.65);
on_20.alpha = 0.2;
on_50.alpha = 0.5;
let mut disabled_border = border; let mut disabled_border = border;
disabled_border.alpha *= 0.5; disabled_border.alpha *= 0.5;
@ -194,10 +192,10 @@ impl Component {
}, },
selected_text: accent, selected_text: accent,
focus: accent, focus: accent,
divider: if is_high_contrast { on_50 } else { on_20 }, divider: if is_high_contrast { on_65 } else { on_20 },
on: on_component, on: on_component,
disabled: over(base_50, base), disabled: base_50,
on_disabled: over(on_50, base), on_disabled: on_65,
border, border,
disabled_border, disabled_border,
} }

View file

@ -1,5 +1,4 @@
#[derive(Default)] #[derive(Default)]
pub struct Layout { pub struct Layout {
corner_radii: [u32;4], corner_radii: [u32; 4],
} }

View file

@ -1 +1 @@
Light((name:"cosmic-light",blue:(red:0.0,green:0.28627452,blue:0.42745098,alpha:1.0),red:(red:0.627451,green:0.14509805,blue:0.16862746,alpha:1.0),green:(red:0.23137255,green:0.43137255,blue:0.2627451,alpha:1.0),yellow:(red:0.5882353,green:0.40784314,blue:0.0,alpha:1.0),gray_1:(red:0.8666667,green:0.8666667,blue:0.8666667,alpha:1.0),gray_2:(red:0.9098039,green:0.9098039,blue:0.9098039,alpha:1.0),gray_3:(red:0.9529412,green:0.9529412,blue:0.9529412,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.8862745,green:0.8862745,blue:0.8862745,alpha:1.0),neutral_2:(red:0.7764706,green:0.7764706,blue:0.7764706,alpha:1.0),neutral_3:(red:0.67058825,green:0.67058825,blue:0.67058825,alpha:1.0),neutral_4:(red:0.5686275,green:0.5686275,blue:0.5686275,alpha:1.0),neutral_5:(red:0.46666667,green:0.46666667,blue:0.46666667,alpha:1.0),neutral_6:(red:0.36862746,green:0.36862746,blue:0.36862746,alpha:1.0),neutral_7:(red:0.2784314,green:0.2784314,blue:0.2784314,alpha:1.0),neutral_8:(red:0.1882353,green:0.1882353,blue:0.1882353,alpha:1.0),neutral_9:(red:0.105882354,green:0.105882354,blue:0.105882354,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),bright_green:(red:0.0,green:0.34117648,blue:0.17254902,alpha:1.0),bright_red:(red:0.5372549,green:0.015686275,blue:0.09411765,alpha:1.0),bright_orange:(red:0.4745098,green:0.17254902,blue:0.0,alpha:1.0),ext_warm_grey:(red:0.60784316,green:0.5568628,blue:0.5411765,alpha:1.0),ext_orange:(red:0.9843137,green:0.72156864,blue:0.42352942,alpha:1.0),ext_yellow:(red:0.96862745,green:0.8784314,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568628,green:0.7921569,blue:0.84705883,alpha:1.0),ext_purple:(red:0.8352941,green:0.54901963,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.6117647,blue:0.8666667,alpha:1.0),ext_indigo:(red:0.58431375,green:0.76862746,blue:0.9882353,alpha:1.0),accent_blue:(red:0.0,green:0.32156864,blue:0.3529412,alpha:1.0),accent_red:(red:0.47058824,green:0.16078432,blue:0.18039216,alpha:1.0),accent_green:(red:0.09411765,green:0.33333334,blue:0.16078432,alpha:1.0),accent_warm_grey:(red:0.33333334,green:0.2784314,blue:0.25882354,alpha:1.0),accent_orange:(red:0.38431373,green:0.2509804,blue:0.0,alpha:1.0),accent_yellow:(red:0.3254902,green:0.28235295,blue:0.0,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941177,blue:0.4862745,alpha:1.0),accent_pink:(red:0.5254902,green:0.015686275,blue:0.22745098,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627452,blue:0.42745098,alpha:1.0))) Light((name:"cosmic-light",bright_red:(red:0.53725490,green:0.01568627,blue:0.09411765,alpha:1.0),bright_green:(red:0.0,green:0.34117647,blue:0.17254901,alpha:1.0),bright_orange:(red:0.47450980,green:0.17254902,blue:0.0,alpha:1.0),gray_1:(red:0.84313725,green:0.84313725,blue:0.84313725,alpha:1.0),gray_2:(red:0.89411765,green:0.89411765,blue:0.89411765,alpha:1.0),neutral_0:(red:1.0,green:1.0,blue:1.0,alpha:1.0),neutral_1:(red:0.87058824,green:0.87058824,blue:0.87058824,alpha:1.0),neutral_2:(red:0.74509804,green:0.74509804,blue:0.74509804,alpha:1.0),neutral_3:(red:0.61960784,green:0.61960784,blue:0.61960784,alpha:1.0),neutral_4:(red:0.50196078,green:0.50196078,blue:0.50196078,alpha:1.0),neutral_5:(red:0.38823529,green:0.38823529,blue:0.38823529,alpha:1.0),neutral_6:(red:0.28235294,green:0.28235294,blue:0.28235294,alpha:1.0),neutral_7:(red:0.18039216,green:0.18039216,blue:0.18039216,alpha:1.0),neutral_8:(red:0.08627451,green:0.08627451,blue:0.08627451,alpha:1.0),neutral_9:(red:0.01176471,green:0.01176471,blue:0.01176471,alpha:1.0),neutral_10:(red:0.0,green:0.0,blue:0.0,alpha:1.0),accent_blue:(red:0.0,green:0.32156863,blue:0.35294118,alpha:1.0),accent_indigo:(red:0.18039216,green:0.28627451,blue:0.42745098,alpha:1.0),accent_purple:(red:0.40784314,green:0.12941176,blue:0.48627451,alpha:1.0),accent_pink:(red:0.52549020,green:0.01568627,blue:0.22745098,alpha:1.0),accent_red:(red:0.47058824,green:0.16078431,blue:0.18039216,alpha:1.0),accent_orange:(red:0.38431373,green:0.25098039,blue:0.0,alpha:1.0),accent_yellow:(red:0.32549020,green:0.28235294,blue:0.0,alpha:1.0),accent_green:(red:0.09411765,green:0.33333333,blue:0.16078431,alpha:1.0),accent_warm_grey:(red:0.33333333,green:0.27843137,blue:0.25882353,alpha:1.0),ext_warm_grey:(red:0.60784314,green:0.55686275,blue:0.54117647,alpha:1.0),ext_orange:(red:0.98431373,green:0.72156863,blue:0.42352941,alpha:1.0),ext_yellow:(red:0.96862745,green:0.87843137,blue:0.38431373,alpha:1.0),ext_blue:(red:0.41568627,green:0.79215686,blue:0.84705882,alpha:1.0),ext_purple:(red:0.83529412,green:0.54901961,blue:1.0,alpha:1.0),ext_pink:(red:1.0,green:0.61176471,blue:0.86666667,alpha:1.0),ext_indigo:(red:0.58431373,green:0.76862745,blue:0.98823529,alpha:1.0)))

View file

@ -16,6 +16,7 @@ pub struct ThemeMode {
} }
impl Default for ThemeMode { impl Default for ThemeMode {
#[inline]
fn default() -> Self { fn default() -> Self {
Self { Self {
is_dark: true, is_dark: true,
@ -25,15 +26,19 @@ impl Default for ThemeMode {
} }
impl ThemeMode { impl ThemeMode {
#[inline]
/// Check if the theme is currently using dark mode /// Check if the theme is currently using dark mode
pub fn is_dark(config: &Config) -> Result<bool, cosmic_config::Error> { pub fn is_dark(config: &Config) -> Result<bool, cosmic_config::Error> {
config.get::<bool>("is_dark") config.get::<bool>("is_dark")
} }
#[inline]
/// The current version of the theme mode config.
pub const fn version() -> u64 { pub const fn version() -> u64 {
Self::VERSION Self::VERSION
} }
#[inline]
/// Get the config for the theme mode /// Get the config for the theme mode
pub fn config() -> Result<Config, cosmic_config::Error> { pub fn config() -> Result<Config, cosmic_config::Error> {
Config::new(THEME_MODE_ID, Self::VERSION) Config::new(THEME_MODE_ID, Self::VERSION)

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,17 @@
use crate::{composite::over, steps::steps, Component, Theme}; use crate::{Component, Theme, composite::over, steps::steps};
use palette::{rgb::Rgba, Darken, IntoColor, Lighten, Srgba}; use palette::{Darken, IntoColor, Lighten, Srgba, WithAlpha, rgb::Rgba};
use std::{ use std::{
fs::{self, File}, fs::{self, File},
io::Write, io::{self, Write},
num::NonZeroUsize, num::NonZeroUsize,
path::Path,
}; };
use super::{to_rgba, OutputError}; use super::{OutputError, to_rgba};
impl Theme { impl Theme {
#[must_use] #[must_use]
#[cold]
/// turn the theme into css /// turn the theme into css
pub fn as_gtk4(&self) -> String { pub fn as_gtk4(&self) -> String {
let Self { let Self {
@ -73,11 +75,10 @@ impl Theme {
Rgba::new(0.0, 0.0, 0.0, 0.08) Rgba::new(0.0, 0.0, 0.0, 0.08)
}); });
let mut inverted_bg_divider = background.base; let inverted_bg_divider = background.base.with_alpha(0.5);
inverted_bg_divider.alpha = 0.5;
let scrollbar_outline = to_rgba(inverted_bg_divider); let scrollbar_outline = to_rgba(inverted_bg_divider);
let mut css = format! {r#" let mut css = format! {r#"/* GENERATED BY COSMIC */
@define-color window_bg_color {window_bg}; @define-color window_bg_color {window_bg};
@define-color window_fg_color {window_fg}; @define-color window_fg_color {window_fg};
@ -122,10 +123,10 @@ impl Theme {
css.push_str(&component_gtk4_css("accent", accent)); css.push_str(&component_gtk4_css("accent", accent));
css.push_str(&component_gtk4_css("error", destructive)); css.push_str(&component_gtk4_css("error", destructive));
css.push_str(&color_css("blue", palette.blue)); css.push_str(&color_css("blue", palette.accent_blue));
css.push_str(&color_css("green", palette.green)); css.push_str(&color_css("green", palette.accent_green));
css.push_str(&color_css("yellow", palette.yellow)); css.push_str(&color_css("yellow", palette.accent_yellow));
css.push_str(&color_css("red", palette.red)); css.push_str(&color_css("red", palette.accent_red));
css.push_str(&color_css("orange", palette.ext_orange)); css.push_str(&color_css("orange", palette.ext_orange));
css.push_str(&color_css("purple", palette.ext_purple)); css.push_str(&color_css("purple", palette.ext_purple));
let neutral_steps = steps(palette.neutral_5, NonZeroUsize::new(10).unwrap()); let neutral_steps = steps(palette.neutral_5, NonZeroUsize::new(10).unwrap());
@ -144,9 +145,10 @@ impl Theme {
/// # Errors /// # Errors
/// ///
/// Returns an `OutputError` if there is an error writing the CSS file. /// Returns an `OutputError` if there is an error writing the CSS file.
#[cold]
pub fn write_gtk4(&self) -> Result<(), OutputError> { pub fn write_gtk4(&self) -> Result<(), OutputError> {
let css_str = self.as_gtk4(); let css_str = self.as_gtk4();
let Some(config_dir) = dirs::config_dir() else { let Some(mut config_dir) = dirs::config_dir() else {
return Err(OutputError::MissingConfigDir); return Err(OutputError::MissingConfigDir);
}; };
@ -156,55 +158,58 @@ impl Theme {
"light.css" "light.css"
}; };
let config_dir = config_dir.join("gtk-4.0").join("cosmic"); config_dir.extend(["gtk-4.0", "cosmic"]);
if !config_dir.exists() { if !config_dir.exists() {
std::fs::create_dir_all(&config_dir).map_err(OutputError::Io)?; std::fs::create_dir_all(&config_dir).map_err(OutputError::Io)?;
} }
let mut file = File::create(config_dir.join(name)).map_err(OutputError::Io)?; let file_path = config_dir.join(name);
file.write_all(css_str.as_bytes()) let tmp_file_path = config_dir.join(name.to_owned() + "~");
.map_err(OutputError::Io)?;
// Write to tmp_file_path first, then move it to file_path
let mut tmp_file = File::create(&tmp_file_path).map_err(OutputError::Io)?;
let res = tmp_file
.write_all(css_str.as_bytes())
.and_then(|_| tmp_file.flush())
.and_then(|_| std::fs::rename(&tmp_file_path, file_path));
if let Err(e) = res {
_ = std::fs::remove_file(&tmp_file_path);
return Err(OutputError::Io(e));
}
Ok(()) Ok(())
} }
/// Apply gtk color variable settings /// Apply gtk color variable settings
///
/// # Errors
///
/// Returns an `OutputError` if there is an error applying the CSS file.
#[cold]
pub fn apply_gtk(is_dark: bool) -> Result<(), OutputError> { pub fn apply_gtk(is_dark: bool) -> Result<(), OutputError> {
let Some(config_dir) = dirs::config_dir() else { let Some(config_dir) = dirs::config_dir() else {
return Err(OutputError::MissingConfigDir); return Err(OutputError::MissingConfigDir);
}; };
let gtk4 = config_dir.join("gtk-4.0"); let mut gtk4 = config_dir.join("gtk-4.0");
let gtk3 = config_dir.join("gtk-3.0"); let mut gtk3 = config_dir.join("gtk-3.0");
fs::create_dir_all(&gtk4).map_err(OutputError::Io)?; fs::create_dir_all(&gtk4).map_err(OutputError::Io)?;
fs::create_dir_all(&gtk3).map_err(OutputError::Io)?; fs::create_dir_all(&gtk3).map_err(OutputError::Io)?;
let cosmic_css = gtk4 let cosmic_css_dir = gtk4.join("cosmic");
.join("cosmic") let cosmic_css = cosmic_css_dir.join(if is_dark { "dark.css" } else { "light.css" });
.join(if is_dark { "dark.css" } else { "light.css" });
let gtk4_dest = gtk4.join("gtk.css"); gtk4.push("gtk.css");
let gtk3_dest = gtk3.join("gtk.css"); gtk3.push("gtk.css");
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
for gtk_dest in [&gtk4_dest, &gtk3_dest] { for gtk_dest in [&gtk4, &gtk3] {
use std::fs::metadata;
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
Self::backup_non_cosmic_css(gtk_dest, &cosmic_css_dir).map_err(OutputError::Io)?;
let mut gtk_dest_bak = gtk_dest.clone();
gtk_dest_bak.set_extension("css.bak");
if gtk_dest.exists() { if gtk_dest.exists() {
if metadata(&gtk_dest) fs::remove_file(gtk_dest).map_err(OutputError::Io)?;
.map_err(OutputError::Io)?
.file_type()
.is_symlink()
{
fs::remove_file(&gtk_dest).map_err(OutputError::Io)?;
} else {
fs::rename(&gtk_dest, gtk_dest_bak).map_err(OutputError::Io)?;
}
} }
symlink(&cosmic_css, gtk_dest).map_err(OutputError::Io)?; symlink(&cosmic_css, gtk_dest).map_err(OutputError::Io)?;
@ -213,6 +218,11 @@ impl Theme {
} }
/// Reset the applied gtk css /// Reset the applied gtk css
///
/// # Errors
///
/// Returns an `OutputError` if there is an error resetting the CSS file.
#[cold]
pub fn reset_gtk() -> Result<(), OutputError> { pub fn reset_gtk() -> Result<(), OutputError> {
let Some(config_dir) = dirs::config_dir() else { let Some(config_dir) = dirs::config_dir() else {
return Err(OutputError::MissingConfigDir); return Err(OutputError::MissingConfigDir);
@ -221,11 +231,49 @@ impl Theme {
let gtk4 = config_dir.join("gtk-4.0"); let gtk4 = config_dir.join("gtk-4.0");
let gtk3 = config_dir.join("gtk-3.0"); let gtk3 = config_dir.join("gtk-3.0");
let gtk4_dest = gtk4.join("gtk.css"); let gtk4_dest = gtk4.join("gtk.css");
let cosmic_css = gtk4.join("cosmic");
let gtk3_dest = gtk3.join("gtk.css"); let gtk3_dest = gtk3.join("gtk.css");
let res = fs::remove_file(gtk3_dest); let res = Self::reset_cosmic_css(&gtk3_dest, &cosmic_css).map_err(OutputError::Io);
fs::remove_file(gtk4_dest).map_err(OutputError::Io)?; Self::reset_cosmic_css(&gtk4_dest, &cosmic_css).map_err(OutputError::Io)?;
Ok(res.map_err(OutputError::Io)?) res
}
#[cold]
fn backup_non_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result<()> {
if !Self::is_cosmic_css(path, cosmic_css)?.unwrap_or(true) {
let backup_path = path.with_extension("css.bak");
fs::rename(path, &backup_path)?;
}
Ok(())
}
#[cold]
fn reset_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result<()> {
if Self::is_cosmic_css(path, cosmic_css)?.unwrap_or_default() {
fs::remove_file(path)?;
}
Ok(())
}
fn is_cosmic_css(path: &Path, cosmic_css: &Path) -> io::Result<Option<bool>> {
if !path.exists() {
return Ok(None);
}
if let Ok(metadata) = fs::symlink_metadata(path) {
if metadata.file_type().is_symlink() {
if let Ok(actual_cosmic_css) = fs::read_link(path) {
let canonical_target = fs::canonicalize(&actual_cosmic_css)?;
let canonical_base = fs::canonicalize(cosmic_css)?;
return Ok(Some(
canonical_target == canonical_base
|| canonical_target.starts_with(&canonical_base),
));
}
}
}
Ok(Some(false))
} }
} }

View file

@ -1,4 +1,5 @@
use palette::{rgb::Rgba, Srgba}; use configparser::ini::WriteOptions;
use palette::{Srgba, rgb::Rgba};
use thiserror::Error; use thiserror::Error;
use crate::Theme; use crate::Theme;
@ -6,6 +7,11 @@ use crate::Theme;
/// Module for outputting the Cosmic gtk4 theme type as CSS /// Module for outputting the Cosmic gtk4 theme type as CSS
pub mod gtk4_output; pub mod gtk4_output;
/// Module for configuring qt5ct and qt6ct to use our qt theme
pub mod qt56ct_output;
/// Module for outputting the Cosmic qt theme type as kdeglobals
pub mod qt_output;
pub mod vs_code; pub mod vs_code;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -14,30 +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]
/// 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]
/// 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]
/// 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(())
} }
} }
@ -57,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

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::Theme; use crate::Theme;
use super::{to_hex, OutputError}; use super::{OutputError, to_hex};
/// Represents the workbench.colorCustomizations section of a VS Code settings.json file /// Represents the workbench.colorCustomizations section of a VS Code settings.json file
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -266,10 +266,12 @@ impl From<Theme> for VsTheme {
} }
impl Theme { impl Theme {
#[cold]
pub fn apply_vs_code(self) -> Result<(), OutputError> { pub fn apply_vs_code(self) -> Result<(), OutputError> {
let vs_theme = VsTheme::from(self); let vs_theme = VsTheme::from(self);
let config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
let vs_code_dir = config_dir.join("Code").join("User"); config_dir.extend(["Code", "User"]);
let vs_code_dir = config_dir;
if !vs_code_dir.exists() { if !vs_code_dir.exists() {
std::fs::create_dir_all(&vs_code_dir).map_err(OutputError::Io)?; std::fs::create_dir_all(&vs_code_dir).map_err(OutputError::Io)?;
} }
@ -289,10 +291,11 @@ impl Theme {
Ok(()) Ok(())
} }
#[cold]
pub fn reset_vs_code() -> Result<(), OutputError> { pub fn reset_vs_code() -> Result<(), OutputError> {
let config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?; let mut config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
let vs_code_dir = config_dir.join("Code").join("User"); config_dir.extend(["Code", "User", "settings.json"]);
let settings_file = vs_code_dir.join("settings.json"); let settings_file = config_dir;
// just remove the json entry for workbench.colorCustomizations // just remove the json entry for workbench.colorCustomizations
let settings = std::fs::read_to_string(&settings_file).unwrap_or_default(); let settings = std::fs::read_to_string(&settings_file).unwrap_or_default();
let mut settings: serde_json::Value = serde_json::from_str(&settings).unwrap_or_default(); let mut settings: serde_json::Value = serde_json::from_str(&settings).unwrap_or_default();

View file

@ -1,7 +1,7 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use almost::equal; use almost::equal;
use palette::{convert::FromColorUnclamped, ClampAssign, FromColor, Oklcha, Srgb, Srgba}; use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba, convert::FromColorUnclamped};
/// Get an array of 100 colors with a specific hue and chroma /// Get an array of 100 colors with a specific hue and chroma
/// over the full range of lightness. /// over the full range of lightness.
@ -35,7 +35,7 @@ pub fn get_index(base_index: usize, steps: usize, step_len: usize, is_dark: bool
pub fn get_surface_color( pub fn get_surface_color(
base_index: usize, base_index: usize,
steps: usize, steps: usize,
step_array: &Vec<Srgba>, step_array: &[Srgba],
mut is_dark: bool, mut is_dark: bool,
fallback: &Srgba, fallback: &Srgba,
) -> Srgba { ) -> Srgba {
@ -48,12 +48,38 @@ pub fn get_surface_color(
.unwrap_or(fallback) .unwrap_or(fallback)
} }
/// get surface color given a base and some steps
#[must_use]
pub fn get_small_widget_color(
base_index: usize,
steps: usize,
step_array: &[Srgba],
fallback: &Srgba,
) -> Srgba {
assert!(step_array.len() == 100);
let is_dark = base_index <= 40 || (base_index > 51 && base_index < 65);
let res = *get_index(base_index, steps, step_array.len(), is_dark)
.and_then(|i| step_array.get(i))
.unwrap_or(fallback);
let mut lch = Lch::from_color(res);
if lch.chroma / Lch::<f32>::max_chroma() > 0.03 {
lch.chroma = 0.03 * Lch::<f32>::max_chroma();
lch.clamp_assign();
Srgba::from_color(lch)
} else {
res
}
}
/// get text color given a base background color /// get text color given a base background color
pub fn get_text( pub fn get_text(
base_index: usize, base_index: usize,
step_array: &Vec<Srgba>, step_array: &[Srgba],
fallback: &Srgba, fallback: &Srgba,
tint_array: Option<&Vec<Srgba>>, tint_array: Option<&[Srgba]>,
) -> Srgba { ) -> Srgba {
assert!(step_array.len() == 100); assert!(step_array.len() == 100);
let step_array = if let Some(tint_array) = tint_array { let step_array = if let Some(tint_array) = tint_array {
@ -67,7 +93,7 @@ pub fn get_text(
let index = get_index(base_index, 70, step_array.len(), is_dark) let index = get_index(base_index, 70, step_array.len(), is_dark)
.or_else(|| get_index(base_index, 50, step_array.len(), is_dark)) .or_else(|| get_index(base_index, 50, step_array.len(), is_dark))
.unwrap_or_else(|| if is_dark { 99 } else { 0 }); .unwrap_or(if is_dark { 99 } else { 0 });
*step_array.get(index).unwrap_or(fallback) *step_array.get(index).unwrap_or(fallback)
} }
@ -119,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};
@ -147,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);
} }
} }

22
examples/about/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "about"
version = "0.1.0"
edition = "2021"
[dependencies]
open = "5.3.3"
[dependencies.libcosmic]
path = "../../"
features = [
"debug",
"winit",
"tokio",
"xdg-portal",
"desktop",
"a11y",
"wayland",
"wgpu",
"single-instance",
"about",
]

148
examples/about/src/main.rs Normal file
View file

@ -0,0 +1,148 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Application API example
use cosmic::app::context_drawer::{self, ContextDrawer};
use cosmic::app::{Core, Settings, Task};
use cosmic::executor;
use cosmic::iced::{alignment, Length, Size};
use cosmic::prelude::*;
use cosmic::widget::{self, about::About, nav_bar};
/// Runs application with these settings
#[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let settings = Settings::default()
.size(Size::new(1024., 768.));
cosmic::app::run::<App>(settings, ())?;
Ok(())
}
/// Messages that are used specifically by our [`App`].
#[derive(Clone, Debug)]
pub enum Message {
ToggleAbout,
Open(String),
}
/// The [`App`] stores application-specific state.
pub struct App {
core: Core,
nav_model: nav_bar::Model,
about: About,
show_about: bool,
}
/// 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.AboutDemo";
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, _flags: Self::Flags) -> (Self, Task<Self::Message>) {
let nav_model = nav_bar::Model::default();
let about = About::default()
.name("About Demo")
.icon(widget::icon::from_name(Self::APP_ID))
.version("0.1.0")
.author("System76")
.license("GPL-3.0-only")
.license_url("https://choosealicense.com/licenses/gpl-3.0/")
.developers([("Michael Murphy", "info@system76.com")])
.links([
("Website", "https://system76.com/cosmic"),
("Repository", "https://github.com/pop-os/libcosmic"),
("Support", "https://github.com/pop-os/libcosmic/issues"),
]);
let mut app = App {
core,
nav_model,
about,
show_about: false,
};
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)
}
/// Allows COSMIC to integrate with your application's [`nav_bar::Model`].
fn nav_model(&self) -> Option<&nav_bar::Model> {
Some(&self.nav_model)
}
/// Called when a navigation item is selected.
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
self.nav_model.activate(id);
Task::none()
}
fn context_drawer(&self) -> Option<ContextDrawer<'_, Self::Message>> {
self.show_about.then(|| {
context_drawer::about(
&self.about,
|url| Message::Open(url.to_owned()),
Message::ToggleAbout,
)
})
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message {
Message::ToggleAbout => {
self.set_show_context(!self.core.window.show_context);
self.show_about = !self.show_about;
}
Message::Open(url) => match open::that_detached(url) {
Ok(_) => (),
Err(err) => eprintln!("Failed to open URL: {err}"),
},
}
Task::none()
}
/// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> {
let show_about_button = widget::button::text("Show about").on_press(Message::ToggleAbout);
let centered = cosmic::widget::container(
widget::column::with_capacity(1)
.push(show_about_button)
.width(Length::Fill)
.height(Length::Shrink)
.align_x(alignment::Horizontal::Center),
)
.width(Length::Fill)
.height(Length::Shrink)
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Center);
Element::from(centered)
}
}

View file

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

View file

@ -3,5 +3,10 @@ use crate::window::Window;
mod window; mod window;
fn main() -> cosmic::iced::Result { fn main() -> cosmic::iced::Result {
cosmic::applet::run::<Window>(true, ()) let env = env_logger::Env::default()
.filter_or("MY_LOG_LEVEL", "warn")
.write_style_or("MY_LOG_STYLE", "always");
env_logger::init_from_env(env);
cosmic::applet::run::<Window>(())
} }

View file

@ -1,26 +1,41 @@
use cosmic::app::Core; use cosmic::app::{Core, Task};
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
use cosmic::iced::core::window;
use cosmic::iced::window::Id; use cosmic::iced::window::Id;
use cosmic::iced::{Command, Limits}; use cosmic::iced::{Length, Rectangle};
use cosmic::iced_runtime::core::window; use cosmic::surface::action::{app_popup, destroy_popup};
use cosmic::iced_style::application; use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler};
use cosmic::widget::{list_column, settings, toggler}; use cosmic::Element;
use cosmic::{Element, Theme};
const ID: &str = "com.system76.CosmicAppletExample"; const ID: &str = "com.system76.CosmicAppletExample";
#[derive(Default)]
pub struct Window { pub struct Window {
core: Core, core: Core,
popup: Option<Id>, popup: Option<Id>,
example_row: bool, example_row: bool,
toggle: bool,
selected: Option<usize>,
}
impl Default for Window {
fn default() -> Self {
Self {
core: Core::default(),
popup: None,
example_row: false,
toggle: false,
selected: None,
}
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
TogglePopup,
PopupClosed(Id), PopupClosed(Id),
ToggleExampleRow(bool), ToggleExampleRow(bool),
Selected(usize),
Surface(cosmic::surface::Action),
Toggle(bool),
} }
impl cosmic::Application for Window { impl cosmic::Application for Window {
@ -37,71 +52,114 @@ impl cosmic::Application for Window {
&mut self.core &mut self.core
} }
fn init( fn init(core: Core, _flags: Self::Flags) -> (Self, Task<Message>) {
core: Core,
_flags: Self::Flags,
) -> (Self, Command<cosmic::app::Message<Self::Message>>) {
let window = Window { let window = Window {
core, core,
..Default::default() ..Default::default()
}; };
(window, Command::none()) (window, Task::none())
} }
fn on_close_requested(&self, id: window::Id) -> Option<Message> { fn on_close_requested(&self, id: window::Id) -> Option<Message> {
Some(Message::PopupClosed(id)) Some(Message::PopupClosed(id))
} }
fn update(&mut self, message: Self::Message) -> Command<cosmic::app::Message<Self::Message>> { fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::TogglePopup => {
return if let Some(p) = self.popup.take() {
destroy_popup(p)
} else {
let new_id = Id::unique();
self.popup.replace(new_id);
let mut popup_settings =
self.core
.applet
.get_popup_settings(Id::MAIN, new_id, None, None, None);
popup_settings.positioner.size_limits = Limits::NONE
.max_width(372.0)
.min_width(300.0)
.min_height(200.0)
.max_height(1080.0);
get_popup(popup_settings)
}
}
Message::PopupClosed(id) => { Message::PopupClosed(id) => {
if self.popup.as_ref() == Some(&id) { if self.popup.as_ref() == Some(&id) {
self.popup = None; self.popup = None;
} }
} }
Message::ToggleExampleRow(toggled) => self.example_row = toggled, Message::ToggleExampleRow(toggled) => {
} self.example_row = toggled;
Command::none() }
Message::Surface(a) => {
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(a),
));
}
Message::Selected(i) => {
self.selected = Some(i);
}
Message::Toggle(v) => {
self.toggle = v;
}
};
Task::none()
} }
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<Message> {
self.core let have_popup = self.popup.clone();
let btn = self
.core
.applet .applet
.icon_button("display-symbolic") .icon_button("display-symbolic")
.on_press(Message::TogglePopup) .on_press_with_rectangle(move |offset, bounds| {
.into() if let Some(id) = have_popup {
Message::Surface(destroy_popup(id))
} else {
Message::Surface(app_popup::<Window>(
move |state: &mut Window| {
let new_id = Id::unique();
state.popup = Some(new_id);
let mut popup_settings = state.core.applet.get_popup_settings(
state.core.main_window_id().unwrap(),
new_id,
None,
None,
None,
);
popup_settings.positioner.anchor_rect = Rectangle {
x: (bounds.x - offset.x) as i32,
y: (bounds.y - offset.y) as i32,
width: bounds.width as i32,
height: bounds.height as i32,
};
popup_settings
},
Some(Box::new(move |state: &Window| {
let content_list = list_column()
.padding(5)
.spacing(0)
.add(settings::item(
"Example row",
cosmic::widget::container(
toggler(state.example_row)
.on_toggle(Message::ToggleExampleRow),
),
))
.add(popup_dropdown(
&["1", "asdf", "hello", "test"],
state.selected,
Message::Selected,
state.popup.unwrap_or(Id::NONE),
Message::Surface,
|m| m,
));
Element::from(state.core.applet.popup_container(content_list))
.map(cosmic::Action::App)
})),
))
}
});
Element::from(self.core.applet.applet_tooltip::<Message>(
btn,
"test",
self.popup.is_some(),
|a| Message::Surface(a),
None,
))
} }
fn view_window(&self, _id: Id) -> Element<Self::Message> { fn view_window(&self, _id: Id) -> Element<Message> {
let content_list = list_column().padding(5).spacing(0).add(settings::item( "oops".into()
"Example row",
toggler(None, self.example_row, |value| {
Message::ToggleExampleRow(value)
}),
));
self.core.applet.popup_container(content_list).into()
} }
fn style(&self) -> Option<<Theme as application::StyleSheet>::Style> { fn style(&self) -> Option<cosmic::iced::theme::Style> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -3,12 +3,23 @@ name = "application"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[features]
default = ["wayland"]
wayland = ["libcosmic/wayland"]
[dependencies] [dependencies]
tracing = "0.1.37" env_logger = "0.11"
tracing-subscriber = "0.3.17"
tracing-log = "0.2.0"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false features = [
features = ["debug", "winit", "tokio", "xdg-portal", "dbus-config", "a11y"] "debug",
"winit",
"tokio",
"xdg-portal",
"a11y",
"single-instance",
"surface-message",
"multi-window",
"wgpu",
]

View file

@ -3,10 +3,15 @@
//! Application API example //! Application API example
use cosmic::app::{Command, Core, Settings}; use cosmic::app::Settings;
use cosmic::iced_core::Size; use cosmic::iced::{Alignment, Length, Size};
use cosmic::widget::menu::{self, KeyBind};
use cosmic::widget::nav_bar; use cosmic::widget::nav_bar;
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, prelude::*, widget, Core};
use std::collections::HashMap;
use std::sync::LazyLock;
static MENU_ID: LazyLock<iced::id::Id> = LazyLock::new(|| iced::id::Id::new("menu_id"));
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Page { pub enum Page {
@ -27,11 +32,31 @@ impl Page {
} }
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Action {
Hi,
Hi2,
Hi3,
}
impl widget::menu::Action for Action {
type Message = Message;
fn message(&self) -> Message {
match self {
Action::Hi => Message::Hi,
Action::Hi2 => Message::Hi2,
Action::Hi3 => Message::Hi3,
}
}
}
/// Runs application with these settings /// Runs application with these settings
#[rustfmt::skip] #[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let _ = tracing_log::LogTracer::init(); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
let input = vec![ let input = vec![
(Page::Page1, "🖖 Hello from libcosmic.".into()), (Page::Page1, "🖖 Hello from libcosmic.".into()),
@ -42,20 +67,33 @@ 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(())
} }
/// 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 {
Input1(String),
Input2(String),
Ignore,
ToggleHide,
Surface(cosmic::surface::Action),
Hi,
Hi2,
Hi3,
Tick,
}
/// The [`App`] stores application-specific state. /// The [`App`] stores application-specific state.
pub struct App { pub struct App {
core: Core, core: Core,
nav_model: nav_bar::Model, nav_model: nav_bar::Model,
input_1: String,
input_2: String,
hidden: bool,
keybinds: HashMap<KeyBind, Action>,
progress: f32,
} }
/// Implement [`cosmic::Application`] to integrate with COSMIC. /// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -80,8 +118,8 @@ impl cosmic::Application for App {
&mut self.core &mut self.core
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits task on initialize.
fn init(core: Core, input: Self::Flags) -> (Self, Command<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 {
@ -90,7 +128,15 @@ impl cosmic::Application for App {
nav_model.activate_position(0); nav_model.activate_position(0);
let mut app = App { core, nav_model }; let mut app = App {
core,
nav_model,
input_1: String::new(),
input_2: String::new(),
hidden: true,
keybinds: HashMap::new(),
progress: 0.0,
};
let command = app.update_title(); let command = app.update_title();
@ -103,33 +149,209 @@ 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) -> Command<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) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> cosmic::app::Task<Self::Message> {
Command::none() match message {
Message::Input1(v) => {
self.input_1 = v;
}
Message::Input2(v) => {
self.input_2 = v;
}
Message::Ignore => {}
Message::ToggleHide => {
self.hidden = !self.hidden;
}
Message::Surface(a) => {
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(a),
));
}
Message::Hi => {
dbg!("hi");
}
Message::Hi2 => {
dbg!("hi 2");
}
Message::Hi3 => {
dbg!("hi 3");
}
Message::Tick => {
self.progress = (self.progress + 0.01) % 1.0;
}
}
Task::none()
}
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(text) .push(widget::text::body(page_content))
.width(iced::Length::Fill) .push(
.height(iced::Length::Shrink) widget::text_input::text_input("", &self.input_1)
.align_x(iced::alignment::Horizontal::Center) .on_input(Message::Input1)
.align_y(iced::alignment::Vertical::Center); .on_clear(Message::Ignore),
)
.push(
widget::text_input::secure_input(
"",
&self.input_1,
Some(Message::ToggleHide),
self.hidden,
)
.on_input(Message::Input1),
)
.push(widget::text_input::text_input("", &self.input_2).on_input(Message::Input2))
.push(
widget::text_input::search_input("", &self.input_2)
.on_input(Message::Input2)
.on_clear(Message::Ignore),
)
.push(widget::progress_bar::circular::Circular::new().size(50.0))
.push(widget::progress_bar::circular::Circular::new().size(20.0))
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()
.bar_height(10.0)
.size(50.0)
.progress(self.progress),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(self.progress)
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()
.size(50.0)
.progress(0.0),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(0.0)
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()
.size(50.0)
.progress(1.0),
)
.push(
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(1.0)
.width(Length::Fill),
)
.spacing(cosmic::theme::spacing().space_s)
.width(Length::Fill)
.height(Length::Shrink)
.align_x(Alignment::Center),
)
.width(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>> {
vec![cosmic::widget::responsive_menu_bar().into_element(
self.core(),
&self.keybinds,
MENU_ID.clone(),
Message::Surface,
vec![
(
"hi 1".into(),
vec![
menu::Item::Button("hi 12", None, Action::Hi),
menu::Item::Button("hi 13", None, Action::Hi2),
],
),
(
"hi 2".into(),
vec![
menu::Item::Button("hi 21", None, Action::Hi),
menu::Item::Button("hi 22", None, Action::Hi2),
menu::Item::Folder(
"nest 3 2 >".into(),
vec![
menu::Item::Button("21", None, Action::Hi),
menu::Item::Button("242", None, Action::Hi2),
menu::Item::Button("2443", None, Action::Hi3),
menu::Item::Folder(
"nest 4 2 >".into(),
vec![
menu::Item::Button("243", None, Action::Hi2),
menu::Item::Button("2444", None, Action::Hi),
],
),
],
),
],
),
(
"hi 3".into(),
vec![
menu::Item::Button("hi 31", None, Action::Hi),
menu::Item::Button("hi 332", None, Action::Hi2),
menu::Item::Button("hi 3333", None, Action::Hi3),
menu::Item::Button("hi 33334", None, Action::Hi3),
menu::Item::Button("hi 333335", None, Action::Hi3),
menu::Item::Button("hi 3333336", None, Action::Hi3),
],
),
(
"hiiiiiiiiiiiiiiiiiii 4".into(),
vec![
menu::Item::Button("hi 4", None, Action::Hi),
menu::Item::Button("hi 44", None, Action::Hi2),
menu::Item::Button("hi 444", None, Action::Hi3),
menu::Item::Folder(
"nest 4 >".into(),
vec![
menu::Item::Button("hi 41", None, Action::Hi),
menu::Item::Button("hi 442", None, Action::Hi2),
menu::Item::Folder(
"nest 3 4 >".into(),
vec![
menu::Item::Button("hi 443", None, Action::Hi2),
menu::Item::Button("hi 4444", None, Action::Hi),
menu::Item::Button("hi 44444", None, Action::Hi3),
menu::Item::Button("hi 444445", None, Action::Hi3),
menu::Item::Button("hi 4444446", None, Action::Hi3),
menu::Item::Button("hi 44444447", None, Action::Hi3),
],
),
],
),
],
),
],
)]
}
} }
impl App impl App
@ -142,10 +364,14 @@ where
.unwrap_or("Unknown Page") .unwrap_or("Unknown Page")
} }
fn update_title(&mut self) -> Command<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);
self.set_window_title(window_title) if let Some(id) = self.core.main_window_id() {
self.set_window_title(window_title, id)
} else {
Task::none()
}
} }
} }

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.35" 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,9 +3,10 @@
//! Calendar widget example //! Calendar widget example
use chrono::{Local, NaiveDate}; use cosmic::app::{Core, Settings, Task};
use cosmic::app::{Command, Core, Settings}; 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]
@ -18,13 +19,15 @@ 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,
NextMonth,
} }
/// The [`App`] stores application-specific state. /// The [`App`] stores application-specific state.
pub struct App { pub struct App {
core: Core, core: Core,
date_selected: NaiveDate, calendar_model: CalendarModel,
} }
/// Implement [`cosmic::Application`] to integrate with COSMIC. /// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -49,13 +52,11 @@ impl cosmic::Application for App {
&mut self.core &mut self.core
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits task on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let now = Local::now();
let mut app = App { let mut app = App {
core, core,
date_selected: NaiveDate::from(now.naive_local()), calendar_model: CalendarModel::now(),
}; };
let command = app.update_title(); let command = app.update_title();
@ -64,32 +65,39 @@ impl cosmic::Application for App {
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::DateSelected(date) => { Message::DateSelected(date) => {
self.date_selected = date; self.calendar_model.selected = date;
}
Message::PrevMonth => {
self.calendar_model.show_prev_month();
}
Message::NextMonth => {
self.calendar_model.show_next_month();
} }
} }
println!("Date selected: {:?}", self.date_selected); println!("Date selected: {:?}", &self.calendar_model.selected);
Command::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 mut content = cosmic::widget::column().spacing(12); let calendar = cosmic::widget::calendar(
&self.calendar_model,
|date| Message::DateSelected(date),
|| Message::PrevMonth,
|| Message::NextMonth,
Weekday::Sunday,
);
let calendar = let centered = cosmic::widget::container(calendar)
cosmic::widget::calendar(&self.date_selected, |date| Message::DateSelected(date));
content = content.push(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::Horizontal::Center) .align_x(iced::Alignment::Center)
.align_y(iced::alignment::Vertical::Center); .align_y(iced::Alignment::Center);
Element::from(centered) Element::from(centered)
} }
@ -99,8 +107,11 @@ impl App
where where
Self: cosmic::Application, Self: cosmic::Application,
{ {
fn update_title(&mut self) -> Command<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

@ -7,4 +7,4 @@ publish = false
[dependencies] [dependencies]
cosmic-config = { path = "../../cosmic-config" } cosmic-config = { path = "../../cosmic-config" }
ron = "0.8.0" ron = "0.9.0"

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,11 +4,18 @@ 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"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false features = [
features = ["debug", "winit", "tokio", "xdg-portal"] "debug",
"winit",
"wgpu",
"tokio",
"xdg-portal",
"surface-message",
"wayland",
]

View file

@ -3,9 +3,9 @@
//! Application API example //! Application API example
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced_core::Size; use cosmic::iced::Size;
use cosmic::widget::{menu, segmented_button}; use cosmic::widget::menu;
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
use std::collections::HashMap; use std::collections::HashMap;
@ -27,9 +27,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
Clicked, Clicked,
ShowContext,
WindowClose, WindowClose,
ShowWindowMenu, Surface(cosmic::surface::Action),
ToggleHideContent, ToggleHideContent,
WindowNew, WindowNew,
} }
@ -38,7 +37,6 @@ pub enum Message {
pub struct App { pub struct App {
core: Core, core: Core,
button_label: String, button_label: String,
show_context: bool,
hide_content: bool, hide_content: bool,
} }
@ -64,40 +62,56 @@ impl cosmic::Application for App {
&mut self.core &mut self.core
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits task on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut app = App { let mut app = 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());
let command = app.set_window_title("COSMIC Context Menu Demo".into()); let command = if let Some(win_id) = app.core.main_window_id() {
app.set_window_title("COSMIC Context Menu Demo".into(), win_id)
} else {
Task::none()
};
(app, command) (app, command)
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
self.button_label = format!("Clicked {message:?}"); match message {
Message::Clicked => {
self.button_label = format!("Clicked {message:?}");
}
Message::Surface(action) => {
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(action),
));
}
Message::WindowClose => {}
Message::ToggleHideContent => {}
Message::WindowNew => {}
}
Command::none() Task::none()
} }
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
let widget = cosmic::widget::context_menu( let widget = cosmic::widget::context_menu(
cosmic::widget::button::text(&self.button_label).on_press(Message::Clicked), cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked),
self.context_menu(), self.context_menu(),
); )
.on_surface_action(Message::Surface);
let centered = cosmic::widget::container(widget) let centered = cosmic::widget::container(widget)
.width(iced::Length::Fill) .width(iced::Length::Fill)
.height(iced::Length::Fill) .height(iced::Length::Fill)
.align_x(iced::alignment::Horizontal::Center) .align_x(iced::Alignment::Center)
.align_y(iced::alignment::Vertical::Center); .align_y(iced::Alignment::Center);
Element::from(centered) Element::from(centered)
} }
@ -108,18 +122,19 @@ impl App {
Some(menu::items( Some(menu::items(
&HashMap::new(), &HashMap::new(),
vec![ vec![
menu::Item::Button("New window", ContextMenuAction::WindowNew), menu::Item::Button("New window", None, ContextMenuAction::WindowNew),
menu::Item::Divider, menu::Item::Divider,
menu::Item::Folder( menu::Item::Folder(
"View", "View",
vec![menu::Item::CheckBox( vec![menu::Item::CheckBox(
"Hide content", "Hide content",
None,
self.hide_content, self.hide_content,
ContextMenuAction::ToggleHideContent, ContextMenuAction::ToggleHideContent,
)], )],
), ),
menu::Item::Divider, menu::Item::Divider,
menu::Item::Button("Quit", ContextMenuAction::WindowClose), menu::Item::Button("Quit", None, ContextMenuAction::WindowClose),
], ],
)) ))
} }

View file

@ -7,14 +7,23 @@ publish = false
[dependencies] [dependencies]
apply = "0.3.0" apply = "0.3.0"
fraction = "0.14.0" fraction = "0.15.3"
libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "dbus-config", "a11y", "wgpu", "xdg-portal"] } libcosmic = { path = "../..", features = [
once_cell = "1.18" "debug",
slotmap = "1.0.6" "winit",
"tokio",
"single-instance",
"dbus-config",
"a11y",
"wgpu",
"xdg-portal",
] }
once_cell = "1.21"
slotmap = "1.1.1"
env_logger = "0.10" env_logger = "0.10"
log = "0.4.17" 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"
default-features = false default-features = false
features = ["libcosmic", "once_cell"] features = ["once_cell"]

View file

@ -6,7 +6,7 @@ use cosmic::{
ThemeBuilder, ThemeBuilder,
}, },
font::load_fonts, font::load_fonts,
iced::{self, Application, Command, Length, Subscription}, iced::{self, Application, Length, Subscription, Task},
iced::{ iced::{
subscription, subscription,
widget::{self, column, container, horizontal_space, row, text}, widget::{self, column, container, horizontal_space, row, text},
@ -17,7 +17,7 @@ use cosmic::{
prelude::*, prelude::*,
theme::{self, Theme}, theme::{self, Theme},
widget::{ widget::{
button, header_bar, icon, list, nav_bar, nav_bar_toggle, scrollable, segmented_button, button, container, header_bar, icon, nav_bar, nav_bar_toggle, scrollable, segmented_button,
settings, warning, settings, warning,
}, },
Element, Element,
@ -231,7 +231,7 @@ impl Window {
} }
fn page_title<Message: 'static>(&self, page: Page) -> Element<Message> { fn page_title<Message: 'static>(&self, page: Page) -> Element<Message> {
row!(text(page.title()).size(28), horizontal_space(Length::Fill),).into() row!(text(page.title()).size(28), horizontal_space(),).into()
} }
fn is_condensed(&self) -> bool { fn is_condensed(&self) -> bool {
@ -253,10 +253,7 @@ impl Window {
.label(page.title()) .label(page.title())
.padding(0) .padding(0)
.on_press(Message::from(page)), .on_press(Message::from(page)),
row!( row!(text(sub_page.title()).size(28), horizontal_space(),),
text(sub_page.title()).size(28),
horizontal_space(Length::Fill),
),
) )
.spacing(10) .spacing(10)
.into() .into()
@ -272,7 +269,7 @@ impl Window {
sub_page: impl SubPage, sub_page: impl SubPage,
) -> Element<Message> { ) -> Element<Message> {
iced::widget::Button::new( iced::widget::Button::new(
list::container( container(
settings::item_row(vec![ settings::item_row(vec![
icon::from_name(sub_page.icon_name()).size(20).icon().into(), icon::from_name(sub_page.icon_name()).size(20).icon().into(),
column!( column!(
@ -281,12 +278,14 @@ impl Window {
) )
.spacing(2) .spacing(2)
.into(), .into(),
horizontal_space(iced::Length::Fill).into(), horizontal_space().into(),
icon::from_name("go-next-symbolic").size(20).icon().into(), icon::from_name("go-next-symbolic").size(20).icon().into(),
]) ])
.spacing(16), .spacing(16),
) )
.padding([20, 24]), .padding([20, 24])
.class(theme::Container::List)
.width(Length::Fill),
) )
.width(Length::Fill) .width(Length::Fill)
.padding(0) .padding(0)
@ -324,7 +323,7 @@ impl Application for Window {
type Message = Message; type Message = Message;
type Theme = Theme; type Theme = Theme;
fn new(_flags: ()) -> (Self, Command<Self::Message>) { fn new(_flags: ()) -> (Self, Task<Self::Message>) {
let mut window = Window::default() let mut window = Window::default()
.nav_bar_toggled(true) .nav_bar_toggled(true)
.show_maximize(true) .show_maximize(true)
@ -361,10 +360,7 @@ impl Application for Window {
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
let window_break = listen_raw(|event, _| match event { let window_break = listen_raw(|event, _| match event {
cosmic::iced::Event::Window( cosmic::iced::Event::Window(window::Event::Resized { width, height: _ }) => {
_window_id,
window::Event::Resized { width, height: _ },
) => {
let old_width = WINDOW_WIDTH.load(Ordering::Relaxed); let old_width = WINDOW_WIDTH.load(Ordering::Relaxed);
if old_width == 0 if old_width == 0
|| old_width < BREAK_POINT && width > BREAK_POINT || old_width < BREAK_POINT && width > BREAK_POINT
@ -389,8 +385,8 @@ impl Application for Window {
]) ])
} }
fn update(&mut self, message: Message) -> iced::Command<Self::Message> { fn update(&mut self, message: Message) -> iced::Task<Self::Message> {
let mut ret = Command::none(); let mut ret = Task::none();
match message { match message {
Message::NavBar(key) => { Message::NavBar(key) => {
if let Some(page) = self.nav_id_to_page.get(key).copied() { if let Some(page) = self.nav_id_to_page.get(key).copied() {
@ -437,10 +433,10 @@ impl Application for Window {
Message::ToggleNavBarCondensed => { Message::ToggleNavBarCondensed => {
self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed
} }
Message::Drag => return drag(window::Id::MAIN), Message::Drag => return drag(self.core.main_window_id().unwrap()),
Message::Close => return close(window::Id::MAIN), Message::Close => return close(self.core.main_window_id().unwrap()),
Message::Minimize => return minimize(window::Id::MAIN, true), Message::Minimize => return minimize(self.core.main_window_id().unwrap(), true),
Message::Maximize => return toggle_maximize(window::Id::MAIN), Message::Maximize => return toggle_maximize(self.core.main_window_id().unwrap()),
Message::InputChanged => {} Message::InputChanged => {}
@ -564,12 +560,9 @@ impl Application for Window {
}; };
widgets.push( widgets.push(
scrollable( scrollable(container(content.debug(self.debug)).align_x(iced::Alignment::Center))
container(content.debug(self.debug)) .width(Length::Fill)
.align_x(iced::alignment::Horizontal::Center), .into(),
)
.width(Length::Fill)
.into(),
); );
} }
@ -587,7 +580,9 @@ impl Application for Window {
header, header,
container(column(vec![ container(column(vec![
warning, warning,
iced::widget::vertical_space(Length::Fixed(12.0)).into(), iced::widget::vertical_space()
.width(Length::Fixed(12.0))
.into(),
content, content,
])) ]))
.style(theme::Container::Background) .style(theme::Container::Background)

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,15 +301,13 @@ impl State {
.add(settings::item( .add(settings::item(
"Progress", "Progress",
progress_bar(0.0..=100.0, self.slider_value) progress_bar(0.0..=100.0, self.slider_value)
.width(Length::Fixed(250.0)) .length(Length::Fixed(250.0))
.height(Length::Fixed(4.0)), .girth(Length::Fixed(4.0)),
)) ))
.add(settings::item_row(vec![checkbox( .add(settings::item_row(vec![checkbox(self.checkbox_value)
"Checkbox", .label("Checkbox")
self.checkbox_value, .on_toggle(Message::CheckboxToggled)
Message::CheckboxToggled, .into()]))
)
.into()]))
.add(settings::item( .add(settings::item(
format!( format!(
"Spin Button (Range {}:{})", "Spin Button (Range {}:{})",
@ -354,8 +354,7 @@ impl State {
.width(Length::Shrink) .width(Length::Shrink)
.on_activate(Message::MultiSelection) .on_activate(Message::MultiSelection)
.apply(container) .apply(container)
.center_x() .center_x(Length::Fill)
.width(Length::Fill)
.into(), .into(),
text("Vertical With Spacing").into(), text("Vertical With Spacing").into(),
cosmic::iced::widget::row(vec![ cosmic::iced::widget::row(vec![
@ -424,13 +423,12 @@ impl State {
]) ])
.padding(0) .padding(0)
.into(), .into(),
Some(DemoView::TabC) => { Some(DemoView::TabC) => settings::view_column(vec![settings::section()
settings::view_column(vec![settings::view_section("Tab C") .title("Tab C")
.add(text("Nothing here yet").width(Length::Fill)) .add(text("Nothing here yet").width(Length::Fill))
.into()]) .into()])
.padding(0) .padding(0)
.into() .into(),
}
}, },
container(text("Background container with some text").size(24)) container(text("Background container with some text").size(24))
.layer(cosmic_theme::Layer::Background) .layer(cosmic_theme::Layer::Background)
@ -482,7 +480,7 @@ impl State {
)) ))
.layer(cosmic::cosmic_theme::Layer::Secondary) .layer(cosmic::cosmic_theme::Layer::Secondary)
.padding(16) .padding(16)
.style(cosmic::theme::Container::Background) .class(cosmic::theme::Container::Background)
.into(), .into(),
cosmic::widget::text_input::secure_input( cosmic::widget::text_input::secure_input(
"Type to search apps or type “?” for more options...", "Type to search apps or type “?” for more options...",

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.37" tracing = "0.1.44"
tracing-subscriber = "0.3.17" 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

@ -3,7 +3,7 @@
//! Application API example //! Application API example
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Core, Settings, Task};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
/// Runs application with these settings /// Runs application with these settings
@ -50,8 +50,8 @@ impl cosmic::Application for App {
&mut self.core &mut self.core
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits task on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut app = App { let mut app = App {
core, core,
selected: 0, selected: 0,
@ -67,7 +67,7 @@ impl cosmic::Application for App {
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::Clicked(id) => self.selected = id, Message::Clicked(id) => self.selected = id,
Message::Remove(id) => { Message::Remove(id) => {
@ -75,12 +75,12 @@ impl cosmic::Application for App {
} }
} }
Command::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 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(
@ -95,8 +95,8 @@ impl cosmic::Application for App {
let centered = cosmic::widget::container(content) 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::Horizontal::Center) .align_x(iced::Alignment::Center)
.align_y(iced::alignment::Vertical::Center); .align_y(iced::Alignment::Center);
Element::from(centered) Element::from(centered)
} }
@ -106,8 +106,11 @@ impl App
where where
Self: cosmic::Application, Self: cosmic::Application,
{ {
fn update_title(&mut self) -> Command<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.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"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]
features = ["debug", "winit", "tokio", "xdg-portal"]

View file

@ -6,15 +6,16 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::{env, process}; use std::{env, process};
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::keyboard::Key;
use cosmic::iced::window; use cosmic::iced::window;
use cosmic::iced_core::alignment::{Horizontal, Vertical}; use cosmic::iced::{Length, Size};
use cosmic::iced_core::keyboard::Key;
use cosmic::iced_core::{Length, Size};
use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::key_bind::Modifier; use cosmic::widget::menu::key_bind::Modifier;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth}; use cosmic::widget::menu::{self, ItemHeight, ItemWidth};
use cosmic::widget::RcElementWrapper;
use cosmic::{executor, Element}; use cosmic::{executor, Element};
/// Runs application with these settings /// Runs application with these settings
@ -96,8 +97,8 @@ impl cosmic::Application for App {
&mut self.core &mut self.core
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits task on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let app = App { let app = App {
core, core,
config: Config { config: Config {
@ -106,18 +107,18 @@ impl cosmic::Application for App {
key_binds: key_binds(), key_binds: key_binds(),
}; };
(app, Command::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)]
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::WindowClose => { Message::WindowClose => {
return window::close(window::Id::MAIN); return window::close(self.core.main_window_id().unwrap());
} }
Message::WindowNew => match env::current_exe() { Message::WindowNew => match env::current_exe() {
Ok(exe) => match process::Command::new(&exe).spawn() { Ok(exe) => match process::Command::new(&exe).spawn() {
@ -132,11 +133,11 @@ impl cosmic::Application for App {
}, },
Message::ToggleHideContent => self.config.hide_content = !self.config.hide_content, Message::ToggleHideContent => self.config.hide_content = !self.config.hide_content,
} }
Command::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 text = if self.config.hide_content { let text = if self.config.hide_content {
cosmic::widget::text("") cosmic::widget::text("")
} else { } else {
@ -155,22 +156,31 @@ impl cosmic::Application for App {
pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> { pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
menu::bar(vec![menu::Tree::with_children( menu::bar(vec![menu::Tree::with_children(
menu::root("File"), RcElementWrapper::new(Element::from(menu::root("File"))),
menu::items( menu::items(
key_binds, key_binds,
vec![ vec![
menu::Item::Button("New window", Action::WindowNew), menu::Item::Button(
"New window",
Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()),
Action::WindowNew,
),
menu::Item::Divider, menu::Item::Divider,
menu::Item::Folder( menu::Item::Folder(
"View", "View",
vec![menu::Item::CheckBox( vec![menu::Item::CheckBox(
"Hide content", "Hide content",
Some(cosmic::widget::icon::from_name("view-conceal-symbolic").into()),
config.hide_content, config.hide_content,
Action::ToggleHideContent, Action::ToggleHideContent,
)], )],
), ),
menu::Item::Divider, menu::Item::Divider,
menu::Item::Button("Quit", Action::WindowClose), menu::Item::Button(
"Quit",
Some(cosmic::widget::icon::from_name("window-close-symbolic").into()),
Action::WindowClose,
),
], ],
), ),
)]) )])

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"] } 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, text_input}, iced::{self, event, window, Subscription},
prelude::*,
widget::{button, header_bar}, widget::{button, header_bar},
ApplicationExt, Command,
}; };
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -42,10 +42,10 @@ impl cosmic::Application for MultiWindow {
&mut self.core &mut self.core
} }
fn init(core: Core, _input: Self::Flags) -> (Self, cosmic::app::Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, cosmic::app::Task<Self::Message>) {
let windows = MultiWindow { let windows = MultiWindow {
windows: HashMap::from([( windows: HashMap::from([(
window::Id::MAIN, core.main_window_id().unwrap(),
Window { Window {
input_id: id::Id::new("main"), input_id: id::Id::new("main"),
input_value: String::new(), input_value: String::new(),
@ -54,12 +54,12 @@ impl cosmic::Application for MultiWindow {
core, core,
}; };
(windows, cosmic::app::Command::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, _| { event::listen_with(|event, _, id| {
if let iced::Event::Window(id, window_event) = event { if let iced::Event::Window(window_event) = event {
match window_event { match window_event {
window::Event::CloseRequested => Some(Message::CloseWindow(id)), window::Event::CloseRequested => Some(Message::CloseWindow(id)),
window::Event::Opened { position, .. } => { window::Event::Opened { position, .. } => {
@ -74,27 +74,24 @@ impl cosmic::Application for MultiWindow {
}) })
} }
fn update( fn update(&mut self, message: Self::Message) -> Task<cosmic::Action<Self::Message>> {
&mut self,
message: Self::Message,
) -> iced::Command<cosmic::app::Message<Self::Message>> {
match message { match message {
Message::CloseWindow(id) => window::close(id), Message::CloseWindow(id) => window::close(id),
Message::WindowClosed(id) => { Message::WindowClosed(id) => {
self.windows.remove(&id); self.windows.remove(&id);
Command::none() Task::none()
} }
Message::WindowOpened(id, ..) => { Message::WindowOpened(id, ..) => {
if let Some(window) = self.windows.get(&id) { if let Some(window) = self.windows.get(&id) {
text_input::focus(window.input_id.clone()) cosmic::widget::text_input::focus(window.input_id.clone())
} else { } else {
Command::none() Task::none()
} }
} }
Message::NewWindow => { Message::NewWindow => {
let count = self.windows.len() + 1; let count = self.windows.len() + 1;
let (id, spawn_window) = window::spawn(window::Settings { let (id, spawn_window) = window::open(window::Settings {
position: Default::default(), position: Default::default(),
exit_on_close_request: count % 2 == 0, exit_on_close_request: count % 2 == 0,
decorations: false, decorations: false,
@ -110,25 +107,23 @@ impl cosmic::Application for MultiWindow {
); );
_ = self.set_window_title(format!("window_{}", count), id); _ = self.set_window_title(format!("window_{}", count), id);
spawn_window spawn_window.map(|id| cosmic::Action::App(Message::WindowOpened(id, None)))
} }
Message::Input(id, value) => { Message::Input(id, value) => {
if let Some(w) = self.windows.get_mut(&window::Id::MAIN) { if let Some((_, w)) = self.windows.iter_mut().find(|e| e.1.input_id == id) {
if id == w.input_id { w.input_value = value;
w.input_value = value;
}
} }
Command::none() Task::none()
} }
} }
} }
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();
let input = text_input("something", &w.input_value) let input = cosmic::widget::text_input::text_input("something", &w.input_value)
.on_input(move |msg| Message::Input(input_id.clone(), msg)) .on_input(move |msg| Message::Input(input_id.clone(), msg))
.id(w.input_id.clone()); .id(w.input_id.clone());
let focused = self let focused = self
@ -136,30 +131,28 @@ impl cosmic::Application for MultiWindow {
.focused_window() .focused_window()
.map(|i| i == id) .map(|i| i == id)
.unwrap_or_default(); .unwrap_or_default();
let new_window_button = button(text("New Window")).on_press(Message::NewWindow); let new_window_button = button::custom(text("New Window")).on_press(Message::NewWindow);
let content = scrollable( let content = scrollable(
column![input, new_window_button] column![input, new_window_button]
.spacing(50) .spacing(50)
.width(Length::Fill) .width(Length::Fill)
.align_items(Alignment::Center), .align_x(Alignment::Center),
); );
let window_content = container(container(content).width(200).center_x()) let window_content = container(container(content).center_x(Length::Fixed(200.)))
.style(cosmic::style::Container::Background) .class(cosmic::style::Container::Background)
.width(Length::Fill) .center_x(Length::Fill)
.height(Length::Fill) .center_y(Length::Fill);
.center_x()
.center_y();
if id == window::Id::MAIN { if id == self.core.main_window_id().unwrap() {
window_content.into() window_content.into()
} else { } else {
column![header_bar().focused(focused), window_content].into() column![header_bar().focused(focused), window_content].into()
} }
} }
fn view(&self) -> cosmic::prelude::Element<Self::Message> { fn view(&self) -> Element<'_, Self::Message> {
self.view_window(window::Id::MAIN) 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.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"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false features = ["debug", "winit", "tokio", "xdg-portal", "wgpu"]
features = ["debug", "winit", "tokio", "xdg-portal"]

View file

@ -5,8 +5,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use cosmic::app::{Command, Core, Settings}; 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};
@ -70,10 +70,10 @@ pub enum NavMenuAction {
} }
impl menu::Action for NavMenuAction { impl menu::Action for NavMenuAction {
type Message = cosmic::app::Message<Message>; type Message = cosmic::Action<Message>;
fn message(&self) -> Self::Message { fn message(&self) -> Self::Message {
cosmic::app::Message::App(Message::NavMenuAction(*self)) cosmic::Action::App(Message::NavMenuAction(*self))
} }
} }
@ -105,8 +105,8 @@ impl cosmic::Application for App {
&mut self.core &mut self.core
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits task on initialize.
fn init(core: Core, input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, input: Self::Flags) -> (Self, 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 {
@ -131,25 +131,25 @@ impl cosmic::Application for App {
fn nav_context_menu( fn nav_context_menu(
&self, &self,
id: nav_bar::Id, id: nav_bar::Id,
) -> Option<Vec<menu::Tree<cosmic::app::Message<Self::Message>>>> { ) -> Option<Vec<menu::Tree<cosmic::Action<Self::Message>>>> {
Some(menu::items( Some(menu::items(
&HashMap::new(), &HashMap::new(),
vec![ vec![
menu::Item::Button("Move Up", NavMenuAction::MoveUp(id)), menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(id)),
menu::Item::Button("Move Down", NavMenuAction::MoveDown(id)), menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(id)),
menu::Item::Button("Delete", NavMenuAction::Delete(id)), menu::Item::Button("Delete", None, NavMenuAction::Delete(id)),
], ],
)) ))
} }
/// Called when a navigation item is selected. /// Called when a navigation item is selected.
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<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() self.update_title()
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::NavMenuAction(message) => match message { Message::NavMenuAction(message) => match message {
NavMenuAction::Delete(id) => self.nav_model.remove(id), NavMenuAction::Delete(id) => self.nav_model.remove(id),
@ -168,11 +168,11 @@ impl cosmic::Application for App {
}, },
} }
Command::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 page_content = self let page_content = self
.nav_model .nav_model
.active_data::<String>() .active_data::<String>()
@ -183,8 +183,8 @@ impl cosmic::Application for App {
let centered = cosmic::widget::container(text) let centered = cosmic::widget::container(text)
.width(iced::Length::Fill) .width(iced::Length::Fill)
.height(iced::Length::Shrink) .height(iced::Length::Shrink)
.align_x(iced::alignment::Horizontal::Center) .align_x(iced::Alignment::Center)
.align_y(iced::alignment::Vertical::Center); .align_y(iced::Alignment::Center);
Element::from(centered) Element::from(centered)
} }
@ -200,10 +200,14 @@ where
.unwrap_or("Unknown Page") .unwrap_or("Unknown Page")
} }
fn update_title(&mut self) -> Command<Message> { fn update_title(&mut self) -> 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);
self.set_window_title(window_title) if let Some(win_id) = self.core.main_window_id() {
self.set_window_title(window_title, win_id)
} else {
Task::none()
}
} }
} }

View file

@ -10,12 +10,11 @@ xdg-portal = ["libcosmic/xdg-portal"]
[dependencies] [dependencies]
apply = "0.3.0" apply = "0.3.0"
tokio = { version = "1.31", features = ["full"] } tokio = { version = "1.49", features = ["full"] }
tracing = "0.1.37" tracing = "0.1.44"
tracing-subscriber = "0.3.17" tracing-subscriber = "0.3.22"
url = "2.4.0" url = "2.5.8"
[dependencies.libcosmic] [dependencies.libcosmic]
features = ["debug", "winit", "wgpu", "wayland", "tokio"]
path = "../../" path = "../../"
default-features = false
features = ["debug", "wayland", "tokio"]

View file

@ -4,9 +4,9 @@
//! An application which provides an open dialog //! An application which provides an open dialog
use apply::Apply; use apply::Apply;
use cosmic::app::{Command, Core, Settings}; 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;
@ -34,6 +34,7 @@ pub enum Message {
OpenError(Arc<file_chooser::Error>), OpenError(Arc<file_chooser::Error>),
OpenFile, OpenFile,
Selected(Url), Selected(Url),
Surface(cosmic::surface::Action),
} }
/// The [`App`] stores application-specific state. /// The [`App`] stores application-specific state.
@ -65,8 +66,9 @@ impl cosmic::Application for App {
&mut self.core &mut self.core
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits task on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let id = core.main_window_id().unwrap();
let mut app = App { let mut app = App {
core, core,
file_contents: String::new(), file_contents: String::new(),
@ -75,31 +77,26 @@ impl cosmic::Application for App {
}; };
app.set_header_title("Open a file".into()); app.set_header_title("Open a file".into());
let cmd = app.set_window_title( let cmd = app.set_window_title("COSMIC OpenDialog Demo".into(), id);
"COSMIC OpenDialog Demo".into(),
cosmic::iced::window::Id::MAIN,
);
(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()]
} }
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::Cancelled => { Message::Cancelled => {
eprintln!("open file dialog cancelled"); eprintln!("open file dialog cancelled");
} }
Message::FileRead(url, contents) => { Message::FileRead(url, contents) => {
eprintln!("read file"); eprintln!("read file");
self.selected_file = Some(url); self.selected_file = Some(url);
self.file_contents = contents; self.file_contents = contents;
} }
Message::Selected(url) => { Message::Selected(url) => {
eprintln!("selected file"); eprintln!("selected file");
@ -111,7 +108,7 @@ impl cosmic::Application for App {
self.set_header_title(url.to_string()); self.set_header_title(url.to_string());
// Reads the selected file into memory. // Reads the selected file into memory.
return cosmic::command::future(async move { return cosmic::task::future(async move {
// Check if its a valid local file path. // Check if its a valid local file path.
let path = match url.scheme() { let path = match url.scheme() {
"file" => url.to_file_path().unwrap(), "file" => url.to_file_path().unwrap(),
@ -144,10 +141,8 @@ impl cosmic::Application for App {
Message::FileRead(url, contents) Message::FileRead(url, contents)
}); });
} }
// Creates a new open dialog.
Message::OpenFile => { Message::OpenFile => {
return cosmic::command::future(async move { return cosmic::task::future(async move {
eprintln!("opening new dialog"); eprintln!("opening new dialog");
#[cfg(feature = "rfd")] #[cfg(feature = "rfd")]
@ -171,13 +166,9 @@ impl cosmic::Application for App {
} }
}); });
} }
// Displays an error in the application's warning bar.
Message::Error(why) => { Message::Error(why) => {
self.error_status = Some(why); self.error_status = Some(why);
} }
// Displays an error in the application's warning bar.
Message::OpenError(why) => { Message::OpenError(why) => {
if let Some(why) = Arc::into_inner(why) { if let Some(why) = Arc::into_inner(why) {
let mut source: &dyn std::error::Error = &why; let mut source: &dyn std::error::Error = &why;
@ -192,16 +183,20 @@ impl cosmic::Application for App {
self.error_status = Some(string); self.error_status = Some(string);
} }
} }
Message::CloseError => { Message::CloseError => {
self.error_status = None; self.error_status = None;
} }
Message::Surface(action) => {
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(action),
));
}
} }
Command::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() {
@ -211,7 +206,11 @@ impl cosmic::Application for App {
.into(), .into(),
); );
content.push(iced::widget::vertical_space(Length::Fixed(12.0)).into()); content.push(
iced::widget::space::vertical()
.height(Length::Fixed(12.0))
.into(),
);
} }
content.push(if self.selected_file.is_none() { content.push(if self.selected_file.is_none() {
@ -231,7 +230,7 @@ fn center<'a>(input: impl Into<Element<'a, Message>> + 'a) -> Element<'a, Messag
iced::widget::container(input.into()) iced::widget::container(input.into())
.width(iced::Length::Fill) .width(iced::Length::Fill)
.height(iced::Length::Fill) .height(iced::Length::Fill)
.align_x(iced::alignment::Horizontal::Center) .align_x(iced::Alignment::Center)
.align_y(iced::alignment::Vertical::Center) .align_y(iced::Alignment::Center)
.into() .into()
} }

View file

@ -0,0 +1,12 @@
[package]
name = "spin-button"
version = "0.1.0"
edition = "2021"
[dependencies]
fraction = "0.15.3"
[dependencies.libcosmic]
features = ["debug", "wgpu", "winit", "desktop", "tokio"]
path = "../.."
default-features = false

View file

@ -0,0 +1,201 @@
use cosmic::iced::Length;
use cosmic::widget::{column, container, spin_button};
use cosmic::Apply;
use cosmic::{
app::{Core, Task},
iced::{
self,
alignment::{Horizontal, Vertical},
Alignment, Size,
},
Application, Element,
};
use fraction::Decimal;
pub struct SpinButtonExamplApp {
core: Core,
i8_num: i8,
i8_str: String,
i16_num: i16,
i16_str: String,
i32_num: i32,
i32_str: String,
i64_num: i64,
i64_str: String,
i128_num: i128,
i128_str: String,
f32_num: f32,
f32_str: String,
f64_num: f64,
f64_str: String,
dec_num: Decimal,
dec_str: String,
}
#[derive(Debug, Clone)]
pub enum Message {
UpdateI8(i8),
UpdateI16(i16),
UpdateI32(i32),
UpdateI64(i64),
UpdateI128(i128),
UpdateF32(f32),
UpdateF64(f64),
UpdateDec(Decimal),
}
impl Application for SpinButtonExamplApp {
type Executor = cosmic::executor::Default;
type Flags = ();
type Message = Message;
const APP_ID: &'static str = "com.system76.SpinButtonExample";
fn core(&self) -> &Core {
&self.core
}
fn core_mut(&mut self) -> &mut Core {
&mut self.core
}
fn init(core: Core, _flags: Self::Flags) -> (Self, Task<Self::Message>) {
(
Self {
core,
i8_num: 0,
i8_str: 0.to_string(),
i16_num: 0,
i16_str: 0.to_string(),
i32_num: 0,
i32_str: 0.to_string(),
i64_num: 15,
i64_str: 15.to_string(),
i128_num: 0,
i128_str: 0.to_string(),
f32_num: 0.,
f32_str: format!("{:.02}", 0.0),
f64_num: 0.,
f64_str: format!("{:.02}", 0.0),
dec_num: Decimal::from(0.0),
dec_str: format!("{:.02}", 0.0),
},
Task::none(),
)
}
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message {
Message::UpdateI8(value) => {
self.i8_num = value;
self.i8_str = value.to_string();
}
Message::UpdateI16(value) => {
self.i16_num = value;
self.i16_str = value.to_string();
}
Message::UpdateI32(value) => {
self.i32_num = value;
self.i32_str = value.to_string();
}
Message::UpdateI64(value) => {
self.i64_num = value;
self.i64_str = value.to_string();
}
Message::UpdateI128(value) => {
self.i128_num = value;
self.i128_str = value.to_string();
}
Message::UpdateF32(value) => {
self.f32_num = value;
self.f32_str = format!("{value:.02}");
}
Message::UpdateF64(value) => {
self.f64_num = value;
self.f64_str = format!("{value:.02}");
}
Message::UpdateDec(value) => {
self.dec_num = value;
self.dec_str = format!("{value:.02}");
}
}
Task::none()
}
fn view(&'_ self) -> Element<'_, Self::Message> {
let space_xs = cosmic::theme::spacing().space_xs;
let vert_spinner_row = iced::widget::row![
spin_button::vertical(&self.i8_str, self.i8_num, 1, -5, 5, Message::UpdateI8),
spin_button::vertical(&self.i16_str, self.i16_num, 1, 0, 10, Message::UpdateI16),
spin_button::vertical(&self.i32_str, self.i32_num, 1, 0, 12, Message::UpdateI32),
spin_button::vertical(&self.i64_str, self.i64_num, 10, 15, 35, Message::UpdateI64),
]
.spacing(space_xs)
.align_y(Vertical::Center);
let horiz_spinner_row = iced::widget::column![
spin_button(
&self.i128_str,
self.i128_num,
100,
-1000,
500,
Message::UpdateI128
),
spin_button(
&self.f32_str,
self.f32_num,
1.3,
-35.3,
12.3,
Message::UpdateF32
),
spin_button(
&self.f64_str,
self.f64_num,
1.3,
0.0,
3.0,
Message::UpdateF64
),
spin_button(
&self.dec_str,
self.dec_num,
Decimal::from(0.25),
Decimal::from(-5.0),
Decimal::from(5.0),
Message::UpdateDec
),
]
.spacing(space_xs)
.align_x(Alignment::Center);
column::with_capacity(3)
.push(vert_spinner_row)
.push(horiz_spinner_row)
.spacing(space_xs)
.align_x(Alignment::Center)
.apply(container)
.width(Length::Fill)
.height(Length::Fill)
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.into()
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let settings = cosmic::app::Settings::default().size(Size::new(550., 1024.));
cosmic::app::run::<SpinButtonExamplApp>(settings, ())?;
Ok(())
}

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

@ -0,0 +1,14 @@
[package]
name = "table-view"
version = "0.1.0"
edition = "2021"
[dependencies]
tracing = "0.1.44"
tracing-subscriber = "0.3.22"
tracing-log = "0.2.0"
chrono = "*"
[dependencies.libcosmic]
features = ["debug", "wgpu", "winit", "desktop", "tokio"]
path = "../.."

View file

@ -0,0 +1,272 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Table API example
use std::collections::HashMap;
use chrono::Datelike;
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Size;
use cosmic::prelude::*;
use cosmic::widget::table;
use cosmic::widget::{self, nav_bar};
use cosmic::{executor, iced};
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Category {
#[default]
Name,
Date,
Size,
}
impl std::fmt::Display for Category {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Name => "Name",
Self::Date => "Date",
Self::Size => "Size",
})
}
}
impl table::ItemCategory for Category {
fn width(&self) -> iced::Length {
match self {
Self::Name => iced::Length::Fill,
Self::Date => iced::Length::Fixed(200.0),
Self::Size => iced::Length::Fixed(150.0),
}
}
}
struct Item {
name: String,
date: chrono::DateTime<chrono::Local>,
size: u64,
}
impl Default for Item {
fn default() -> Self {
Self {
name: Default::default(),
date: Default::default(),
size: Default::default(),
}
}
}
impl table::ItemInterface<Category> for Item {
fn get_icon(&self, category: Category) -> Option<cosmic::widget::Icon> {
if category == Category::Name {
Some(cosmic::widget::icon::from_name("application-x-executable-symbolic").icon())
} else {
None
}
}
fn get_text(&self, category: Category) -> std::borrow::Cow<'static, str> {
match category {
Category::Name => self.name.clone().into(),
Category::Date => self.date.format("%Y/%m/%d").to_string().into(),
Category::Size => format!("{} items", self.size).into(),
}
}
fn compare(&self, other: &Self, category: Category) -> std::cmp::Ordering {
match category {
Category::Name => self.name.to_lowercase().cmp(&other.name.to_lowercase()),
Category::Date => self.date.cmp(&other.date),
Category::Size => self.size.cmp(&other.size),
}
}
}
/// Runs application with these settings
#[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let _ = tracing_log::LogTracer::init();
let settings = Settings::default()
.size(Size::new(1024., 768.));
cosmic::app::run::<App>(settings, ())?;
Ok(())
}
/// Messages that are used specifically by our [`App`].
#[derive(Clone, Debug)]
pub enum Message {
ItemSelect(table::Entity),
CategorySelect(Category),
PrintMsg(String),
NoOp,
}
/// The [`App`] stores application-specific state.
pub struct App {
core: Core,
table_model: table::SingleSelectModel<Item, Category>,
}
/// 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.AppDemoTable";
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, _: Self::Flags) -> (Self, Task<Self::Message>) {
let mut nav_model = nav_bar::Model::default();
nav_model.activate_position(0);
let mut table_model =
table::Model::new(vec![Category::Name, Category::Date, Category::Size]);
let _ = table_model.insert(Item {
name: "Foo".into(),
date: chrono::DateTime::default()
.with_day(1)
.unwrap()
.with_month(1)
.unwrap()
.with_year(1970)
.unwrap(),
size: 2,
});
let _ = table_model.insert(Item {
name: "Bar".into(),
date: chrono::DateTime::default()
.with_day(2)
.unwrap()
.with_month(1)
.unwrap()
.with_year(1970)
.unwrap(),
size: 4,
});
let _ = table_model.insert(Item {
name: "Baz".into(),
date: chrono::DateTime::default()
.with_day(3)
.unwrap()
.with_month(1)
.unwrap()
.with_year(1970)
.unwrap(),
size: 12,
});
let app = App { core, table_model };
let command = Task::none();
(app, command)
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message {
Message::ItemSelect(entity) => self.table_model.activate(entity),
Message::CategorySelect(category) => {
let mut ascending = true;
if let Some(old_sort) = self.table_model.get_sort() {
if old_sort.0 == category {
ascending = !old_sort.1;
}
}
self.table_model.sort(category, ascending)
}
Message::PrintMsg(string) => tracing_log::log::info!("{}", string),
Message::NoOp => {}
}
Task::none()
}
/// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> {
cosmic::widget::responsive(|size| {
if size.width < 600.0 {
widget::compact_table(&self.table_model)
.on_item_left_click(Message::ItemSelect)
.item_context(move |item| {
Some(widget::menu::items(
&HashMap::new(),
vec![widget::menu::Item::Button(
format!("Action on {}", item.name.to_string()),
None,
Action::None,
)],
))
})
.apply(Element::from)
} else {
widget::table(&self.table_model)
.on_item_left_click(Message::ItemSelect)
.on_category_left_click(Message::CategorySelect)
.item_context(|item| {
Some(widget::menu::items(
&HashMap::new(),
vec![widget::menu::Item::Button(
format!("Action on {}", item.name),
None,
Action::None,
)],
))
})
.category_context(|category| {
Some(widget::menu::items(
&HashMap::new(),
vec![
widget::menu::Item::Button(
format!("Action on {} category", category.to_string()),
None,
Action::None,
),
widget::menu::Item::Button(
format!("Other action on {} category", category.to_string()),
None,
Action::None,
),
],
))
})
.apply(Element::from)
}
})
.into()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Action {
None,
}
impl widget::menu::Action for Action {
type Message = Message;
fn message(&self) -> Self::Message {
Message::NoOp
}
}

View file

@ -4,11 +4,10 @@ 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"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false features = ["debug", "winit", "wgpu", "tokio", "xdg-portal"]
features = ["debug", "winit", "tokio", "xdg-portal"]

View file

@ -3,7 +3,7 @@
//! Application API example //! Application API example
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Core, Settings, Task};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
/// Runs application with these settings /// Runs application with these settings
@ -54,8 +54,8 @@ impl cosmic::Application for App {
&mut self.core &mut self.core
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits task on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut app = App { let mut app = App {
core, core,
editing: false, editing: false,
@ -63,7 +63,7 @@ impl cosmic::Application for App {
search_id: cosmic::widget::Id::unique(), search_id: cosmic::widget::Id::unique(),
}; };
let commands = Command::batch(vec![ let commands = Task::batch(vec![
cosmic::widget::text_input::focus(app.search_id.clone()), cosmic::widget::text_input::focus(app.search_id.clone()),
app.update_title(), app.update_title(),
]); ]);
@ -72,7 +72,7 @@ impl cosmic::Application for App {
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::Input(text) => { Message::Input(text) => {
self.input = text; self.input = text;
@ -83,11 +83,11 @@ impl cosmic::Application for App {
} }
} }
Command::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 editable = cosmic::widget::editable_input( let editable = cosmic::widget::editable_input(
"Input text here", "Input text here",
&self.input, &self.input,
@ -99,13 +99,15 @@ 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)
.height(iced::Length::Shrink) .height(iced::Length::Shrink)
.align_x(iced::alignment::Horizontal::Center) .align_x(iced::Alignment::Center)
.align_y(iced::alignment::Vertical::Center); .align_y(iced::Alignment::Center);
Element::from(centered) Element::from(centered)
} }
@ -115,9 +117,9 @@ impl App
where where
Self: cosmic::Application, Self: cosmic::Application,
{ {
fn update_title(&mut self) -> Command<Message> { fn update_title(&mut self) -> Task<Message> {
let window_title = format!("COSMIC TextInputs Demo"); let window_title = format!("COSMIC TextInputs Demo");
self.set_header_title(window_title.clone()); self.set_header_title(window_title.clone());
self.set_window_title(window_title) self.set_window_title(window_title, self.core.main_window_id().unwrap())
} }
} }

4
i18n.toml Normal file
View file

@ -0,0 +1,4 @@
fallback_language = "en"
[fluent]
assets_dir = "i18n"

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

36
i18n/ar/libcosmic.ftl Normal file
View file

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

27
i18n/be/libcosmic.ftl Normal file
View file

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

29
i18n/bg/libcosmic.ftl Normal file
View file

@ -0,0 +1,29 @@
# Context Drawer
close = Затваряне
# About
license = Лиценз
links = Връзки
developers = Разработчици
designers = Дизайнери
artists = Художници
translators = Преводачи
documenters = Документатори
january = Януари { $year }
february = Февруари { $year }
march = Март { $year }
april = Април { $year }
may = Май { $year }
june = Юни { $year }
july = Юли { $year }
august = Август { $year }
september = Септември { $year }
october = Октомври { $year }
november = Ноември { $year }
december = Декември { $year }
monday = Пн
tuesday = Вт
wednesday = Ср
thursday = Чт
friday = Пт
saturday = Сб
sunday = Нд

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

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

36
i18n/cs/libcosmic.ftl Normal file
View file

@ -0,0 +1,36 @@
# Context Drawer
close = Zavřít
# About
license = Licence
links = Odkazy
developers = Vývojáři
designers = Designéři
artists = Grafici
translators = Překladatelé
documenters = Tvůrci dokumentace
sunday = Neděle
january = Leden { $year }
february = Únor { $year }
march = Březen { $year }
april = Duben { $year }
may = Květen { $year }
june = Červen { $year }
july = Červenec { $year }
august = Srpen { $year }
september = Září { $year }
october = Říjen { $year }
november = Listopad { $year }
december = Prosinec { $year }
monday = Pondělí
tuesday = Úterý
wednesday = Středa
thursday = Čtvrtek
friday = Pátek
saturday = Sobota
mon = Po
tue = Út
wed = St
thu = Čt
fri = Pá
sat = So
sun = Ne

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

37
i18n/de/libcosmic.ftl Normal file
View file

@ -0,0 +1,37 @@
# Context Drawer
close = Schließen
# About
license = Lizenz
links = Links
developers = Entwickler(innen)
designers = Designer(innen)
artists = Künstler(innen)
translators = Übersetzer(innen)
documenters = Dokumentierer(innen)
# Calendar
january = Januar { $year }
february = Februar { $year }
march = März { $year }
april = April { $year }
may = Mai { $year }
june = Juni { $year }
july = Juli { $year }
august = August { $year }
september = September { $year }
october = Oktober { $year }
november = November { $year }
december = Dezember { $year }
monday = Montag
tuesday = Dienstag
wednesday = Mittwoch
thursday = Donnerstag
friday = Freitag
saturday = Samstag
sunday = Sonntag
wed = Mi
thu = Do
fri = Fr
sat = Sa
sun = So
tue = Di
mon = Mo

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

0
i18n/en-GB/libcosmic.ftl Normal file
View file

39
i18n/en/libcosmic.ftl Normal file
View file

@ -0,0 +1,39 @@
# Context Drawer
close = Close
# About
license = License
links = Links
developers = Developers
designers = Designers
artists = Artists
translators = Translators
documenters = Documenters
# Calendar
january = January { $year }
february = February { $year }
march = March { $year }
april = April { $year }
may = May { $year }
june = June { $year }
july = July { $year }
august = August { $year }
september = September { $year }
october = October { $year }
november = November { $year }
december = December { $year }
monday = Monday
mon = Mon
tuesday = Tuesday
tue = Tue
wednesday = Wednesday
wed = Wed
thursday = Thursday
thu = Thu
friday = Friday
fri = Fri
saturday = Saturday
sat = Sat
sunday = Sunday
sun = Sun

11
i18n/eo/libcosmic.ftl Normal file
View file

@ -0,0 +1,11 @@
# Context Drawer
close = Fermi
# About
license = Permesilo
links = Ligiloj
developers = Programistoj
designers = Grafikistoj
artists = Artistoj
translators = Tradukantoj
documenters = Dokumentantoj

View file

@ -0,0 +1,8 @@
close = Cerrar
license = Licencia
links = Enlaces
developers = Desarrolladores
designers = Diseñadores
artists = Artistas
translators = Traductores
documenters = Documentalistas

0
i18n/es-MX/libcosmic.ftl Normal file
View file

8
i18n/es/libcosmic.ftl Normal file
View file

@ -0,0 +1,8 @@
license = Licencia
links = Enlaces
developers = Desarrolladores
designers = Diseñadores
artists = Artistas
translators = Traductores
documenters = Documentadores
close = Cerrar

8
i18n/et/libcosmic.ftl Normal file
View file

@ -0,0 +1,8 @@
close = Sulge
license = Litsents
links = Lingid
developers = Arendajad
artists = Kunstnikud
translators = Tõlkijad
documenters = Dokumenteerijad
designers = Kujundajad

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

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

34
i18n/fi/libcosmic.ftl Normal 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 }

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

@ -0,0 +1,34 @@
close = Fermer
documenters = Rédacteurs
translators = Traducteurs
artists = Artistes
license = Licence
links = Liens
developers = Développeurs
january = Janvier { $year }
february = Février { $year }
april = Avril { $year }
march = Mars { $year }
november = Novembre { $year }
friday = Vendredi
tuesday = Mardi
may = Mai { $year }
wednesday = Mercredi
monday = Lundi
december = Décembre { $year }
sunday = Dimanche
june = Juin { $year }
saturday = Samedi
august = Août { $year }
july = Juillet { $year }
thursday = Jeudi
september = Septembre { $year }
october = Octobre { $year }
designers = Designers
mon = Lun
tue = Mar
wed = Mer
thu = Jeu
fri = Ven
sat = Sam
sun = Dim

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

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

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

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

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

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

12
i18n/hi/libcosmic.ftl Normal file
View file

@ -0,0 +1,12 @@
close = बंद करें
license = लाइसेंस
links = लिंक
developers = डेवलपर्स
designers = डिज़ाइनर
february = फ़रवरी { $year }
documenters = दस्तावेज़ बनाने वाले
april = अप्रैल { $year }
translators = अनुवादक
artists = कलाकार
march = मार्च { $year }
january = जनवरी { $year }

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

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