105 Commits
Author SHA1 Message Date
zach 74261f0a49 Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 14s
Python / static (pull_request) Successful in 1m5s
Rust / fmt (pull_request) Successful in 32s
Rust / build (pull_request) Successful in 1m57s
C++ / build (pull_request) Successful in 3m57s
Rust / clippy (pull_request) Successful in 1m32s
Python / verify (pull_request) Successful in 3m6s
C++ / clang-tidy (pull_request) Successful in 5m23s
2026-08-18 21:47:03 +02:00
zach eb65e44ac8 remove unneeded files 2026-08-18 21:46:39 +02:00
zach e76350fa7f Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 3s
JS/TS / lint (pull_request) Successful in 16s
JS/TS / fmt (pull_request) Successful in 17s
Python / static (pull_request) Successful in 56s
Rust / fmt (pull_request) Successful in 58s
Rust / build (pull_request) Successful in 1m51s
C++ / build (pull_request) Successful in 2m32s
Rust / clippy (pull_request) Successful in 1m32s
Python / verify (pull_request) Successful in 2m39s
C++ / clang-tidy (pull_request) Successful in 4m2s
2026-08-18 19:31:42 +02:00
zach de44096132 remove log file 2026-08-18 19:25:21 +02:00
zach 59213cf30d fix property changed signals firing multiple times 2026-08-18 19:25:03 +02:00
zach 8ec454306f fix popout jitter due to height changes 2026-08-18 00:47:16 +02:00
zach 015c1fb27d Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 17s
JS/TS / lint (pull_request) Successful in 20s
Python / static (pull_request) Successful in 57s
Rust / fmt (pull_request) Successful in 1m9s
C++ / build (pull_request) Successful in 2m30s
Rust / build (pull_request) Successful in 2m8s
Rust / clippy (pull_request) Successful in 1m33s
Python / verify (pull_request) Successful in 3m0s
C++ / clang-tidy (pull_request) Successful in 4m7s
2026-08-18 00:04:59 +02:00
zach 968055a24e fix wallpaper preview 2026-08-17 22:15:54 +02:00
zach f6cb7e44c2 Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 8s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 15s
Python / static (pull_request) Successful in 54s
Rust / fmt (pull_request) Successful in 30s
Rust / build (pull_request) Successful in 2m2s
C++ / build (pull_request) Successful in 4m0s
Rust / clippy (pull_request) Successful in 1m29s
Python / verify (pull_request) Successful in 3m13s
C++ / clang-tidy (pull_request) Successful in 5m7s
2026-08-17 22:05:33 +02:00
Inorishio 5a8939bc1e changed the repetaer for the networkpopout to be a listview, changed some variables in the bluetooth popout
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 21s
JS/TS / lint (pull_request) Successful in 19s
Python / static (pull_request) Successful in 1m1s
Rust / fmt (pull_request) Successful in 52s
C++ / build (pull_request) Successful in 2m46s
Rust / build (pull_request) Successful in 2m25s
Rust / clippy (pull_request) Successful in 1m58s
Python / verify (pull_request) Successful in 3m16s
C++ / clang-tidy (pull_request) Successful in 6m59s
2026-08-17 22:04:49 +02:00
zach 46855b6a7f fix new scheme preview bug with wallpaper carousel 2026-08-17 21:21:51 +02:00
zach dfc203f905 Merge pull request 'Fix: Proper color scheme preview to avoid watcher creation spam' (#150) from fix/proper-scheme-previews into main
Reviewed-on: #150
2026-08-17 21:01:25 +02:00
zach 78c0a6fdfc Merge branch 'main' into fix/proper-scheme-previews
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 20s
JS/TS / lint (pull_request) Successful in 21s
Python / static (pull_request) Successful in 1m1s
Rust / fmt (pull_request) Successful in 54s
C++ / build (pull_request) Successful in 2m31s
Rust / build (pull_request) Successful in 2m5s
Rust / clippy (pull_request) Successful in 1m29s
Python / verify (pull_request) Successful in 2m41s
C++ / clang-tidy (pull_request) Successful in 4m0s
2026-08-17 21:01:15 +02:00
zach 98fc9aa97a add wavyline slider to dash media widget 2026-08-17 21:01:04 +02:00
zach db76ae7dc6 fix dash currentTab property 2026-08-17 18:49:47 +02:00
zach de55b5f1e4 Merge branch 'main' into fix/proper-scheme-previews
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 28s
JS/TS / lint (pull_request) Successful in 20s
Python / static (pull_request) Successful in 58s
Rust / fmt (pull_request) Successful in 1m4s
Rust / build (pull_request) Successful in 1m52s
C++ / build (pull_request) Successful in 2m29s
Rust / clippy (pull_request) Successful in 1m23s
Python / verify (pull_request) Successful in 2m43s
C++ / clang-tidy (pull_request) Successful in 3m59s
2026-08-17 18:46:40 +02:00
zach 2c62ad093c fix incorrect font property for other text objects in clock 2026-08-17 18:30:43 +02:00
zach 97c48d03f4 Merge branch 'main' into fix/proper-scheme-previews
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 17s
Python / static (pull_request) Successful in 1m0s
Rust / fmt (pull_request) Successful in 1m0s
C++ / build (pull_request) Successful in 2m14s
Rust / build (pull_request) Successful in 2m9s
Rust / clippy (pull_request) Successful in 1m26s
Python / verify (pull_request) Successful in 2m50s
C++ / clang-tidy (pull_request) Successful in 4m1s
2026-08-17 18:02:22 +02:00
zach 69d8f6d727 Merge pull request 'Feat: MD3 wallpaper launcher carousel' (#146) from feat/wallpaper-picker-rework into main
Reviewed-on: #146
Reviewed-by: AramJonghu <aramjonghu@tutamail.com>
2026-08-17 18:01:41 +02:00
zach ddfa6cd3c4 Merge branch 'main' into feat/wallpaper-picker-rework
C++ / fmt (pull_request) Successful in 11s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 15s
Python / static (pull_request) Successful in 54s
Rust / fmt (pull_request) Successful in 34s
Rust / build (pull_request) Successful in 1m54s
C++ / build (pull_request) Successful in 3m51s
Rust / clippy (pull_request) Successful in 1m34s
Python / verify (pull_request) Successful in 3m5s
C++ / clang-tidy (pull_request) Successful in 5m15s
2026-08-17 16:55:00 +02:00
zach c35e73886a Merge branch 'main' into fix/proper-scheme-previews
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 19s
JS/TS / lint (pull_request) Successful in 21s
Python / static (pull_request) Successful in 1m0s
Rust / fmt (pull_request) Successful in 1m6s
C++ / build (pull_request) Successful in 2m38s
Rust / build (pull_request) Successful in 2m18s
Rust / clippy (pull_request) Successful in 1m51s
Python / verify (pull_request) Successful in 3m28s
C++ / clang-tidy (pull_request) Successful in 7m1s
2026-08-17 16:54:49 +02:00
zach c7bf7cdc0b fix recursive wallpaper model in settings + clock font 2026-08-17 16:53:47 +02:00
zach 177b0f319c proper color scheme preview to avoid watcher creation spam
C++ / fmt (pull_request) Successful in 6s
JS/TS / fmt (pull_request) Successful in 19s
JS/TS / lint (pull_request) Successful in 21s
Python / static (pull_request) Successful in 53s
Rust / fmt (pull_request) Successful in 47s
C++ / build (pull_request) Successful in 1m54s
Rust / build (pull_request) Successful in 2m7s
Rust / clippy (pull_request) Successful in 1m31s
Python / verify (pull_request) Successful in 2m57s
C++ / clang-tidy (pull_request) Successful in 3m49s
2026-08-17 14:23:24 +02:00
zach d3e781731e Merge branch 'main' into feat/wallpaper-picker-rework
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 19s
JS/TS / lint (pull_request) Successful in 21s
Python / static (pull_request) Successful in 1m1s
Rust / fmt (pull_request) Successful in 1m2s
Rust / build (pull_request) Successful in 2m8s
Rust / clippy (pull_request) Successful in 1m31s
C++ / build (pull_request) Successful in 2m42s
Python / verify (pull_request) Successful in 2m57s
C++ / clang-tidy (pull_request) Successful in 4m3s
2026-08-17 13:24:22 +02:00
zach abb180c947 fix status icons visibility in fullscreen + wrong property name 2026-08-17 13:24:08 +02:00
zach babfe4b3de flick velocity and deceleration
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 17s
Python / static (pull_request) Successful in 58s
Rust / fmt (pull_request) Successful in 1m4s
Rust / build (pull_request) Successful in 2m14s
C++ / build (pull_request) Successful in 2m36s
Rust / clippy (pull_request) Successful in 1m30s
Python / verify (pull_request) Successful in 2m53s
C++ / clang-tidy (pull_request) Successful in 4m4s
2026-08-17 10:37:00 +02:00
zach 31f73b05e4 Merge branch 'main' into feat/wallpaper-picker-rework
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 21s
Python / static (pull_request) Successful in 1m6s
Rust / fmt (pull_request) Successful in 55s
Rust / build (pull_request) Successful in 2m13s
C++ / build (pull_request) Successful in 2m38s
Rust / clippy (pull_request) Successful in 1m20s
Python / verify (pull_request) Successful in 3m10s
C++ / clang-tidy (pull_request) Successful in 4m3s
2026-08-16 23:24:51 +02:00
zach a6c1985bff Merge pull request 'Feat: Reveal bar on hover in fullscreen' (#149) from feat/fullscreen-reveal-bar into main
Reviewed-on: #149
2026-08-16 23:24:36 +02:00
zach 69c2bfae9d fix search trickle through actions and variants view and mouse event stealing by wallpapertile
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Successful in 1m6s
Rust / fmt (pull_request) Successful in 1m3s
Rust / build (pull_request) Successful in 2m17s
Rust / clippy (pull_request) Successful in 1m29s
C++ / build (pull_request) Successful in 2m48s
Python / verify (pull_request) Successful in 3m16s
C++ / clang-tidy (pull_request) Successful in 4m17s
2026-08-16 23:13:37 +02:00
Inorishio 6071223009 Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 19s
Python / static (pull_request) Successful in 57s
Rust / fmt (pull_request) Successful in 1m9s
Rust / build (pull_request) Successful in 2m11s
C++ / build (pull_request) Successful in 2m34s
Rust / clippy (pull_request) Successful in 1m30s
Python / verify (pull_request) Successful in 2m55s
C++ / clang-tidy (pull_request) Successful in 3m59s
2026-08-16 22:57:54 +02:00
zach 43647dd486 fix currentItem property and signals
C++ / fmt (pull_request) Successful in 4s
JS/TS / lint (pull_request) Successful in 16s
JS/TS / fmt (pull_request) Successful in 18s
Python / static (pull_request) Successful in 58s
Rust / fmt (pull_request) Successful in 59s
C++ / build (pull_request) Successful in 2m16s
Rust / build (pull_request) Successful in 2m5s
Rust / clippy (pull_request) Successful in 1m30s
Python / verify (pull_request) Successful in 2m43s
C++ / clang-tidy (pull_request) Successful in 3m58s
2026-08-16 20:45:55 +02:00
zach 43f070f887 clean-up plugin files
C++ / fmt (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 15s
Python / static (pull_request) Successful in 55s
Rust / fmt (pull_request) Successful in 32s
Rust / build (pull_request) Successful in 1m52s
C++ / build (pull_request) Successful in 3m56s
Rust / clippy (pull_request) Successful in 1m30s
Python / verify (pull_request) Successful in 3m3s
C++ / clang-tidy (pull_request) Successful in 5m20s
2026-08-16 20:29:48 +02:00
zach 4e75f21446 improve sizing and dynamic width for view
C++ / fmt (pull_request) Successful in 6s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 17s
Python / static (pull_request) Successful in 1m2s
Rust / fmt (pull_request) Successful in 1m5s
C++ / build (pull_request) Successful in 2m36s
Rust / build (pull_request) Successful in 2m27s
Rust / clippy (pull_request) Successful in 1m51s
Python / verify (pull_request) Successful in 3m33s
C++ / clang-tidy (pull_request) Successful in 7m12s
2026-08-16 20:28:21 +02:00
zach 654632128f clean-up plugin files
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 22s
Rust / fmt (pull_request) Successful in 56s
Python / static (pull_request) Successful in 1m0s
C++ / build (pull_request) Successful in 2m9s
Rust / build (pull_request) Successful in 2m8s
Rust / clippy (pull_request) Successful in 1m9s
Python / verify (pull_request) Successful in 2m57s
C++ / clang-tidy (pull_request) Successful in 3m58s
2026-08-16 18:14:04 +02:00
zach 797fe31b2b Merge branch 'main' into feat/fullscreen-reveal-bar
C++ / fmt (pull_request) Successful in 8s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 15s
Python / static (pull_request) Successful in 51s
Rust / fmt (pull_request) Successful in 40s
Rust / build (pull_request) Successful in 1m45s
C++ / build (pull_request) Successful in 3m40s
Rust / clippy (pull_request) Successful in 1m32s
Python / verify (pull_request) Successful in 3m19s
C++ / clang-tidy (pull_request) Successful in 5m21s
2026-08-16 18:04:01 +02:00
zach 4037a1dd0f Merge branch 'main' into feat/wallpaper-picker-rework
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 16s
Python / static (pull_request) Successful in 58s
Rust / fmt (pull_request) Successful in 1m9s
C++ / build (pull_request) Successful in 2m25s
Rust / build (pull_request) Successful in 2m19s
Rust / clippy (pull_request) Successful in 1m53s
Python / verify (pull_request) Successful in 3m37s
C++ / clang-tidy (pull_request) Successful in 7m5s
2026-08-16 18:03:53 +02:00
zach 03ef51cd0c Merge pull request 'Feat: Peripheral status and integration' (#140) from feat/peripheral-integration into main
Reviewed-on: #140
Reviewed-by: AramJonghu <aramjonghu@tutamail.com>
2026-08-16 18:03:42 +02:00
zach a41c0adb3e move carouselview to standalone c++ component, fix crash due to memory leak in wallpaper object creation
C++ / fmt (pull_request) Successful in 3s
JS/TS / lint (pull_request) Successful in 18s
JS/TS / fmt (pull_request) Successful in 17s
Python / fmt (pull_request) Successful in 28s
Python / lint (pull_request) Successful in 30s
Python / test (pull_request) Successful in 1m5s
Python / typecheck (pull_request) Successful in 1m24s
Rust / fmt (pull_request) Successful in 31s
C++ / build (pull_request) Successful in 2m21s
Rust / build (pull_request) Successful in 1m47s
Python / buildcheck (pull_request) Successful in 2m18s
Rust / clippy (pull_request) Successful in 1m12s
C++ / clang-tidy (pull_request) Successful in 4m3s
2026-08-16 17:59:23 +02:00
zach 33c9432390 fix python errors
C++ / fmt (pull_request) Successful in 10s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 15s
Python / static (pull_request) Successful in 53s
Rust / fmt (pull_request) Successful in 1m6s
Rust / build (pull_request) Successful in 2m10s
C++ / build (pull_request) Successful in 2m37s
Rust / clippy (pull_request) Successful in 1m33s
Python / verify (pull_request) Successful in 2m53s
C++ / clang-tidy (pull_request) Successful in 3m59s
2026-08-16 13:11:17 +02:00
zach bfaba488f3 Merge branch 'main' into feat/peripheral-integration
C++ / fmt (pull_request) Successful in 7s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 15s
Python / static (pull_request) Successful in 59s
Rust / fmt (pull_request) Successful in 50s
Python / verify (pull_request) Failing after 2m37s
Rust / build (pull_request) Successful in 2m1s
C++ / build (pull_request) Successful in 4m7s
Rust / clippy (pull_request) Successful in 1m36s
C++ / clang-tidy (pull_request) Successful in 5m17s
2026-08-16 12:51:07 +02:00
zach 0c788ca4dc Merge branch 'main' into feat/wallpaper-picker-rework
C++ / fmt (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 13s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Successful in 26s
Python / lint (pull_request) Successful in 24s
Python / test (pull_request) Successful in 55s
Python / typecheck (pull_request) Successful in 1m9s
Rust / fmt (pull_request) Successful in 41s
Python / buildcheck (pull_request) Successful in 2m36s
C++ / build (pull_request) Successful in 4m3s
Rust / build (pull_request) Successful in 1m53s
Rust / clippy (pull_request) Successful in 1m44s
C++ / clang-tidy (pull_request) Successful in 8m35s
2026-08-16 12:50:58 +02:00
zach 4f4b3ce8c6 Merge branch 'main' into feat/fullscreen-reveal-bar
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 18s
JS/TS / lint (pull_request) Successful in 18s
Python / fmt (pull_request) Successful in 34s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 1m1s
Python / typecheck (pull_request) Successful in 1m21s
Rust / fmt (pull_request) Successful in 33s
C++ / build (pull_request) Successful in 2m17s
Rust / build (pull_request) Successful in 1m43s
Python / buildcheck (pull_request) Successful in 2m26s
Rust / clippy (pull_request) Successful in 1m39s
C++ / clang-tidy (pull_request) Successful in 7m58s
2026-08-16 12:50:52 +02:00
zach c42feec312 clean up code in updates singleton and popout 2026-08-16 12:50:39 +02:00
zach 4f544981c1 delete unknown json objects + write missing objects to config file 2026-08-16 12:17:50 +02:00
zach ba567efc22 fix unused vars
C++ / fmt (pull_request) Successful in 7s
Python / static (pull_request) Failing after 2s
JS/TS / fmt (pull_request) Successful in 25s
JS/TS / lint (pull_request) Successful in 21s
Rust / fmt (pull_request) Successful in 55s
Rust / build (pull_request) Successful in 2m22s
Python / verify (pull_request) Failing after 2m41s
Rust / clippy (pull_request) Successful in 1m45s
C++ / build (pull_request) Successful in 3m38s
C++ / clang-tidy (pull_request) Successful in 4m46s
2026-08-16 11:23:57 +02:00
zach cbb24a2c40 Merge branch 'main' into feat/fullscreen-reveal-bar
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 19s
Python / fmt (pull_request) Successful in 31s
Python / lint (pull_request) Successful in 34s
Python / test (pull_request) Successful in 56s
Python / typecheck (pull_request) Successful in 1m23s
Rust / fmt (pull_request) Successful in 41s
C++ / build (pull_request) Successful in 2m35s
Rust / build (pull_request) Successful in 1m49s
Rust / clippy (pull_request) Successful in 59s
Python / buildcheck (pull_request) Successful in 2m19s
C++ / clang-tidy (pull_request) Successful in 6m2s
2026-08-16 11:20:59 +02:00
zach da21675f03 Merge branch 'main' into feat/peripheral-integration
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 18s
JS/TS / lint (pull_request) Successful in 20s
Python / static (pull_request) Failing after 59s
Rust / fmt (pull_request) Successful in 1m4s
Rust / build (pull_request) Successful in 1m56s
C++ / build (pull_request) Successful in 2m31s
Python / verify (pull_request) Failing after 2m26s
Rust / clippy (pull_request) Successful in 1m22s
C++ / clang-tidy (pull_request) Successful in 3m53s
2026-08-16 11:11:36 +02:00
zach 02bb7e43d8 Merge pull request 'Feat: Reposition bar' (#147) from feat/reposition-bar into main
Reviewed-on: #147
Reviewed-by: AramJonghu <aramjonghu@tutamail.com>
2026-08-16 11:02:22 +02:00
AramJonghu 8883d98b00 chore: added missing dependency to pyproject
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 17s
JS/TS / lint (pull_request) Successful in 21s
Python / static (pull_request) Failing after 1m24s
Rust / fmt (pull_request) Successful in 1m16s
C++ / build (pull_request) Successful in 2m26s
Rust / build (pull_request) Successful in 2m7s
Rust / clippy (pull_request) Successful in 1m4s
Python / verify (pull_request) Failing after 3m16s
C++ / clang-tidy (pull_request) Successful in 3m53s
2026-08-15 22:07:45 +02:00
AramJonghu cf907089da ci(python): rework of jobs + fix import issue 2026-08-15 22:07:45 +02:00
zach 070fe7add0 Merge branch 'main' into feat/peripheral-integration
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 20s
JS/TS / lint (pull_request) Successful in 18s
Python / lint (pull_request) Failing after 30s
Python / fmt (pull_request) Successful in 35s
Python / test (pull_request) Failing after 1m1s
Python / typecheck (pull_request) Failing after 1m24s
Rust / fmt (pull_request) Successful in 33s
Rust / build (pull_request) Successful in 1m51s
C++ / build (pull_request) Successful in 2m42s
Rust / clippy (pull_request) Successful in 1m1s
Python / buildcheck (pull_request) Successful in 2m34s
C++ / clang-tidy (pull_request) Successful in 4m2s
2026-08-15 20:54:59 +02:00
Inorishio 2d40ce33d1 Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 17s
JS/TS / lint (pull_request) Successful in 16s
Python / fmt (pull_request) Successful in 31s
Python / lint (pull_request) Successful in 41s
Python / test (pull_request) Successful in 1m5s
Python / typecheck (pull_request) Successful in 1m15s
Rust / clippy (pull_request) Failing after 12m43s
Rust / fmt (pull_request) Failing after 12m57s
Rust / build (pull_request) Failing after 13m33s
Python / buildcheck (pull_request) Failing after 13m45s
C++ / clang-tidy (pull_request) Failing after 14m23s
C++ / build (pull_request) Failing after 14m25s
2026-08-15 20:38:54 +02:00
zach 052e3a7800 Merge branch 'main' into feat/fullscreen-reveal-bar
C++ / fmt (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 14s
JS/TS / lint (pull_request) Successful in 16s
Python / fmt (pull_request) Successful in 25s
Python / lint (pull_request) Successful in 26s
Python / test (pull_request) Successful in 52s
Python / typecheck (pull_request) Successful in 1m6s
Rust / fmt (pull_request) Successful in 33s
Rust / build (pull_request) Successful in 1m30s
Python / buildcheck (pull_request) Successful in 2m17s
C++ / build (pull_request) Successful in 3m49s
Rust / clippy (pull_request) Successful in 1m11s
C++ / clang-tidy (pull_request) Successful in 5m7s
2026-08-15 19:53:59 +02:00
zach fb0c75a2ba remove debug logging
C++ / fmt (pull_request) Successful in 8s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Successful in 24s
Python / lint (pull_request) Successful in 25s
Python / test (pull_request) Successful in 48s
Python / typecheck (pull_request) Successful in 1m1s
Rust / fmt (pull_request) Successful in 34s
Rust / build (pull_request) Successful in 1m45s
C++ / build (pull_request) Successful in 4m18s
Python / buildcheck (pull_request) Successful in 2m20s
Rust / clippy (pull_request) Successful in 1m1s
C++ / clang-tidy (pull_request) Successful in 5m23s
2026-08-15 19:38:33 +02:00
zach 5efa0de3a5 WidgetBase for bar widget + fixed resources and dashboard popouts in left- and bottom-edge bar
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 17s
JS/TS / lint (pull_request) Successful in 20s
Python / fmt (pull_request) Successful in 31s
Python / lint (pull_request) Successful in 30s
Python / test (pull_request) Successful in 1m40s
Python / typecheck (pull_request) Successful in 1m38s
C++ / build (pull_request) Successful in 2m15s
Rust / build (pull_request) Successful in 1m41s
Rust / fmt (pull_request) Successful in 59s
Python / buildcheck (pull_request) Successful in 2m40s
Rust / clippy (pull_request) Successful in 2m15s
C++ / clang-tidy (pull_request) Successful in 6m29s
2026-08-15 19:37:44 +02:00
zach 858ef68565 Merge branch 'main' into feat/reposition-bar
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 21s
JS/TS / lint (pull_request) Successful in 22s
Python / lint (pull_request) Successful in 31s
Python / fmt (pull_request) Successful in 34s
Python / test (pull_request) Successful in 59s
Python / typecheck (pull_request) Successful in 1m22s
Rust / fmt (pull_request) Successful in 34s
Rust / build (pull_request) Successful in 1m46s
C++ / build (pull_request) Successful in 2m33s
Rust / clippy (pull_request) Successful in 1m5s
Python / buildcheck (pull_request) Successful in 2m29s
C++ / clang-tidy (pull_request) Successful in 3m56s
2026-08-15 18:04:14 +02:00
zach 5e97ea6867 Merge pull request 'CI: typecheck fixes include dependencies + arch Dockerfile build fix' (#148) from fix/cli-typecheck into main
Reviewed-on: #148
Reviewed-by: zach <zach@brohn.se>
2026-08-15 18:03:54 +02:00
zach 9823c66299 restructure bar components layout
C++ / fmt (pull_request) Successful in 4s
JS/TS / lint (pull_request) Successful in 19s
JS/TS / fmt (pull_request) Successful in 21s
Python / fmt (pull_request) Successful in 30s
Python / lint (pull_request) Successful in 31s
Python / test (pull_request) Successful in 1m2s
Python / typecheck (pull_request) Failing after 1m5s
Rust / fmt (pull_request) Successful in 43s
C++ / build (pull_request) Successful in 2m25s
Rust / build (pull_request) Successful in 1m42s
Python / buildcheck (pull_request) Successful in 2m27s
Rust / clippy (pull_request) Successful in 1m29s
C++ / clang-tidy (pull_request) Successful in 3m56s
2026-08-15 14:18:00 +02:00
zach 0092b41a05 fix config file writing + positioning issues
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 22s
JS/TS / lint (pull_request) Successful in 27s
Python / fmt (pull_request) Successful in 33s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 1m3s
Python / typecheck (pull_request) Failing after 1m4s
Rust / fmt (pull_request) Successful in 41s
Rust / build (pull_request) Successful in 1m39s
C++ / build (pull_request) Successful in 2m33s
Rust / clippy (pull_request) Successful in 1m18s
Python / buildcheck (pull_request) Successful in 2m23s
C++ / clang-tidy (pull_request) Successful in 3m56s
2026-08-14 22:15:11 +02:00
AramJonghu abf7382771 ci: libcava dev headers
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 16s
Python / lint (pull_request) Successful in 33s
Python / fmt (pull_request) Successful in 36s
Python / test (pull_request) Successful in 1m3s
Python / typecheck (pull_request) Successful in 1m21s
Rust / fmt (pull_request) Successful in 33s
C++ / build (pull_request) Successful in 2m26s
Rust / build (pull_request) Successful in 1m43s
Python / buildcheck (pull_request) Successful in 2m12s
Rust / clippy (pull_request) Successful in 1m41s
C++ / clang-tidy (pull_request) Successful in 4m10s
2026-08-14 20:00:27 +02:00
zach ec80adf0a5 improve image caching for better performance
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 27s
JS/TS / lint (pull_request) Successful in 33s
Python / fmt (pull_request) Successful in 27s
Python / lint (pull_request) Successful in 30s
Python / test (pull_request) Successful in 59s
Python / typecheck (pull_request) Failing after 1m5s
C++ / build (pull_request) Successful in 2m26s
Rust / build (pull_request) Successful in 1m30s
Rust / fmt (pull_request) Successful in 1m12s
Python / buildcheck (pull_request) Successful in 2m19s
Rust / clippy (pull_request) Successful in 1m56s
C++ / clang-tidy (pull_request) Successful in 4m1s
2026-08-14 19:49:48 +02:00
AramJonghu a1a8a6e695 ci: add relevant dependencies; resolves MissingImport
C++ / fmt (pull_request) Successful in 4s
JS/TS / lint (pull_request) Successful in 15s
JS/TS / fmt (pull_request) Successful in 21s
Python / fmt (pull_request) Successful in 31s
Python / lint (pull_request) Successful in 34s
Python / test (pull_request) Successful in 1m0s
Python / typecheck (pull_request) Successful in 1m20s
Rust / fmt (pull_request) Successful in 34s
C++ / build (pull_request) Successful in 2m29s
Rust / build (pull_request) Successful in 1m45s
Rust / clippy (pull_request) Successful in 1m2s
Python / buildcheck (pull_request) Successful in 2m18s
C++ / clang-tidy (pull_request) Successful in 3m52s
2026-08-14 19:40:28 +02:00
zach 88a2808a47 clean up imports
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 19s
JS/TS / lint (pull_request) Successful in 23s
Python / fmt (pull_request) Successful in 35s
Python / lint (pull_request) Successful in 34s
Python / test (pull_request) Successful in 1m1s
Python / typecheck (pull_request) Failing after 1m0s
Rust / fmt (pull_request) Successful in 55s
Rust / build (pull_request) Successful in 1m41s
C++ / build (pull_request) Successful in 2m37s
Rust / clippy (pull_request) Successful in 1m19s
Python / buildcheck (pull_request) Successful in 2m27s
C++ / clang-tidy (pull_request) Successful in 3m55s
2026-08-14 19:23:47 +02:00
zach a13daf27a6 Merge branch 'main' into feat/wallpaper-picker-rework
C++ / fmt (pull_request) Successful in 8s
JS/TS / fmt (pull_request) Successful in 13s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Successful in 25s
Python / lint (pull_request) Successful in 28s
Python / test (pull_request) Successful in 55s
Python / typecheck (pull_request) Failing after 58s
Rust / fmt (pull_request) Successful in 44s
C++ / build (pull_request) Successful in 3m35s
Rust / build (pull_request) Successful in 1m53s
Python / buildcheck (pull_request) Successful in 2m36s
Rust / clippy (pull_request) Successful in 1m28s
C++ / clang-tidy (pull_request) Successful in 5m15s
2026-08-14 19:18:15 +02:00
zach abef1eb259 Merge branch 'main' into feat/reposition-bar
C++ / fmt (pull_request) Successful in 4s
JS/TS / lint (pull_request) Successful in 16s
JS/TS / fmt (pull_request) Successful in 23s
Python / fmt (pull_request) Successful in 33s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 1m0s
Python / typecheck (pull_request) Failing after 1m8s
Rust / fmt (pull_request) Successful in 34s
Rust / build (pull_request) Successful in 1m46s
C++ / build (pull_request) Successful in 2m33s
Rust / clippy (pull_request) Successful in 1m30s
Python / buildcheck (pull_request) Successful in 2m41s
C++ / clang-tidy (pull_request) Successful in 7m9s
2026-08-14 19:16:01 +02:00
zach ef8811724f better height/width for bar
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 14s
JS/TS / lint (pull_request) Successful in 19s
Python / lint (pull_request) Successful in 30s
Python / fmt (pull_request) Successful in 34s
Python / test (pull_request) Successful in 1m0s
Python / typecheck (pull_request) Failing after 1m18s
C++ / build (pull_request) Successful in 1m49s
Rust / fmt (pull_request) Successful in 33s
Rust / build (pull_request) Successful in 1m33s
Rust / clippy (pull_request) Successful in 1m17s
Python / buildcheck (pull_request) Successful in 2m40s
C++ / clang-tidy (pull_request) Successful in 3m25s
2026-08-14 18:03:56 +02:00
zach 4517e5ecd0 left-bar working, bottom bar semi-broken 2026-08-14 16:31:52 +02:00
zach 75de349ff4 MD3 wallpaper launcher carousel
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 20s
JS/TS / lint (pull_request) Successful in 20s
Python / lint (pull_request) Successful in 32s
Python / fmt (pull_request) Successful in 36s
Python / test (pull_request) Successful in 1m3s
Python / typecheck (pull_request) Failing after 1m8s
C++ / build (pull_request) Successful in 1m49s
Rust / build (pull_request) Successful in 1m31s
Rust / fmt (pull_request) Successful in 47s
Rust / clippy (pull_request) Successful in 1m32s
Python / buildcheck (pull_request) Successful in 2m34s
C++ / clang-tidy (pull_request) Successful in 3m22s
2026-08-14 00:15:52 +02:00
zach 1d150292d3 initial commit for repositioning bar, currently broken 2026-08-12 20:15:55 +02:00
zach 439a14d29e Initial commit implementing reveal on hover in fullscreen
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 20s
JS/TS / lint (pull_request) Successful in 26s
Python / fmt (pull_request) Successful in 34s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 59s
Python / typecheck (pull_request) Failing after 1m2s
C++ / build (pull_request) Successful in 1m50s
Rust / fmt (pull_request) Successful in 46s
Rust / build (pull_request) Successful in 1m40s
Python / buildcheck (pull_request) Successful in 2m27s
Rust / clippy (pull_request) Successful in 2m5s
C++ / clang-tidy (pull_request) Successful in 5m56s
2026-08-12 01:35:23 +02:00
zach bf5a8b8049 simplify dedupe
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 19s
Python / fmt (pull_request) Successful in 34s
Python / lint (pull_request) Failing after 31s
Python / test (pull_request) Failing after 59s
Python / typecheck (pull_request) Failing after 1m7s
C++ / build (pull_request) Successful in 1m54s
Rust / fmt (pull_request) Successful in 49s
Rust / build (pull_request) Successful in 1m41s
Rust / clippy (pull_request) Successful in 1m33s
Python / buildcheck (pull_request) Successful in 2m38s
C++ / clang-tidy (pull_request) Successful in 3m31s
2026-07-13 22:10:41 +02:00
zach a9bbb21f54 improve device detection when plugging/unplugging
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 19s
JS/TS / lint (pull_request) Successful in 19s
Python / fmt (pull_request) Successful in 36s
Python / lint (pull_request) Failing after 34s
Python / test (pull_request) Failing after 1m2s
Python / typecheck (pull_request) Failing after 1m11s
C++ / build (pull_request) Successful in 2m11s
Rust / fmt (pull_request) Successful in 52s
Rust / build (pull_request) Successful in 1m45s
Rust / clippy (pull_request) Successful in 1m35s
Python / buildcheck (pull_request) Successful in 2m44s
C++ / clang-tidy (pull_request) Successful in 3m37s
2026-07-13 19:31:40 +02:00
zach bb50cf756d fix: cmake nuitka command again
C++ / fmt (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 13s
JS/TS / lint (pull_request) Successful in 17s
Python / fmt (pull_request) Successful in 43s
Python / lint (pull_request) Failing after 32s
Python / test (pull_request) Failing after 57s
Python / typecheck (pull_request) Failing after 1m0s
C++ / build (pull_request) Successful in 3m5s
Rust / fmt (pull_request) Successful in 41s
Rust / build (pull_request) Successful in 1m39s
Python / buildcheck (pull_request) Successful in 2m36s
Rust / clippy (pull_request) Successful in 1m21s
C++ / clang-tidy (pull_request) Successful in 4m49s
2026-07-13 15:03:16 +02:00
zach 57147dc7c1 fix: cmake nuitka command
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 16s
Python / lint (pull_request) Failing after 32s
Python / fmt (pull_request) Successful in 36s
Python / test (pull_request) Failing after 1m3s
C++ / build (pull_request) Successful in 1m39s
Python / typecheck (pull_request) Failing after 1m9s
Rust / fmt (pull_request) Successful in 38s
Rust / build (pull_request) Successful in 1m33s
Python / buildcheck (pull_request) Successful in 2m48s
Rust / clippy (pull_request) Successful in 2m1s
C++ / clang-tidy (pull_request) Successful in 5m51s
2026-07-13 14:59:52 +02:00
zach 620fe001a4 backend now more robust, threaded writes use locks
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 21s
Python / lint (pull_request) Failing after 31s
Python / fmt (pull_request) Successful in 38s
Python / test (pull_request) Failing after 1m0s
Python / typecheck (pull_request) Failing after 1m1s
C++ / build (pull_request) Successful in 2m0s
Rust / fmt (pull_request) Successful in 44s
Rust / build (pull_request) Successful in 1m40s
Rust / clippy (pull_request) Successful in 1m34s
Python / buildcheck (pull_request) Successful in 2m35s
C++ / clang-tidy (pull_request) Successful in 3m44s
2026-07-13 14:56:57 +02:00
zach 85320e9ea1 prevent writing battery json when there has been no change, filter out devices with unknown state
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Successful in 29s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 1m4s
Python / typecheck (pull_request) Failing after 1m10s
C++ / build (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 36s
Rust / build (pull_request) Successful in 1m34s
Rust / clippy (pull_request) Successful in 1m28s
Python / buildcheck (pull_request) Successful in 2m35s
C++ / clang-tidy (pull_request) Successful in 3m32s
2026-07-13 01:33:55 +02:00
zach 259a4dde7a gitignore
C++ / fmt (pull_request) Successful in 8s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Successful in 26s
Python / lint (pull_request) Successful in 31s
Python / test (pull_request) Successful in 54s
Python / typecheck (pull_request) Failing after 50s
C++ / build (pull_request) Successful in 3m19s
Rust / fmt (pull_request) Successful in 41s
Rust / build (pull_request) Successful in 1m43s
Python / buildcheck (pull_request) Successful in 2m35s
Rust / clippy (pull_request) Successful in 1m27s
C++ / clang-tidy (pull_request) Successful in 4m52s
2026-07-13 01:13:57 +02:00
zach d6230bef34 Fix cmake nuitka build command
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 20s
Python / fmt (pull_request) Successful in 31s
Python / lint (pull_request) Successful in 33s
Python / test (pull_request) Successful in 1m0s
Python / typecheck (pull_request) Failing after 1m5s
C++ / build (pull_request) Successful in 1m39s
Rust / fmt (pull_request) Successful in 50s
Rust / build (pull_request) Successful in 1m41s
Python / buildcheck (pull_request) Successful in 2m35s
Rust / clippy (pull_request) Successful in 2m5s
C++ / clang-tidy (pull_request) Successful in 6m13s
2026-07-13 01:12:55 +02:00
zach 4ee9bb3f12 add support for all UPower peripherals
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 17s
JS/TS / lint (pull_request) Successful in 16s
Python / fmt (pull_request) Successful in 34s
Python / lint (pull_request) Successful in 35s
Python / typecheck (pull_request) Failing after 1m5s
C++ / build (pull_request) Successful in 1m36s
Python / test (pull_request) Successful in 1m26s
Rust / fmt (pull_request) Successful in 48s
Rust / build (pull_request) Successful in 1m41s
Rust / clippy (pull_request) Successful in 1m37s
Python / buildcheck (pull_request) Successful in 2m41s
C++ / clang-tidy (pull_request) Successful in 3m32s
2026-07-13 00:48:05 +02:00
zach 8789d2d322 initial addition of battery display
JS/TS / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 9s
Python / fmt (pull_request) Successful in 15s
Python / lint (pull_request) Failing after 19s
Python / typecheck (pull_request) Failing after 1m5s
C++ / fmt (pull_request) Successful in 6s
Python / test (pull_request) Successful in 1m20s
Rust / fmt (pull_request) Successful in 1m42s
Python / buildcheck (pull_request) Successful in 2m23s
Rust / clippy (pull_request) Successful in 2m42s
Rust / build (pull_request) Successful in 2m53s
C++ / build (pull_request) Successful in 2m22s
C++ / clang-tidy (pull_request) Successful in 3m17s
2026-07-12 17:47:25 +02:00
zach 954dc10fbb bluetooth model delegate animations
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 19s
JS/TS / lint (pull_request) Successful in 21s
Python / fmt (pull_request) Successful in 35s
Python / lint (pull_request) Successful in 36s
Python / test (pull_request) Successful in 1m3s
Python / typecheck (pull_request) Failing after 1m4s
C++ / build (pull_request) Successful in 1m52s
Rust / build (pull_request) Successful in 1m41s
Rust / fmt (pull_request) Successful in 1m6s
Python / buildcheck (pull_request) Successful in 2m28s
Rust / clippy (pull_request) Successful in 1m55s
C++ / clang-tidy (pull_request) Successful in 3m36s
2026-07-11 14:00:08 +02:00
zach 15ccfd24c6 Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 16s
Python / fmt (pull_request) Successful in 32s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 1m3s
Python / typecheck (pull_request) Failing after 1m3s
C++ / build (pull_request) Successful in 1m41s
Rust / build (pull_request) Successful in 1m42s
Rust / fmt (pull_request) Successful in 1m17s
Python / buildcheck (pull_request) Successful in 2m43s
Rust / clippy (pull_request) Successful in 2m7s
C++ / clang-tidy (pull_request) Successful in 3m43s
2026-07-10 18:20:25 +02:00
zach 33b08523e9 add preliminary bluetooth widget + popout
C++ / fmt (pull_request) Successful in 3s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 17s
Python / fmt (pull_request) Successful in 35s
Python / lint (pull_request) Successful in 34s
Python / test (pull_request) Successful in 1m0s
C++ / build (pull_request) Successful in 1m46s
Rust / fmt (pull_request) Successful in 1m19s
Rust / build (pull_request) Successful in 1m54s
Python / buildcheck (pull_request) Successful in 2m42s
Rust / clippy (pull_request) Successful in 1m54s
C++ / clang-tidy (pull_request) Successful in 3m42s
2026-07-10 16:17:47 +02:00
zach 742bece30b Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 7s
JS/TS / fmt (pull_request) Successful in 14s
JS/TS / lint (pull_request) Successful in 20s
Python / fmt (pull_request) Successful in 28s
Python / lint (pull_request) Successful in 29s
Python / test (pull_request) Successful in 59s
Rust / fmt (pull_request) Successful in 57s
C++ / build (pull_request) Successful in 3m47s
Rust / build (pull_request) Successful in 2m10s
Python / buildcheck (pull_request) Successful in 2m51s
Rust / clippy (pull_request) Successful in 1m32s
C++ / clang-tidy (pull_request) Successful in 5m9s
2026-07-10 15:23:23 +02:00
zach a64eadd14a feat: add password overlay for network popout + redesign
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 24s
Python / fmt (pull_request) Successful in 30s
Python / lint (pull_request) Successful in 34s
Python / test (pull_request) Successful in 53s
C++ / build (pull_request) Successful in 1m52s
Rust / fmt (pull_request) Successful in 1m14s
Rust / build (pull_request) Successful in 1m57s
Rust / clippy (pull_request) Successful in 2m0s
Python / buildcheck (pull_request) Successful in 2m37s
C++ / clang-tidy (pull_request) Successful in 3m43s
2026-07-09 17:33:13 +02:00
zach f9eeb793db Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 18s
JS/TS / lint (pull_request) Successful in 19s
Python / fmt (pull_request) Successful in 32s
Python / lint (pull_request) Successful in 36s
Python / test (pull_request) Successful in 55s
C++ / build (pull_request) Successful in 1m55s
Rust / fmt (pull_request) Successful in 1m15s
Rust / build (pull_request) Successful in 1m59s
Python / buildcheck (pull_request) Successful in 2m45s
Rust / clippy (pull_request) Successful in 1m55s
C++ / clang-tidy (pull_request) Successful in 3m41s
2026-07-09 14:22:34 +02:00
zach 6bb193c78c Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 19s
JS/TS / lint (pull_request) Successful in 22s
Python / lint (pull_request) Successful in 33s
Python / fmt (pull_request) Successful in 36s
Python / test (pull_request) Successful in 1m5s
C++ / build (pull_request) Successful in 1m42s
Rust / fmt (pull_request) Successful in 1m11s
Rust / build (pull_request) Successful in 1m56s
Python / buildcheck (pull_request) Successful in 2m40s
Rust / clippy (pull_request) Successful in 1m48s
C++ / clang-tidy (pull_request) Successful in 3m42s
2026-07-08 22:56:46 +02:00
Inorishio aadecbfe44 Chore: created Spacer component, moved x available networks, added connected networks
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 59s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m31s
C++ / build (pull_request) Successful in 2m37s
2026-07-08 22:22:03 +02:00
zach 53f0aa80ae Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 30s
Python / test (pull_request) Successful in 42s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m36s
C++ / build (pull_request) Successful in 2m20s
2026-07-08 13:41:27 +02:00
Inorishio d9856c9409 Chore: created NetworkItem component, renamed network header to Wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 14s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m51s
C++ / build (pull_request) Successful in 2m36s
2026-07-02 22:46:09 +02:00
zach 8dc9753e2b feat: don't add the same network twice
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 30s
Python / lint-format (pull_request) Successful in 48s
Python / test (pull_request) Successful in 40s
C++ / build (pull_request) Successful in 2m8s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m13s
2026-07-02 22:09:25 +02:00
zach 2a4bd3dfdd chore: remove old code
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 17s
Python / lint-format (pull_request) Successful in 31s
Python / test (pull_request) Successful in 39s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m31s
C++ / build (pull_request) Successful in 2m17s
2026-07-02 21:58:01 +02:00
zach f75f51ef50 fix: sort is now correct
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 18s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 48s
C++ / build (pull_request) Successful in 2m59s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m12s
2026-07-02 21:54:58 +02:00
zach e0873304d1 add sorting by strength
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 15s
Python / lint-format (pull_request) Successful in 32s
Python / test (pull_request) Successful in 39s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m45s
C++ / build (pull_request) Successful in 2m47s
2026-07-02 21:53:42 +02:00
zach 99a0155c17 Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 14s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m30s
C++ / build (pull_request) Successful in 2m32s
2026-07-02 21:46:15 +02:00
zach f309debea5 set scannerEnabled when popout is visible + remove scan button
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 18s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 47s
C++ / build (pull_request) Successful in 3m9s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m15s
2026-07-02 16:06:52 +02:00
zach 0979fd014d Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 16s
Python / lint-format (pull_request) Successful in 32s
Python / test (pull_request) Successful in 38s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m49s
C++ / build (pull_request) Successful in 3m5s
2026-07-02 15:16:22 +02:00
zach 656bd0a54d Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m37s
C++ / build (pull_request) Successful in 2m22s
2026-06-30 22:42:51 +02:00
zach f01001dcfa merge main into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 49s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m42s
C++ / build (pull_request) Successful in 2m28s
2026-06-30 18:15:43 +02:00
zach bc6b0d50ab Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 34s
Python / lint-format (pull_request) Successful in 35s
Python / test (pull_request) Successful in 1m11s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m58s
C++ / build (pull_request) Successful in 4m31s
2026-06-29 22:10:45 +02:00
zach f27c9afc25 more methods for wifi and scanning + layout
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 15s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-26 15:15:03 +02:00
Inorishio 5962fc8354 Merge branch 'main' into module/wifi
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 17s
Python / lint-format (pull_request) Successful in 27s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
2026-06-24 18:58:36 +02:00
Inorishio 1adcb9841c Added network properties, nicNames, netDevices, networkName < reminder I should use layouts.
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m52s
2026-06-22 23:34:58 +02:00
Inorishio 0e0f8555ed fixing singleton in helpers for network + getting output from systray 2026-06-15 20:48:01 +02:00
Inorishio 1d63e67554 removed network module and moved it to tray, have a popout window without content! everything still in wi.i.p. 2026-06-13 18:22:03 +02:00
116 changed files with 6245 additions and 2085 deletions
+22 -87
View File
@@ -1,12 +1,15 @@
name: Python name: Python
env:
APT_DEPS: git python3 python3-pip python3-venv python3-gi python3-cairo python3-dbus python3-pyudev python3-psutil python3-evdev python3-yaml python3-xlib python3-pillow gir1.2-gtk-3.0
on: on:
pull_request: pull_request:
jobs: jobs:
fmt: static:
runs-on: alpine runs-on: debian
container: node:26-alpine container: node:26-trixie-slim
steps: steps:
- name: Checkout - name: Checkout
@@ -14,10 +17,8 @@ jobs:
- name: Install tools - name: Install tools
run: | run: |
apk add --no-cache \ apt-get update
git \ apt-get install -y --no-install-recommends git python3 python3-pip python3-venv
python3 \
py3-pip
python3 -m venv .venv python3 -m venv .venv
. .venv/bin/activate . .venv/bin/activate
pip install --no-cache-dir ruff pip install --no-cache-dir ruff
@@ -27,32 +28,15 @@ jobs:
. .venv/bin/activate . .venv/bin/activate
ruff format --check . ruff format --check .
lint:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir ruff
- name: Lint - name: Lint
run: | run: |
. .venv/bin/activate . .venv/bin/activate
ruff check . ruff check .
test: verify:
runs-on: alpine if: always()
container: node:26-alpine runs-on: debian
container: node:26-trixie-slim
steps: steps:
- name: Checkout - name: Checkout
@@ -60,20 +44,16 @@ jobs:
- name: Install tools - name: Install tools
run: | run: |
apk add --no-cache \ apt-get update
git \ apt-get install -y --no-install-recommends $APT_DEPS build-essential python3-dev
python3 \ python3 -m venv --system-site-packages .venv
py3-pip \
py3-pillow \
build-base
python3 -m venv .venv
. .venv/bin/activate . .venv/bin/activate
pip install --no-cache-dir \ pip install --no-cache-dir basedpyright nuitka .
typer \
pillow \ - name: Type check
materialyoucolor \ run: |
jinja2 \ . .venv/bin/activate
pytest basedpyright
- name: Test - name: Test
run: | run: |
@@ -81,52 +61,7 @@ jobs:
cd cli cd cli
python -m pytest tests/ -v python -m pytest tests/ -v
typecheck:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir basedpyright
- name: Type check
run: |
. .venv/bin/activate
basedpyright
buildcheck:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip \
build-base \
python3-dev \
gcc \
g++
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir nuitka
- name: Nuitka module check - name: Nuitka module check
run: | run: |
. .venv/bin/activate . .venv/bin/activate
nuitka --module --include-package=zshell cli/src/zshell/ nuitka --module --include-package=zshell cli/src/zshell/
+2
View File
@@ -16,3 +16,5 @@ dist/
**/test-plugins/ **/test-plugins/
**/Charts/ **/Charts/
network-dev/ network-dev/
**/zshell.build/
**/zshell.dist/
+2
View File
@@ -93,6 +93,8 @@ if("shell" IN_LIST ENABLE_MODULES)
${NUITKA_EXECUTABLE} ${NUITKA_EXECUTABLE}
--standalone --standalone
--include-data-dir=${CMAKE_SOURCE_DIR}/cli/src/zshell/assets=zshell/assets --include-data-dir=${CMAKE_SOURCE_DIR}/cli/src/zshell/assets=zshell/assets
--include-package=gi.overrides
--include-package-data=solaar
--output-dir=${ZSHELL_CLI_BUILD_DIR} --output-dir=${ZSHELL_CLI_BUILD_DIR}
--output-filename=zshell-cli --output-filename=zshell-cli
${CMAKE_SOURCE_DIR}/cli/src/zshell/ ${CMAKE_SOURCE_DIR}/cli/src/zshell/
+1 -1
View File
@@ -15,7 +15,7 @@ CustomRect {
property color activeOnColor property color activeOnColor
property bool checked property bool checked
property real checkedRadius: Tokens.rounding.medium property real checkedRadius: Tokens.rounding.medium
property real defaultRadius: Tokens.rounding.large property real defaultRadius: Math.min(width, height) / 2
property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1) property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1)
property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38) property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38)
property bool fillWidth property bool fillWidth
+2
View File
@@ -34,6 +34,7 @@ BusyIndicator {
anchors.fill: parent anchors.fill: parent
bgColor: root.bgColor bgColor: root.bgColor
fgColor: root.fgColor fgColor: root.fgColor
hasEndIndicator: false
padding: root.padding padding: root.padding
rotation: manager.rotation rotation: manager.rotation
startAngle: manager.startFraction * 360 startAngle: manager.startFraction * 360
@@ -53,6 +54,7 @@ BusyIndicator {
Anim { Anim {
duration: manager.completeEndDuration * Tokens.anim.durations.scale duration: manager.completeEndDuration * Tokens.anim.durations.scale
properties: "opacity,internalStrokeWidth" properties: "opacity,internalStrokeWidth"
type: Anim.DefaultEffects
} }
} }
+5 -2
View File
@@ -13,11 +13,13 @@ Slider {
property bool animateWave property bool animateWave
property color bgColor: enabled ? Colors.palette.m3secondaryContainer : Qt.alpha(Colors.palette.m3onSurface, 0.1) property color bgColor: enabled ? Colors.palette.m3secondaryContainer : Qt.alpha(Colors.palette.m3onSurface, 0.1)
readonly property bool dragging: mouse.pressed
property color fgColor: enabled ? Colors.palette.m3primary : Qt.alpha(Colors.palette.m3onSurface, 0.38) property color fgColor: enabled ? Colors.palette.m3primary : Qt.alpha(Colors.palette.m3onSurface, 0.38)
property real filledWidth property real filledWidth
property alias inset: inset property alias inset: inset
property color insetColor: enabled ? (inset.attached ? Colors.palette.m3onSecondaryContainer : Colors.palette.m3onPrimary) : Colors.palette.m3onSurface property color insetColor: enabled ? (inset.attached ? Colors.palette.m3onSecondaryContainer : Colors.palette.m3onPrimary) : Colors.palette.m3onSurface
property string insetIcon: "" property string insetIcon: ""
property bool interactionOnMove: true
property real pos: visualPosition property real pos: visualPosition
property int radius: Tokens.rounding.small property int radius: Tokens.rounding.small
property int waveDuration: 1000 property int waveDuration: 1000
@@ -139,7 +141,7 @@ Slider {
CAnim { CAnim {
} }
} }
Anim on waveProgress { NumberAnimation on waveProgress {
duration: root.waveDuration duration: root.waveDuration
easing.type: Easing.Linear easing.type: Easing.Linear
from: 0 from: 0
@@ -186,7 +188,8 @@ Slider {
onPositionChanged: e => { onPositionChanged: e => {
dragMovement = (e.x - pressStartX) / width; dragMovement = (e.x - pressStartX) / width;
root.interaction(posBinding.value); if (root.interactionOnMove)
root.interaction(posBinding.value);
} }
onPressed: e => { onPressed: e => {
widthBehavior.enabled = false; widthBehavior.enabled = false;
+2
View File
@@ -23,6 +23,7 @@ TextFieldBase {
property string leadingIcon property string leadingIcon
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0 readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
property int radius: Tokens.rounding.small property int radius: Tokens.rounding.small
property alias sLayer: stateLayer
readonly property real smallFontScale: smallFontSize / font.pointSize readonly property real smallFontScale: smallFontSize / font.pointSize
property int smallFontSize: Tokens.font.size.small property int smallFontSize: Tokens.font.size.small
property string supportingText property string supportingText
@@ -145,6 +146,7 @@ TextFieldBase {
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.larger
text: root.leadingIcon text: root.leadingIcon
} }
} }
+148 -130
View File
@@ -18,13 +18,14 @@ Item {
property real leftFadeStrength: overflowing && leftFadeEnabled ? fadeStrengthMoving : fadeStrengthIdle property real leftFadeStrength: overflowing && leftFadeEnabled ? fadeStrengthMoving : fadeStrengthIdle
property int leftFadeWidth: 28 property int leftFadeWidth: 28
property bool marqueeEnabled: true property bool marqueeEnabled: true
readonly property bool overflowing: metrics.width > root.width readonly property bool overflowing: metrics.width > content.width
property int pauseMs: 1200 property int pauseMs: 1200
property real pixelsPerSecond: 40 property real pixelsPerSecond: 40
property real rightFadeStrength: overflowing ? fadeStrengthMoving : fadeStrengthIdle property real rightFadeStrength: overflowing ? fadeStrengthMoving : fadeStrengthIdle
property int rightFadeWidth: 28 property int rightFadeWidth: 28
property bool sliding: false property bool sliding: false
property alias text: elideText.text property alias text: elideText.text
property bool vertical: false
function durationForDistance(px): int { function durationForDistance(px): int {
return Math.max(1, Math.round(Math.abs(px) / root.pixelsPerSecond * 1000)); return Math.max(1, Math.round(Math.abs(px) / root.pixelsPerSecond * 1000));
@@ -41,8 +42,9 @@ Item {
} }
} }
clip: true clip: false
implicitHeight: elideText.implicitHeight implicitHeight: vertical ? elideText.implicitWidth : elideText.implicitHeight
implicitWidth: vertical ? elideText.implicitHeight : elideText.implicitWidth
Behavior on leftFadeStrength { Behavior on leftFadeStrength {
Anim { Anim {
@@ -53,171 +55,187 @@ Item {
} }
} }
onHeightChanged: resetMarquee()
onTextChanged: resetMarquee() onTextChanged: resetMarquee()
onVerticalChanged: resetMarquee()
onVisibleChanged: if (!visible) onVisibleChanged: if (!visible)
resetMarquee() resetMarquee()
onWidthChanged: resetMarquee() onWidthChanged: resetMarquee()
TextMetrics {
id: metrics
font: elideText.font
text: elideText.text
}
CustomText {
id: elideText
anchors.verticalCenter: parent.verticalCenter
animate: root.animate
animateProp: "scale,opacity"
color: root.color
elide: Text.ElideNone
visible: !root.overflowing
width: root.width
}
Item { Item {
id: marqueeViewport id: content
anchors.fill: parent anchors.centerIn: parent
clip: true height: root.vertical ? root.width : root.height
layer.enabled: true width: root.vertical ? root.height : root.width
visible: root.overflowing
layer.effect: OpacityMask { transform: Rotation {
maskSource: rightFadeMask angle: root.vertical ? -90 : 0
origin.x: content.width / 2
origin.y: content.height / 2
}
TextMetrics {
id: metrics
font: elideText.font
text: elideText.text
}
CustomText {
id: elideText
anchors.verticalCenter: parent.verticalCenter
animate: root.animate
animateProp: "scale,opacity"
color: root.color
elide: Text.ElideNone
visible: !root.overflowing
width: content.width
} }
Item { Item {
id: strip id: marqueeViewport
anchors.verticalCenter: parent.verticalCenter anchors.fill: parent
height: t1.implicitHeight clip: false
width: t1.width + root.gap + t2.width layer.enabled: true
x: 0 visible: root.overflowing
CustomText { layer.effect: OpacityMask {
id: t1 maskSource: rightFadeMask
animate: root.animate
animateProp: "opacity"
color: root.color
font.pointSize: elideText.font.pointSize
text: elideText.text
} }
CustomText { Item {
id: t2 id: strip
animate: root.animate anchors.verticalCenter: parent.verticalCenter
animateProp: "opacity" height: t1.implicitHeight
color: root.color width: t1.width + root.gap + t2.width
font.pointSize: elideText.font.pointSize x: 0
text: t1.text
x: t1.width + root.gap
}
}
SequentialAnimation { CustomText {
id: marqueeAnim id: t1
running: false animate: root.animate
animateProp: "opacity"
color: root.color
font.pointSize: elideText.font.pointSize
text: elideText.text
}
onFinished: pauseTimer.restart() CustomText {
id: t2
ScriptAction { animate: root.animate
script: { animateProp: "opacity"
root.sliding = true; color: root.color
root.leftFadeEnabled = true; font.pointSize: elideText.font.pointSize
text: t1.text
x: t1.width + root.gap
} }
} }
Anim { SequentialAnimation {
duration: root.durationForDistance(t1.width) id: marqueeAnim
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: 0
property: "x"
target: strip
to: -t1.width
}
ScriptAction { running: false
script: {
root.leftFadeEnabled = false; onFinished: pauseTimer.restart()
ScriptAction {
script: {
root.sliding = true;
root.leftFadeEnabled = true;
}
}
Anim {
duration: root.durationForDistance(t1.width)
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: 0
property: "x"
target: strip
to: -t1.width
}
ScriptAction {
script: {
root.leftFadeEnabled = false;
}
}
Anim {
duration: root.durationForDistance(root.gap)
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: -t1.width
property: "x"
target: strip
to: -(t1.width + root.gap)
}
ScriptAction {
script: {
root.sliding = false;
strip.x = 0;
}
} }
} }
Anim { Timer {
duration: root.durationForDistance(root.gap) id: pauseTimer
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: -t1.width
property: "x"
target: strip
to: -(t1.width + root.gap)
}
ScriptAction { interval: root.pauseMs
script: { repeat: false
root.sliding = false; running: true
strip.x = 0;
onTriggered: {
if (root.marqueeEnabled)
marqueeAnim.start();
} }
} }
} }
Timer { Rectangle {
id: pauseTimer id: rightFadeMask
interval: root.pauseMs readonly property real fadeStartPos: {
repeat: false const w = Math.max(1, width);
running: true return Math.max(0, Math.min(1, (w - root.rightFadeWidth) / w));
onTriggered: {
if (root.marqueeEnabled)
marqueeAnim.start();
} }
} readonly property real leftFadeEndPos: {
} const w = Math.max(1, width);
return Math.max(0, Math.min(1, root.leftFadeWidth / w));
Rectangle {
id: rightFadeMask
readonly property real fadeStartPos: {
const w = Math.max(1, width);
return Math.max(0, Math.min(1, (w - root.rightFadeWidth) / w));
}
readonly property real leftFadeEndPos: {
const w = Math.max(1, width);
return Math.max(0, Math.min(1, root.leftFadeWidth / w));
}
anchors.fill: marqueeViewport
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.leftFadeStrength)
position: 0.0
} }
GradientStop { anchors.fill: marqueeViewport
color: Qt.rgba(1, 1, 1, 1.0) layer.enabled: true
position: rightFadeMask.leftFadeEndPos visible: false
}
GradientStop { gradient: Gradient {
color: Qt.rgba(1, 1, 1, 1.0) orientation: Gradient.Horizontal
position: rightFadeMask.fadeStartPos
}
GradientStop { GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.rightFadeStrength) color: Qt.rgba(1, 1, 1, 1.0 - root.leftFadeStrength)
position: 1.0 position: 0.0
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: rightFadeMask.leftFadeEndPos
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: rightFadeMask.fadeStartPos
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.rightFadeStrength)
position: 1.0
}
} }
} }
} }
+4 -4
View File
@@ -126,7 +126,7 @@ MouseArea {
CustomRect { CustomRect {
id: item id: item
readonly property bool active: modelData === root.active readonly property bool active: modelData === root?.active
required property int index required property int index
required property MenuItem modelData required property MenuItem modelData
@@ -163,19 +163,19 @@ MouseArea {
MaterialIcon { MaterialIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
text: item.modelData.icon text: item.modelData?.icon ?? ""
} }
CustomText { CustomText {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
text: item.modelData.text text: item.modelData?.text ?? ""
} }
Loader { Loader {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0 active: item.modelData?.trailingIcon.length > 0
asynchronous: true asynchronous: true
visible: active visible: active
+33 -2
View File
@@ -11,15 +11,46 @@ Variants {
required property ShellScreen modelData required property ShellScreen modelData
function rebuild(): void {
loader.active = false;
loader.active = true;
}
LazyLoader {
id: loader
active: true
Drawer {
screen: scope.modelData
}
}
Connections {
function onConfigPositionChanged(): void {
Qt.callLater(scope.rebuild);
}
target: (loader.item as Drawer)?.geometry ?? null
}
}
component Drawer: Scope {
id: drawer
readonly property alias geometry: content.geometry
required property ShellScreen screen
Exclusions { Exclusions {
bar: content.bar bar: content.bar
screen: scope.modelData geometry: content.geometry
screen: drawer.screen
} }
Windows { Windows {
id: content id: content
screen: scope.modelData screen: drawer.screen
} }
} }
} }
+55
View File
@@ -0,0 +1,55 @@
import QtQuick
import qs.Modules.Bar as Bar
QtObject {
id: root
required property Bar.BarLoader bar
readonly property real barClamped: bar.clampedExtent
readonly property real barExtent: bar.extent
readonly property bool barOnBottom: position === "bottom"
readonly property bool barOnLeft: position === "left"
readonly property bool barOnTop: position === "top"
required property string configPosition
readonly property bool horizontal: position !== "left"
readonly property string position: configPosition === "top" || configPosition === "bottom" ? configPosition : "left"
required property var win
function axisPos(x: real, y: real): real {
return horizontal ? x : y;
}
function barContains(x: real, y: real, clamped = false): bool {
const extent = clamped ? barClamped : barExtent;
if (barOnTop)
return y < extent;
if (barOnBottom)
return y > win.height - extent;
return x < extent;
}
function insetBottom(border: real, clamped = false): real {
return barOnBottom ? (clamped ? barClamped : barExtent) : border;
}
function insetLeft(border: real, clamped = false): real {
return barOnLeft ? (clamped ? barClamped : barExtent) : border;
}
function insetTop(border: real, clamped = false): real {
return barOnTop ? (clamped ? barClamped : barExtent) : border;
}
function inwardDrag(dragX: real, dragY: real): real {
if (barOnTop)
return dragY;
if (barOnBottom)
return -dragY;
return dragX;
}
onConfigPositionChanged: {
if (!["left", "top", "bottom"].includes(configPosition))
console.warn(`Invalid bar position '${configPosition}', falling back to left`);
}
}
+11 -2
View File
@@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Wayland
import QtQuick import QtQuick
import ZShell.Config import ZShell.Config
import qs.Components import qs.Components
@@ -9,19 +10,24 @@ Scope {
id: root id: root
required property Item bar required property Item bar
required property EdgeGeometry geometry
required property ShellScreen screen required property ShellScreen screen
ExclusionZone { ExclusionZone {
id: top id: top
WlrLayershell.layer: WlrLayer.Overlay
anchors.left: true
anchors.right: true
anchors.top: true anchors.top: true
exclusiveZone: root.bar.exclusiveZone hasBar: root.geometry.barOnTop
} }
ExclusionZone { ExclusionZone {
id: left id: left
anchors.left: true anchors.left: true
hasBar: root.geometry.barOnLeft
} }
ExclusionZone { ExclusionZone {
@@ -34,6 +40,7 @@ Scope {
id: bottom id: bottom
anchors.bottom: true anchors.bottom: true
hasBar: root.geometry.barOnBottom
} }
Timer { Timer {
@@ -44,7 +51,9 @@ Scope {
} }
component ExclusionZone: CustomWindow { component ExclusionZone: CustomWindow {
exclusiveZone: Config.bar.border property bool hasBar
exclusiveZone: hasBar ? root.bar.exclusiveZone : Config.bar.border
implicitHeight: 1 implicitHeight: 1
implicitWidth: 1 implicitWidth: 1
name: "Bar-Exclusion" name: "Bar-Exclusion"
+42 -24
View File
@@ -3,7 +3,7 @@ import QtQuick
import qs.Components import qs.Components
import ZShell.Config import ZShell.Config
import qs.Helpers import qs.Helpers
import qs.Modules as BarPopouts import qs.Modules.Bar.Popouts
Item { Item {
id: root id: root
@@ -12,43 +12,47 @@ Item {
required property real borderThickness required property real borderThickness
property bool dashboardShortcutActive property bool dashboardShortcutActive
required property Drawing drawing required property Drawing drawing
required property EdgeGeometry geometry
property bool osdShortcutActive property bool osdShortcutActive
required property Panels panels required property Panels panels
required property BarPopouts.Wrapper popouts required property Wrapper popouts
required property ShellScreen screen required property ShellScreen screen
property bool singleGestureTriggered: false property bool singleGestureTriggered: false
property bool utilitiesShortcutActive property bool utilitiesShortcutActive
required property PersistentProperties visibilities required property PersistentProperties visibilities
function inBottomPanel(panel: Item, x: real, y: real): bool { function inBottomPanel(panel: Item, x: real, y: real): bool {
return y > root.height - panel.height - Config.bar.border && withinPanelWidth(panel, x, y); const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
return y > root.height - (panelHeight + geometry.insetBottom(borderThickness)) && withinPanelWidth(panel, x, y);
} }
function inLeftPanel(panel: Item, x: real, y: real): bool { function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width + Config.bar.border && withinPanelHeight(panel, x, y); return x < panel.x + panel.width + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
}
function inPopoutArea(x: real, y: real): bool {
const panel = panels.popoutsWrapper;
if (!geometry.horizontal)
return inLeftPanel(panel, x, y);
return withinPanelWidth(panel, x, y) && withinPanelHeight(panel, x, y);
} }
function inRightPanel(panel: Item, x: real, y: real): bool { function inRightPanel(panel: Item, x: real, y: real): bool {
return x > panel.x - Config.bar.border && withinPanelHeight(panel, x, y); return x > panel.x + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
} }
function inTopPanel(panel: Item, x: real, y: real): bool { function inTopPanel(panel: Item, x: real, y: real): bool {
return y < bar.implicitHeight + panel.height && withinPanelWidth(panel, x, y); const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
} return y < geometry.insetTop(borderThickness) + panelHeight && withinPanelWidth(panel, x, y);
function onWheel(event: WheelEvent): void {
if (event.x < bar.implicitWidth) {
bar.handleWheel(event.y, event.angleDelta);
}
} }
function withinPanelHeight(panel: Item, x: real, y: real): bool { function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelY = panel.y + bar.implicitHeight; const panelY = panel.y + geometry.insetTop(borderThickness);
return y >= panelY && y <= panelY + panel.height; return y >= panelY && y <= panelY + panel.height;
} }
function withinPanelWidth(panel: Item, x: real, y: real): bool { function withinPanelWidth(panel: Item, x: real, y: real): bool {
const panelX = panel.x + root.borderThickness; const panelX = panel.x + geometry.insetLeft(borderThickness);
return x >= panelX && x <= panelX + panel.width; return x >= panelX && x <= panelX + panel.width;
} }
@@ -81,7 +85,7 @@ Item {
if (root.singleGestureTriggered) if (root.singleGestureTriggered)
return; return;
if (centroid.pressPosition.y < root.bar.implicitHeight) { if (root.geometry.insetTop(root.borderThickness) > centroid.pressPosition.y) {
if (dragY > 20) { if (dragY > 20) {
root.visibilities.settings = true; root.visibilities.settings = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
@@ -91,10 +95,10 @@ Item {
} }
} }
if (centroid.pressPosition.y > root.screen.height - Config.bar.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50) if (centroid.pressPosition.y > root.screen.height - root.geometry.insetBottom(root.borderThickness) && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50)
root.visibilities.clipboard = true; root.visibilities.clipboard = true;
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight && centroid.pressPosition.x > root.screen.width / 5 && !root.visibilities.launcher) if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.geometry.insetTop(root.borderThickness) && centroid.pressPosition.x > root.screen.width / 5 && !root.visibilities.launcher)
if (dragY < -10) { if (dragY < -10) {
root.visibilities.dock = true; root.visibilities.dock = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
@@ -158,6 +162,19 @@ Item {
} }
} }
Timer {
id: unhideTimer
interval: Config.bar.revealDelay
repeat: false
running: false
onTriggered: {
if (hoverHandler.hovered)
root.bar.isHovered = true;
}
}
HoverHandler { HoverHandler {
id: hoverHandler id: hoverHandler
@@ -174,7 +191,7 @@ Item {
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} }
if (Config.bar.autoHide) if (Config.bar.autoHide || root.bar.fullscreen)
root.bar.isHovered = false; root.bar.isHovered = false;
} }
} }
@@ -193,9 +210,12 @@ Item {
return; return;
} }
if (!root.visibilities.bar && Config.bar.autoHide && y < root.bar.implicitHeight) if (!root.visibilities.bar && Config.bar.autoHide && root.geometry.barContains(x, y, true))
root.bar.isHovered = true; root.bar.isHovered = true;
if (root.bar.fullscreen && !root.bar.isHovered)
unhideTimer.start();
if (root.panels.sidebar.offsetScale === 1) { if (root.panels.sidebar.offsetScale === 1) {
const showOsd = root.inRightPanel(root.panels.osdWrapper, x, y); const showOsd = root.inRightPanel(root.panels.osdWrapper, x, y);
@@ -216,11 +236,11 @@ Item {
} }
} }
if (Config.dock.enable && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y)) if (Config.dock.enabled && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y))
root.visibilities.dock = true; root.visibilities.dock = true;
if (y < root.bar.implicitHeight) if (root.geometry.barContains(x, y))
root.bar.checkPopout(x); root.bar.checkPopout(root.geometry.axisPos(x, y));
} }
} }
@@ -298,8 +318,6 @@ Item {
root.visibilities.sidebar = false; root.visibilities.sidebar = false;
root.panels.popouts.hasCurrent = false; root.panels.popouts.hasCurrent = false;
root.visibilities.launcher = false; root.visibilities.launcher = false;
} else {
Config.save();
} }
} }
+19 -16
View File
@@ -1,7 +1,7 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Modules as Modules import qs.Modules.Bar.Popouts as Popouts
import qs.Modules.Notifications as Notifications import qs.Modules.Notifications as Notifications
import qs.Modules.Notifications.Sidebar as Sidebar import qs.Modules.Notifications.Sidebar as Sidebar
import qs.Modules.Notifications.Sidebar.Utils as Utils import qs.Modules.Notifications.Sidebar.Utils as Utils
@@ -27,6 +27,7 @@ Item {
readonly property alias dock: dock readonly property alias dock: dock
readonly property alias drawing: drawing readonly property alias drawing: drawing
required property var drawingItem required property var drawingItem
required property EdgeGeometry geometry
readonly property alias launcher: launcher readonly property alias launcher: launcher
readonly property alias notifications: notifications readonly property alias notifications: notifications
readonly property alias osd: osd readonly property alias osd: osd
@@ -43,24 +44,26 @@ Item {
readonly property alias utilities: utilities readonly property alias utilities: utilities
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.bottomMargin: geometry.insetBottom(borderThickness)
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: geometry.insetLeft(borderThickness)
anchors.margins: borderThickness anchors.margins: borderThickness
anchors.topMargin: bar.implicitHeight anchors.topMargin: geometry.insetTop(borderThickness)
Item { Item {
id: resourcesWrapper id: resourcesWrapper
anchors.bottom: root.geometry.position === "bottom" ? parent.bottom : undefined
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: root.geometry.position !== "bottom" ? parent.top : undefined
clip: true clip: true
implicitHeight: resources.implicitHeight * (1 - resources.offsetScale) implicitHeight: root.geometry.horizontal ? resources.implicitHeight * (1 - resources.offsetScale) : resources.implicitHeight
implicitWidth: resources.implicitWidth implicitWidth: root.geometry.horizontal ? resources.implicitWidth : resources.implicitWidth * (1 - resources.offsetScale)
Resources.Wrapper { Resources.Wrapper {
id: resources id: resources
anchors.left: parent.left position: root.geometry.position
anchors.top: parent.top
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
@@ -96,11 +99,11 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Modules.ClipWrapper { Popouts.ClipWrapper {
id: popouts id: popouts
anchors.top: parent.top
borderThickness: root.borderThickness borderThickness: root.borderThickness
position: root.geometry.position
screen: root.screen screen: root.screen
} }
@@ -147,11 +150,13 @@ Item {
property real offsetScale: dashboard.shouldBeActive ? 0 : 1 property real offsetScale: dashboard.shouldBeActive ? 0 : 1
anchors.right: parent.right anchors.bottom: root.geometry.barOnLeft || root.geometry.barOnBottom ? parent.bottom : undefined
anchors.top: parent.top anchors.left: root.geometry.barOnLeft ? parent.left : undefined
anchors.right: root.geometry.barOnLeft ? undefined : parent.right
anchors.top: root.geometry.barOnBottom || root.geometry.barOnLeft ? undefined : parent.top
clip: true clip: true
implicitHeight: dashboard.implicitHeight * (1 - offsetScale) implicitHeight: root.geometry.horizontal ? dashboard.implicitHeight * (1 - offsetScale) : dashboard.implicitHeight
implicitWidth: dashboard.implicitWidth implicitWidth: root.geometry.horizontal ? dashboard.implicitWidth : dashboard.implicitWidth * (1 - offsetScale)
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
@@ -163,10 +168,8 @@ Item {
Dashboard.Wrapper { Dashboard.Wrapper {
id: dashboard id: dashboard
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: (-implicitHeight - 5) * offsetScale
offsetScale: dashboardWrapper.offsetScale offsetScale: dashboardWrapper.offsetScale
position: root.geometry.position
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
+13 -8
View File
@@ -7,17 +7,18 @@ import qs.Modules.Bar as Bar
Region { Region {
id: root id: root
required property Bar.BarLoader bar // required property Bar.BarLoader bar
readonly property real borderThickness: win.borderThickness readonly property real borderThickness: win.borderThickness
required property EdgeGeometry geometry
readonly property alias menuPopoutRegion: menuPopoutRegion readonly property alias menuPopoutRegion: menuPopoutRegion
required property Panels panels required property Panels panels
required property var win required property var win
height: win.height - bar.implicitHeight - win.borderThickness - win.dragMaskPadding * 2 height: win.height - geometry.insetTop(borderThickness, true) - geometry.insetBottom(borderThickness, true) - win.dragMaskPadding * 2
intersection: Intersection.Xor intersection: Intersection.Xor
width: win.width - win.borderThickness * 2 - win.dragMaskPadding * 2 width: win.width - geometry.insetLeft(borderThickness, true) * 2 - win.dragMaskPadding * 2
x: win.borderThickness + win.dragMaskPadding x: geometry.insetLeft(borderThickness, true) + win.dragMaskPadding
y: bar.implicitHeight + win.dragMaskPadding y: geometry.insetTop(borderThickness, true) + win.dragMaskPadding
R { R {
panel: root.panels.dashboardWrapper panel: root.panels.dashboardWrapper
@@ -41,17 +42,21 @@ Region {
} }
R { R {
height: panel.height + root.geometry.insetTop(root.borderThickness)
panel: root.panels.notifications panel: root.panels.notifications
y: 0
} }
R { R {
height: panel.height * (1 - root.panels.utilities.offsetScale) + root.borderThickness height: panel.height * (1 - root.panels.utilities.offsetScale) + root.geometry.insetBottom(root.borderThickness)
panel: root.panels.utilities panel: root.panels.utilities
y: root.win.height - height y: root.win.height - height
} }
R { R {
height: root.geometry.horizontal ? panel.height * (1 - root.panels.popoutsWrapper.offsetScale) : panel.height
panel: root.panels.popoutsWrapper panel: root.panels.popoutsWrapper
width: root.geometry.horizontal ? panel.width : panel.width * (1 - root.panels.popoutsWrapper.offsetScale)
} }
R { R {
@@ -82,7 +87,7 @@ Region {
height: panel.height height: panel.height
intersection: Intersection.Subtract intersection: Intersection.Subtract
width: panel.width width: panel.width
x: panel.x + root.borderThickness x: panel.x + root.geometry.insetLeft(root.borderThickness)
y: panel.y + root.bar.implicitHeight y: panel.y + root.geometry.insetTop(root.borderThickness)
} }
} }
+59 -28
View File
@@ -11,6 +11,7 @@ import ZShell.Blobs
import qs.Daemons import qs.Daemons
import qs.Components import qs.Components
import qs.Modules.Bar import qs.Modules.Bar
import qs.Modules.Bar.Popouts.Network as N
import ZShell.Config import ZShell.Config
import qs.Helpers import qs.Helpers
import qs.Drawers import qs.Drawers
@@ -33,6 +34,7 @@ CustomWindow {
return 100; return 100;
} }
property real fsTransitionProg: hasFullscreen ? 1 : 0 property real fsTransitionProg: hasFullscreen ? 1 : 0
readonly property alias geometry: geometry
readonly property bool hasFullscreen: { readonly property bool hasFullscreen: {
if (hasSpecialWorkspace) { if (hasSpecialWorkspace) {
const specialName = monitor?.lastIpcObject.specialWorkspace?.name; const specialName = monitor?.lastIpcObject.specialWorkspace?.name;
@@ -50,7 +52,7 @@ CustomWindow {
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen) readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
property var root: Quickshell.shellDir property var root: Quickshell.shellDir
readonly property real sdfBorderOffset: 2 * fsTransitionProg readonly property real sdfBorderOffset: 2 * fsTransitionProg
readonly property real shadowOpacity: 0.7 * (1 - fsTransitionProg) readonly property real shadowOpacity: 0.7 * (1 - (bar.isHovered ? 0 : fsTransitionProg))
property color surfaceColor: Colors.tPalette.m3surface property color surfaceColor: Colors.tPalette.m3surface
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
@@ -58,7 +60,7 @@ CustomWindow {
WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
color: "transparent" color: "transparent"
contentItem.focus: true contentItem.focus: true
mask: visibilities.isDrawing ? null : (hasFullscreen ? emptyRegion : regions) mask: visibilities.isDrawing ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions)
name: "Bar" name: "Bar"
Behavior on fsTransitionProg { Behavior on fsTransitionProg {
@@ -95,13 +97,21 @@ CustomWindow {
panels.popouts.hasCurrent = false; panels.popouts.hasCurrent = false;
} }
EdgeGeometry {
id: geometry
bar: bar
configPosition: Config.bar.position
win: root
}
Region { Region {
id: emptyRegion id: emptyRegion
height: panels.notifications.height height: panels.notifications.height
width: panels.notifications.width width: panels.notifications.width
x: panels.notifications.x + root.borderThickness x: panels.notifications.x + geometry.insetLeft(root.borderThickness)
y: panels.notifications.y + bar.implicitHeight y: panels.notifications.y + geometry.insetTop(root.borderThickness)
Region { Region {
height: panels.osd.height height: panels.osd.height
@@ -109,12 +119,18 @@ CustomWindow {
x: root.width - width x: root.width - width
y: panels.osdWrapper.y + bar.implicitHeight y: panels.osdWrapper.y + bar.implicitHeight
} }
Region {
height: geometry.horizontal ? 1 : root.screen.height
width: geometry.horizontal ? root.screen.width : 1
y: geometry.position === "bottom" ? root.screen.height - 1 : 0
}
} }
Regions { Regions {
id: regions id: regions
bar: bar geometry: geometry
panels: panels panels: panels
win: root win: root
} }
@@ -129,7 +145,17 @@ CustomWindow {
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!Config.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1)) active: {
const v = visibilities;
const conf = Config;
if (!N.PopupManager.closed && panels.popouts.hasCurrent)
return true;
if ((v.launcher && conf.launcher.enabled) || (v.sidebar && conf.sidebar.enabled) || (v.resources && conf.dashboard.performance.enabled) || (v.dashboard && conf.dashboard.enabled) || v.settings || (v.clipboard && conf.clipboard.enabled) || (v.dock && conf.dock.enabled))
return true;
if (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!conf.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
return true;
return false;
}
windows: [root] windows: [root]
onCleared: { onCleared: {
@@ -193,10 +219,10 @@ CustomWindow {
BlobInvertedRect { BlobInvertedRect {
anchors.fill: parent anchors.fill: parent
anchors.margins: -50 anchors.margins: -50
borderBottom: root.borderThickness - anchors.margins - root.sdfBorderOffset borderBottom: geometry.insetBottom(root.borderThickness) - anchors.margins - root.sdfBorderOffset
borderLeft: root.borderThickness - anchors.margins - root.sdfBorderOffset borderLeft: geometry.insetLeft(root.borderThickness) - anchors.margins - root.sdfBorderOffset
borderRight: root.borderThickness - anchors.margins - root.sdfBorderOffset borderRight: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderTop: bar.implicitHeight - anchors.margins - root.sdfBorderOffset borderTop: geometry.insetTop(root.borderThickness) - anchors.margins - root.sdfBorderOffset
group: blobGroup group: blobGroup
radius: root.borderRounding radius: root.borderRounding
} }
@@ -204,15 +230,15 @@ CustomWindow {
PanelBg { PanelBg {
id: dashBg id: dashBg
property real extraHeight: 0.2 property real extraExtent: 0.2
deformAmount: 0.06 deformAmount: 0.06
implicitHeight: panels.dashboard.height * (1 + extraHeight) implicitHeight: panels.dashboard.height * (geometry.horizontal ? 1 + extraExtent : 1)
implicitWidth: panels.dashboard.width implicitWidth: panels.dashboard.width * (geometry.horizontal ? 1 : 1 + extraExtent)
panel: panels.dashboardWrapper panel: panels.dashboardWrapper
radius: Tokens.rounding.normal radius: Tokens.rounding.normal
x: panels.dashboardWrapper.x + panels.dashboard.x + root.borderThickness x: panels.dashboardWrapper.x + panels.dashboard.x + geometry.insetLeft(root.borderThickness) - (geometry.horizontal ? 0 : panels.dashboard.width * extraExtent)
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight y: panels.dashboardWrapper.y + panels.dashboard.y + geometry.insetTop(root.borderThickness) - (geometry.horizontal && geometry.position !== "bottom" ? panels.dashboard.height * extraExtent : 0)
} }
PanelBg { PanelBg {
@@ -224,7 +250,7 @@ CustomWindow {
implicitHeight: panels.launcher.height * (1 + extraHeight) implicitHeight: panels.launcher.height * (1 + extraHeight)
panel: panels.launcher panel: panels.launcher
radius: Tokens.rounding.smallest + 5 radius: Tokens.rounding.smallest + 5
y: panels.launcher.y + bar.implicitHeight y: panels.launcher.y + geometry.insetTop(root.borderThickness)
} }
PanelBg { PanelBg {
@@ -245,8 +271,7 @@ CustomWindow {
implicitWidth: panels.osd.width implicitWidth: panels.osd.width
panel: panels.osdWrapper panel: panels.osdWrapper
radius: 20 radius: 20
x: panels.osdWrapper.x + panels.osd.x + root.borderThickness x: panels.osdWrapper.x + panels.osd.x + geometry.insetLeft(root.borderThickness)
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
} }
PanelBg { PanelBg {
@@ -268,17 +293,17 @@ CustomWindow {
PanelBg { PanelBg {
id: popoutBg id: popoutBg
property real extraHeight: 0.2 property real extraExtent: 0.2
deformAmount: panels.popouts.currentName.startsWith("traymenu") ? 0.15 : 0.08 deformAmount: panels.popouts.currentName.startsWith("traymenu") ? 0.15 : 0.08
implicitHeight: panels.popouts.height * (1 + extraHeight) implicitHeight: panels.popouts.height * (geometry.horizontal ? 1 + extraExtent : 1)
implicitWidth: panels.popouts.width implicitWidth: panels.popouts.width * (geometry.horizontal ? 1 : 1 + extraExtent)
panel: panels.popoutsWrapper panel: panels.popoutsWrapper
radius: panels.popouts.current?.panelRadius ?? Tokens.rounding.normal radius: panels.popouts.current?.panelRadius ?? Tokens.rounding.normal
x: panels.popoutsWrapper.x + panels.popouts.x + root.borderThickness x: panels.popoutsWrapper.x + panels.popouts.x + geometry.insetLeft(root.borderThickness) - (geometry.horizontal ? 0 : panels.popouts.width * extraExtent)
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight y: panels.popoutsWrapper.y + panels.popouts.y + geometry.insetTop(root.borderThickness) - (geometry.barOnTop ? panels.popouts.height * extraExtent : 0)
Behavior on extraHeight { Behavior on extraExtent {
Anim { Anim {
} }
} }
@@ -292,8 +317,8 @@ CustomWindow {
implicitWidth: panels.resources.width implicitWidth: panels.resources.width
panel: panels.resourcesWrapper panel: panels.resourcesWrapper
radius: Tokens.rounding.large radius: Tokens.rounding.large
x: panels.resourcesWrapper.x + panels.resources.x + root.borderThickness x: panels.resourcesWrapper.x + panels.resources.x + geometry.insetLeft(root.borderThickness)
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight y: panels.resourcesWrapper.y + panels.resources.y + geometry.insetTop(root.borderThickness)
} }
PanelBg { PanelBg {
@@ -371,6 +396,7 @@ CustomWindow {
borderThickness: root.borderLayoutThickness borderThickness: root.borderLayoutThickness
drawing: drawing drawing: drawing
enabled: true enabled: true
geometry: geometry
panels: panels panels: panels
popouts: panels.popouts popouts: panels.popouts
screen: root.screen screen: root.screen
@@ -382,6 +408,7 @@ CustomWindow {
bar: bar bar: bar
borderThickness: root.borderThickness borderThickness: root.borderThickness
drawingItem: drawing drawingItem: drawing
geometry: geometry
screen: root.screen screen: root.screen
visibilities: visibilities visibilities: visibilities
@@ -423,14 +450,18 @@ CustomWindow {
BarLoader { BarLoader {
id: bar id: bar
anchors.bottom: geometry.barOnBottom ? parent.bottom : undefined
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.top: geometry.barOnBottom ? undefined : parent.top
enabled: !visibilities.isDrawing enabled: !visibilities.isDrawing
fullscreen: root.hasFullscreen fullscreen: root.hasFullscreen
height: geometry.horizontal ? implicitHeight : parent.height
popouts: panels.popouts popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper popoutsWrapper: panels.popoutsWrapper
position: geometry.position
screen: root.screen screen: root.screen
visibilities: visibilities visibilities: visibilities
width: geometry.horizontal ? parent.width : implicitWidth
} }
} }
@@ -443,7 +474,7 @@ CustomWindow {
implicitHeight: panel.height implicitHeight: panel.height
implicitWidth: panel.width implicitWidth: panel.width
radius: Tokens.rounding.smallest radius: Tokens.rounding.smallest
x: panel.x + root.borderThickness x: panel.x + geometry.insetLeft(root.borderThickness)
y: panel.y + bar.implicitHeight y: panel.y + geometry.insetTop(root.borderThickness)
} }
} }
-33
View File
@@ -1,33 +0,0 @@
import QtQuick
import QtQuick.Effects
Item {
id: root
property real radius
Rectangle {
id: shadowRect
anchors.fill: root
color: "black"
layer.enabled: true
radius: root.radius
visible: false
}
MultiEffect {
id: effects
anchors.fill: shadowRect
autoPaddingEnabled: true
maskEnabled: true
maskInverted: true
maskSource: shadowRect
shadowBlur: 2.0
shadowColor: "black"
shadowEnabled: true
shadowOpacity: 1
source: shadowRect
}
}
+1 -2
View File
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Controls
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
@@ -11,6 +10,6 @@ TextInput {
font { font {
family: Config.appearance?.font.family.sans ?? "sans-serif" family: Config.appearance?.font.family.sans ?? "sans-serif"
hintingPreference: Font.PreferFullHinting hintingPreference: Font.PreferFullHinting
pixelSize: Config.appearance?.font.size.normal ?? 15 pixelSize: Tokens?.font.size.normal ?? 15
} }
} }
+40 -12
View File
@@ -1,35 +1,63 @@
pragma Singleton pragma Singleton
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Services.UPower import Quickshell.Services.UPower
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
import qs.Paths
Singleton { Singleton {
id: root id: root
readonly property var colors: { readonly property list<var> allPeripherals: [...logiDevices, ...upowerDevices.filter(d => !Battery.logiDevices.some(l => l.nativePath === d.nativePath))]
if (deviceState === UPowerDeviceState.Charging || deviceState === UPowerDeviceState.FullyCharged) readonly property real currentPerc: UPower.displayDevice.percentage
readonly property var deviceState: UPower.displayDevice.state
readonly property string deviceStateString: UPowerDeviceState.toString(deviceState)
readonly property bool isLaptop: UPower.displayDevice.isLaptopBattery
readonly property alias logiDevices: adapter.devices
readonly property var lowestPeripheral: allPeripherals.reduce((lowest, current) => current.percentage < lowest.percentage ? current : lowest)
readonly property bool onBattery: UPower.onBattery
readonly property bool ready: UPower.displayDevice.ready
readonly property real timeToEmpty: UPower.displayDevice.timeToEmpty
readonly property real timeToFull: UPower.displayDevice.timeToFull
readonly property list<UPowerDevice> upowerDevices: UPower.devices.values.filter(d => d.state !== UPowerDeviceState.Unknown)
function getColors(percentage: real, state: string): var {
if (state === "charging" || state === "full" || state === "recharging")
return { return {
fg: Colors.swapRG(Colors.palette.m3error), fg: Colors.swapRG(Colors.palette.m3error),
bg: Colors.swapRG(Colors.palette.m3onError) bg: Colors.swapRG(Colors.palette.m3onError)
}; };
else if (currentPerc <= 0.2) else if (percentage <= 0.2)
return { return {
fg: Colors.palette.m3error, fg: Colors.palette.m3error,
bg: Colors.palette.m3onError bg: Colors.palette.m3onError
}; };
else else
return { return {
fg: Colors.palette.m3onSurface, fg: Colors.palette.m3tertiary,
bg: Colors.palette.m3surface bg: Colors.palette.m3onTertiary
}; };
} }
readonly property real currentPerc: UPower.displayDevice.percentage
readonly property var deviceState: UPower.displayDevice.state function startDaemon(): void {
readonly property bool isLaptop: UPower.displayDevice.isLaptopBattery Quickshell.execDetached(["zshell-cli", "battery", "daemon"]);
readonly property bool onBattery: UPower.onBattery }
readonly property bool ready: UPower.displayDevice.ready
readonly property real timeToEmpty: UPower.displayDevice.timeToEmpty FileView {
readonly property real timeToFull: UPower.displayDevice.timeToFull id: fileView
path: `${Paths.cache}/battery.json`
watchChanges: true
onFileChanged: reload()
JsonAdapter {
id: adapter
property list<var> devices: []
property real updated: 0.0
}
}
} }
-70
View File
@@ -1,70 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Components
import ZShell.Config
Singleton {
id: root
property list<var> apps: {
var map = new Map();
const pinnedApps = Config.dock?.pinnedApps ?? [];
for (const appId of pinnedApps) {
if (!map.has(appId.toLowerCase()))
map.set(appId.toLowerCase(), ({
pinned: true,
toplevels: []
}));
}
if (pinnedApps.length > 0) {
map.set("SEPARATOR", {
pinned: false,
toplevels: []
});
}
var values = [];
for (const [key, value] of map) {
values.push(appEntryComp.createObject(null, {
appId: key,
toplevels: value.toplevels,
pinned: value.pinned
}));
}
return values;
}
function isPinned(appId) {
return Config.dock.pinnedApps.indexOf(appId) !== -1;
}
function togglePin(appId) {
if (root.isPinned(appId)) {
Config.dock.pinnedApps = Config.dock.pinnedApps.filter(id => id !== appId);
} else {
Config.dock.pinnedApps = Config.dock.pinnedApps.concat([appId]);
}
}
Component {
id: appEntryComp
TaskbarAppEntry {
}
}
component TaskbarAppEntry: QtObject {
id: wrapper
required property string appId
required property bool pinned
required property list<var> toplevels
}
}
-63
View File
@@ -1,63 +0,0 @@
// FetchPresets.qml
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property var parsedPresets: ({})
readonly property var presets: parsedPresets
property bool ready: false
function accents(presetName, variantName) {
const variant = parsedPresets[presetName]?.variants?.[variantName];
return variant?.accents ?? [];
}
function defaultAccent(presetName, variantName) {
const variant = parsedPresets[presetName]?.variants?.[variantName];
return variant?.default_accent ?? "";
}
function modes(presetName, variantName) {
const variant = parsedPresets[presetName]?.variants?.[variantName];
return variant?.modes ?? [];
}
function presetNames() {
return Object.keys(parsedPresets);
}
function variantNames(presetName) {
const preset = parsedPresets[presetName];
if (!preset || !preset.variants)
return [];
return Object.keys(preset.variants);
}
Process {
command: ["zshell-cli", "scheme", "list-presets", "--json"]
running: true
stdout: StdioCollector {
onStreamFinished: {
try {
const parsed = JSON.parse(text);
root.parsedPresets = parsed.presets ?? {};
root.ready = true;
} catch (e) {
console.error("Failed to parse presets JSON:", e);
}
}
}
}
}
+51 -12
View File
@@ -1,9 +1,8 @@
pragma Singleton pragma Singleton
import ZShell.Config import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import QtQuick
Singleton { Singleton {
id: root id: root
@@ -78,16 +77,6 @@ Singleton {
"99": "thunderstorm" "99": "thunderstorm"
}) })
function getAppCategoryIcon(name: string, fallback: string): string {
const categories = DesktopEntries.heuristicLookup(name)?.categories;
if (categories)
for (const [key, value] of Object.entries(categoryIcons))
if (categories.includes(key))
return value;
return fallback;
}
function getAppIcon(name: string, fallback: string): string { function getAppIcon(name: string, fallback: string): string {
const icon = DesktopEntries.heuristicLookup(name)?.icon; const icon = DesktopEntries.heuristicLookup(name)?.icon;
if (fallback !== "undefined") if (fallback !== "undefined")
@@ -95,6 +84,15 @@ Singleton {
return Quickshell.iconPath(icon); return Quickshell.iconPath(icon);
} }
function getBatteryIcon(percentage: real, charging = false): string {
if (percentage === 1)
return charging ? "battery_charging_full" : "battery_full";
let level = Math.floor(percentage * 7);
if (charging && (level === 4 || level === 1))
level--;
return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
}
function getBluetoothIcon(icon: string): string { function getBluetoothIcon(icon: string): string {
if (icon.includes("headset") || icon.includes("headphones")) if (icon.includes("headset") || icon.includes("headphones"))
return "headphones"; return "headphones";
@@ -168,6 +166,32 @@ Singleton {
return "chat"; return "chat";
} }
function getSpecialWsIcon(name: string): string {
name = name.toLowerCase().slice("special:".length);
if (name === "special")
return "star";
if (name === "communication")
return "forum";
if (name === "music")
return "music_cast";
if (name === "todo")
return "checklist";
if (name === "sysmon")
return "monitor_heart";
return name[0].toUpperCase();
}
function getTrayIcon(id: string, icon: string): string {
if (icon.includes("?path=")) {
const [name, path] = icon.split("?path=");
const file = name.slice(name.lastIndexOf("/") + 1);
const themed = Quickshell.iconPath(file, true);
icon = themed ? themed : Qt.resolvedUrl(`${path}/${file}`);
}
return icon;
}
function getVolumeIcon(volume: real, isMuted: bool): string { function getVolumeIcon(volume: real, isMuted: bool): string {
if (isMuted) if (isMuted)
return "no_sound"; return "no_sound";
@@ -183,4 +207,19 @@ Singleton {
return weatherIcons[code]; return weatherIcons[code];
return "air"; return "air";
} }
function matchIconConfig(name: string, iconConfig: var): bool {
if (!iconConfig.icon)
return false;
if (iconConfig.regex) {
const re = new RegExp(iconConfig.regex, iconConfig.flags ?? "");
if (re.test(name))
return true;
} else if (iconConfig.name === name) {
return true;
}
return false;
}
} }
+114
View File
@@ -0,0 +1,114 @@
pragma Singleton
import Quickshell
import Quickshell.Networking
import QtQuick
Singleton {
id: root
property bool active: false
readonly property list<Network> connectedNetworks: networks.filter(n => n.connected)
property Network connectingNetwork
readonly property list<NetworkDevice> devices: Networking.devices.values
property Network failedNetwork
readonly property list<Network> knownNetworks: networks.filter(n => n.known && !n.connected)
readonly property list<Network> networks: {
const list = [];
for (const d of wifiDevices) {
for (const n of d.networks.values) {
if (!list.includes(n))
list.push(n);
}
}
return list;
}
property bool scanning: false
readonly property list<Network> unknownNetworks: networks.filter(n => !n.known)
readonly property list<WifiDevice> wifiDevices: devices.filter(d => wifiDevice(d))
readonly property bool wifiEnabled: Networking.wifiEnabled
signal networkConnected
function isSecure(security): bool {
return (security === WifiSecurityType.WpaPsk || security === WifiSecurityType.Wpa2Psk || security === WifiSecurityType.Sae);
}
function requestConnect(network: Network, secret = undefined): void {
connectingNetwork = network;
if (secret === undefined || secret === "") {
network.connect();
} else {
(network as WifiNetwork).connectWithPsk(secret);
}
}
function setScan(value: bool): void {
for (const d of wifiDevices) {
d.scannerEnabled = value;
}
}
function setWifi(value: bool): void {
Networking.wifiEnabled = value;
}
function wifiDevice(dev): bool {
return dev.type === DeviceType.Wifi;
}
onActiveChanged: {
for (const d of wifiDevices)
d.scannerEnabled = active;
}
Timer {
id: flushFailed
interval: 5000
running: root.failedNetwork
onTriggered: {
root.failedNetwork = null;
}
}
Timer {
id: checkConnection
interval: 500
repeat: true
running: root.connectingNetwork
onTriggered: {
var completedNetwork;
switch (root.connectingNetwork.state) {
case ConnectionState.Connecting:
break;
case ConnectionState.Connected:
completedNetwork = root.connectingNetwork;
root.networkConnected();
break;
case ConnectionState.Disconnected:
root.failedNetwork = root.connectingNetwork;
completedNetwork = root.connectingNetwork;
break;
}
if (completedNetwork) {
root.connectingNetwork = null;
}
}
}
Timer {
interval: 10000
running: checkConnection.running
onTriggered: {
root.failedNetwork = root.connectingNetwork;
root.connectingNetwork = null;
}
}
}
-1
View File
@@ -73,7 +73,6 @@ Singleton {
Config.dock.pinnedApps = pinnedApps; Config.dock.pinnedApps = pinnedApps;
root.unpinnedOrder = visibleUnpinned.concat(root.unpinnedOrder.map(normalizeId).filter(id => !pinnedApps.includes(id) && !visibleUnpinned.includes(id))); root.unpinnedOrder = visibleUnpinned.concat(root.unpinnedOrder.map(normalizeId).filter(id => !pinnedApps.includes(id) && !visibleUnpinned.includes(id)));
Config.saveNoToast();
} }
function isPinned(appId) { function isPinned(appId) {
+8 -41
View File
@@ -11,8 +11,8 @@ Singleton {
id: root id: root
property int availableUpdates: 0 property int availableUpdates: 0
property string cmd: ""
property bool commandReady property bool commandReady
property bool hasHelper
property bool loaded property bool loaded
property double now: Date.now() property double now: Date.now()
property var updates: ({}) property var updates: ({})
@@ -38,18 +38,12 @@ Singleton {
} }
function performPackageUpdate(pkg: string): void { function performPackageUpdate(pkg: string): void {
if (root.cmd === "pacman") pkgUpdateProc.command = ["pkexec", "pacman", "--noconfirm", "-Sy", pkg];
pkgUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Sy", pkg];
else
pkgUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Sy", pkg];
pkgUpdateProc.running = true; pkgUpdateProc.running = true;
} }
function performSystemUpdate(): void { function performSystemUpdate(): void {
if (root.cmd === "pacman") sysUpdateProc.command = ["pkexec", "pacman", "--noconfirm", "-Syu"];
sysUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Syu"];
else
sysUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Syu"];
sysUpdateProc.running = true; sysUpdateProc.running = true;
} }
@@ -87,7 +81,7 @@ Singleton {
Process { Process {
id: cmdDetect id: cmdDetect
command: ["sh", "-c", "command -v checkupdates || command -v yay || command -v paru"] command: ["sh", "-c", "command -v checkupdates"]
running: true running: true
stdout: StdioCollector { stdout: StdioCollector {
@@ -96,47 +90,20 @@ Singleton {
let helper; let helper;
if (cmd.length > 0) { if (cmd.length > 0) {
helper = cmd.split("/").pop(); helper = true;
} else { } else {
helper = "pacman"; helper = false;
}
if (helper === "checkupdates") {
updatesProc.command = [helper];
} else {
updatesProc.command = [helper, "-Qu"];
} }
root.hasHelper = helper;
root.commandReady = true; root.commandReady = true;
} }
} }
} }
Process {
id: updateCmdDetect
command: ["sh", "-c", "command -v yay || command -v paru"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const cmd = this.text.trim();
let helper;
if (cmd.length > 0) {
helper = cmd.split("/").pop();
} else {
helper = "pacman";
}
root.cmd = helper;
}
}
}
Process { Process {
id: updatesProc id: updatesProc
command: [] command: root.hasHelper ? ["checkupdates"] : []
running: false running: false
stdout: StdioCollector { stdout: StdioCollector {
+4 -1
View File
@@ -17,7 +17,10 @@ Singleton {
watchChanges: true watchChanges: true
onAdapterUpdated: writeAdapter() onAdapterUpdated: writeAdapter()
onFileChanged: reload() onFileChanged: {
reload();
Wallpapers.previewColorLock = false;
}
JsonAdapter { JsonAdapter {
id: adapter id: adapter
+28 -6
View File
@@ -6,7 +6,7 @@ import Quickshell.Io
import QtQuick import QtQuick
import ZShell.Models import ZShell.Models
import ZShell.Config import ZShell.Config
import qs.Modules import qs.Services
import qs.Helpers import qs.Helpers
import qs.Paths import qs.Paths
@@ -17,6 +17,8 @@ Searcher {
property alias crops: adapter.monitorCrops property alias crops: adapter.monitorCrops
readonly property string current: showPreview ? previewPath : actualCurrent readonly property string current: showPreview ? previewPath : actualCurrent
property alias monitorCrops: monitorCrops property alias monitorCrops: monitorCrops
property bool pendingPreviewClear
property bool previewColorLock
property string previewPath property string previewPath
property bool recentlyChanged property bool recentlyChanged
property bool showPreview: false property bool showPreview: false
@@ -34,9 +36,10 @@ Searcher {
function preview(path: string): void { function preview(path: string): void {
previewPath = path; previewPath = path;
if (Config.general.color.schemeGeneration)
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${previewPath}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]);
showPreview = true; showPreview = true;
if (Config.general.color.schemeGeneration)
previewColorsProc.running = true;
} }
function setCrop(screen: string, rect: rect, zoom: real): void { function setCrop(screen: string, rect: rect, zoom: real): void {
@@ -52,7 +55,6 @@ Searcher {
height: rect.height, height: rect.height,
zoom: zoom zoom: zoom
}; };
// root.crops = updated;
} }
function setWallpaper(path: string): void { function setWallpaper(path: string): void {
@@ -66,8 +68,10 @@ Searcher {
function stopPreview(): void { function stopPreview(): void {
showPreview = false; showPreview = false;
if (Config.general.color.schemeGeneration) if (previewColorLock)
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]); pendingPreviewClear = true;
else
Colors.showPreview = false;
} }
extraOpts: useFuzzy ? ({}) : ({ extraOpts: useFuzzy ? ({}) : ({
@@ -77,6 +81,11 @@ Searcher {
list: wallpapers.entries list: wallpapers.entries
useFuzzy: true useFuzzy: true
onPreviewColorLockChanged: {
if (!previewColorLock && pendingPreviewClear)
Colors.showPreview = false;
}
IpcHandler { IpcHandler {
function set(path: string): void { function set(path: string): void {
root.setWallpaper(path); root.setWallpaper(path);
@@ -118,4 +127,17 @@ Searcher {
path: Config.general.wallpaperPath path: Config.general.wallpaperPath
recursive: true recursive: true
} }
Process {
id: previewColorsProc
command: ["zshell-cli", "scheme", "generate", "-p", "-i", root.previewPath]
stdout: StdioCollector {
onStreamFinished: {
Colors.load(text, true);
Colors.showPreview = true;
}
}
}
} }
+52 -40
View File
@@ -3,24 +3,29 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Modules
import ZShell.Config import ZShell.Config
import qs.Modules.SysTray import qs.Modules.Bar.Components
import qs.Modules.Network import qs.Modules.Bar.Popouts
import qs.Modules.Updates
RowLayout { GridLayout {
id: root id: root
required property bool fullscreen required property bool fullscreen
required property bool horizontal
required property bool isHovered
required property Wrapper popouts required property Wrapper popouts
required property ClipWrapper popoutsWrapper required property ClipWrapper popoutsWrapper
required property ShellScreen screen required property ShellScreen screen
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property PersistentProperties visibilities
function checkPopout(x: real): void { function axisCenterOf(item: Item): real {
const ch = childAt(x, height / 2) as EntryWrapper; const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2);
return horizontal ? c.x : c.y;
}
function checkPopout(pos: real): void {
const ch = (horizontal ? childAt(pos, height / 2) : childAt(width / 2, pos)) as EntryWrapper;
const id = ch?.entryId; const id = ch?.entryId;
if (!ch || id === undefined) { if (!ch || id === undefined) {
@@ -34,26 +39,21 @@ RowLayout {
if (id === "statusIcons" && Config.bar.popouts.statusIcons) { if (id === "statusIcons" && Config.bar.popouts.statusIcons) {
const items = (ch.item as StatusIcons).items; const items = (ch.item as StatusIcons).items;
const localX = mapToItem(items, x, 0).x; const icon = horizontal ? items.childAt(mapToItem(items, pos, 0).x, items.height / 2) : items.childAt(items.width / 2, mapToItem(items, 0, pos).y);
const icon = items.childAt(localX, items.height / 2);
if (icon) { if (icon) {
popouts.currentName = icon.name; popouts.currentName = icon.name;
popouts.currentCenter = Qt.binding(() => icon.mapToItem(root, icon.implicitWidth / 2, 0).x); popouts.currentCenter = Qt.binding(() => axisCenterOf(icon));
popouts.hasCurrent = true; popouts.hasCurrent = true;
} }
} else if (id === "tray" && Config.bar.popouts.tray && Config.bar.tray.showOnHover) { } else if (id === "tray" && Config.bar.popouts.tray && Config.bar.tray.showOnHover) {
const tray = ch.item as TrayIcons; const tray = ch.item as TrayIcons;
const layout = tray.layout; const layout = tray.layout;
const localX = mapToItem(layout, x, 0).x; const trayItem = horizontal ? layout.childAt(mapToItem(layout, pos, 0).x, layout.height / 2) : layout.childAt(tray.width / 2, mapToItem(layout, 0, pos).y);
const trayItem = layout.childAt(localX, layout.height / 2);
if (trayItem) { if (trayItem) {
const idx = trayItem.index; const idx = trayItem.index;
popouts.currentName = `traymenu${idx}`; popouts.currentName = `traymenu${idx}`;
popouts.currentCenter = Qt.binding(() => { popouts.currentCenter = Qt.binding(() => axisCenterOf(trayItem));
const it = tray.items.itemAt(idx);
return it ? it.mapToItem(root, it.implicitWidth / 2, 0).x : popouts.currentCenter;
});
popouts.hasCurrent = true; popouts.hasCurrent = true;
} }
@@ -63,12 +63,19 @@ RowLayout {
if (id === "updates") { if (id === "updates") {
popouts.currentName = "updates"; popouts.currentName = "updates";
popouts.currentCenter = Qt.binding(() => ch.item.mapToItem(root, (ch.item as Item).implicitWidth / 2, 0).x); popouts.currentCenter = Qt.binding(() => axisCenterOf(ch.item as Item));
popouts.hasCurrent = true; popouts.hasCurrent = true;
} }
} }
spacing: Tokens.spacing.small function entryAt(pos: real): string {
const ch = (horizontal ? childAt(pos, height / 2) : childAt(width / 2, pos)) as EntryWrapper;
return ch?.entryId ?? "";
}
columnSpacing: Tokens.spacing.small
columns: horizontal ? -1 : 1
rowSpacing: Tokens.spacing.small
Repeater { Repeater {
id: repeater id: repeater
@@ -85,7 +92,8 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
HyprsunsetWidget { HyprsunsetWidget {
visible: !root.fullscreen horizontal: root.horizontal
visible: !root.fullscreen || root.isHovered
} }
} }
} }
@@ -94,7 +102,8 @@ RowLayout {
roleValue: "spacer" roleValue: "spacer"
delegate: EntryWrapper { delegate: EntryWrapper {
Layout.fillWidth: true Layout.fillHeight: !root.horizontal
Layout.fillWidth: root.horizontal
} }
} }
@@ -103,8 +112,9 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
Workspaces { Workspaces {
horizontal: root.horizontal
screen: root.screen screen: root.screen
visible: !root.fullscreen visible: !root.fullscreen || root.isHovered
} }
} }
} }
@@ -114,9 +124,10 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
TrayIcons { TrayIcons {
horizontal: root.horizontal
loader: root loader: root
popouts: root.popouts popouts: root.popouts
visible: !root.fullscreen visible: !root.fullscreen || root.isHovered
} }
} }
} }
@@ -126,6 +137,8 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
StatusIcons { StatusIcons {
horizontal: root.horizontal
visible: !root.fullscreen || root.isHovered
} }
} }
} }
@@ -135,8 +148,9 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
Resources { Resources {
horizontal: root.horizontal
visibilities: root.visibilities visibilities: root.visibilities
visible: !root.fullscreen visible: !root.fullscreen || root.isHovered
} }
} }
} }
@@ -146,7 +160,8 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
UpdatesWidget { UpdatesWidget {
visible: !root.fullscreen horizontal: root.horizontal
visible: !root.fullscreen || root.isHovered
} }
} }
} }
@@ -156,9 +171,10 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
NotifBell { NotifBell {
horizontal: root.horizontal
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities visibilities: root.visibilities
visible: !root.fullscreen visible: !root.fullscreen || root.isHovered
} }
} }
} }
@@ -168,10 +184,11 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
Clock { Clock {
horizontal: root.horizontal
loader: root loader: root
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities visibilities: root.visibilities
visible: !root.fullscreen visible: !root.fullscreen || root.isHovered
} }
} }
} }
@@ -182,16 +199,8 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
WindowTitle { WindowTitle {
bar: root bar: root
visible: !root.fullscreen horizontal: root.horizontal
} visible: !root.fullscreen || root.isHovered
}
}
DelegateChoice {
roleValue: "network"
delegate: EntryWrapper {
NetworkWidget {
} }
} }
} }
@@ -201,7 +210,8 @@ RowLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
MediaWidget { MediaWidget {
visible: !root.fullscreen horizontal: root.horizontal
visible: !root.fullscreen || root.isHovered
} }
} }
} }
@@ -214,9 +224,11 @@ RowLayout {
default property Item item default property Item item
required property var modelData required property var modelData
Layout.alignment: Qt.AlignVCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
Layout.leftMargin: index === 0 ? root.vPadding : (entryId === "statusIcons") ? -root.spacing + Tokens.spacing.extraSmall : 0 Layout.bottomMargin: !root.horizontal && index === repeater.count - 1 ? root.vPadding : 0
Layout.rightMargin: index === repeater.count - 1 ? root.vPadding : 0 Layout.leftMargin: root.horizontal && index === 0 ? root.vPadding : (entryId === "statusIcons" && root.horizontal) ? -root.rowSpacing + Tokens.spacing.extraSmall : 0
Layout.rightMargin: root.horizontal && index === repeater.count - 1 ? root.vPadding : 0
Layout.topMargin: !root.horizontal && index === 0 ? root.vPadding : (entryId === "statusIcons" && !root.horizontal) ? -root.columnSpacing + Tokens.spacing.extraSmall : 0
children: item children: item
implicitHeight: item?.implicitHeight ?? 0 implicitHeight: item?.implicitHeight ?? 0
implicitWidth: item?.implicitWidth ?? 0 implicitWidth: item?.implicitWidth ?? 0
+29 -21
View File
@@ -1,45 +1,48 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Hyprland
import QtQuick import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules
import ZShell.Config import ZShell.Config
import qs.Helpers import qs.Modules.Bar.Popouts
import qs.Modules.SysTray import qs.Components
import qs.Modules.SysTray.Widgets
import qs.Modules.Network
Item { Item {
id: root id: root
readonly property int contentHeight: Config.bar.height + padding * 2 readonly property int clampedExtent: Math.max(Config.bar.border, extent)
readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentHeight readonly property int contentThickness: Math.floor(Math.max(Config.bar.height, Tokens.bar.innerSize * (horizontal ? 1 : 1.5))) + padding * 2
readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentThickness
property real extent: fullscreen ? 0 : Config.bar.border
required property bool fullscreen required property bool fullscreen
readonly property bool horizontal: position !== "left"
property bool isHovered property bool isHovered
readonly property int padding: Math.max(Tokens.padding.smaller, Config.bar.border) readonly property int padding: Math.max(Tokens.padding.smaller, Config.bar.border)
required property Wrapper popouts required property Wrapper popouts
required property ClipWrapper popoutsWrapper required property ClipWrapper popoutsWrapper
required property string position
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeVisible: !fullscreen && (!Config.bar.autoHide || visibilities.bar || isHovered) readonly property bool shouldBeVisible: (!fullscreen && (!Config.bar.autoHide || visibilities.bar)) || isHovered
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property PersistentProperties visibilities
function checkPopout(x: real): void { function checkPopout(pos: real): void {
content.item?.checkPopout(x); (content.item as Bar)?.checkPopout(pos);
} }
implicitHeight: fullscreen ? 0 : Config.bar.border function entryAt(pos: real): string {
visible: height > Config.bar.border return (content.item as Bar)?.entryAt(pos) ?? "";
}
implicitHeight: extent
implicitWidth: extent
visible: extent > Config.bar.border
states: State { states: State {
name: "visible" name: "visible"
when: root.shouldBeVisible when: root.shouldBeVisible
PropertyChanges { PropertyChanges {
root.implicitHeight: root.contentHeight root.extent: root.contentThickness
} }
} }
transitions: [ transitions: [
@@ -48,7 +51,7 @@ Item {
to: "visible" to: "visible"
Anim { Anim {
property: "implicitHeight" property: "extent"
target: root target: root
} }
}, },
@@ -57,7 +60,7 @@ Item {
to: "" to: ""
Anim { Anim {
property: "implicitHeight" property: "extent"
target: root target: root
} }
} }
@@ -67,13 +70,18 @@ Item {
id: content id: content
active: root.shouldBeVisible || root.visible active: root.shouldBeVisible || root.visible
anchors.bottom: parent.bottom anchors.bottom: root.position !== "bottom" ? parent.bottom : undefined
anchors.left: parent.left anchors.left: root.horizontal ? parent.left : undefined
anchors.right: parent.right anchors.right: parent.right
anchors.top: root.position !== "top" ? parent.top : undefined
height: root.contentThickness
width: root.contentThickness
sourceComponent: Bar { sourceComponent: Bar {
fullscreen: root.fullscreen fullscreen: root.fullscreen
height: root.contentHeight height: root.contentThickness
horizontal: root.horizontal
isHovered: root.isHovered
popouts: root.popouts popouts: root.popouts
popoutsWrapper: root.popoutsWrapper popoutsWrapper: root.popoutsWrapper
screen: root.screen screen: root.screen
+157
View File
@@ -0,0 +1,157 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Modules.Bar.Popouts
import qs.Modules.Bar.Components.Common
import qs.Helpers
import qs.Components
import qs.Services
WidgetBase {
id: root
readonly property color contentColor: visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
readonly property string effectiveFormat: {
let fmt = Config.general.dateFormat;
if (!root.horizontal)
fmt = fmt.replace("dddd", "ddd");
return fmt;
}
readonly property var formatParts: {
const segments = root.effectiveFormat.split(" - ");
let datePart = "";
let timePart = "";
if (segments.length > 1) {
datePart = segments[0];
timePart = segments[1];
} else if (/[hms]/.test(segments[0])) {
timePart = segments[0];
} else {
datePart = segments[0];
}
const dateTokens = datePart.trim().length ? datePart.trim().split(/\s+/) : [];
const timeTokens = timePart.trim().length ? timePart.trim().split(":") : [];
return {
dateTokens: dateTokens,
timeTokens: timeTokens
};
}
required property GridLayout loader
required property Wrapper popouts
readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.normal * 2 : verticalColumn.implicitHeight + Tokens.padding.larger * 2
required property PersistentProperties visibilities
color: visibilities.dashboard ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? baseSize : size
implicitWidth: horizontal ? size : baseSize
radius: Tokens.rounding.full
CustomText {
id: timeText
anchors.centerIn: parent
color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type
height: implicitHeight
text: Time.dateStr
visible: root.horizontal
Behavior on color {
CAnim {
}
}
}
ColumnLayout {
id: verticalColumn
anchors.centerIn: parent
spacing: Tokens.padding.small ?? 2
visible: !root.horizontal
width: root.baseSize
Repeater {
model: root.formatParts.dateTokens
CustomText {
required property string modelData
Layout.alignment: Qt.AlignHCenter
color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type
text: Qt.formatDateTime(Time.date, modelData)
Behavior on color {
CAnim {
}
}
}
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Tokens.padding.small ?? 2
Layout.topMargin: Tokens.padding.small ?? 2
color: root.contentColor
height: 1
implicitWidth: root.baseSize * 0.5
opacity: 0.4
radius: 1
visible: root.formatParts.dateTokens.length > 0 && root.formatParts.timeTokens.length > 0
}
Repeater {
model: root.formatParts.timeTokens
CustomText {
required property string modelData
Layout.alignment: Qt.AlignHCenter
color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type
text: {
if (modelData.includes("h"))
return Time.hourStr;
if (modelData.includes("m"))
return Time.minuteStr;
if (modelData.includes("s"))
return Time.secondStr;
return "";
}
Behavior on color {
CAnim {
}
}
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type
text: Qt.formatDateTime(Time.date, "AP")
visible: Config.services.useTwelveHourClock && root.formatParts.timeTokens.length > 0
Behavior on color {
CAnim {
}
}
}
}
StateLayer {
acceptedButtons: Qt.LeftButton
color: root.contentColor
onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard;
}
}
}
@@ -0,0 +1,85 @@
import QtQuick
import ZShell.Config
import qs.Services
import qs.Components
import qs.Helpers
Row {
id: root
property real batHeight: Tokens.padding.smaller * 2
property real batWidth: Tokens.padding.larger * 2
required property string devState
property real nubHeight: Tokens.padding.small
property real nubWidth: 2
required property real percentage
property real radius: Tokens.rounding.smallest / 2
spacing: 1
CustomRect {
id: track
anchors.verticalCenter: parent.verticalCenter
color: Battery.getColors(root.percentage, root.devState).bg
height: root.batHeight
radius: root.radius
width: root.batWidth
CustomText {
color: Battery.getColors(root.percentage, root.devState).fg
font.pointSize: Tokens.font.size.larger / 1.5
font.weight: 800
height: track.height
horizontalAlignment: Text.AlignHCenter
text: Math.round(root.percentage * 100)
verticalAlignment: Text.AlignVCenter
width: track.width
}
Item {
clip: true
width: parent.width * root.percentage
anchors {
bottom: parent.bottom
left: parent.left
top: parent.top
}
CustomRect {
id: fill
color: Battery.getColors(root.percentage, root.devState).fg
height: track.height
radius: track.radius
width: track.width
CustomText {
id: batteryLabel
clip: true
color: Battery.getColors(root.percentage, root.devState).bg
font.pointSize: Tokens.font.size.larger / 1.5
font.weight: 800
height: track.height
horizontalAlignment: Text.AlignHCenter
text: Math.round(root.percentage * 100)
verticalAlignment: Text.AlignVCenter
width: track.width
}
}
}
}
CustomRect {
id: nub
anchors.verticalCenter: parent.verticalCenter
bottomRightRadius: 20
color: root.percentage < 0.99 ? track.color : fill.color
height: root.nubHeight
topRightRadius: 20
width: root.nubWidth
}
}
@@ -0,0 +1,16 @@
import QtQuick
import ZShell.Config
import qs.Components
import qs.Services
CustomRect {
id: root
readonly property int baseSize: horizontal ? Math.max(Config.bar.height, Tokens.bar.innerSize) : Math.max(Config.bar.height, Math.floor(Tokens.bar.innerSize * 1.5))
required property bool horizontal
color: Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? baseSize : width
implicitWidth: horizontal ? height : baseSize
radius: Tokens.rounding.full
}
@@ -1,17 +1,16 @@
import QtQuick import QtQuick
import qs.Components
import qs.Helpers
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Modules.Bar.Components.Common
import qs.Helpers
import qs.Services import qs.Services
CustomRect { WidgetBase {
id: root id: root
property bool tempEnabled: Hyprsunset.enabled property bool tempEnabled: Hyprsunset.enabled
color: root.tempEnabled ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: root.tempEnabled ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: Config.bar.height + Tokens.padding.smallest * 2
implicitWidth: implicitHeight
radius: Tokens.rounding.full radius: Tokens.rounding.full
StateLayer { StateLayer {
@@ -26,6 +25,7 @@ CustomRect {
animate: true animate: true
color: root.tempEnabled ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.tempEnabled ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: root.tempEnabled ? 1 : 0 fill: root.tempEnabled ? 1 : 0
font.pointSize: Tokens.font.size.larger
text: root.tempEnabled ? "lightbulb" : "light_off" text: root.tempEnabled ? "lightbulb" : "light_off"
Behavior on fill { Behavior on fill {
+92
View File
@@ -0,0 +1,92 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Modules.Bar.Components.Common
import qs.Helpers
import qs.Services
WidgetBase {
id: root
readonly property string currentMedia: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
readonly property int textWidth: Math.min(metrics.width, 200)
implicitHeight: horizontal ? baseSize : layout.implicitHeight + Tokens.padding.normal * 2
implicitWidth: horizontal ? layout.implicitWidth + Tokens.padding.normal * 2 : baseSize
radius: Tokens.rounding.full
Behavior on implicitHeight {
enabled: !root.horizontal
Anim {
}
}
Behavior on implicitWidth {
enabled: root.horizontal
Anim {
}
}
TextMetrics {
id: metrics
font: mediatext.font
text: mediatext.text
}
GridLayout {
id: layout
anchors.bottomMargin: root.horizontal ? 0 : Tokens.padding.normal
anchors.fill: parent
anchors.leftMargin: root.horizontal ? Tokens.padding.normal : 0
anchors.rightMargin: root.horizontal ? Tokens.padding.normal : 0
anchors.topMargin: root.horizontal ? 0 : Tokens.padding.normal
columns: root.horizontal ? -1 : 1
Behavior on implicitWidth {
Anim {
}
}
MaterialIcon {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
animate: true
color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.larger
text: Players.active?.isPlaying ? "music_note" : "music_off"
}
MarqueeText {
id: mediatext
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
animate: true
color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter
implicitHeight: root.horizontal ? root.implicitHeight : root.textWidth
implicitWidth: root.horizontal ? root.textWidth : root.implicitWidth
marqueeEnabled: false
pauseMs: 4000
text: root.currentMedia
vertical: !root.horizontal
}
}
CustomMouseArea {
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: {
if (!containsMouse) {
mediatext.marqueeEnabled = false;
} else {
mediatext.marqueeEnabled = true;
mediatext.anim.start();
}
}
}
}
@@ -1,19 +1,19 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import ZShell.Config import ZShell.Config
import qs.Modules.Bar.Components.Common
import qs.Modules.Bar.Popouts
import qs.Daemons import qs.Daemons
import qs.Components import qs.Components
import qs.Services import qs.Services
CustomRect { WidgetBase {
id: root id: root
required property Wrapper popouts required property Wrapper popouts
required property PersistentProperties visibilities required property PersistentProperties visibilities
color: visibilities.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: visibilities.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: Config.bar.height + Tokens.padding.smallest * 2
implicitWidth: implicitHeight
radius: Tokens.rounding.full radius: Tokens.rounding.full
MaterialIcon { MaterialIcon {
@@ -22,7 +22,6 @@ CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: root.visibilities.sidebar ? 1 : 0 fill: root.visibilities.sidebar ? 1 : 0
font.family: "Material Symbols Rounded"
font.pointSize: Tokens.font.size.larger font.pointSize: Tokens.font.size.larger
text: NotifServer.list.length ? "\uf4fe" : "\ue7f4" text: NotifServer.list.length ? "\uf4fe" : "\ue7f4"
@@ -5,13 +5,14 @@ import qs.Components
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
RowLayout { GridLayout {
id: root id: root
property color accentColor: warning ? Colors.palette.m3error : mainColor property color accentColor: warning ? Colors.palette.m3error : mainColor
property real animatedPercentage: 0 property real animatedPercentage: 0
readonly property real arcStartAngle: 0.75 * Math.PI readonly property real arcStartAngle: 0.75 * Math.PI
readonly property real arcSweep: 1.5 * Math.PI readonly property real arcSweep: 1.5 * Math.PI
required property bool horizontal
property string icon property string icon
required property color iconColor required property color iconColor
required property color mainColor required property color mainColor
@@ -21,8 +22,9 @@ RowLayout {
property bool warning: percentage * 100 >= warningThreshold property bool warning: percentage * 100 >= warningThreshold
property int warningThreshold: 80 property int warningThreshold: 80
columnSpacing: Tokens.spacing.smaller
columns: horizontal ? -1 : 1
percentage: 0 percentage: 0
spacing: Tokens.spacing.smaller
Behavior on animatedPercentage { Behavior on animatedPercentage {
Anim { Anim {
@@ -47,8 +49,8 @@ RowLayout {
} }
CustomClippingRect { CustomClippingRect {
Layout.preferredHeight: root.height - Tokens.padding.small Layout.preferredHeight: root.horizontal ? icon.implicitHeight - Tokens.padding.small : 4
Layout.preferredWidth: 4 Layout.preferredWidth: root.horizontal ? 4 : icon.implicitWidth
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2) color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
radius: Tokens.rounding.full radius: Tokens.rounding.full
@@ -57,9 +59,11 @@ RowLayout {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: root.horizontal ? parent.right : undefined
anchors.top: root.horizontal ? undefined : parent.top
color: root.mainColor color: root.mainColor
implicitHeight: Math.ceil(root.percentage * parent.height) implicitHeight: root.horizontal ? Math.ceil(root.percentage * parent.height) : 0
implicitWidth: root.horizontal ? 0 : Math.ceil(root.percentage * parent.width)
// Behavior on implicitHeight { // Behavior on implicitHeight {
// Anim { // Anim {
@@ -6,18 +6,18 @@ import QtQuick.Layouts
import ZShell.Config import ZShell.Config
import ZShell.Services import ZShell.Services
import qs.Services import qs.Services
import qs.Modules import qs.Modules.Bar.Components.Common
import qs.Components import qs.Components
CustomRect { WidgetBase {
id: root id: root
required property PersistentProperties visibilities required property PersistentProperties visibilities
clip: true clip: true
color: visibilities.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: visibilities.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: Config.bar.height + Tokens.padding.smallest * 2 implicitHeight: horizontal ? baseSize : gridLayout.implicitHeight + Tokens.padding.normal * 2
implicitWidth: rowLayout.implicitWidth + Tokens.padding.larger * 2 implicitWidth: horizontal ? gridLayout.implicitWidth + Tokens.padding.larger * 2 : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
StateLayer { StateLayer {
@@ -26,13 +26,14 @@ CustomRect {
onClicked: root.visibilities.resources = !root.visibilities.resources onClicked: root.visibilities.resources = !root.visibilities.resources
} }
RowLayout { GridLayout {
id: rowLayout id: gridLayout
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: -2 anchors.horizontalCenterOffset: root.horizontal ? 0 : 1
implicitHeight: root.implicitHeight anchors.verticalCenterOffset: root.horizontal ? 0 : -3
spacing: Tokens.spacing.smaller columnSpacing: Tokens.spacing.smaller
columns: root.horizontal ? -1 : 1
ServiceRef { ServiceRef {
service: Gpu service: Gpu
@@ -48,7 +49,9 @@ CustomRect {
Resource { Resource {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true Layout.fillHeight: root.horizontal
Layout.fillWidth: !root.horizontal
horizontal: root.horizontal
icon: "memory" icon: "memory"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
@@ -57,7 +60,9 @@ CustomRect {
} }
Resource { Resource {
Layout.fillHeight: true Layout.fillHeight: root.horizontal
Layout.fillWidth: !root.horizontal
horizontal: root.horizontal
icon: "memory_alt" icon: "memory_alt"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3secondary mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3secondary
@@ -66,7 +71,9 @@ CustomRect {
} }
Resource { Resource {
Layout.fillHeight: true Layout.fillHeight: root.horizontal
Layout.fillWidth: !root.horizontal
horizontal: root.horizontal
icon: "gamepad" icon: "gamepad"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3tertiary mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3tertiary
@@ -74,7 +81,9 @@ CustomRect {
} }
Resource { Resource {
Layout.fillHeight: true Layout.fillHeight: root.horizontal
Layout.fillWidth: !root.horizontal
horizontal: root.horizontal
icon: "developer_board" icon: "developer_board"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
@@ -3,13 +3,14 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Helpers
import qs.Modules.SysTray.Widgets
import ZShell.Config import ZShell.Config
import qs.Helpers
import qs.Modules.Bar.Components.StatusIcons
import qs.Modules.Bar.Components.Common
import qs.Components import qs.Components
import qs.Services import qs.Services
CustomClippingRect { WidgetBase {
id: root id: root
readonly property int firstPresent: { readonly property int firstPresent: {
@@ -21,7 +22,7 @@ CustomClippingRect {
} }
// Index of the first/last entry that isn't collapsed, for edge margin gating // Index of the first/last entry that isn't collapsed, for edge margin gating
readonly property alias items: row readonly property alias items: grid
readonly property int lastPresent: { readonly property int lastPresent: {
const values = model.values; const values = model.values;
for (let i = values.length - 1; i >= 0; i--) for (let i = values.length - 1; i >= 0; i--)
@@ -29,6 +30,7 @@ CustomClippingRect {
return i; return i;
return -1; return -1;
} }
readonly property real size: horizontal ? grid.implicitWidth + Tokens.padding.small * 2 : grid.implicitHeight + Tokens.padding.small * 2
readonly property int spacing: Tokens.spacing.normal / 2 readonly property int spacing: Tokens.spacing.normal / 2
// Entries that can shrink to nothing, spacing included // Entries that can shrink to nothing, spacing included
@@ -38,19 +40,36 @@ CustomClippingRect {
return false; return false;
} }
bottomLeftRadius: Tokens.rounding.smallest / 2 bottomLeftRadius: horizontal ? Tokens.rounding.smallest / 2 : Tokens.rounding.full
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
implicitHeight: Config.bar.height + Tokens.padding.smallest * 2 implicitHeight: horizontal ? baseSize : size
implicitWidth: row.implicitWidth + Tokens.padding.small * 2 implicitWidth: horizontal ? size : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
topLeftRadius: Tokens.rounding.smallest / 2 topLeftRadius: Tokens.rounding.smallest / 2
topRightRadius: horizontal ? Tokens.rounding.full : Tokens.rounding.smallest / 2
RowLayout { Behavior on implicitHeight {
id: row enabled: !root.horizontal
Anim {
}
}
Behavior on implicitWidth {
enabled: root.horizontal
Anim {
}
}
GridLayout {
id: grid
anchors.bottomMargin: root.horizontal ? 0 : Tokens.padding.small
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Tokens.padding.small anchors.leftMargin: root.horizontal ? Tokens.padding.small : 0
anchors.rightMargin: Tokens.padding.small anchors.rightMargin: root.horizontal ? Tokens.padding.small : 0
anchors.topMargin: root.horizontal ? 0 : Tokens.padding.small
columns: root.horizontal ? -1 : 1
Repeater { Repeater {
model: ScriptModel { model: ScriptModel {
@@ -69,6 +88,7 @@ CustomClippingRect {
name: "audio" name: "audio"
AudioWidget { AudioWidget {
horizontal: root.horizontal
} }
} }
} }
@@ -80,6 +100,29 @@ CustomClippingRect {
name: "audio" name: "audio"
MicWidget { MicWidget {
horizontal: root.horizontal
}
}
}
DelegateChoice {
roleValue: "bluetooth"
delegate: EntryWrapper {
name: "bluetooth"
BluetoothWidget {
}
}
}
DelegateChoice {
roleValue: "network"
delegate: EntryWrapper {
name: "network"
NetworkWidget {
} }
} }
} }
@@ -109,8 +152,10 @@ CustomClippingRect {
property real rightGap: present && index !== root.firstPresent ? margin : 0 property real rightGap: present && index !== root.firstPresent ? margin : 0
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: Math.round(leftGap) Layout.bottomMargin: root.horizontal ? 0 : Math.round(rightGap)
Layout.rightMargin: Math.round(rightGap) Layout.leftMargin: root.horizontal ? Math.round(leftGap) : 0
Layout.rightMargin: root.horizontal ? Math.round(rightGap) : 0
Layout.topMargin: root.horizontal ? 0 : Math.round(leftGap)
children: item children: item
implicitHeight: item?.implicitHeight ?? 0 implicitHeight: item?.implicitHeight ?? 0
implicitWidth: item?.implicitWidth ?? 0 implicitWidth: item?.implicitWidth ?? 0
@@ -1,16 +1,15 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Daemons
import qs.Modules
import ZShell.Config import ZShell.Config
import qs.Daemons
import qs.Components import qs.Components
import qs.Services import qs.Services
MaterialIcon { MaterialIcon {
id: speaker id: speaker
required property bool horizontal
animate: true animate: true
color: Audio.muted ? Colors.palette.m3error : Colors.palette.m3onSurface color: Audio.muted ? Colors.palette.m3error : Colors.palette.m3onSurface
fill: 1 fill: 1
@@ -0,0 +1,14 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Services
import qs.Components
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
color: Colors.palette.m3onSurface // Network.connected ? root.textColor : Colors.palette.m3error
fill: 1
font.pointSize: Tokens.font.size.larger
text: "bluetooth"
}
@@ -1,16 +1,15 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Daemons
import qs.Modules
import ZShell.Config import ZShell.Config
import qs.Daemons
import qs.Components import qs.Components
import qs.Services import qs.Services
MaterialIcon { MaterialIcon {
id: mic id: mic
required property bool horizontal
animate: true animate: true
color: (Audio.sourceMuted ?? false) ? Colors.palette.m3error : Colors.palette.m3onSurface color: (Audio.sourceMuted ?? false) ? Colors.palette.m3error : Colors.palette.m3onSurface
fill: 1 fill: 1
@@ -0,0 +1,14 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Services
import qs.Components
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
color: Colors.palette.m3onSurface // Network.connected ? root.textColor : Colors.palette.m3error
fill: 1
font.pointSize: Tokens.font.size.larger
text: "android_wifi_4_bar"
}
@@ -0,0 +1,80 @@
import Quickshell.Services.UPower
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Modules.Bar.Components.Common
import qs.Components
import qs.Helpers
Item {
id: root
readonly property Item currentItem: batteryIconLoader?.item ?? peripheralIconLoader?.item ?? upowerIconLoader.item
implicitHeight: currentItem?.implicitHeight ?? 0
implicitWidth: currentItem?.implicitWidth ?? 0
Behavior on Layout.preferredHeight {
Anim {
}
}
Behavior on Layout.preferredWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Component.onCompleted: Battery.startDaemon()
Loader {
id: batteryIconLoader
active: Battery.isLaptop
anchors.centerIn: parent
sourceComponent: BatteryIcon {
devState: Battery.deviceStateString
percentage: Battery.currentPerc
}
}
Loader {
id: peripheralIconLoader
active: (Battery.lowestPeripheral?.isPresent ?? false) && !batteryIconLoader.active
anchors.centerIn: parent
sourceComponent: BatteryIcon {
devState: Battery.lowestPeripheral?.state ?? ""
percentage: Battery.lowestPeripheral?.percentage ?? 0
}
}
Loader {
id: upowerIconLoader
active: !batteryIconLoader.active && !peripheralIconLoader.active
anchors.centerIn: parent
sourceComponent: MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
fill: 1
font.pointSize: Tokens.font.size.larger
text: {
if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "energy_savings_leaf";
if (PowerProfiles.profile === PowerProfile.Performance)
return "bolt";
return "balance";
}
}
}
}
@@ -1,10 +1,9 @@
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick import QtQuick
import QtQuick.VectorImage
import Quickshell import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import qs.Helpers import qs.Helpers
import qs.Modules import qs.Modules.Bar.Popouts
import qs.Components import qs.Components
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
@@ -13,10 +12,10 @@ Item {
id: root id: root
property bool current: popouts.currentName.startsWith(`traymenu${ind}`) && popouts.hasCurrent property bool current: popouts.currentName.startsWith(`traymenu${ind}`) && popouts.hasCurrent
readonly property real dpr: Hypr.monitorFor(loader.screen).scale readonly property real dpr: Hypr.monitorFor(loader?.screen).scale
required property int ind required property int ind
required property SystemTrayItem item required property SystemTrayItem item
required property RowLayout loader required property GridLayout loader
required property Wrapper popouts required property Wrapper popouts
function resolveIcon(app: string, icon: string): string { function resolveIcon(app: string, icon: string): string {
@@ -3,33 +3,37 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import qs.Components
import ZShell.Config import ZShell.Config
import qs.Modules.SysTray.Widgets import qs.Components
import qs.Modules import qs.Modules.Bar.Components.Common
import qs.Modules.Bar.Components.Tray
import qs.Modules.Bar.Popouts
import qs.Services import qs.Services
CustomClippingRect { WidgetBase {
id: root id: root
readonly property alias items: repeater readonly property alias items: repeater
readonly property alias layout: sysRow readonly property alias layout: sysGrid
required property RowLayout loader required property GridLayout loader
readonly property int padding: Tokens.padding.small readonly property int padding: Tokens.padding.small
required property Wrapper popouts required property Wrapper popouts
readonly property real size: horizontal ? sysGrid.implicitWidth + Tokens.padding.small : sysGrid.implicitHeight + Tokens.padding.small
bottomLeftRadius: horizontal ? Tokens.rounding.full : Tokens.rounding.smallest / 2
bottomRightRadius: Tokens.rounding.smallest / 2 bottomRightRadius: Tokens.rounding.smallest / 2
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
implicitHeight: Config.bar.height + Tokens.padding.smallest * 2 implicitHeight: horizontal ? baseSize : size
implicitWidth: sysRow.implicitWidth + Tokens.padding.small * 2 implicitWidth: horizontal ? size : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
topRightRadius: Tokens.rounding.smallest / 2 topRightRadius: horizontal ? Tokens.rounding.smallest / 2 : Tokens.rounding.full
RowLayout { GridLayout {
id: sysRow id: sysGrid
anchors.centerIn: parent anchors.centerIn: parent
spacing: 0 columns: root.horizontal ? -1 : 1
rowSpacing: -2
Repeater { Repeater {
id: repeater id: repeater
+41
View File
@@ -0,0 +1,41 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Modules.Bar.Components.Common
import qs.Helpers
import qs.Services
WidgetBase {
id: root
property int countUpdates: Updates.availableUpdates
property color textColor: Colors.palette.m3onSurface
color: Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? baseSize : content.implicitHeight + Tokens.spacing.small
implicitWidth: horizontal ? content.implicitWidth + Tokens.spacing.small * 2 : baseSize
radius: Tokens.rounding.full
GridLayout {
id: content
anchors.centerIn: parent
columnSpacing: Tokens.spacing.small
columns: root.horizontal ? -1 : 1
rowSpacing: -Tokens.spacing.extraSmall / 2
MaterialIcon {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
font.pointSize: root.horizontal ? Tokens.font.size.larger : Tokens.font.size.large
text: "package_2"
}
CustomText {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
color: root.textColor
font.pointSize: Tokens.font.size.normal
text: root.countUpdates
}
}
}
+98
View File
@@ -0,0 +1,98 @@
pragma ComponentBehavior: Bound
import QtQuick
import ZShell.Config
import qs.Components
import qs.Helpers
import qs.Services
Item {
id: root
required property var bar
property color color: Colors.palette.m3primary
property Title current: text1
required property bool horizontal
// readonly property int maxWidth: 300
readonly property int maxSize: {
const otherModules = bar.children.filter(c => c.enabled && c.entryId && c.item !== this && c.entryId !== "spacer");
const otherSize = otherModules.reduce((acc, curr) => acc + (horizontal ? (curr.item?.nonAnimWidth ?? curr.width ?? 0) : (curr.item.nonAnimHeight ?? curr.height ?? 0)), 0);
return horizontal ? (bar.width - otherSize - bar.columnSpacing * (bar.children.length - 1) - bar.vPadding * 2) : (bar.height - otherSize - bar.rowSpacing * (bar.children.length - 1) - bar.vPadding * 4);
}
clip: true
implicitHeight: horizontal ? current.implicitHeight : current.implicitWidth + current.anchors.topMargin
implicitWidth: horizontal ? current.implicitWidth + current.anchors.leftMargin : current.implicitHeight + current.anchors.topMargin
Behavior on implicitHeight {
enabled: !root.horizontal
Anim {
type: Anim.DefaultEffects
}
}
Behavior on implicitWidth {
enabled: root.horizontal
Anim {
type: Anim.DefaultEffects
}
}
Title {
id: text1
}
Title {
id: text2
}
TextMetrics {
id: metrics
elide: Qt.ElideRight
elideWidth: root.maxSize - (root.horizontal ? root.current.anchors.leftMargin : 0)
font.family: "Rubik"
font.pointSize: Tokens.font.size.normal
text: Hypr.activeToplevel?.title ?? qsTr("Desktop")
onElideWidthChanged: root.current.text = elidedText
onTextChanged: {
const next = root.current === text1 ? text2 : text1;
next.text = elidedText;
root.current = next;
}
}
component Title: CustomText {
id: text
anchors.horizontalCenter: root.horizontal ? undefined : parent.horizontalCenter
anchors.left: root.horizontal ? parent.left : undefined
anchors.leftMargin: root.horizontal ? Tokens.spacing.small : 0
anchors.top: root.horizontal ? undefined : parent.top
anchors.topMargin: root.horizontal ? 0 : Tokens.spacing.small
anchors.verticalCenter: root.horizontal ? parent.verticalCenter : undefined
color: root.color
font.family: metrics.font.family
font.pointSize: metrics.font.pointSize
height: root.horizontal ? implicitHeight : implicitWidth
opacity: root.current === this ? 1 : 0
width: root.horizontal ? implicitWidth : implicitHeight
Behavior on opacity {
Anim {
}
}
transform: [
Translate {
x: !root.horizontal ? -text.implicitWidth + text.implicitHeight : 0
},
Rotation {
angle: root.horizontal ? 0 : 270
origin.x: text.implicitHeight / 2
origin.y: text.implicitHeight / 2
}
]
}
}
@@ -2,10 +2,10 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import ZShell.Config import ZShell.Config
import qs.Modules.Bar.Components.Workspaces
import qs.Components import qs.Components
import qs.Services import qs.Services
@@ -14,41 +14,57 @@ Item {
property real activeWorkspaceMargin: Math.ceil(Tokens.padding.small / 2) property real activeWorkspaceMargin: Math.ceil(Tokens.padding.small / 2)
readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1 readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1
required property bool horizontal
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
required property ShellScreen screen required property ShellScreen screen
property int workspaceButtonWidth: bgRect.implicitHeight - root.activeWorkspaceMargin * 2 readonly property real shortSize: Math.max(Config.bar.height, Tokens.bar.innerSize)
readonly property real size: (workspaceButtonWidth * workspacesShown) + activeWorkspaceMargin * 2
property int workspaceButtonWidth: (horizontal ? bgRect.implicitHeight : bgRect.implicitWidth) - root.activeWorkspaceMargin * 2
property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown
readonly property list<var> workspaces: Hyprland.workspaces.values.filter(w => w.monitor === root.monitor) readonly property list<var> workspaces: Hyprland.workspaces.values.filter(w => w.monitor === root.monitor)
readonly property int workspacesShown: workspaces.length readonly property int workspacesShown: workspaces.length
height: implicitHeight implicitHeight: horizontal ? shortSize : size
implicitHeight: Config.bar.height + Tokens.padding.smaller * 2 implicitWidth: horizontal ? size : shortSize
implicitWidth: (root.workspaceButtonWidth * root.workspacesShown) + root.activeWorkspaceMargin * 2
Behavior on implicitHeight {
enabled: !root.horizontal
Anim {
}
}
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: root.horizontal
Anim { Anim {
} }
} }
CustomRect { CustomRect {
// qmllint disable Quick.anchor-combinations
id: bgRect id: bgRect
anchors.left: parent.left anchors.bottom: root.horizontal ? undefined : parent.bottom
anchors.right: parent.right anchors.horizontalCenter: root.horizontal ? undefined : parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.left: root.horizontal ? parent.left : undefined
anchors.right: root.horizontal ? parent.right : undefined
anchors.top: root.horizontal ? undefined : parent.top
anchors.verticalCenter: root.horizontal ? parent.verticalCenter : undefined
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
implicitHeight: root.implicitHeight - ((Tokens.padding.small - 1) * 2) implicitHeight: root.horizontal ? root.implicitHeight : 0
implicitWidth: root.horizontal ? 0 : root.implicitWidth
radius: height / 2 radius: height / 2
// qmllint enable Quick.anchor-combinations
Grid { Grid {
id: grid id: grid
anchors.fill: parent anchors.fill: parent
anchors.margins: root.activeWorkspaceMargin anchors.margins: root.activeWorkspaceMargin
columnSpacing: 0 columnSpacing: 0
columns: root.workspacesShown columns: root.horizontal ? root.workspacesShown : 1
rowSpacing: 0 rowSpacing: 0
rows: 1 rows: root.horizontal ? 1 : root.workspacesShown
Repeater { Repeater {
model: root.workspaces model: root.workspaces
@@ -74,7 +90,7 @@ Item {
color: Colors.palette.m3onSecondaryContainer color: Colors.palette.m3onSecondaryContainer
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: button.modelData.name text: button.modelData?.name ?? ""
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }
@@ -94,13 +110,15 @@ Item {
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorThickness: root.workspaceButtonWidth property real indicatorThickness: root.workspaceButtonWidth
anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: root.horizontal ? undefined : parent.horizontalCenter
anchors.verticalCenter: root.horizontal ? parent.verticalCenter : undefined
clip: true clip: true
color: Colors.palette.m3primary color: Colors.palette.m3primary
implicitHeight: indicatorThickness implicitHeight: root.horizontal ? indicatorThickness : indicatorLength
implicitWidth: indicatorLength implicitWidth: root.horizontal ? indicatorLength : indicatorThickness
radius: Tokens.rounding.full radius: Tokens.rounding.full
x: indicatorPosition x: root.horizontal ? indicatorPosition : 0
y: root.horizontal ? 0 : indicatorPosition
AnimatedTabIndexPair { AnimatedTabIndexPair {
id: idxPair id: idxPair
@@ -114,7 +132,8 @@ Item {
implicitWidth: grid.width implicitWidth: grid.width
source: grid source: grid
sourceColor: Colors.palette.m3onSurface sourceColor: Colors.palette.m3onSurface
x: -indicator.x + 3 x: root.horizontal ? -indicator.x + 3 : 0
y: root.horizontal ? 0 : -indicator.y + 3
} }
} }
} }
+385
View File
@@ -0,0 +1,385 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQml.Models
import Quickshell
import Quickshell.Bluetooth
import ZShell.Components
import ZShell.Config
import qs.Services
import qs.Components
import qs.Helpers
CustomClippingRect {
id: root
readonly property list<var> btConnected: Bluetooth.devices.values.filter(d => d.connected)
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
implicitWidth: 500
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.larger
spacing: Tokens.spacing.extraSmall / 2
CustomText {
Layout.rightMargin: Tokens.padding.extraSmall
Layout.topMargin: Tokens.padding.normal
font.pointSize: Tokens.font.size.large
text: qsTr("Bluetooth")
}
Toggle {
checked: Bluetooth.defaultAdapter?.enabled ?? false // qmllint disable unresolved-type
first: true
subtext: qsTr("Toggle bluetooth device state")
text: checked ? qsTr("Enabled") : qsTr("Disabled")
onToggled: {
const adapter = Bluetooth.defaultAdapter; // qmllint disable unresolved-type
if (adapter)
adapter.enabled = checked;
}
}
Toggle {
checked: Bluetooth.defaultAdapter?.discovering ?? false // qmllint disable unresolved-type
last: root.btConnected.length === 0
subtext: qsTr("Enable scanning for new devices")
text: qsTr("Discover")
onToggled: {
const adapter = Bluetooth.defaultAdapter; // qmllint disable unresolved-type
if (adapter)
adapter.discovering = checked;
}
}
ListView {
id: rows
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
section.property: "connected"
spacing: Tokens.spacing.extraSmall / 2
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
}
}
delegate: DelegateChooser {
role: "isDivider"
DelegateChoice {
roleValue: true
delegate: StatusDivider {
}
}
DelegateChoice {
delegate: BluetoothItem {
list: rows
width: rows.width
}
}
}
displaced: Transition {
Anim {
easing.type: Easing.OutCubic
property: "y"
}
}
model: ScriptModel {
objectProp: "address"
values: {
const byName = (a, b) => a.name.localeCompare(b.name);
return [...[...Bluetooth.devices.values].filter(d => d.connected).sort(byName).slice(0, 5),
{
isDivider: true
},
...[...Bluetooth.devices.values].filter(d => !d.connected).sort(byName).slice(0, 5)];
}
}
move: Transition {
Anim {
easing.type: Easing.OutCubic
property: "y"
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
}
}
section.delegate: Rectangle {
required property string section
color: "transparent"
height: childrenRect.height
width: ListView.view.width
CustomText {
text: parent.section
}
}
}
}
component BluetoothItem: ConnectedRect {
id: btItem
readonly property int connectedCount: root.btConnected.length
readonly property bool connecting: Network.connectingNetwork === modelData
property int horizontalPadding: Tokens.padding.largeIncreased
readonly property bool inConnectedGroup: index < connectedCount
required property int index
required property ListView list
readonly property bool loading: modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting // qmllint disable unresolved-type
required property BluetoothDevice modelData
property int verticalPadding: Tokens.padding.smaller
first: !inConnectedGroup && index === connectedCount + 1
implicitHeight: rowLayout.implicitHeight + verticalPadding * 2
last: inConnectedGroup ? index === connectedCount - 1 : index === list.count - 1
radius: Tokens.rounding.small
Timer {
interval: 50
running: btItem.ListView.delayRemove
onTriggered: btItem.ListView.delayRemove = false
}
Connections {
function onConnectedChanged(): void {
if (!btItem.modelData.connected)
btItem.ListView.delayRemove = true;
}
function onPairedChanged(): void {
if (btItem.modelData.paired)
btItem.modelData.connect();
}
target: btItem.modelData
}
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: btItem.horizontalPadding
anchors.rightMargin: btItem.horizontalPadding
Column {
id: column
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.leftMargin: Tokens.spacing.extraSmall
Layout.rightMargin: Tokens.spacing.extraSmall
elide: Text.ElideRight
text: btItem.modelData.name
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
text: btItem.modelData.connected ? (btItem.modelData.batteryAvailable ? qsTr("%1 battery left").arg(Math.round(btItem.modelData.battery * 100) + "%") : qsTr("Battery status unknown")) : btItem.modelData.address
}
}
Item {
Layout.alignment: Qt.AlignRight
implicitHeight: icon.height
implicitWidth: icon.width
MaterialIcon {
id: icon
font.pointSize: Tokens.font.size.large
opacity: btItem.connecting ? 0 : 1
text: btItem.modelData.batteryAvailable ? Icons.getBatteryIcon(btItem.modelData.battery) : "battery_unknown"
visible: btItem.modelData.state === BluetoothDeviceState.Connected // qmllint disable unresolved-type
Behavior on opacity {
Anim {
}
}
}
Loader {
active: opacity > 0
anchors.fill: parent
opacity: btItem.connecting ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: CircularIndicator {
bgColor: "transparent"
running: true
}
}
}
ButtonRow {
spacing: Tokens.spacing.small
IconTextButton {
font.pointSize: Tokens.font.size.small
icon: btItem.modelData.connected ? "link_off" : btItem.modelData.paired ? "link" : "add_link"
text: btItem.modelData.connected ? qsTr("Disconnect") : btItem.modelData.paired ? qsTr("Connect") : qsTr("Pair")
onClicked: {
if (btItem.modelData.connected)
btItem.modelData.disconnect();
else if (btItem.modelData.paired)
btItem.modelData.connect();
else if (!btItem.modelData.paired)
btItem.modelData.pair();
else if (btItem.modelData.pairing)
btItem.modelData.cancelPair();
}
}
}
}
}
component ConnectedRect: CustomRect {
id: bg
property bool first
property bool last
bottomLeftRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
bottomRightRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
color: Colors.tPalette.m3surfaceContainer
topLeftRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
topRightRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
Behavior on bottomLeftRadius {
Anim {
}
}
Behavior on bottomRightRadius {
Anim {
}
}
Behavior on topLeftRadius {
Anim {
}
}
Behavior on topRightRadius {
Anim {
}
}
}
component StatusDivider: CustomText {
bottomPadding: Tokens.spacing.extraSmall
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.smaller
text: {
const devices = Bluetooth.devices.values; // qmllint disable unresolved-type
const connectedCount = devices.filter(d => d.connected).length;
let available = qsTr("%1 device%2 available").arg(devices.length - connectedCount).arg(devices.length === 1 ? "" : "s");
return available;
}
topPadding: Tokens.spacing.small
}
component Toggle: CustomSwitch {
id: toggleItem
readonly property alias bg: bg
property alias first: bg.first
property alias last: bg.last
property string subtext
Layout.fillWidth: true
cLayer: 2
horizontalPadding: Tokens.padding.largeIncreased
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
indicator.anchors.right: right
indicator.anchors.rightMargin: toggleItem.horizontalPadding
indicator.anchors.verticalCenter: verticalCenter
verticalPadding: Tokens.padding.normal
background: ConnectedRect {
id: bg
StateLayer {
id: stateLayer
manualPressOverride: toggleItem.pressed
}
}
contentItem: Item {
anchors.left: parent.left
anchors.leftMargin: toggleItem.horizontalPadding
anchors.right: toggleItem.indicator.left
anchors.rightMargin: Tokens.spacing.normal
implicitHeight: column.implicitHeight
implicitWidth: column.implicitWidth
Column {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: label
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font: {
const f = Qt.font(label.font);
f.pointSize = Tokens.font.size.smaller;
return f;
}
text: toggleItem.text
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font: {
const f = Qt.font(subtext.font);
f.pointSize = Tokens.font.size.small;
return f;
}
text: toggleItem.subtext
visible: toggleItem.subtext
}
}
}
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
}
}
+87
View File
@@ -0,0 +1,87 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Components
import ZShell.Config
Item {
id: root
required property real borderThickness
readonly property alias content: content
readonly property bool horizontal: position !== "left"
// property real offsetScale: y > 0 || content.hasCurrent ? 0 : 1
property real offsetScale: {
if (content.hasCurrent)
return 0;
if (position === "top")
return y > 0 ? 0 : 1;
if (position === "bottom")
return 1;
return x > 0 ? 0 : 1;
}
required property string position
required property ShellScreen screen
anchors.bottom: position === "bottom" ? parent.bottom : undefined
clip: true
implicitHeight: horizontal ? content.implicitHeight * (1 - offsetScale) : content.implicitHeight
implicitWidth: horizontal ? content.implicitWidth : content.implicitWidth * (1 - offsetScale)
visible: width > 0 && height > 0
x: {
if (!horizontal)
return 0;
const off = content.currentCenter - borderThickness - content.nonAnimWidth / 2;
const diff = parent.width - Math.floor(off + content.nonAnimWidth);
if (diff < 0)
return off + diff;
return Math.floor(Math.max(off, 0));
}
y: {
if (horizontal)
return 0;
const off = content.currentCenter - borderThickness - content.nonAnimHeight / 2;
const diff = parent.height - Math.floor(off + content.nonAnimHeight);
if (diff < 0)
return off + diff;
return Math.max(off, 0);
}
Behavior on offsetScale {
Anim {
}
}
Behavior on x {
enabled: !root.horizontal || root.offsetScale < 1
Anim {
duration: content.animLength
easing.bezierCurve: content.animCurve
}
}
Behavior on y {
enabled: root.horizontal || root.offsetScale < 1
Anim {
duration: content.animLength
easing.bezierCurve: content.animCurve
}
}
Wrapper {
id: content
anchors.bottom: root.position === "bottom" ? parent.bottom : undefined
anchors.bottomMargin: (-implicitHeight - 5) * root.offsetScale
anchors.horizontalCenter: root.horizontal ? parent.horizontalCenter : undefined
anchors.left: root.horizontal ? undefined : parent.left
anchors.leftMargin: (-implicitWidth - 5) * root.offsetScale
anchors.top: root.position === "top" ? parent.top : undefined
anchors.topMargin: (-implicitHeight - 5) * root.offsetScale
anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter
offsetScale: root.offsetScale
screen: root.screen
}
}
@@ -5,10 +5,6 @@ import Quickshell.Services.SystemTray
import QtQuick import QtQuick
import ZShell.Config import ZShell.Config
import qs.Components import qs.Components
import qs.Modules.WSOverview
import qs.Modules.Network
import qs.Modules.SysTray.Popouts
import qs.Modules.Updates
Item { Item {
id: root id: root
@@ -84,6 +80,13 @@ Item {
} }
} }
Popout {
name: "bluetooth"
sourceComponent: BluetoothPopout {
}
}
Popout { Popout {
name: "updates" name: "updates"
@@ -100,10 +103,10 @@ Item {
readonly property bool shouldBeActive: root.popouts.currentName === name readonly property bool shouldBeActive: root.popouts.currentName === name
active: false active: false
// anchors.horizontalCenter: parent.horizontalCenter anchors.centerIn: opacity === 1 ? undefined : parent
// anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter
// anchors.topMargin: 5 anchors.top: opacity === 1 ? parent.top : undefined
anchors.centerIn: parent anchors.topMargin: 5
opacity: 0 opacity: 0
scale: 0.8 scale: 0.8
@@ -0,0 +1,71 @@
import QtQuick
import ZShell.Config
import qs.Services
import qs.Helpers
import qs.Components
CustomRect {
id: root
readonly property bool closing: PopupManager.closed
readonly property url currentPopup: PopupManager.currentPopup
property bool shouldBeVisible: loader.status === Loader.Ready
color: Qt.alpha(Colors.palette.m3shadow, 0.3)
opacity: shouldBeVisible && !closing ? 1 : 0
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
onVisibleChanged: {
if (!visible) {
PopupManager.requestClose();
PopupManager.currentPopup = "";
}
}
CustomMouseArea {
anchors.fill: parent
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
onClicked: {
const insideItemWidth = mouseX < loader.x + loader.item.width && mouseX > loader.x;
const insideItemHeight = mouseY < loader.y + loader.item.height && mouseY > loader.y;
if (insideItemHeight && insideItemWidth)
return;
PopupManager.requestClose();
}
}
Loader {
id: loader
anchors.centerIn: parent
Connections {
function onCurrentPopupChanged(): void {
if (PopupManager.currentPopup) {
loader.setSource(PopupManager.currentPopup, PopupManager.currentPopupProps);
} else {
loader.source = null;
}
}
target: PopupManager
}
}
Connections {
function onNetworkConnected(): void {
PopupManager.requestClose();
}
target: Network
}
}
+103
View File
@@ -0,0 +1,103 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import ZShell.Config
import qs.Services
import qs.Components
import qs.Helpers
CustomClippingRect {
id: root
required property var network
color: Colors.palette.m3surfaceContainer
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
implicitWidth: layout.implicitWidth + layout.anchors.margins * 2
radius: Tokens.rounding.large
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.extraLarge
spacing: Tokens.spacing.normal
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
font.pointSize: Tokens.font.size.extraLarge * 2
text: "lock"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3onSurfaceVariant
text: qsTr("Connect to %1").arg(root.network.name)
}
CustomTextField {
id: passwordField
Layout.fillWidth: true
Layout.preferredWidth: 400
echoMode: CustomTextField.Password
errorText: qsTr("Failed to connect with given password")
isError: Network.failedNetwork
leadingIcon: "lock"
passwordMaskDelay: 5000
placeholderText: qsTr("Input password")
}
ButtonRow {
id: buttonRow
Layout.fillWidth: true
spacing: Tokens.spacing.smaller
IconTextButton {
fillWidth: true
font.pointSize: Tokens.font.size.normal
icon: "close"
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: Colors.palette.m3onSurfaceVariant
isRound: true
isToggle: false
shapeMorph: true
text: "Cancel"
onClicked: PopupManager.requestClose()
}
IconTextButton {
fillWidth: true
font.pointSize: Tokens.font.size.normal
icon: Network.connectingNetwork ? "" : "check"
inactiveColor: Network.connectingNetwork ? Colors.palette.m3primaryContainer : Network.failedNetwork ? Colors.palette.m3error : Colors.palette.m3primary
inactiveOnColor: Network.connectingNetwork ? Colors.palette.m3onPrimaryContainer : Network.failedNetwork ? Colors.palette.m3onError : Colors.palette.m3onPrimary
isRound: true
isToggle: false
shapeMorph: true
text: Network.connectingNetwork ? "" : "Apply"
onClicked: Network.requestConnect(root.network, passwordField.text)
Loader {
active: opacity > 0
anchors.centerIn: parent
opacity: Network.connectingNetwork ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: CircularIndicator {
bgColor: "transparent"
implicitSize: buttonRow.height - Tokens.padding.normal
running: true
}
}
}
}
}
}
@@ -0,0 +1,22 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
property bool closed: true
property url currentPopup: ""
property var currentPopupProps: ({})
function requestClose(): void {
closed = true;
}
function requestOpen(source: url, properties = {}): void {
currentPopupProps = properties;
currentPopup = source;
closed = false;
}
}
+362
View File
@@ -0,0 +1,362 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Components
import ZShell.Config
import qs.Services
import qs.Modules.Bar.Popouts.Network
import qs.Components
import qs.Helpers
CustomClippingRect {
id: root
required property var wrapper
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: networkPopContent.height + networkPopContent.anchors.margins * 2
implicitWidth: 500 + 8 * 2
radius: (20 - Tokens.padding.small) * Tokens.rounding.scale
Component.onCompleted: Network.active = true
Component.onDestruction: Network.active = false
ColumnLayout {
id: networkPopContent
anchors.left: parent.left
anchors.margins: Tokens.padding.large
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.extraSmall / 2
CustomText {
Layout.preferredHeight: visible ? implicitHeight : 0
Layout.rightMargin: Tokens.padding.extraSmall
font.pointSize: Tokens.font.size.large
text: qsTr("Wifi")
}
Toggle {
Layout.preferredHeight: visible ? implicitHeight : 0
checked: Network.wifiEnabled
first: true
last: Network.connectedNetworks.length === 0
subtext: qsTr("Toggle WiFi device state")
text: checked ? qsTr("Enabled") : qsTr("Disabled")
onToggled: Network.setWifi(checked)
}
ListView {
id: rows
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
section.property: "connected"
spacing: Tokens.spacing.extraSmall / 2
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
}
}
delegate: DelegateChooser {
role: "isDivider"
DelegateChoice {
roleValue: true
delegate: StatusDivider {
}
}
DelegateChoice {
delegate: NetworkItem {
list: rows
width: rows.width
}
}
}
displaced: Transition {
Anim {
easing.type: Easing.OutCubic
property: "y"
}
}
model: ScriptModel {
values: {
const byStrengthName = (a, b) => b.signalStrength - a.signalStrength || a.name.localeCompare(b.name);
const cNetworks = [...Network.connectedNetworks];
const kNetworks = [...Network.knownNetworks];
const uNetworks = [...Network.unknownNetworks];
return [...cNetworks.sort(byStrengthName), ...(kNetworks.length > 0 ? [
{
isDivider: true,
type: "known"
},
...kNetworks.sort(byStrengthName)] : []), ...(uNetworks.length > 0 ? [
{
isDivider: true,
type: "unknown"
},
...uNetworks.sort(byStrengthName)] : [])];
}
}
move: Transition {
Anim {
easing.type: Easing.OutCubic
property: "y"
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
}
}
section.delegate: Rectangle {
required property string section
color: "transparent"
height: childrenRect.height
width: ListView.view.width
CustomText {
text: parent.section
}
}
}
}
NetworkOverlay {
anchors.fill: parent
}
component ConnectedRect: CustomRect {
id: bg
property bool first
property bool last
bottomLeftRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
bottomRightRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
color: Colors.tPalette.m3surfaceContainer
topLeftRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
topRightRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
}
component NetworkItem: ConnectedRect {
id: networkItem
readonly property int connectedCount: Network.connectedNetworks.length
readonly property bool connecting: Network.connectingNetwork === modelData
property int horizontalPadding: Tokens.padding.largeIncreased
readonly property bool inConnectedGroup: index < connectedCount
// sectioning first / last item in list reminder
// if index === network.connectedNetworks.length - 1
// if index === network.connectedNetworks.length - 1 + network.knownNetworks.length - 2
// if index === network.connectedNetworks.length - 1 + network.knownNetworks.length
// list.model.values.length - 1
required property int index
readonly property bool isSecure: Network.isSecure(modelData.security)
required property ListView list
required property var modelData
property int verticalPadding: Tokens.padding.smaller
Layout.fillWidth: true
first: !inConnectedGroup && index === connectedCount + 1
implicitHeight: rowLayout.implicitHeight + verticalPadding * 2
last: inConnectedGroup ? index === connectedCount - 1 : index === list.count - 1
radius: Tokens.rounding.small
RowLayout {
id: rowLayout
Layout.preferredHeight: visible ? rowLayout.implicitHeight + Tokens.padding.smaller * 2 : 0
anchors.fill: parent
anchors.leftMargin: networkItem.horizontalPadding
anchors.rightMargin: networkItem.horizontalPadding
Column {
id: column
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.leftMargin: Tokens.spacing.extraSmall
Layout.rightMargin: Tokens.spacing.extraSmall
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurface
elide: Text.ElideRight
text: networkItem.modelData.name
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
text: networkItem.modelData.connected ? qsTr("Connected: %1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%") : qsTr("%1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%")
}
}
Item {
Layout.alignment: Qt.AlignRight
implicitHeight: icon.height
implicitWidth: icon.width
MaterialIcon {
id: icon
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.large
opacity: networkItem.connecting ? 0 : 1
text: Icons.getNetworkIcon(networkItem.modelData.signalStrength * 100, networkItem.isSecure)
Behavior on opacity {
Anim {
}
}
}
Loader {
active: opacity > 0
anchors.fill: parent
opacity: networkItem.connecting ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: CircularIndicator {
bgColor: "transparent"
running: true
}
}
}
}
StateLayer {
color: networkItem.modelData.active ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: {
if (networkItem.isSecure && !networkItem.known) {
PopupManager.requestOpen(Qt.resolvedUrl("Network/PSKOverlay.qml"), {
network: networkItem.modelData
});
} else {
Network.requestConnect(networkItem.modelData);
}
}
}
}
component StatusDivider: CustomText {
required property var modelData
bottomPadding: Tokens.spacing.extraSmall
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.smaller
text: {
if (modelData.type === "known") {
const count = Network.knownNetworks.filter(n => !n.connected).length;
return qsTr("%1 known network%2").arg(count).arg(count === 1 ? "" : "s");
}
if (modelData.type === "unknown") {
const count = Network.unknownNetworks.filter(n => !n.connected).length;
return qsTr("%1 network%2 available").arg(count).arg(count === 1 ? "" : "s");
}
return qsTr("object unknown");
}
topPadding: Tokens.spacing.small
}
component Toggle: CustomSwitch {
id: toggleItem
readonly property alias bg: bg
property alias first: bg.first
property alias last: bg.last
property string subtext
Layout.fillWidth: true
cLayer: 2
horizontalPadding: Tokens.padding.largeIncreased
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
indicator.anchors.right: right
indicator.anchors.rightMargin: toggleItem.horizontalPadding
indicator.anchors.verticalCenter: verticalCenter
verticalPadding: Tokens.padding.normal
background: ConnectedRect {
id: bg
StateLayer {
id: stateLayer
manualPressOverride: toggleItem.pressed
}
}
contentItem: Item {
anchors.left: parent.left
anchors.leftMargin: toggleItem.horizontalPadding
anchors.right: toggleItem.indicator.left
anchors.rightMargin: Tokens.spacing.normal
implicitHeight: column.implicitHeight
implicitWidth: column.implicitWidth
Column {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: label
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font: {
const f = Qt.font(label.font);
f.pointSize = Tokens.font.size.smaller;
return f;
}
text: toggleItem.text
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font: {
const f = Qt.font(subtext.font);
f.pointSize = Tokens.font.size.small;
return f;
}
text: toggleItem.subtext
visible: toggleItem.subtext
}
}
}
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
}
}
@@ -1,8 +1,10 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts
import Quickshell.Services.UPower import Quickshell.Services.UPower
import ZShell.Config import ZShell.Config
import qs.Modules.Bar.Components.Common
import qs.Helpers import qs.Helpers
import qs.Components import qs.Components
import qs.Services import qs.Services
@@ -22,6 +24,31 @@ Column {
} }
} }
Repeater {
model: Battery.allPeripherals
RowLayout {
id: layout
required property var modelData
anchors.left: parent.left
anchors.leftMargin: Tokens.padding.normal
anchors.right: parent.right
anchors.rightMargin: Tokens.padding.normal
CustomText {
Layout.fillWidth: true
text: layout.modelData.model
}
BatteryIcon {
devState: layout.modelData.state
percentage: layout.modelData.percentage
}
}
}
CustomText { CustomText {
function formatSeconds(s: int, fallback: string): string { function formatSeconds(s: int, fallback: string): string {
const day = Math.floor(s / 86400); const day = Math.floor(s / 86400);
@@ -75,7 +102,7 @@ Column {
CustomText { CustomText {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onError color: Colors.palette.m3onError
font.family: Appearance.font.family.mono font.family: Config.appearance.font.family.mono
text: qsTr("Performance Degraded") text: qsTr("Performance Degraded")
} }
@@ -78,8 +78,6 @@ CustomClippingRect {
required property var modelData required property var modelData
readonly property list<string> sections: modelData.update.split(" ") readonly property list<string> sections: modelData.update.split(" ")
// anchors.left: parent.left
// anchors.right: parent.right
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
implicitHeight: root.itemHeight implicitHeight: root.itemHeight
implicitWidth: 600 implicitWidth: 600
@@ -54,7 +54,7 @@ Item {
Comp { Comp {
id: content id: content
anchors.centerIn: parent anchors.fill: parent
shouldBeActive: root.hasCurrent shouldBeActive: root.hasCurrent
sourceComponent: Content { sourceComponent: Content {
-56
View File
@@ -1,56 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Components
import ZShell.Config
Item {
id: root
required property real borderThickness
readonly property alias content: content
property real offsetScale: y > 0 || content.hasCurrent ? 0 : 1
required property ShellScreen screen
clip: true
implicitHeight: content.implicitHeight * (1 - offsetScale)
implicitWidth: content.implicitWidth
visible: width > 0 && height > 0
x: {
const off = content.currentCenter - borderThickness - content.nonAnimWidth / 2;
const diff = parent.width - Math.floor(off + content.nonAnimWidth);
if (diff < 0)
return off + diff;
return Math.floor(Math.max(off, 0));
}
Behavior on offsetScale {
Anim {
}
}
Behavior on x {
enabled: root.offsetScale < 1
Anim {
duration: content.animLength
easing.bezierCurve: content.animCurve
}
}
Behavior on y {
Anim {
duration: content.animLength
easing.bezierCurve: content.animCurve
}
}
Wrapper {
id: content
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: (-implicitHeight - 5) * root.offsetScale
offsetScale: root.offsetScale
screen: root.screen
}
}
-44
View File
@@ -1,44 +0,0 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Modules
import qs.Helpers
import qs.Components
import qs.Services
CustomRect {
id: root
required property RowLayout loader
required property Wrapper popouts
required property PersistentProperties visibilities
color: visibilities.dashboard ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: Config.bar.height + Tokens.padding.smallest * 2
implicitWidth: timeText.contentWidth + Tokens.padding.normal * 2
radius: Tokens.rounding.full
CustomText {
id: timeText
anchors.centerIn: parent
color: root.visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
font: Config.appearance.font.family.mono
text: Time.dateStr
Behavior on color {
CAnim {
}
}
}
StateLayer {
acceptedButtons: Qt.LeftButton
color: root.visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard;
}
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ Item {
Item { Item {
id: view id: view
readonly property int currentIndex: root.state.currentTab readonly property int currentIndex: root.dashState?.currentTab
readonly property Item currentItem: row.children[currentIndex] readonly property Item currentItem: row.children[currentIndex]
anchors.fill: parent anchors.fill: parent
+140 -147
View File
@@ -1,10 +1,12 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Services.Mpris
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Shapes import QtQuick.Shapes
import QtQuick.Effects import QtQuick.Effects
import ZShell.Components
import ZShell.Services import ZShell.Services
import ZShell.Config import ZShell.Config
import qs.Daemons import qs.Daemons
@@ -14,7 +16,7 @@ import qs.Services
Item { Item {
id: root id: root
readonly property bool hasUnknownLength: (Players.active?.length ?? 0) > 2147483647
property real playerProgress: { property real playerProgress: {
const active = Players.active; const active = Players.active;
return active?.length ? (active.position % active.length) / active.length : 0; return active?.length ? (active.position % active.length) / active.length : 0;
@@ -23,7 +25,19 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
implicitHeight: cover.height + rowHeight * 2 implicitHeight: layout.implicitHeight
function lengthStr(length: int): string {
if (length < 0)
return "-1:-1";
const hours = Math.floor(length / 3600);
const mins = Math.floor((length % 3600) / 60);
const secs = Math.floor(length % 60).toString().padStart(2, "0");
if (hours > 0)
return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
return `${mins}:${secs}`;
}
Behavior on playerProgress { Behavior on playerProgress {
Anim { Anim {
@@ -32,11 +46,10 @@ Item {
} }
Timer { Timer {
interval: Config.dashboard.mediaUpdateInterval
repeat: true
running: Players.active?.isPlaying ?? false running: Players.active?.isPlaying ?? false
interval: Config.dashboard.mediaUpdateInterval
triggeredOnStart: true triggeredOnStart: true
repeat: true
onTriggered: Players.active?.positionChanged() onTriggered: Players.active?.positionChanged()
} }
@@ -72,7 +85,7 @@ Item {
const n = values.length; const n = values.length;
if (n < 2) if (n < 2)
return ""; return "";
const h = layout.height + shape.strokeWidth / 2; const h = layout.height + shape.strokeWidth / 2;
const w = layout.width + shape.strokeWidth; const w = layout.width + shape.strokeWidth;
@@ -126,58 +139,11 @@ Item {
} }
} }
Shape {
preferredRendererType: Shape.CurveRenderer
ShapePath {
capStyle: Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent"
strokeColor: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
strokeWidth: Config.dashboard.sizes.mediaProgressThickness
Behavior on strokeColor {
CAnim {
}
}
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Tokens.spacing.small
radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Tokens.spacing.small
startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
sweepAngle: Config.dashboard.sizes.mediaProgressSweep
}
}
ShapePath {
capStyle: Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent"
strokeColor: Colors.palette.m3primary
strokeWidth: Config.dashboard.sizes.mediaProgressThickness
Behavior on strokeColor {
CAnim {
}
}
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Tokens.spacing.small
radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Tokens.spacing.small
startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
sweepAngle: Config.dashboard.sizes.mediaProgressSweep * root.playerProgress
}
}
}
RowLayout { RowLayout {
id: layout id: layout
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
implicitHeight: root.implicitHeight
CustomClippingRect { CustomClippingRect {
id: cover id: cover
@@ -211,147 +177,174 @@ Item {
} }
} }
CustomRect { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Tokens.spacing.large
Layout.rightMargin: Tokens.spacing.large
Layout.preferredHeight: childrenRect.height Layout.preferredHeight: childrenRect.height
spacing: Tokens.spacing.normal
MarqueeText { MarqueeText {
id: title id: title
anchors.horizontalCenter: parent.horizontalCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
anchors.top: parent.top
color: Colors.palette.m3primary color: Colors.palette.m3primary
font.pointSize: Tokens.font.size.normal font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
pauseMs: 4000 pauseMs: 4000
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title") text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
width: parent.width - Tokens.padding.large * 4 implicitWidth: parent.width - Tokens.padding.large * 4
} }
MarqueeText { MarqueeText {
id: album id: album
anchors.horizontalCenter: parent.horizontalCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
anchors.top: title.bottom
anchors.topMargin: Tokens.spacing.small
color: Colors.palette.m3outline color: Colors.palette.m3outline
font.pointSize: Tokens.font.size.small font.pointSize: Tokens.font.size.small
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
pauseMs: 4000 pauseMs: 4000
text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album") text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album")
width: parent.width - Tokens.padding.large * 4 implicitWidth: parent.width - Tokens.padding.large * 4
} }
MarqueeText { MarqueeText {
id: artist id: artist
anchors.horizontalCenter: parent.horizontalCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
anchors.top: album.bottom
anchors.topMargin: Tokens.spacing.small
color: Colors.palette.m3secondary color: Colors.palette.m3secondary
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
pauseMs: 4000 pauseMs: 4000
text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist") text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
width: parent.width - Tokens.padding.large * 4 implicitWidth: parent.width - Tokens.padding.large * 4
} }
RowLayout { RowLayout {
id: controls id: positionSliderLayout
Layout.topMargin: Tokens.spacing.extraLargeIncreased
anchors.horizontalCenter: parent.horizontalCenter Layout.fillWidth: true
anchors.top: artist.bottom
anchors.topMargin: Tokens.spacing.smaller
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
Control { TextMetrics {
function onClicked(): void { id: timeMetrics
Players.active?.previous();
}
canUse: Players.active?.canGoPrevious ?? false text: Players.active ? root.lengthStr(Math.max(Players.active.position, root.hasUnknownLength ? 0 : Players.active.length)).replace(/[1-9]/g, "0") : "00:00"
}
CustomText {
id: positionLabel
Layout.preferredWidth: timeMetrics.width
text: root.lengthStr(Players.active?.position ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
CustomSlider {
id: positionSlider
Layout.fillWidth: true
value: Players.active ? Players.active.position / (Players.active.length || 1) : 0
enabled: (Players.active?.canSeek ?? false) && !root.hasUnknownLength
wavy: true
animateWave: Players.active?.isPlaying ?? false
waveFrequency: 5
waveDuration: 2000
interactionOnMove: false
onInteraction: value => {
const active = Players.active;
if (active?.canSeek && active?.positionSupported)
active.position = value * active.length;
}
Binding {
target: positionLabel
property: "text"
value: root.lengthStr(positionSlider.pos * (Players.active?.length ?? 0))
when: positionSlider.dragging
}
}
CustomText {
Layout.preferredWidth: timeMetrics.width
text: root.hasUnknownLength ? "--:--" : root.lengthStr(Players.active?.length ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
}
ButtonRow {
Layout.topMargin: Tokens.spacing.largeIncreased
Layout.fillWidth: true
spacing: Tokens.spacing.extraSmall
Layout.leftMargin: positionSliderLayout.implicitWidth / 4
Layout.rightMargin: positionSliderLayout.implicitWidth / 4
IconButton {
type: IconButton.Tonal
icon: "shuffle"
isRound: true
shapeMorph: true
checked: Players.active?.shuffle ?? false
enabled: Players.active?.shuffleSupported
onClicked: Players.active.shuffle = !Players.active?.shuffle
implicitWidth: Math.round(implicitHeight * 0.9)
}
IconButton {
id: previousBtn
type: IconButton.Tonal
icon: "skip_previous" icon: "skip_previous"
isRound: true
shapeMorph: true
enabled: Players.active?.canGoPrevious
onClicked: Players.active?.previous()
} }
Control { IconButton {
function onClicked(): void { id: playPauseBtn
Players.active?.togglePlaying();
}
canUse: Players.active?.canTogglePlaying ?? false
icon: Players.active?.isPlaying ? "pause" : "play_arrow" icon: Players.active?.isPlaying ? "pause" : "play_arrow"
isRound: true
shapeMorph: true
fillWidth: true
checked: Players.active?.isPlaying ?? false
enabled: Players.active?.canTogglePlaying
onClicked: Players.active?.togglePlaying()
} }
Control { IconButton {
function onClicked(): void { id: nextBtn
Players.active?.next();
}
canUse: Players.active?.canGoNext ?? false type: IconButton.Tonal
icon: "skip_next" icon: "skip_next"
isRound: true
shapeMorph: true
enabled: Players.active?.canGoNext
onClicked: Players.active?.next()
}
IconButton {
type: IconButton.Tonal
icon: Players.active?.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
isRound: true
shapeMorph: true
checked: Players.active?.loopState === MprisLoopState.Track || Players.active?.loopState === MprisLoopState.Playlist
enabled: Players.active?.loopSupported
onClicked: {
const state = Players.active.loopState;
if (state === MprisLoopState.None)
Players.active.loopState = MprisLoopState.Track;
else if (state === MprisLoopState.Track)
Players.active.loopState = MprisLoopState.Playlist;
else
Players.active.loopState = MprisLoopState.None;
}
implicitWidth: Math.round(implicitHeight * 0.9)
} }
} }
} }
} }
component Control: CustomRect {
id: control
required property bool canUse
required property string icon
property int level: 1
property string set_color: "Secondary"
function onClicked(): void {
}
Layout.preferredWidth: implicitWidth + (controlState.pressed ? Tokens.padding.normal * 2 : 0)
color: canUse ? Colors.palette[`m3${set_color.toLowerCase()}`] : Colors.palette[`m3${set_color.toLowerCase()}Container`]
implicitHeight: implicitWidth
implicitWidth: Math.max(icon.implicitHeight, icon.implicitHeight) + Tokens.padding.small
radius: Tokens.rounding.full
Behavior on Layout.preferredWidth {
Anim {
duration: Tokens.anim.durations.expressiveFastSpatial
easing.bezierCurve: Tokens.anim.curves.expressiveFastSpatial
}
}
Behavior on radius {
Anim {
duration: Tokens.anim.durations.expressiveFastSpatial
easing.bezierCurve: Tokens.anim.curves.expressiveFastSpatial
}
}
Elevation {
anchors.fill: parent
level: controlState.containsMouse && !controlState.pressed ? control.level + 1 : control.level
radius: parent.radius
z: -1
}
StateLayer {
id: controlState
color: control.canUse ? Colors.palette[`m3on${control.set_color}`] : Colors.palette[`m3on${control.set_color}Container`]
enabled: control.canUse
onClicked: {
control.onClicked();
}
// radius: Tokens.rounding.full
}
MaterialIcon {
id: icon
anchors.centerIn: parent
anchors.verticalCenterOffset: font.pointSize * 0.05
animate: true
color: control.canUse ? Colors.palette[`m3on${control.set_color}`] : Colors.palette[`m3on${control.set_color}Container`]
fill: control.canUse ? 1 : 0
font.pointSize: Tokens.font.size.large
text: control.icon
}
}
} }
+12 -2
View File
@@ -16,9 +16,16 @@ Item {
} }
readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0 readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
required property real offsetScale required property real offsetScale
required property string position
readonly property bool shouldBeActive: root.visibilities.dashboard && Config.dashboard.enabled readonly property bool shouldBeActive: root.visibilities.dashboard && Config.dashboard.enabled
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.bottom: position === "bottom" ? parent.bottom : undefined
anchors.bottomMargin: position === "bottom" ? (-implicitHeight - 5) * offsetScale : 0
anchors.left: position === "left" ? parent.left : undefined
anchors.leftMargin: position === "left" ? (-implicitWidth - 5) * offsetScale : 0
anchors.top: position === "top" ? parent.top : undefined
anchors.topMargin: position === "top" ? (-implicitHeight - 5) * offsetScale : 0
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 implicitWidth: content.implicitWidth || 854
opacity: 1 - offsetScale opacity: 1 - offsetScale
@@ -28,8 +35,11 @@ Item {
id: content id: content
active: root.shouldBeActive || root.visible active: root.shouldBeActive || root.visible
anchors.bottom: parent.bottom anchors.bottom: root.position === "top" ? parent.bottom : undefined
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: root.position !== "left" ? parent.horizontalCenter : undefined
anchors.right: root.position !== "left" ? undefined : parent.right
anchors.top: root.position === "bottom" ? parent.top : undefined
anchors.verticalCenter: root.position === "left" ? undefined : parent.verticalCenter
sourceComponent: Content { sourceComponent: Content {
dashState: root.dashState dashState: root.dashState
+1 -1
View File
@@ -12,7 +12,7 @@ Item {
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
required property var panels required property var panels
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.dock && Config.dock.enable readonly property bool shouldBeActive: visibilities.dock && Config.dock.enabled
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
+81 -30
View File
@@ -1,28 +1,36 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell
import QtQuick import QtQuick
import qs.Modules.Launcher.Services import Quickshell
import qs.Modules.Launcher.Items
import qs.Components
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Services import qs.Services
import qs.Modules.Launcher.Items
import qs.Modules.Launcher.Services
CustomListView { CustomListView {
id: root id: root
readonly property string displayState: stateForText(displayText)
property string displayText
readonly property string requestedState: stateForText(search.text)
required property SearchBar search required property SearchBar search
required property PersistentProperties visibilities required property PersistentProperties visibilities
highlightFollowsCurrentItem: false function resultsForText(text: string): var {
highlightRangeMode: ListView.ApplyRange switch (stateForText(text)) {
implicitHeight: (Config.launcher.sizes.itemHeight + spacing) * Math.min(Config.launcher.maxAppsShown, count) - spacing case "actions":
orientation: Qt.Vertical return Actions.query(text);
preferredHighlightBegin: 0 case "calc":
preferredHighlightEnd: height return [0];
spacing: Tokens.spacing.small case "variant":
state: { return SchemeVariants.query(text);
const text = search.text; default:
return Apps.search(text);
}
}
function stateForText(text: string): string {
const prefix = Config.launcher.actionPrefix; const prefix = Config.launcher.actionPrefix;
if (text.startsWith(prefix)) { if (text.startsWith(prefix)) {
for (const action of ["calc", "scheme", "variant"]) for (const action of ["calc", "scheme", "variant"])
@@ -34,6 +42,20 @@ CustomListView {
return "apps"; return "apps";
} }
function syncDisplayText(): void {
if (visibilities.launcher && requestedState === displayState)
displayText = search.text;
}
highlightFollowsCurrentItem: false
highlightRangeMode: ListView.ApplyRange
implicitHeight: (Config.launcher.sizes.itemHeight + spacing) * Math.min(Config.launcher.maxAppsShown, count) - spacing
orientation: Qt.Vertical
preferredHighlightBegin: 0
preferredHighlightEnd: height
spacing: Tokens.spacing.small
state: visibilities.launcher ? requestedState : displayState
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
CustomScrollBar.vertical: CustomScrollBar { CustomScrollBar.vertical: CustomScrollBar {
@@ -44,19 +66,21 @@ CustomListView {
Anim { Anim {
from: 0 from: 0
properties: "opacity,scale" property: "opacity"
to: 1 to: 1
type: Anim.DefaultEffects
} }
} }
addDisplaced: Transition { addDisplaced: Transition {
Anim { Anim {
duration: Tokens.anim.durations.small
property: "y" property: "y"
type: Anim.StandardSmall
} }
Anim { Anim {
properties: "opacity,scale" property: "opacity"
to: 1 to: 1
type: Anim.DefaultEffects
} }
} }
displaced: Transition { displaced: Transition {
@@ -65,8 +89,9 @@ CustomListView {
} }
Anim { Anim {
properties: "opacity,scale" property: "opacity"
to: 1 to: 1
type: Anim.DefaultEffects
} }
} }
highlight: CustomRect { highlight: CustomRect {
@@ -85,7 +110,7 @@ CustomListView {
} }
} }
model: ScriptModel { model: ScriptModel {
id: model values: root.resultsForText(root.displayText)
onValuesChanged: root.currentIndex = 0 onValuesChanged: root.currentIndex = 0
} }
@@ -95,8 +120,9 @@ CustomListView {
} }
Anim { Anim {
properties: "opacity,scale" property: "opacity"
to: 1 to: 1
type: Anim.DefaultEffects
} }
} }
remove: Transition { remove: Transition {
@@ -104,8 +130,9 @@ CustomListView {
Anim { Anim {
from: 1 from: 1
properties: "opacity,scale" property: "opacity"
to: 0 to: 0
type: Anim.DefaultEffects
} }
} }
states: [ states: [
@@ -113,7 +140,6 @@ CustomListView {
name: "apps" name: "apps"
PropertyChanges { PropertyChanges {
model.values: Apps.search(search.text)
root.delegate: appItem root.delegate: appItem
} }
}, },
@@ -121,7 +147,6 @@ CustomListView {
name: "actions" name: "actions"
PropertyChanges { PropertyChanges {
model.values: Actions.query(search.text)
root.delegate: actionItem root.delegate: actionItem
} }
}, },
@@ -129,7 +154,6 @@ CustomListView {
name: "calc" name: "calc"
PropertyChanges { PropertyChanges {
model.values: [0]
root.delegate: calcItem root.delegate: calcItem
} }
}, },
@@ -137,7 +161,6 @@ CustomListView {
name: "variant" name: "variant"
PropertyChanges { PropertyChanges {
model.values: SchemeVariants.query(search.text)
root.delegate: variantItem root.delegate: variantItem
} }
} }
@@ -147,7 +170,7 @@ CustomListView {
ParallelAnimation { ParallelAnimation {
Anim { Anim {
duration: Tokens.anim.durations.small duration: Tokens.anim.durations.small
easing.bezierCurve: Tokens.anim.curves.expressiveEffects easing.bezierCurve: Tokens.anim.curves.standardAccel
from: 1 from: 1
property: "opacity" property: "opacity"
target: root target: root
@@ -156,7 +179,7 @@ CustomListView {
Anim { Anim {
duration: Tokens.anim.durations.small duration: Tokens.anim.durations.small
easing.bezierCurve: Tokens.anim.curves.expressiveEffects easing.bezierCurve: Tokens.anim.curves.standardAccel
from: 1 from: 1
property: "scale" property: "scale"
target: root target: root
@@ -165,14 +188,24 @@ CustomListView {
} }
PropertyAction { PropertyAction {
properties: "values,delegate" property: "delegate"
targets: [model, root] target: root
value: null
}
ScriptAction {
script: root.displayText = root.search.text
}
PropertyAction {
property: "delegate"
target: root
} }
ParallelAnimation { ParallelAnimation {
Anim { Anim {
duration: Tokens.anim.durations.small duration: Tokens.anim.durations.small
easing.bezierCurve: Tokens.anim.curves.expressiveEffects easing.bezierCurve: Tokens.anim.curves.standardDecel
from: 0 from: 0
property: "opacity" property: "opacity"
target: root target: root
@@ -181,7 +214,7 @@ CustomListView {
Anim { Anim {
duration: Tokens.anim.durations.small duration: Tokens.anim.durations.small
easing.bezierCurve: Tokens.anim.curves.expressiveEffects easing.bezierCurve: Tokens.anim.curves.standardDecel
from: 0.9 from: 0.9
property: "scale" property: "scale"
target: root target: root
@@ -197,6 +230,8 @@ CustomListView {
} }
} }
Component.onCompleted: displayText = search.text
Component { Component {
id: appItem id: appItem
@@ -228,4 +263,20 @@ CustomListView {
list: root list: root
} }
} }
Connections {
function onTextChanged() {
root.syncDisplayText();
}
target: root.search
}
Connections {
function onLauncherChanged() {
root.syncDisplayText();
}
target: root.visibilities
}
} }
+53
View File
@@ -0,0 +1,53 @@
import Quickshell
import QtQuick
import ZShell.Components
import ZShell.Config
import qs.Modules.Launcher.Items
import qs.Components
import qs.Helpers
CarouselView {
id: root
required property var panels
readonly property var screen: QsWindow.window?.screen
required property SearchBar search
readonly property var values: Wallpapers.query(root.search.text.split(" ").slice(1).join(" "))
required property var visibilities
currentIndex: {
const idx = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent);
return idx >= 0 ? idx : 0;
}
delegateProperties: ({
visibilities: root.visibilities
})
focalWidth: Config.launcher.sizes.wallpaperWidth
glideDuration: 250
glideEasingType: Easing.OutCubic
implicitHeight: tileHeight + Tokens.spacing.small / 2 + Tokens.padding.large + Tokens.padding.normal
itemSpacing: Tokens.spacing.small
maxWidth: {
if (!screen)
return 0;
const barMargins = Config.bar.border * 2;
let margins = Math.round(screen.width / 6);
if (visibilities.utilities || visibilities.sidebar)
margins = Config.sidebar.sizes.width;
return screen.width - (barMargins + margins) * 2 - Config.bar.rounding * 4;
}
minEdgeWidth: 56
model: values
tileHeight: focalWidth / 16 * 9
delegate: Component {
WallpaperTile {
}
}
Component.onDestruction: Wallpapers.stopPreview()
onCurrentItemChanged: {
if (currentItem)
Wallpapers.preview(currentItem.modelData.path);
}
}
+1 -1
View File
@@ -79,7 +79,7 @@ Item {
if (currentItem) { if (currentItem) {
if (list.showWallpapers) { if (list.showWallpapers) {
if (Colors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent) if (Colors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent)
Wallpapers.previewColourLock = true; Wallpapers.previewColorLock = true;
Wallpapers.setWallpaper(currentItem.modelData.path); Wallpapers.setWallpaper(currentItem.modelData.path);
root.visibilities.launcher = false; root.visibilities.launcher = false;
} else if (text.startsWith(Config.launcher.actionPrefix)) { } else if (text.startsWith(Config.launcher.actionPrefix)) {
+2 -2
View File
@@ -110,9 +110,9 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
width: root.implicitWidth
sourceComponent: WallpaperList { sourceComponent: CarouselView {
content: root.content
panels: root.panels panels: root.panels
search: root.search search: root.search
visibilities: root.visibilities visibilities: root.visibilities
-96
View File
@@ -1,96 +0,0 @@
import ZShell.Models
import Quickshell
import QtQuick
import qs.Components
import qs.Helpers
import ZShell.Config
import qs.Modules
import qs.Services
Item {
id: root
required property FileSystemEntry modelData
required property PersistentProperties visibilities
implicitHeight: image.height + label.height + Tokens.spacing.small / 2 + Tokens.padding.large + Tokens.padding.normal
implicitWidth: image.width + Tokens.padding.larger * 2
opacity: 0
scale: 0.5
z: PathView.z ?? 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Component.onCompleted: {
scale = Qt.binding(() => PathView.isCurrentItem ? 1 : PathView.onPath ? 0.8 : 0);
opacity = Qt.binding(() => PathView.onPath ? 1 : 0);
}
StateLayer {
radius: Tokens.rounding.normal
onClicked: {
Wallpapers.setWallpaper(root.modelData.path);
root.visibilities.launcher = false;
}
}
Elevation {
anchors.fill: image
level: 4
opacity: root.PathView.isCurrentItem ? 1 : 0
radius: image.radius
Behavior on opacity {
Anim {
}
}
}
CustomClippingRect {
id: image
anchors.horizontalCenter: parent.horizontalCenter
color: Colors.tPalette.m3surfaceContainer
implicitHeight: implicitWidth / 16 * 9
implicitWidth: Config.launcher.sizes.wallpaperWidth
radius: Tokens.rounding.small
y: Tokens.padding.large
MaterialIcon {
anchors.centerIn: parent
color: Colors.tPalette.m3outline
font.pointSize: Tokens.font.size.extraLarge * 2
font.weight: 600
text: "image"
}
CachingImage {
anchors.fill: parent
cache: true
path: root.modelData.path
smooth: !root.PathView.view.moving
}
}
CustomText {
id: label
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: image.bottom
anchors.topMargin: Tokens.spacing.small / 2
elide: Text.ElideRight
font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter
renderType: Text.QtRendering
text: root.modelData.relativePath
width: image.width - Tokens.padding.normal * 2
}
}
+58
View File
@@ -0,0 +1,58 @@
import Quickshell
import QtQuick
import ZShell.Config
import qs.Components
import qs.Helpers
import qs.Services
Item {
id: root
required property bool isCurrent
required property var modelData // FileSystemEntry-like {path, relativePath}
required property real tileHeight
required property real tileWidth
required property PersistentProperties visibilities
signal requestActivate
implicitHeight: tileHeight + Tokens.padding.large * 2
implicitWidth: tileWidth
CustomClippingRect {
id: image
anchors.horizontalCenter: parent.horizontalCenter
color: Colors.tPalette.m3surfaceContainer
implicitHeight: root.tileHeight
implicitWidth: root.tileWidth
radius: Tokens.rounding.small
y: Tokens.padding.large
MaterialIcon {
anchors.centerIn: parent
color: Colors.tPalette.m3outline
font.pointSize: Tokens.font.size.extraLarge * 2
font.weight: 600
text: "image"
}
CachingImage {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
cache: true
fillMode: Image.PreserveAspectCrop
height: root.tileHeight
path: root.modelData ? root.modelData.path : ""
smooth: root.isCurrent
sourceSize.height: root.tileHeight
sourceSize.width: root.tileHeight * (16 / 9)
width: root.tileHeight * (16 / 9)
}
StateLayer {
onClicked: root.requestActivate()
}
}
}
@@ -82,7 +82,6 @@ Searcher {
list.visibilities.launcher = false; list.visibilities.launcher = false;
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", variant]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", variant]);
Config.colors.schemeType = variant; Config.colors.schemeType = variant;
Config.save();
} }
} }
} }
-93
View File
@@ -1,93 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Components
import qs.Helpers
import ZShell.Config
import qs.Modules.Launcher.Items
PathView {
id: root
required property var content
readonly property int itemWidth: Config.launcher.sizes.wallpaperWidth * 0.9 + Tokens.padding.larger * 2
readonly property int numItems: {
const screen = QsWindow.window?.screen;
if (!screen)
return 0;
// Screen width - 4x outer rounding - 2x max side thickness (cause centered)
const barMargins = panels.bar.implicitWidth;
let outerMargins = 0;
if ((visibilities.utilities || visibilities.sidebar) && panels.utilities.implicitWidth > outerMargins)
outerMargins = panels.utilities.implicitWidth;
const maxWidth = screen.width - Config.bar.rounding * 4 - (barMargins + outerMargins) * 2;
if (maxWidth <= 0)
return 0;
const maxItemsOnScreen = Math.floor(maxWidth / itemWidth);
const visible = Math.min(maxItemsOnScreen, Config.launcher.maxWallpapers, scriptModel.values.length);
if (visible === 2)
return 1;
if (visible > 1 && visible % 2 === 0)
return visible - 1;
return visible;
}
required property var panels
required property SearchBar search
required property var visibilities
cacheItemCount: 4
highlightRangeMode: PathView.StrictlyEnforceRange
implicitWidth: Math.min(numItems, count) * itemWidth
pathItemCount: numItems
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
snapMode: PathView.SnapToItem
delegate: WallpaperItem {
visibilities: root.visibilities
}
model: ScriptModel {
id: scriptModel
readonly property string search: root.search.text.split(" ").slice(1).join(" ")
values: Wallpapers.query(search)
onValuesChanged: root.currentIndex = search ? 0 : values.findIndex(w => w.path === Wallpapers.actualCurrent)
}
path: Path {
startY: root.height / 2
PathAttribute {
name: "z"
value: 0
}
PathLine {
relativeY: 0
x: root.width / 2
}
PathAttribute {
name: "z"
value: 1
}
PathLine {
relativeY: 0
x: root.width
}
}
Component.onCompleted: currentIndex = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent)
Component.onDestruction: Wallpapers.stopPreview()
onCurrentItemChanged: {
if (currentItem)
Wallpapers.preview(currentItem.modelData.path);
}
}
-77
View File
@@ -1,77 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Daemons
import ZShell.Config
import qs.Helpers
import qs.Services
CustomRect {
id: root
readonly property string currentMedia: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
readonly property int textWidth: Math.min(metrics.width, 200)
color: Colors.tPalette.m3surfaceContainer
implicitHeight: Config.bar.height + Tokens.padding.smallest * 2
implicitWidth: layout.implicitWidth + Tokens.padding.normal * 2
radius: Tokens.rounding.full
Behavior on implicitWidth {
Anim {
}
}
TextMetrics {
id: metrics
font: mediatext.font
text: mediatext.text
}
RowLayout {
id: layout
anchors.centerIn: parent
Behavior on implicitWidth {
Anim {
}
}
MaterialIcon {
animate: true
color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.larger
text: Players.active?.isPlaying ? "music_note" : "music_off"
}
MarqueeText {
id: mediatext
Layout.preferredWidth: root.textWidth
animate: true
color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: false
pauseMs: 4000
text: root.currentMedia
width: root.textWidth
CustomMouseArea {
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: {
if (!containsMouse) {
mediatext.marqueeEnabled = false;
} else {
mediatext.marqueeEnabled = true;
mediatext.anim.start();
}
}
}
}
}
}
-36
View File
@@ -1,36 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Networking
import QtQuick
import QtQuick.Layouts
import qs.Components
import ZShell.Config
import qs.Modules
import qs.Helpers as Helpers
Item {
id: root
required property var wrapper
ColumnLayout {
id: layout
spacing: 8
Repeater {
model: Helpers.Network.devices
CustomRadioButton {
id: network
required property NetworkDevice modelData
checked: Helpers.Network.activeDevice?.name === modelData.name
text: modelData.description
visible: modelData.name !== "lo"
}
}
}
}
+1 -5
View File
@@ -464,11 +464,7 @@ CustomRect {
enabled: root.expanded enabled: root.expanded
fillWidth: true fillWidth: true
font: { font.pointSize: Tokens.font.size.small
const f = Qt.font(font);
f.pointSize = Tokens.font.size.small;
return f;
}
implicitWidth: label.implicitWidth implicitWidth: label.implicitWidth
inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondary : Colors.layer(Colors.palette.m3surfaceContainerHighest, 2) inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondary : Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3onSecondary : Colors.palette.m3onSurfaceVariant inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3onSecondary : Colors.palette.m3onSurfaceVariant
+12 -2
View File
@@ -10,10 +10,16 @@ Item {
readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0 readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
required property string position
readonly property bool shouldBeActive: root.visibilities.resources readonly property bool shouldBeActive: root.visibilities.resources
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.topMargin: (-implicitHeight - 5) * offsetScale anchors.bottom: position === "bottom" ? parent.bottom : undefined
anchors.bottomMargin: position === "bottom" ? (-implicitHeight - 5) * offsetScale : 0
anchors.left: parent.left
anchors.leftMargin: position === "left" ? (-implicitWidth - 5) * offsetScale : 0
anchors.top: position !== "bottom" ? parent.top : undefined
anchors.topMargin: position === "top" ? (-implicitHeight - 5) * offsetScale : 0
clip: true clip: true
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open
@@ -31,7 +37,11 @@ Item {
id: content id: content
active: root.shouldBeActive || root.visible active: root.shouldBeActive || root.visible
anchors.centerIn: parent anchors.bottom: root.position === "top" ? parent.bottom : undefined
anchors.horizontalCenter: root.position !== "left" ? parent.horizontalCenter : undefined
anchors.right: root.position !== "left" ? undefined : parent.right
anchors.top: root.position === "bottom" ? parent.top : undefined
anchors.verticalCenter: root.position === "left" ? undefined : parent.verticalCenter
sourceComponent: Content { sourceComponent: Content {
wrapper: root wrapper: root
+6 -2
View File
@@ -2,8 +2,8 @@ pragma Singleton
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Components
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Modules.Settings.Common import qs.Modules.Settings.Common
import qs.Modules.Settings.Pages import qs.Modules.Settings.Pages
import qs.Modules.Settings.Pages.Wallpaper import qs.Modules.Settings.Pages.Wallpaper
@@ -45,7 +45,11 @@ QtObject {
// Connectivity // Connectivity
Component { Component {
PlaceholderComp { StackPage {
Component {
NetworkPage {
}
}
} }
}, },
Component { Component {
+240
View File
@@ -0,0 +1,240 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Config
import qs.Modules.Settings.Common
import qs.Modules.Bar.Popouts.Network
import qs.Components
import qs.Helpers
import qs.Services
PageBase {
id: root
title: qsTr("Network")
ColumnLayout {
id: networkContent
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Tokens.spacing.extraSmall / 2
width: root.cappedWidth
Toggle {
Layout.preferredHeight: visible ? implicitHeight : 0
checked: Network.wifiEnabled
first: true
last: connected.count === 0
subtext: qsTr("Toggle WiFi device state")
text: checked ? qsTr("Enabled") : qsTr("Disabled")
onToggled: Network.setWifi(checked)
}
Repeater {
id: connected
visible: Network.connectedNetworks.length > 0
model: ScriptModel {
values: [...Network.connectedNetworks].sort((a, b) => {
return b.signalStrength - a.signalStrength;
})
}
NetworkItem {
first: false
known: true
repeater: connected
}
}
}
component NetworkItem: ConnectedRect {
id: networkItem
readonly property bool connecting: Network.connectingNetwork === modelData
property int horizontalPadding: Tokens.padding.largeIncreased
required property int index
readonly property bool isSecure: Network.isSecure(modelData.security)
required property bool known
required property var modelData
required property Repeater repeater
Layout.fillWidth: true
Layout.preferredHeight: visible ? rowLayout.implicitHeight + Tokens.padding.smaller * 2 : 0
first: index === 0
last: index === (repeater.count - 1)
radius: Tokens.rounding.small
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: networkItem.horizontalPadding
anchors.rightMargin: networkItem.horizontalPadding
Column {
id: column
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.leftMargin: Tokens.spacing.extraSmall
Layout.rightMargin: Tokens.spacing.extraSmall
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurface
elide: Text.ElideRight
text: networkItem.modelData.name
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
text: qsTr("%1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%")
}
}
Item {
Layout.alignment: Qt.AlignRight
implicitHeight: icon.height
implicitWidth: icon.width
MaterialIcon {
id: icon
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.large
opacity: networkItem.connecting ? 0 : 1
text: Icons.getNetworkIcon(networkItem.modelData.signalStrength * 100, networkItem.isSecure)
Behavior on opacity {
Anim {
}
}
}
Loader {
active: opacity > 0
anchors.fill: parent
opacity: networkItem.connecting ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: CircularIndicator {
bgColor: "transparent"
running: true
}
}
}
}
StateLayer {
color: networkItem.modelData.active ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: {
if (networkItem.isSecure && !networkItem.known) {
PopupManager.requestOpen(Qt.resolvedUrl("Network/PSKOverlay.qml"), {
network: networkItem.modelData
});
} else {
Network.requestConnect(networkItem.modelData);
}
}
}
}
component Spacer: Item {
id: spacer
required property Repeater repeater
Layout.preferredHeight: repeater.count > 0 ? Tokens.spacing.extraSmall : 0
}
component Toggle: CustomSwitch {
id: toggleItem
readonly property alias bg: bg
property alias first: bg.first
property alias last: bg.last
property string subtext
Layout.fillWidth: true
cLayer: 2
horizontalPadding: Tokens.padding.largeIncreased
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
indicator.anchors.right: right
indicator.anchors.rightMargin: toggleItem.horizontalPadding
indicator.anchors.verticalCenter: verticalCenter
verticalPadding: Tokens.padding.normal
background: ConnectedRect {
id: bg
StateLayer {
id: stateLayer
manualPressOverride: toggleItem.pressed
}
}
contentItem: Item {
anchors.left: parent.left
anchors.leftMargin: toggleItem.horizontalPadding
anchors.right: toggleItem.indicator.left
anchors.rightMargin: Tokens.spacing.normal
implicitHeight: column.implicitHeight
implicitWidth: column.implicitWidth
Column {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: label
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font: {
const f = Qt.font(label.font);
f.pointSize = Tokens.font.size.smaller;
return f;
}
text: toggleItem.text
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font: {
const f = Qt.font(subtext.font);
f.pointSize = Tokens.font.size.small;
return f;
}
text: toggleItem.subtext
visible: toggleItem.subtext
}
}
}
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
}
}
@@ -8,7 +8,9 @@ PageBase {
readonly property var builtinIcons: ({ readonly property var builtinIcons: ({
audio: qsTr("Speakers"), audio: qsTr("Speakers"),
microphone: qsTr("Microphone"), microphone: qsTr("Microphone"),
power: qsTr("Battery") power: qsTr("Battery"),
network: qsTr("Network"),
bluetooth: qsTr("Bluetooth")
}) })
isSubPage: true isSubPage: true
@@ -91,5 +93,15 @@ PageBase {
onToggled: Config.bar.popouts.statusIcons = checked onToggled: Config.bar.popouts.statusIcons = checked
} }
ToggleRow {
checked: Config.bar.popouts.network
last: true
settingAnchor: "bar-status-network-popout"
subtext: qsTr("Show a details popout when hovering the network icon")
text: qsTr("Network popout on hover")
onToggled: Config.bar.popouts.network = checked
}
} }
} }
+50 -1
View File
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick.Layouts import QtQuick.Layouts
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Modules.Settings.Common import qs.Modules.Settings.Common
PageBase { PageBase {
@@ -25,7 +26,6 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.autoHide checked: Config.bar.autoHide
first: true first: true
last: true
settingAnchor: "bar-autohide" settingAnchor: "bar-autohide"
subtext: qsTr("Hide the bar, reveal on hover") subtext: qsTr("Hide the bar, reveal on hover")
text: qsTr("Auto hide") text: qsTr("Auto hide")
@@ -33,6 +33,55 @@ PageBase {
onToggled: Config.bar.autoHide = checked onToggled: Config.bar.autoHide = checked
} }
SelectRow {
active: Config.bar.position === "top" ? menuItems[0] : Config.bar.position === "left" ? menuItems[1] : menuItems[2]
settingAnchor: "bar-position"
subtext: qsTr("Change which edge the bar appears on")
text: qsTr("Position")
menuItems: [
MenuItem {
icon: "build"
text: qsTr("Top")
value: "top"
},
MenuItem {
icon: "rotate_auto"
text: qsTr("Left")
value: "left"
},
MenuItem {
icon: "rotate_auto"
text: qsTr("Bottom")
value: "bottom"
}
]
onSelected: item => Config.bar.position = item.value
}
ToggleRow {
checked: Config.bar.fullscreenReveal
settingAnchor: "bar-fullscreen-reveal"
subtext: qsTr("Hover top edge to show bar in fullscreen")
text: qsTr("Reveal on edge")
onToggled: Config.bar.fullscreenReveal = checked
}
SpinRow {
from: 0
last: true
settingAnchor: "bar-reveal-delay"
stepSize: 100
subtext: qsTr("The delay before bar is revealed when cursor is at edge")
text: qsTr("Reveal delay")
to: 2000
value: Config.bar.revealDelay
onMoved: v => Config.bar.revealDelay = v
}
// Components // Components
SectionHeader { SectionHeader {
text: qsTr("Components") text: qsTr("Components")
+1 -4
View File
@@ -70,10 +70,7 @@ PageBase {
} }
] ]
onSelected: item => { onSelected: item => Config.screenshot.mode = item.value
Config.screenshot.mode = item.value;
Config.save();
}
} }
SectionHeader { SectionHeader {
@@ -40,40 +40,19 @@ PageBase {
id: localWalls id: localWalls
model: { model: {
const walls = Wallpapers.list; const walls = [...Wallpapers.list];
var baseDir = Paths.wallsdir; var baseDir = Paths.wallsdir;
const categories = {}; walls.sort((a, b) => ((a.parentDir === baseDir) - (b.parentDir === baseDir)) || a.name.localeCompare(b.name));
const list = []; while (walls.length < 4)
for (const w of walls) { walls.push(null);
var parentDir = w.parentDir;
if (!parentDir.endsWith("/"))
parentDir = parentDir + "/";
if (!baseDir.endsWith("/")) return walls;
baseDir = baseDir + "/";
if (parentDir !== baseDir) {
const category = Wallpapers.getCategoryFor(w);
if (category && (!(category in categories) || categories[category].name.localeCompare(w.name) > 0))
categories[category] = w;
} else {
list.push(w);
}
}
list.push(...Object.values(categories));
list.sort((a, b) => ((a.parentDir === baseDir) - (b.parentDir === baseDir)) || a.name.localeCompare(b.name));
while (list.length < 4)
list.push(null);
return list;
} }
WallItem { WallItem {
required property FileSystemEntry modelData required property FileSystemEntry modelData
enabled: modelData enabled: modelData
// Empty placeholders for sizing
opacity: modelData ? 1 : 0 opacity: modelData ? 1 : 0
source: String(modelData?.path ?? "") source: String(modelData?.path ?? "")
-2
View File
@@ -104,7 +104,6 @@ PageBase {
onApplySettings: (start, end) => { onApplySettings: (start, end) => {
Config.general.color.scheduleDarkStart = start; Config.general.color.scheduleDarkStart = start;
Config.general.color.scheduleDarkEnd = end; Config.general.color.scheduleDarkEnd = end;
Config.save();
ModeScheduler.checkStartup(); ModeScheduler.checkStartup();
PopupManager.requestClose(); PopupManager.requestClose();
} }
@@ -146,7 +145,6 @@ PageBase {
onApplySettings: (start, end) => { onApplySettings: (start, end) => {
Config.general.color.scheduleHyprsunsetStart = start; Config.general.color.scheduleHyprsunsetStart = start;
Config.general.color.scheduleHyprsunsetEnd = end; Config.general.color.scheduleHyprsunsetEnd = end;
Config.save();
Hyprsunset.checkStartup(); Hyprsunset.checkStartup();
PopupManager.requestClose(); PopupManager.requestClose();
} }
+4 -2
View File
@@ -1,14 +1,16 @@
pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import ZShell.Config import ZShell.Config
import qs.Helpers
Item { Item {
id: root id: root
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
required property var panels required property var panels
property string queuedMode: "wallpaper"
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.settings readonly property bool shouldBeActive: visibilities.settings
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -37,7 +39,7 @@ Item {
sourceComponent: Content { sourceComponent: Content {
sState.animatingContainer: content.opacity < 1 sState.animatingContainer: content.opacity < 1
sState.currentPageIdx: ["wallpaper"][0] sState.currentPageIdx: ["wallpaper"].indexOf(root.queuedMode)
sState.screen: root.screen sState.screen: root.screen
} }
} }
-144
View File
@@ -1,144 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.SystemTray
import qs.Components
import ZShell.Config
import qs.Modules.SysTray.Widgets
import qs.Modules
import qs.Services
RowLayout {
id: root
readonly property alias items: repeater
required property RowLayout loader
required property Wrapper popouts
function closestRowChild(row, x) {
let child = row.childAt(x, row.height / 2);
if (child)
return child;
let closest = null;
let closestDistance = Infinity;
for (let i = 0; i < row.children.length; ++i) {
let c = row.children[i];
if (!c.visible || c.width <= 0)
continue;
let centerX = c.x + c.width / 2;
let dist = Math.abs(x - centerX);
if (dist < closestDistance) {
closestDistance = dist;
closest = c;
}
}
return closest;
}
function getHoveredSubItem(localX, localY) {
let modPos = mapToItem(sysTrayMod, localX, localY);
if (sysTrayMod.contains(Qt.point(modPos.x, modPos.y))) {
let modRowPos = sysTrayMod.mapToItem(sysIcons, modPos.x, modPos.y);
let child = closestRowChild(sysIcons, modRowPos.x);
if (child) {
if (child.objectName === "audio" && Config.bar.popouts.audio)
return {
id: "audio",
item: child
};
if (child.objectName === "microphone" && Config.bar.popouts.audio)
return {
id: "audio",
item: child
};
if (child.objectName === "upower" && Config.bar.popouts.upower)
return {
id: "upower",
item: child
};
}
}
let trayPos = mapToItem(sysTray, localX, localY);
if (Config.bar.tray.showOnHover && sysTray.contains(Qt.point(trayPos.x, trayPos.y))) {
let trayRowPos = sysTray.mapToItem(sysRow, trayPos.x, trayPos.y);
let child = sysRow.childAt(trayRowPos.x, trayRowPos.y);
if (child && child.hasOwnProperty("ind")) {
return {
id: `traymenu${child.ind}`,
item: child
};
}
}
return null;
}
height: Config.bar.height + Tokens.padding.smallest * 2
spacing: Tokens.padding.small
width: sysTray.implicitWidth + sysTrayMod.implicitWidth + Tokens.padding.small
CustomClippingRect {
id: sysTray
Layout.fillHeight: true
bottomRightRadius: Tokens.rounding.smallest / 2
color: Colors.tPalette.m3surfaceContainer
implicitWidth: sysRow.width + Tokens.padding.small * 2
radius: Tokens.rounding.full
topRightRadius: Tokens.rounding.smallest / 2
Row {
id: sysRow
anchors.centerIn: parent
spacing: 0
Repeater {
id: repeater
model: SystemTray.items
TrayItem {
id: trayItem
required property int index
required property SystemTrayItem modelData
implicitHeight: 34
implicitWidth: 34
ind: index
item: modelData
loader: root.loader
popouts: root.popouts
}
}
}
}
CustomClippingRect {
id: sysTrayMod
Layout.fillHeight: true
bottomLeftRadius: Tokens.rounding.smallest / 2
color: Colors.tPalette.m3surfaceContainer
implicitWidth: sysIcons.implicitWidth + Tokens.padding.smaller + Tokens.padding.normal
radius: Tokens.rounding.full
topLeftRadius: Tokens.rounding.smallest / 2
StatusIcons {
id: sysIcons
anchors.fill: parent
anchors.leftMargin: Tokens.padding.smaller
anchors.rightMargin: Tokens.padding.normal
}
}
}
-135
View File
@@ -1,135 +0,0 @@
import Quickshell.Services.UPower
import QtQuick
import QtQuick.Layouts
import qs.Components
import ZShell.Config
import qs.Helpers
Item {
id: root
implicitHeight: Battery.isLaptop ? batteryIconLoader.item.implicitHeight : upowerIconLoader.item.implicitHeight
implicitWidth: Battery.isLaptop ? batteryIconLoader.item.implicitWidth : upowerIconLoader.item.implicitWidth
Behavior on Layout.preferredHeight {
Anim {
}
}
Behavior on Layout.preferredWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Loader {
id: batteryIconLoader
active: Battery.isLaptop
anchors.centerIn: parent
sourceComponent: Row {
id: batteryIcon
property real batHeight: 16
property real batWidth: 30
property real nubHeight: 6
property real nubWidth: 2
property real radius: Tokens.rounding.smallest / 2
spacing: 1
CustomRect {
id: track
anchors.verticalCenter: parent.verticalCenter
color: Battery.colors.bg
height: batteryIcon.batHeight
radius: batteryIcon.radius
width: batteryIcon.batWidth
CustomText {
color: Battery.colors.fg
font.pointSize: Tokens.font.size.larger / 1.5
font.weight: 800
height: track.height
horizontalAlignment: Text.AlignHCenter
text: Math.round(Battery.currentPerc * 100)
verticalAlignment: Text.AlignVCenter
width: track.width
}
Item {
clip: true
width: parent.width * Battery.currentPerc
anchors {
bottom: parent.bottom
left: parent.left
top: parent.top
}
CustomRect {
id: fill
color: Battery.colors.fg
height: track.height
radius: track.radius
width: track.width
CustomText {
id: batteryLabel
clip: true
color: Battery.colors.bg
font.pointSize: 7.5
font.weight: 800
height: track.height
horizontalAlignment: Text.AlignHCenter
text: Math.round(Battery.currentPerc * 100)
verticalAlignment: Text.AlignVCenter
width: track.width
}
}
}
}
CustomRect {
id: nub
anchors.verticalCenter: parent.verticalCenter
bottomRightRadius: 20
color: Battery.currentPerc < 0.99 ? track.color : fill.color
height: batteryIcon.nubHeight
topRightRadius: 20
width: batteryIcon.nubWidth
}
}
}
Loader {
id: upowerIconLoader
active: !Battery.isLaptop
anchors.centerIn: parent
sourceComponent: MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
fill: 1
text: {
if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "energy_savings_leaf";
if (PowerProfiles.profile === PowerProfile.Performance)
return "bolt";
return "balance";
}
}
}
}
-37
View File
@@ -1,37 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules
import qs.Helpers
import ZShell.Config
import qs.Services
CustomRect {
id: root
property int countUpdates: Updates.availableUpdates
property color textColor: Colors.palette.m3onSurface
color: Colors.tPalette.m3surfaceContainer
implicitHeight: Config.bar.height + Tokens.padding.smallest * 2
implicitWidth: contentRow.implicitWidth + Tokens.spacing.small * 2
radius: Tokens.rounding.full
RowLayout {
id: contentRow
anchors.centerIn: parent
spacing: Tokens.spacing.small
MaterialIcon {
font.pointSize: Tokens.font.size.larger
text: "package_2"
}
CustomText {
color: root.textColor
font.pointSize: Tokens.font.size.normal
text: root.countUpdates
}
}
}
-86
View File
@@ -1,86 +0,0 @@
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Modules
import qs.Helpers
import qs.Services
Item {
id: root
required property ShellScreen screen
required property Item wrapper
implicitHeight: layout.implicitHeight + 16
implicitWidth: layout.implicitWidth + 16
GridLayout {
id: layout
anchors.centerIn: parent
columnSpacing: 8
rowSpacing: 8
Repeater {
model: Hypr.workspaces
CustomRect {
id: workspacePreview
required property HyprlandWorkspace modelData
Layout.preferredHeight: 180 + 10
Layout.preferredWidth: 320 + 10
border.color: "white"
border.width: 1
radius: Tokens.rounding.smallest
Repeater {
model: workspacePreview.modelData.toplevels
Item {
id: preview
property rect appPosition: {
let {
at: [cx, cy],
size: [cw, ch]
} = modelData.lastIpcObject;
cx -= modelData.monitor.x;
cy -= modelData.monitor.y;
return Qt.rect((cx / 8), (cy / 8), (cw / 8), (ch / 8));
}
required property HyprlandToplevel modelData
anchors.fill: parent
anchors.margins: 5
CustomRect {
border.color: Colors.tPalette.m3outline
border.width: 1
implicitHeight: preview.appPosition.height
implicitWidth: preview.appPosition.width
radius: Tokens.rounding.smallest / 2
x: preview.appPosition.x
y: preview.appPosition.y - 3.4
ScreencopyView {
id: previewCopy
anchors.fill: parent
captureSource: preview.modelData.wayland
live: true
}
}
}
}
}
}
}
}
+22 -79
View File
@@ -5,103 +5,46 @@ import Quickshell.Hyprland
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
import ZShell.Config
import ZShell.Internal import ZShell.Internal
Item { Item {
id: root id: root
property bool completed
property real cropHeight: displayData?.height ?? 1.0
property real cropWidth: displayData?.width ?? 1.0
property real cropX: displayData?.x ?? 0.0
property real cropY: displayData?.y ?? 0.0
property WallpaperImage current
readonly property var displayData: Wallpapers.getCrop(screen.name) readonly property var displayData: Wallpapers.getCrop(screen.name)
required property ShellScreen screen required property ShellScreen screen
property size screenResolution: Qt.size(screen.width * screenScale, screen.height * screenScale) property size screenResolution: Qt.size(screen.width * screenScale, screen.height * screenScale)
property real screenScale: Hyprland.monitorFor(screen).scale property real screenScale: Hyprland.monitorFor(screen).scale
property string source: Wallpapers.current
anchors.fill: parent anchors.fill: parent
Component.onCompleted: { Component.onCompleted: Hyprland.refreshMonitors()
Hyprland.refreshMonitors();
if (source) WallpaperImage {
Qt.callLater(() => { id: img
current = imgComp.createObject(this, {
source
});
completed = true;
});
}
onSourceChanged: {
if (!source)
current = null;
else
current = imgComp.createObject(this, {
source: source
});
}
Component { anchors.fill: parent
id: imgComp cropHeight: root.displayData?.height ?? 1.0
cropWidth: root.displayData?.width ?? 1.0
cropX: root.displayData?.x ?? 0.0
cropY: root.displayData?.y ?? 0.0
fadeDuration: 150
screenResolution: root.screenResolution
source: Wallpapers.current
WallpaperImage { Behavior on cropHeight {
id: img Anim {
anchors.fill: parent
cropHeight: root.cropHeight
cropWidth: root.cropWidth
cropX: root.cropX
cropY: root.cropY
opacity: 0
screenResolution: root.screenResolution
source: root.source
Behavior on cropHeight {
Anim {
id: heightAnim
}
} }
Behavior on cropWidth { }
Anim { Behavior on cropWidth {
id: widthAnim Anim {
}
} }
Behavior on cropX { }
Anim { Behavior on cropX {
id: xAnim Anim {
}
} }
Behavior on cropY { }
Anim { Behavior on cropY {
id: yAnim Anim {
}
}
Anim on opacity {
id: anim
from: 0
running: false
to: 1
type: Anim.SlowEffects
}
onStatusChanged: {
if (status === Image.Ready) {
anim.start();
}
}
Timer {
id: destroyTimer
interval: anim.duration * 2
running: root.current !== img && root.current?.status === Image.Ready
onTriggered: Qt.callLater(() => img.destroy())
} }
} }
} }
-75
View File
@@ -1,75 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Components
import ZShell.Config
import qs.Helpers
import qs.Services
Item {
id: root
required property var bar
property color colour: Colors.palette.m3primary
property Title current: text1
// readonly property int maxWidth: 300
readonly property int maxWidth: {
const otherModules = bar.children.filter(c => c.enabled && c.id && c.item !== this && c.id !== "spacer");
const otherWidth = otherModules.reduce((acc, curr) => {
return acc + (curr.item?.nonAnimWidth ?? curr.width ?? 0);
}, 0);
return bar.width - otherWidth - bar.spacing * (bar.children.length - 1) - bar.vPadding * 2;
}
clip: true
implicitHeight: current.implicitHeight
implicitWidth: Math.min(current.implicitWidth, root.maxWidth)
Behavior on implicitWidth {
Anim {
}
}
Title {
id: text1
}
Title {
id: text2
}
TextMetrics {
id: metrics
elide: Qt.ElideRight
elideWidth: root.maxWidth
font.family: "Rubik"
font.pointSize: Tokens.font.size.normal
text: Hypr.activeToplevel?.title ?? qsTr("Desktop")
onElideWidthChanged: root.current.text = elidedText
onTextChanged: {
const next = root.current === text1 ? text2 : text1;
next.text = elidedText;
root.current = next;
}
}
component Title: CustomText {
id: text
anchors.leftMargin: 7
anchors.verticalCenter: parent.verticalCenter
color: root.colour
font.family: metrics.font.family
font.pointSize: metrics.font.pointSize
height: implicitHeight
opacity: root.current === this ? 1 : 0
width: implicitWidth
Behavior on opacity {
Anim {
}
}
}
}
+1
View File
@@ -4,6 +4,7 @@ qml_module(ZShell-components
lazylistview.hpp lazylistview.cpp lazylistview.hpp lazylistview.cpp
wavyline.hpp wavyline.cpp wavyline.hpp wavyline.cpp
buttonrow.hpp buttonrow.cpp buttonrow.hpp buttonrow.cpp
carouselview.hpp carouselview.cpp
LIBRARIES LIBRARIES
Qt::Quick Qt::Quick
) )
File diff suppressed because it is too large Load Diff
+255
View File
@@ -0,0 +1,255 @@
#pragma once
#include <QQmlParserStatus>
#include <QElapsedTimer>
#include <QEasingCurve>
#include <QList>
#include <QPointF>
#include <QQuickItem>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QVariantAnimation>
#include <QVariantList>
#include <QVector>
#include <qtypes.h>
#include <qvectornd.h>
namespace ZShell::components {
class CarouselView : public QQuickItem {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(
QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY
delegateChanged)
Q_PROPERTY(QVariantList model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(
qreal focalWidth READ focalWidth WRITE setFocalWidth NOTIFY
focalWidthChanged)
Q_PROPERTY(
qreal minEdgeWidth READ minEdgeWidth WRITE setMinEdgeWidth NOTIFY
minEdgeWidthChanged)
Q_PROPERTY(
qreal itemSpacing READ itemSpacing WRITE setItemSpacing NOTIFY
itemSpacingChanged)
Q_PROPERTY(
qreal tileHeight READ tileHeight WRITE setTileHeight NOTIFY
tileHeightChanged)
Q_PROPERTY(
qreal maxWidth READ maxWidth WRITE setMaxWidth NOTIFY maxWidthChanged)
Q_PROPERTY(
int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY
currentIndexChanged)
Q_PROPERTY(
QQuickItem* currentItem READ currentItem NOTIFY currentItemChanged)
Q_PROPERTY(
int glideDuration READ glideDuration WRITE setGlideDuration NOTIFY
glideDurationChanged)
Q_PROPERTY(
QEasingCurve::Type glideEasingType READ glideEasingType WRITE
setGlideEasingType NOTIFY glideEasingTypeChanged)
Q_PROPERTY(
QVariantMap delegateProperties READ delegateProperties WRITE
setDelegateProperties NOTIFY delegatePropertiesChanged)
public:
explicit CarouselView(QQuickItem* parent = nullptr);
~CarouselView() override;
QQmlComponent* delegate() const { return m_delegate; }
void setDelegate(QQmlComponent* c);
QVariantList model() const { return m_model; }
void setModel(const QVariantList& m);
qreal focalWidth() const { return m_focalWidth; }
void setFocalWidth(qreal v);
qreal minEdgeWidth() const { return m_minEdgeWidth; }
void setMinEdgeWidth(qreal v);
qreal itemSpacing() const { return m_itemSpacing; }
void setItemSpacing(qreal v);
qreal tileHeight() const { return m_tileHeight; }
void setTileHeight(qreal v);
qreal maxWidth() const { return m_maxWidth; }
void setMaxWidth(qreal v);
int currentIndex() const { return m_currentRealIndex; }
void setCurrentIndex(int idx);
QQuickItem* currentItem() const { return m_currentItem; }
int glideDuration() const { return m_glideDuration; }
void setGlideDuration(int v);
QEasingCurve::Type glideEasingType() const { return m_glideEasingType; }
void setGlideEasingType(QEasingCurve::Type t);
QVariantMap delegateProperties() const { return m_delegateProperties; }
void setDelegateProperties(const QVariantMap& v);
Q_INVOKABLE void goToIndex(int realIndex);
Q_INVOKABLE void incrementCurrentIndex();
Q_INVOKABLE void decrementCurrentIndex();
signals:
void delegateChanged();
void modelChanged();
void focalWidthChanged();
void minEdgeWidthChanged();
void itemSpacingChanged();
void tileHeightChanged();
void maxWidthChanged();
void currentIndexChanged();
void currentItemChanged();
void glideDurationChanged();
void glideEasingTypeChanged();
void delegatePropertiesChanged();
void activated(int realIndex, QVariant modelData);
void previewIndexChanged(int realIndex, QVariant modelData);
protected:
void classBegin() override;
void componentComplete() override;
bool childMouseEventFilter(QQuickItem* item, QEvent* event) override;
void geometryChange(const QRectF& newGeo, const QRectF& oldGeo) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseUngrabEvent() override;
void wheelEvent(QWheelEvent* event) override;
private slots:
void handleDelegateActivate();
private:
struct Arrangement {
int n = 0;
qreal width = 0;
qreal outerSpacing = 0;
QVector<qreal> sizes;
QVector<qreal> centers;
qreal effectiveExtraWidth = 0;
};
struct Slot {
QQuickItem* item = nullptr;
int virtualIndex = 0;
int realIndex = -1;
bool active = false;
};
struct VelocitySample {
qint64 t;
qreal x;
};
void rebuildArrangement();
void relayout();
Arrangement arrangementForCount(int n) const;
int layoutCapacityForWidth(qreal width) const;
qreal targetWidthForMaxWidth(qreal maxWidth) const;
void updateSlotContent(Slot& slot, int virtualIndex);
void positionSlot(const Slot& slot) const;
void applyPropertiesToItem(QQuickItem* item, int realIndex) const;
qreal pitch() const { return m_focalWidth + m_itemSpacing; }
qreal contentXForIndex(qreal index) const;
int indexForContentX(qreal x) const;
qreal snapTargetX(qreal x) const;
void snapToNearest();
void glideTo(qreal dest);
void cancelAnimations();
qreal keylineSize(int io) const;
qreal sampleSize(qreal childLoc) const;
qreal continuousIndex() const;
int realIndexOf(int virtualIndex) const;
void updateCurrentIndexFromContentX(bool emitPreview);
void updateCurrentItem();
void wrapContentIfNeeded();
void centerInstantlyOnReal(int realIndex);
QQuickItem* acquireItem();
void releaseAllItems();
void addVelocitySample(qint64 t, qreal x);
qreal sampledVelocity() const;
void notifyCurrentIndexChanged();
void notifyCurrentItemChanged();
void notifyPreviewIndexChanged(int realIndex);
QQmlComponent* m_delegate = nullptr;
QVariantList m_model;
QVariantMap m_delegateProperties;
qreal m_focalWidth = 0;
qreal m_minEdgeWidth = 56;
qreal m_itemSpacing = 8;
qreal m_tileHeight = 0;
qreal m_maxWidth = 0;
Arrangement m_arrangement;
int m_layoutCapacity = 0;
qreal m_contentX = 0;
int m_currentVirtualIndex = 0;
int m_currentRealIndex = -1;
int m_previewRealIndex = -1;
QQuickItem* m_currentItem = nullptr;
int m_centerBlockStart = 0;
QList<Slot> m_slots;
int m_glideDuration = 250;
QEasingCurve::Type m_glideEasingType = QEasingCurve::OutCubic;
QVariantAnimation* m_glideAnim = nullptr;
bool m_dragging = false;
bool m_dragActive = false;
QPointF m_pressPos;
qreal m_pressContentX = 0;
QElapsedTimer m_dragTimer;
QVector<VelocitySample> m_velocitySamples;
static constexpr qint64 kVelocitySampleWindowMs = 80;
QVariantAnimation* m_flickAnim = nullptr;
bool m_completed = false;
int m_pendingCurrentIndex = -1;
bool m_initializedLayout = false;
bool m_relayoutQueued = false;
};
} // namespace ZShell::components
+3
View File
@@ -2,6 +2,8 @@
#include "configlist.hpp" #include "configlist.hpp"
#include "configobject.hpp" #include "configobject.hpp"
#include <qcontainerfwd.h>
#include <qhashfunctions.h>
#include <qqmlregistration.h> #include <qqmlregistration.h>
namespace ZShell::config { namespace ZShell::config {
@@ -61,6 +63,7 @@ class Bar : public ConfigObject {
CFG_PROPERTY(int, revealDelay, 100) CFG_PROPERTY(int, revealDelay, 100)
CFG_PROPERTY(int, rounding, 14) CFG_PROPERTY(int, rounding, 14)
CFG_PROPERTY(int, smoothing, 32) CFG_PROPERTY(int, smoothing, 32)
CFG_PROPERTY(QString, position, QStringLiteral("top"))
CONFIG_SUBOBJECT(Tray, tray) CONFIG_SUBOBJECT(Tray, tray)
CONFIG_LIST( CONFIG_LIST(
EntryList, EntryList,
+40 -19
View File
@@ -61,6 +61,8 @@ Config::Config(QObject* parent)
m_reloadTimer.setInterval(50); m_reloadTimer.setInterval(50);
connect(&m_reloadTimer, &QTimer::timeout, this, &Config::reloadAsync); connect(&m_reloadTimer, &QTimer::timeout, this, &Config::reloadAsync);
connectAutoSave(this);
connect( connect(
&m_watcher, &m_watcher,
&QFileSystemWatcher::directoryChanged, &QFileSystemWatcher::directoryChanged,
@@ -93,15 +95,31 @@ QString Config::filePath() const {
void Config::loadSync() { void Config::loadSync() {
m_loading = true; m_loading = true;
QFile f(filePath()); QFile f(filePath());
QJsonObject before;
bool existed = false;
if (f.open(QIODevice::ReadOnly)) { if (f.open(QIODevice::ReadOnly)) {
const auto doc = QJsonDocument::fromJson(f.readAll()); const auto doc = QJsonDocument::fromJson(f.readAll());
if (doc.isObject()) loadFromJson(QJsonValue(doc.object())); if (doc.isObject()) {
before = doc.object();
existed = true;
} else {
qInfo() << "Config: existing config at" << filePath()
<< "is empty or not a valid JSON object - using defaults";
}
} else { } else {
qInfo() << "Config: no existing config at" << filePath() qInfo() << "Config: no existing config at" << filePath()
<< "- using defaults"; << "- using defaults";
} }
loadFromJson(QJsonValue(before));
m_loading = false; m_loading = false;
const auto after = toJson().toObject();
if (!existed || after != before) saveNow();
} }
void Config::updateWatch() { void Config::updateWatch() {
@@ -146,27 +164,21 @@ void Config::loadAsync() {
if (f.open(QIODevice::ReadOnly)) { if (f.open(QIODevice::ReadOnly)) {
const auto doc = QJsonDocument::fromJson(f.readAll()); const auto doc = QJsonDocument::fromJson(f.readAll());
const bool valid = doc.isObject();
const QJsonObject before = valid ? doc.object() : QJsonObject();
if (doc.isObject()) { QMetaObject::invokeMethod(
QMetaObject::invokeMethod( this,
this, [this, valid, before]() {
[this, doc]() { loadFromJson(QJsonValue(before));
loadFromJson(QJsonValue(doc.object())); m_loading = false;
m_loading = false;
if (m_reloadPending) m_reloadTimer.start(); const auto after = toJson().toObject();
}, if (!valid || after != before) saveNow();
Qt::QueuedConnection);
} else {
QMetaObject::invokeMethod(
this,
[this]() {
m_loading = false;
if (m_reloadPending) m_reloadTimer.start(); if (m_reloadPending) m_reloadTimer.start();
}, },
Qt::QueuedConnection); Qt::QueuedConnection);
}
} else { } else {
qInfo() << "Config: failed to reload from" << filePath() qInfo() << "Config: failed to reload from" << filePath()
<< "- using in-memory values"; << "- using in-memory values";
@@ -232,6 +244,15 @@ void Config::saveNow() {
flushAsync(); flushAsync();
} }
void Config::connectAutoSave(ConfigNode* node) {
connect(node, &ConfigNode::propertiesChanged, this, [this] {
scheduleSave();
});
for (auto* child : node->childNodes())
connectAutoSave(child);
}
bool Config::writeAtomically(const QByteArray& data) { bool Config::writeAtomically(const QByteArray& data) {
QSaveFile f(filePath()); QSaveFile f(filePath());

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