131 Commits

Author SHA1 Message Date
zach 04fb534a05 scroll deceleration env
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-06 21:21:32 +02:00
zach 50e99501de switches and popouts
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 25s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m11s
2026-06-06 21:13:44 +02:00
zach 65c56bc598 updated components
Python / lint-format (pull_request) Successful in 41s
Python / test (pull_request) Successful in 1m9s
Lint & Format (Rust) / lint-format (pull_request) Successful in 2m42s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 20s
2026-06-06 00:49:19 +02:00
zach 82518006c3 revert toaster enum 2026-06-05 19:45:43 +02:00
zach 9cefdf509c cmake build hotfix 2026-06-05 12:31:37 +02:00
zach 27924aca37 disable incompatible-type for qmllint in clock 2026-06-04 23:22:28 +02:00
zach b4716d25c0 nodiscard 2026-06-04 22:57:45 +02:00
zach d8f047dbc9 nodiscard 2026-06-04 22:38:17 +02:00
zach 91b50b312d open launcher with gesture from bottom border 2026-06-04 19:01:02 +02:00
zach 3e933a8b78 remove logging 2026-06-04 18:56:42 +02:00
zach e127928126 Merge pull request 'Rewrite the manager responsible for handling automatic hyprsunset temperature activation as a qml plugin' (#117) from hyprsunset-manager-rewrite into main
Reviewed-on: #117
2026-06-04 15:05:45 +02:00
zach 94f2cf076c fix applying end() on init
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 49s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m3s
2026-06-04 15:05:18 +02:00
zach 9168b6e893 Merge branch 'main' into hyprsunset-manager-rewrite
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 24s
Python / test (pull_request) Successful in 42s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m3s
2026-06-04 14:56:53 +02:00
zach a477fb2e22 fix namespaces and logging names 2026-06-04 14:56:20 +02:00
zach 6586cc2788 do not use apply() in initializer
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-06-03 23:21:07 +02:00
zach 0be98a64ac Merge branch 'main' into hyprsunset-manager-rewrite
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 15s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 54s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-06-03 18:02:27 +02:00
zach b6e7ee7a54 Merge pull request 'lid behavior watcher to lock session' (#115) from lid-switch-behavior into main
Reviewed-on: #115
2026-06-03 18:02:21 +02:00
AramJonghu 59789ab8d3 unused imports in shell.qml
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-06-03 18:01:10 +02:00
AramJonghu a128c0fa40 removal reduntant config option and settings, unused lines in Lock.qml
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 52s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-06-03 17:59:03 +02:00
AramJonghu 6f856e2162 fix typo
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 44s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-06-03 17:47:12 +02:00
AramJonghu a19701222b forgotton import and removal LidService
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 26s
Python / test (pull_request) Successful in 41s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-06-03 17:43:58 +02:00
AramJonghu 0d8f558f66 Took caelestia lid logic (prepare to sleep) instead
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 45s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m3s
2026-06-03 17:41:29 +02:00
AramJonghu ed28d8b56a Took caelestia lid logic (prepare to sleep) instead 2026-06-03 17:41:17 +02:00
AramJonghu deef85d10c troubleshooting dbus
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 15s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 49s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-06-03 17:02:46 +02:00
AramJonghu f9ab1e2a10 inability to connect to DBus.Properties resolved
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m3s
2026-06-03 16:51:15 +02:00
AramJonghu 7dbce0bf8c qmlls lied to me - import ZShell returned
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 45s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-06-03 16:29:11 +02:00
AramJonghu 2920c57163 removes direct extern access, receives signal properly now, removed declaration unused method
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 14s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 44s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-03 16:27:31 +02:00
AramJonghu 0584cd618e missing changes
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 31s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m3s
2026-06-03 02:30:13 +02:00
AramJonghu 896e5e520d Added clang-format/tidy for additional rules. Adjusted inbranch cpp files.
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 15s
Python / lint-format (pull_request) Successful in 37s
Python / test (pull_request) Successful in 59s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m20s
2026-06-03 02:05:18 +02:00
AramJonghu 45f36ce71c added lock. in shell.qml, wrong scope
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-06-03 01:14:37 +02:00
AramJonghu 3bcdbbabbb build fix
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 16s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m9s
2026-06-03 01:02:50 +02:00
AramJonghu 016dcc008f Cpp changes, minor refactor plus separate signal logic, typo.
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 26s
Python / test (pull_request) Successful in 1m3s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m9s
- typo in searchindex
- Cpp plugin changed to use enum lidstate
- minor refactor
- Using signals instead of direct property access.
- Using states instead
- checking lidclosed instead of preparetosleep
2026-06-03 00:54:44 +02:00
zach d246ba1800 rewrite the manager responsible for handling automatic hyprsunset temperature activation as a qml plugin
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m2s
2026-06-02 16:09:48 +02:00
AramJonghu c91b53fbaa QTQuick
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 24s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m7s
2026-06-02 00:03:30 +02:00
AramJonghu ad2ee99d9c format using qmlformat
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-06-01 23:59:07 +02:00
AramJonghu 7da2c7827a refactor: using more oop
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 14s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 52s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-01 23:53:26 +02:00
AramJonghu db9c98b322 set default enabled for laptops, disabled for non battery devices
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 15s
Python / lint-format (pull_request) Successful in 22s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-06-01 21:03:59 +02:00
AramJonghu 1c1c6275df move lid watch toggle into lock settings 2026-06-01 20:45:04 +02:00
AramJonghu 83cf008a19 setting option to disable lidwatcher, also in settingswindow 2026-06-01 20:28:55 +02:00
AramJonghu c514e98687 nonexistant locked should be resolved 2026-06-01 20:12:17 +02:00
AramJonghu 0d097524c3 shell.qml could not load Connections, now fixed 2026-06-01 20:07:09 +02:00
AramJonghu 7de8cc3104 lidwatcher plugin initial setup 2026-06-01 20:02:37 +02:00
AramJonghu 82aa7c415f initial commit 2026-06-01 19:50:40 +02:00
zach 807b2525b7 Merge pull request 'initial refactor of Interactions.qml to add better support for touch screen gestures' (#114) from feat/improved-gestures into main
Reviewed-on: #114
Reviewed-by: AramJonghu <2+aramjonghu@noreply.git.zach-dev.cc>
Reviewed-by: Inorishio <inorishio@gmail.com>
2026-06-01 19:18:53 +02:00
zach 9e3cad6dbd Merge branch 'main' into feat/improved-gestures
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 25s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m3s
2026-06-01 19:01:12 +02:00
zach 9436acd626 change charging color 2026-06-01 17:12:28 +02:00
AramJonghu 17e600e78e Merge branch 'main' into feat/improved-gestures
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-01 15:05:04 +02:00
zach 7eba84c8be Better battery indicator and dedicated Battery singleton in helpers 2026-06-01 15:04:48 +02:00
zach 0820c8e023 Merge branch 'main' into feat/improved-gestures
Python / lint-format (pull_request) Successful in 22s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 27s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m2s
2026-06-01 12:13:33 +02:00
zach ebedf4b6fe increase slider visibility and remove debug logging 2026-06-01 12:12:00 +02:00
zach 6926880074 remove multi-touch handlers, single point for sidebar
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m1s
2026-05-31 23:05:31 +02:00
zach 1e1c90a0c5 remove redundant logging
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 22s
Python / test (pull_request) Successful in 43s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m2s
2026-05-31 22:45:32 +02:00
zach db6051457f fix drag gestures firing more than once from one drag
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m2s
2026-05-31 22:30:20 +02:00
zach 8f381fa8f0 Merge branch 'main' into feat/improved-gestures
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 14s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-05-31 22:08:09 +02:00
zach 2a7cd66f40 Actually fix background desync, blobs didn't follow final settle after deform shader 2026-05-31 19:06:17 +02:00
zach cf22b41f07 allow pointer takeover by anything, restrict takeover from items
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 41s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m2s
2026-05-31 14:32:39 +02:00
zach c267dfb0c2 remove leftover test timer and import
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 58s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-05-31 14:16:36 +02:00
zach e06fd71f11 initial refactor of Interactions.qml to add better support for touch screen gestures
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-05-31 13:58:21 +02:00
zach d041ce2471 Merge pull request 'zshell-img-tools' (#104) from zshell-img-tools into main
Reviewed-on: #104
2026-05-31 00:30:12 +02:00
Inorishio 7f3397e27c changes comments
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 42s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-05-31 00:23:13 +02:00
Inorishio 0d7c5d199a Update zshell-img-tools/src/main.rs
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m2s
Lint had issues with --help so for now I'm just removing it.
2026-05-30 20:59:30 +02:00
Inorishio 8ea295d1ce Some prework for the binary, linter issue is resolved, unnecessary comments removed
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Failing after 49s
2026-05-30 20:40:14 +02:00
zach 164c0a18c2 disable screenshot options in gui when disabling effects
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 52s
Lint & Format (Rust) / lint-format (pull_request) Failing after 1m7s
2026-05-29 23:44:36 +02:00
zach 1d0fc63177 Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 33s
Python / test (pull_request) Successful in 44s
Lint & Format (Rust) / lint-format (pull_request) Failing after 1m9s
2026-05-29 23:25:07 +02:00
zach 79998a36f0 Merge pull request 'either fail ? then check fail else pass' (#112) from format-check-rust-fix into main
Reviewed-on: #112
Reviewed-by: zach <zach@brohn.se>
2026-05-29 23:24:49 +02:00
AramJonghu a747f21af5 Merge branch 'main' into format-check-rust-fix
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 23s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 43s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m43s
2026-05-29 20:14:06 +02:00
AramJonghu 21c6940fb1 either fail ? then check fail else pass
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Lint & Format (Rust) / lint-format (pull_request) Has been cancelled
Python / lint-format (pull_request) Successful in 29s
Python / test (pull_request) Successful in 45s
2026-05-29 20:12:36 +02:00
zach c4cca73fd9 Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 24s
Python / test (pull_request) Successful in 45s
Lint & Format (Rust) / lint-format (pull_request) Successful in 50s
2026-05-29 12:28:13 +02:00
zach d6102d9ebe fix battery popup thresholds firing more than once per charging period 2026-05-29 12:25:28 +02:00
zach 84db7f41af Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 32s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m3s
2026-05-29 11:50:27 +02:00
zach 0819e8e2d1 Merge pull request 'format check shows green check, not anymore when format shows fail' (#110) from format-check-rust-fix into main
Reviewed-on: #110
Reviewed-by: zach <zach@brohn.se>
2026-05-29 11:50:10 +02:00
AramJonghu 0c47ba805f format check shows green check, not anymore when format shows fail
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m43s
2026-05-29 00:19:25 +02:00
zach 84ecd1481b Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 25s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-05-28 23:24:26 +02:00
zach 428809fee8 Merge pull request 'hotfix zshell-cli autocompletion failing' (#108) from hotfix-zshell-autocompletion into main
Reviewed-on: #108
Reviewed-by: zach <zach@brohn.se>
2026-05-28 23:24:17 +02:00
zach 1d667bedfd incorrect color channel assignment fixed
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 17s
Python / lint-format (pull_request) Successful in 24s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m11s
Python / test (pull_request) Successful in 1m16s
2026-05-28 23:19:57 +02:00
zach 760b8bfb93 pass hyprland options when auto is enabled
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 53s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-05-28 23:16:04 +02:00
AramJonghu 52be099914 removed exception in favor of sys.exit() to exit silently with print
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 44s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
2026-05-28 22:43:25 +02:00
Inorishio 1d0ee72498 Merge branch 'zshell-img-tools' of git.zach-dev.cc:zach/z-bar-qt into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 15s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 43s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m3s
2026-05-28 20:23:38 +02:00
Inorishio d0a3a0a269 updated 2026-05-28 20:23:28 +02:00
zach 9a026b2484 respect new arguments
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 21s
Python / lint-format (pull_request) Successful in 36s
Python / test (pull_request) Successful in 1m11s
Lint & Format (Rust) / lint-format (pull_request) Failing after 1m16s
2026-05-28 20:21:22 +02:00
Inorishio 0305f5a2be --corners > --corner 2026-05-28 19:11:47 +02:00
Inorishio 6e43bac52b Merge branch 'zshell-img-tools' of git.zach-dev.cc:zach/z-bar-qt into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Failing after 1m2s
2026-05-28 18:55:43 +02:00
Inorishio d098375da6 SHOULD have a handler for config/CLI parses 2026-05-28 18:55:03 +02:00
zach 239de807d4 Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m3s
2026-05-28 17:06:19 +02:00
zach 494653e029 Merge branch 'main' into hotfix-zshell-autocompletion
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 1m20s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m57s
2026-05-28 16:55:42 +02:00
zach d4a53b06e0 remove obsolete background files 2026-05-28 16:53:27 +02:00
zach 6d0813089a fix launcher height calculations 2026-05-28 16:47:19 +02:00
zach 4005e197eb better wallpaper cropping on load, cache images to disk and fix image aspect ratio from creating black bars 2026-05-28 14:40:47 +02:00
AramJonghu e9aa8268be ctx is unused
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
2026-05-28 14:25:43 +02:00
AramJonghu 0ad28ac017 now a check if cli completion is installed 2026-05-28 14:08:02 +02:00
AramJonghu fda3712855 attempt hotfix 2026-05-28 13:49:34 +02:00
zach ef1bcf6c73 new config option to set tray icon base size 2026-05-28 11:48:23 +02:00
zach ba67e56fda properly load/unload settings 2026-05-28 02:12:11 +02:00
zach f22c08991c revert notification icon oopsie 2026-05-28 02:03:39 +02:00
zach 8323bc31a0 properly handle disabling popouts 2026-05-28 01:10:00 +02:00
zach fa87789fcd remove logging 2026-05-28 00:54:39 +02:00
zach 6209264744 use loader for updates popout 2026-05-28 00:53:49 +02:00
zach 8e14993633 debug logging of battery percent props 2026-05-27 23:22:09 +02:00
zach 36fb925495 fix gpu name in resource popout 2026-05-27 23:14:02 +02:00
zach 1a72757e41 more tray icon replacements 2026-05-27 22:51:03 +02:00
zach 90e0987f22 load icon-theme versions of tray icons for some apps 2026-05-27 14:19:13 +02:00
zach afa3b0e3c4 cache icons based on pixel content instead of image string 2026-05-27 13:46:15 +02:00
zach 41c9d9e9b4 avoid unnecessary JS bindings by using narrower conditions, remove logging 2026-05-27 13:05:45 +02:00
zach d92e5b4cd7 fix drawing input anchoring incorrectly when using loader 2026-05-27 12:03:06 +02:00
zach 3963a48a9d load higher resolution tray icons for high-dpi screens 2026-05-27 11:43:42 +02:00
zach bd576e17dc Revert to OpenGL 2026-05-27 09:28:35 +02:00
zach 54e8a265d0 Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 43s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s
2026-05-26 23:03:28 +02:00
zach ae2a349247 optimize notification icon caching by copying image rather than item 2026-05-26 22:52:54 +02:00
AramJonghu 93ffe6ca2d Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 24s
Python / test (pull_request) Successful in 49s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m10s
2026-05-26 18:54:18 +02:00
zach e33901b23c Merge pull request 'hotfix zshell-cli shell show had no output' (#103) from 100-cli-autocompletion into main
Reviewed-on: #103
2026-05-26 18:50:27 +02:00
AramJonghu 7d4f563b43 replaced click
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 25s
Python / lint-format (pull_request) Successful in 33s
Python / test (pull_request) Successful in 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m49s
2026-05-26 18:47:56 +02:00
zach 74ce5bb868 Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 25s
Python / lint-format (pull_request) Successful in 31s
Python / test (pull_request) Failing after 54s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m13s
2026-05-26 18:42:18 +02:00
AramJonghu 439aa9ed1e fix ci/cl for python testing. Added click dep
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 22s
Python / test (pull_request) Failing after 52s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
2026-05-26 18:36:07 +02:00
AramJonghu 697de725fb change test to expect stdout
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Failing after 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m47s
2026-05-26 18:31:07 +02:00
AramJonghu f8a10698ea Merge branch 'main' into 100-cli-autocompletion
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 17s
Python / lint-format (pull_request) Successful in 29s
Python / test (pull_request) Failing after 1m5s
Lint & Format (Rust) / lint-format (pull_request) Successful in 3m1s
2026-05-26 18:26:57 +02:00
AramJonghu e5936aa730 hotfix zshell-cli shell show had no output
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Failing after 54s
Lint & Format (Rust) / lint-format (pull_request) Successful in 3m19s
2026-05-26 18:25:15 +02:00
zach a3f38e6414 fetch necessary hyprland options for screenshot tool
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 45s
Python / lint-format (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m50s
2026-05-26 16:46:45 +02:00
zach a2505ee875 add low battery toast, unload if not laptop battery. 2026-05-26 15:57:40 +02:00
zach f475e43c54 Merge pull request '100 shell autocomplete, type fixes, Pillow deprecation cleanup' (#101) from 100-cli-autocompletion into main
Reviewed-on: #101
Reviewed-by: zach <zach@brohn.se>
2026-05-26 13:10:56 +02:00
zach c33d6ae2dd Merge branch 'main' into 100-cli-autocompletion
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 33s
Python / test (pull_request) Successful in 49s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
2026-05-26 13:05:10 +02:00
zach ca19a60e5c temporary fix for focus being stolen even after release. Change to async loaders 2026-05-26 13:03:46 +02:00
Inorishio 6aedf6f8b7 scale fix 2026-05-26 11:41:31 +02:00
AramJonghu 233ea3efb9 accidental duplicate logic removed
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m45s
2026-05-26 09:30:58 +02:00
AramJonghu d19eead1f5 autocomplete now optional. Includes hint on first command input
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 57s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
2026-05-26 09:25:06 +02:00
zach 4ea74ed516 pass monitor scale to screenshot tool 2026-05-26 01:25:09 +02:00
zach f00af9d70f actually fixed config fetching in hyprland lua 2026-05-26 01:12:36 +02:00
Inorishio de11767d3b zshell-img-tools crate reduction to 53, process release fix, blur-passes, scale impl, settings passes setting, scale only avail in config 2026-05-25 23:15:00 +02:00
AramJonghu d0b2a5fc1d adding hint if is ran without -- flag
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 26s
Python / test (pull_request) Successful in 45s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m47s
2026-05-25 19:19:55 +02:00
AramJonghu 32acfa6b9f pyright/ruff error fixes. Autoinstall check of autocomplete 2026-05-25 19:03:00 +02:00
AramJonghu 17fcf1a02c pyright error fixes. added autocomplete to some commands 2026-05-25 18:42:34 +02:00
AramJonghu 1c11549811 initial commit 2026-05-25 17:45:39 +02:00
131 changed files with 4481 additions and 3739 deletions
+58
View File
@@ -0,0 +1,58 @@
---
BasedOnStyle: LLVM
AccessModifierOffset: 0
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignOperands: Align
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
ColumnLimit: 80
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
FixNamespaceComments: true
IndentCaseLabels: false
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 100
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++20
TabWidth: 4
UseTab: Always
+31
View File
@@ -0,0 +1,31 @@
---
Checks: >
-*,
bugprone-*,
clang-analyzer-*,
modernize-*,
-modernize-use-trailing-return-type,
performance-*,
readability-braces-around-statements,
readability-else-after-return,
readability-identifier-naming,
readability-redundant-*,
readability-simplify-*,
CheckOptions:
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.EnumCase: CamelCase
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.MemberCase: camelBack
readability-identifier-naming.MemberPrefix: m_
readability-identifier-naming.MethodCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.ParameterCase: camelBack
readability-identifier-naming.PrivateMemberPrefix: m_
readability-identifier-naming.StaticConstantCase: UPPER_CASE
readability-identifier-naming.StaticConstantPrefix: k
readability-identifier-naming.VariableCase: camelBack
WarningsAsErrors: "*"
HeaderFilterRegex: ".*"
FormatStyle: file
...
+16 -3
View File
@@ -38,15 +38,18 @@ jobs:
rustfmt \ rustfmt \
rust-clippy rust-clippy
- name: Format check - id: format-check
name: Format check
continue-on-error: true continue-on-error: true
run: | run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do for manifest in $(find . -name "Cargo.toml"); do
cargo fmt --manifest-path "$manifest" --check && \ cargo fmt --manifest-path "$manifest" --check && \
echo "$manifest: formatting OK" || \ echo "$manifest: formatting OK" || \
echo "$manifest: needs formatting" { echo "$manifest: needs formatting"; status=1; }
done done
exit $status
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml" echo "Rust files found but no Cargo.toml"
exit 1 exit 1
@@ -54,7 +57,8 @@ jobs:
echo "No Rust project found" echo "No Rust project found"
fi fi
- name: Clippy - id: clippy
name: Clippy
run: | run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0 status=0
@@ -70,3 +74,12 @@ jobs:
else else
echo "No Rust project found" echo "No Rust project found"
fi fi
- name: Check results
if: always()
run: |
if [ "${{ steps.format-check.outcome }}" = "failure" ] || [ "${{ steps.clippy.outcome }}" = "failure" ]; then
echo "One or more checks failed"
exit 1
fi
echo "All checks passed"
+2
View File
@@ -13,3 +13,5 @@ uv.lock
.qtcreator/ .qtcreator/
dist/ dist/
**/target/ **/target/
**/test-plugins/
**/Charts/
+1
View File
@@ -25,6 +25,7 @@ add_compile_options(
if("plugin" IN_LIST ENABLE_MODULES) if("plugin" IN_LIST ENABLE_MODULES)
add_subdirectory(Plugins) add_subdirectory(Plugins)
endif() endif()
if("shell" IN_LIST ENABLE_MODULES) if("shell" IN_LIST ENABLE_MODULES)
+58 -3
View File
@@ -2,7 +2,62 @@ import QtQuick
import qs.Config import qs.Config
NumberAnimation { NumberAnimation {
duration: Appearance.anim.durations.normal enum Type {
easing.bezierCurve: Appearance.anim.curves.standard StandardSmall = 0,
easing.type: Easing.BezierSpline Standard,
StandardLarge,
StandardExtraLarge,
EmphasizedSmall,
Emphasized,
EmphasizedLarge,
EmphasizedExtraLarge,
FastSpatial,
DefaultSpatial,
SlowSpatial,
FastEffects,
DefaultEffects,
SlowEffects
}
property int type: Anim.DefaultSpatial
duration: {
if (type < Anim.StandardSmall || type > Anim.SlowEffects)
return Appearance.anim.durations.normal;
if (type === Anim.FastSpatial)
return Appearance.anim.durations.expressiveFastSpatial;
if (type === Anim.DefaultSpatial)
return Appearance.anim.durations.expressiveDefaultSpatial;
if (type === Anim.SlowSpatial)
return Appearance.anim.durations.large;
if (type === Anim.FastEffects)
return Appearance.anim.durations.expressiveFastEffects;
if (type === Anim.DefaultEffects)
return Appearance.anim.durations.expressiveEffects;
if (type === Anim.SlowEffects)
return Appearance.anim.durations.expressiveSlowEffects;
const types = ["small", "normal", "large", "extraLarge"];
const idx = type % 4; // 0-7 are the 4 standard types
return Appearance.anim.durations[types[idx]];
}
easing.bezierCurve: {
if (type === Anim.FastSpatial)
return Appearance.anim.curves.expressiveFastSpatial;
if (type === Anim.DefaultSpatial)
return Appearance.anim.curves.expressiveDefaultSpatial;
if (type === Anim.SlowSpatial)
return Appearance.anim.curves.expressiveSlowSpatial;
if (type === Anim.FastEffects)
return Appearance.anim.curves.expressiveFastEffects;
if (type === Anim.DefaultEffects)
return Appearance.anim.curves.expressiveDefaultEffects;
if (type === Anim.SlowEffects)
return Appearance.anim.curves.expressiveSlowEffects;
if (type >= Anim.EmphasizedSmall && type <= Anim.EmphasizedExtraLarge)
return Appearance.anim.curves.emphasized;
return Appearance.anim.curves.standard;
}
} }
+154 -27
View File
@@ -1,47 +1,174 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import ZShell.Components
import ZShell
import qs.Components
import qs.Config import qs.Config
Slider { Slider {
id: root id: root
property color color: DynamicColors.palette.m3primary property bool animateWave
property color bgColor: enabled ? DynamicColors.palette.m3secondaryContainer : Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color fgColor: enabled ? DynamicColors.palette.m3primary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
property real filledWidth
property real pos: visualPosition
property int waveDuration: 1000
property real waveFrequency: 6
property bool wavy
signal interaction(v: real)
implicitHeight: 12
implicitWidth: 200
contentItem: Item {
anchors.fill: parent
background: Item {
CustomRect { CustomRect {
anchors.bottom: parent.bottom id: remaining
anchors.left: parent.left
anchors.top: parent.top anchors.left: handle.right
bottomRightRadius: root.implicitHeight / 6 anchors.leftMargin: Appearance.spacing.extraSmall
color: root.color anchors.right: parent.right
implicitWidth: root.handle.x - root.implicitHeight / 2 anchors.verticalCenter: parent.verticalCenter
radius: Appearance.rounding.full bottomLeftRadius: Appearance.rounding.extraSmall / 2
topRightRadius: root.implicitHeight / 6 color: root.bgColor
implicitHeight: parent.height * (parent.height <= 12 ? opacity : Math.min(opacity * 2, 1))
opacity: Math.min(width, 12) / 12
radius: Appearance.rounding.small
topLeftRadius: Appearance.rounding.extraSmall / 2
} }
CustomRect { CustomRect {
anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.rightMargin: 4 * remaining.opacity
bottomLeftRadius: root.implicitHeight / 6 anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.tPalette.m3surfaceContainer color: root.fgColor
implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 2 implicitHeight: 4 * remaining.opacity
implicitWidth: implicitHeight
opacity: remaining.opacity
radius: Appearance.rounding.full radius: Appearance.rounding.full
topLeftRadius: root.implicitHeight / 6 }
CustomRect {
id: handle
anchors.left: filled.right
anchors.leftMargin: Appearance.spacing.extraSmall
anchors.verticalCenter: parent.verticalCenter
color: root.fgColor
implicitHeight: {
const mult = parent.height <= 12 ? 3 : 1.2;
const pressMult = parent.height <= 12 ? 4 : 1.5;
return parent.height * (mouse.pressed ? pressMult : mult);
}
implicitWidth: 4
radius: Appearance.rounding.full
Behavior on implicitHeight {
Anim {
type: Anim.FastSpatial
}
}
}
Loader {
id: filled
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: root.wavy ? waveComp : lineComp
}
Component {
id: lineComp
CustomRect {
bottomRightRadius: Appearance.rounding.extraSmall / 2
color: root.fgColor
implicitHeight: root.height
implicitWidth: root.filledWidth
radius: Appearance.rounding.small
topRightRadius: Appearance.rounding.extraSmall / 2
}
}
Component {
id: waveComp
WavyLine {
color: root.fgColor
frequency: root.waveFrequency
fullLength: root.width - handle.implicitWidth - handle.anchors.leftMargin
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
implicitWidth: root.filledWidth
lineWidth: root.height * 0.7
startX: x
Behavior on color {
CAnim {
}
}
Anim on waveProgress {
duration: root.waveDuration
easing.type: Easing.Linear
from: 0
loops: Animation.Infinite
paused: !root.animateWave
running: true
to: 1
}
}
} }
} }
handle: CustomRect { Behavior on filledWidth {
anchors.verticalCenter: parent.verticalCenter id: widthBehavior
color: root.color
implicitHeight: 15
implicitWidth: 5
radius: Appearance.rounding.full
x: root.visualPosition * root.availableWidth - implicitWidth / 2
MouseArea { Anim {
acceptedButtons: Qt.NoButton }
anchors.fill: parent }
cursorShape: Qt.PointingHandCursor
Component.onCompleted: filledWidth = Qt.binding(() => (width - handle.implicitWidth - handle.anchors.leftMargin) * pos)
Binding {
id: posBinding
property: "pos"
target: root
value: ZUtils.clamp(mouse.pressStartPos + mouse.dragMovement, 0, 1)
when: mouse.pressed
}
MouseArea {
id: mouse
property real dragMovement
property real pressStartPos
property real pressStartX
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitHeight: handle.implicitHeight
preventStealing: true
onPositionChanged: e => {
dragMovement = (e.x - pressStartX) / width;
root.interaction(posBinding.value);
}
onPressed: e => {
widthBehavior.enabled = false;
pressStartX = e.x;
pressStartPos = root.visualPosition;
}
onReleased: e => {
root.interaction(posBinding.value);
widthBehavior.enabled = true;
dragMovement = 0;
} }
} }
} }
+6 -5
View File
@@ -28,6 +28,7 @@ RowLayout {
CustomTextField { CustomTextField {
id: textField id: textField
color: root.enabled ? DynamicColors.palette.m3onSurface : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5)
implicitHeight: upButton.implicitHeight implicitHeight: upButton.implicitHeight
inputMethodHints: Qt.ImhFormattedNumbersOnly inputMethodHints: Qt.ImhFormattedNumbersOnly
leftPadding: Appearance.padding.normal leftPadding: Appearance.padding.normal
@@ -36,7 +37,7 @@ RowLayout {
text: root.isEditing ? text : root.displayText text: root.isEditing ? text : root.displayText
background: CustomRect { background: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainerHigh color: root.enabled ? DynamicColors.tPalette.m3surfaceContainerHigh : DynamicColors.tPalette.m3surfaceContainerLow
implicitWidth: 100 implicitWidth: 100
radius: Appearance.rounding.full radius: Appearance.rounding.full
} }
@@ -85,7 +86,7 @@ RowLayout {
CustomRect { CustomRect {
id: upButton id: upButton
color: DynamicColors.palette.m3primary color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1)
implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2 implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -113,13 +114,13 @@ RowLayout {
id: upIcon id: upIcon
anchors.centerIn: parent anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5)
text: "keyboard_arrow_up" text: "keyboard_arrow_up"
} }
} }
CustomRect { CustomRect {
color: DynamicColors.palette.m3primary color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1)
implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2 implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -147,7 +148,7 @@ RowLayout {
id: downIcon id: downIcon
anchors.centerIn: parent anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5)
text: "keyboard_arrow_down" text: "keyboard_arrow_down"
} }
} }
+36 -69
View File
@@ -1,7 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Config import qs.Config
import qs.Helpers
Row { Row {
id: root id: root
@@ -12,66 +11,46 @@ Row {
} }
property alias active: menu.active property alias active: menu.active
property color color: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer property color colour: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer
property bool disabled property bool disabled
property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledTextColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) property color disabledTextColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
readonly property alias expandBtn: expandBtn
property alias expanded: menu.expanded property alias expanded: menu.expanded
property string fallbackIcon property string fallbackIcon
property string fallbackText property string fallbackText
property real horizontalPadding: Appearance.padding.normal property real horizontalPadding: Appearance.padding.larger
property alias iconLabel: iconLabel readonly property alias iconLabel: iconLabel
property alias label: label readonly property alias label: label
property alias menu: menu readonly property alias menu: menu
property alias menuItems: menu.items property alias menuItems: menu.items
property bool menuOnTop property bool menuOnTop
property alias stateLayer: stateLayer property real minLeftWidth
property color textColor: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer readonly property alias stateLayer: stateLayer
property color textColour: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
readonly property alias textRow: textRow
property int type: CustomSplitButton.Filled property int type: CustomSplitButton.Filled
property real verticalPadding: Appearance.padding.smaller property real verticalPadding: Appearance.padding.small
function closeDropdown(): void { spacing: Math.floor(Appearance.spacing.extraSmall)
SettingsDropdowns.close(menu);
}
function openDropdown(): void {
if (root.disabled)
return;
SettingsDropdowns.open(menu, root);
}
function toggleDropdown(): void {
if (root.disabled)
return;
SettingsDropdowns.toggle(menu, root);
}
spacing: Math.floor(Appearance.spacing.small / 2)
onExpandedChanged: {
if (!expanded)
SettingsDropdowns.forget(menu);
}
CustomRect { CustomRect {
bottomRightRadius: Appearance.rounding.small / 2 bottomRightRadius: Appearance.rounding.small / 2
color: root.disabled ? root.disabledColor : root.color color: root.disabled ? root.disabledColour : root.colour
implicitHeight: expandBtn.implicitHeight implicitHeight: expandBtn.implicitHeight
implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2 implicitWidth: Math.max(root.minLeftWidth, textRow.implicitWidth + root.horizontalPadding * 2)
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
topRightRadius: Appearance.rounding.small / 2 topRightRadius: Appearance.rounding.small / 2
StateLayer { StateLayer {
id: stateLayer id: stateLayer
function onClicked(): void { bottomRightRadius: parent.bottomRightRadius
root.active?.clicked(); color: root.textColour
}
color: root.textColor
disabled: root.disabled disabled: root.disabled
rect.bottomRightRadius: parent.bottomRightRadius topRightRadius: parent.topRightRadius
rect.topRightRadius: parent.topRightRadius
onClicked: root.active?.clicked()
} }
RowLayout { RowLayout {
@@ -86,7 +65,7 @@ Row {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
color: root.disabled ? root.disabledTextColor : root.textColor color: root.disabled ? root.disabledTextColour : root.textColour
fill: 1 fill: 1
text: root.active?.activeIcon ?? root.fallbackIcon text: root.active?.activeIcon ?? root.fallbackIcon
} }
@@ -98,12 +77,12 @@ Row {
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
animate: true animate: true
clip: true clip: true
color: root.disabled ? root.disabledTextColor : root.textColor color: root.disabled ? root.disabledTextColour : root.textColour
text: root.active?.activeText ?? root.fallbackText text: root.active?.activeText ?? root.fallbackText
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {
Anim { Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized type: Anim.Emphasized
} }
} }
} }
@@ -116,7 +95,7 @@ Row {
property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2 property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2
bottomLeftRadius: rad bottomLeftRadius: rad
color: root.disabled ? root.disabledColor : root.color color: root.disabled ? root.disabledColour : root.colour
implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2 implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
@@ -130,14 +109,12 @@ Row {
StateLayer { StateLayer {
id: expandStateLayer id: expandStateLayer
function onClicked(): void { color: root.textColour
root.toggleDropdown();
}
color: root.textColor
disabled: root.disabled disabled: root.disabled
rect.bottomLeftRadius: parent.bottomLeftRadius rect.bottomLeftRadius: parent.bottomLeftRadius
rect.topLeftRadius: parent.topLeftRadius rect.topLeftRadius: parent.topLeftRadius
onClicked: root.expanded = !root.expanded
} }
MaterialIcon { MaterialIcon {
@@ -145,7 +122,7 @@ Row {
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4) anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
color: root.disabled ? root.disabledTextColor : root.textColor color: root.disabled ? root.disabledTextColour : root.textColour
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 180 : 0
text: "expand_more" text: "expand_more"
@@ -158,24 +135,14 @@ Row {
} }
} }
} }
}
Menu { Menu {
id: menu id: menu
anchors.bottomMargin: Appearance.spacing.small attachSideY: root.menuOnTop ? Menu.Top : Menu.Bottom
anchors.right: parent.right attachTo: expandBtn
anchors.top: parent.bottom marginY: Appearance.spacing.small * (root.menuOnTop ? -1 : 1)
anchors.topMargin: Appearance.spacing.small thisSideY: root.menuOnTop ? Menu.Bottom : Menu.Top
states: State {
when: root.menuOnTop
AnchorChanges {
anchors.bottom: expandBtn.top
anchors.top: undefined
target: menu
}
}
}
} }
} }
+6 -6
View File
@@ -9,7 +9,6 @@ Item {
property alias active: splitButton.active property alias active: splitButton.active
property alias buttonAlias: splitButton property alias buttonAlias: splitButton
property bool enabled: true
property alias expanded: splitButton.expanded property alias expanded: splitButton.expanded
property int expandedZ: 100 property int expandedZ: 100
required property string label required property string label
@@ -25,7 +24,7 @@ Item {
implicitHeight: row.implicitHeight + Appearance.padding.smaller * 2 implicitHeight: row.implicitHeight + Appearance.padding.smaller * 2
opacity: shouldBeActive ? 1 : 0 opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8 scale: shouldBeActive ? 1 : 0.8
z: root.expanded ? expandedZ : -1 z: splitButton.menu.implicitHeight > 0 ? expandedZ : 1
Behavior on opacity { Behavior on opacity {
Anim { Anim {
@@ -50,7 +49,6 @@ Item {
color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: root.label text: root.label
z: root.expanded ? root.expandedZ : -1
} }
CustomSplitButton { CustomSplitButton {
@@ -58,14 +56,16 @@ Item {
enabled: root.enabled enabled: root.enabled
type: CustomSplitButton.Filled type: CustomSplitButton.Filled
z: root.expanded ? root.expandedZ : -1 z: 2
menu.onItemSelected: item => { menu.onItemSelected: item => {
root.selected(item); root.selected(item);
splitButton.closeDropdown(); // splitButton.closeDropdown();
} }
stateLayer.onClicked: { stateLayer.onClicked: {
splitButton.toggleDropdown(); // splitButton.toggleDropdown();
splitButton.expanded = !splitButton.expanded;
console.log(root.z);
} }
} }
} }
+18 -16
View File
@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Templates
import QtQuick.Shapes import QtQuick.Shapes
import QtQuick.Templates
import qs.Config import qs.Config
Switch { Switch {
@@ -12,38 +12,41 @@ Switch {
implicitWidth: implicitIndicatorWidth implicitWidth: implicitIndicatorWidth
indicator: CustomRect { indicator: CustomRect {
color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer)
implicitHeight: 13 + 7 * 2 implicitHeight: Appearance.font.size.medium + Appearance.padding.normal * 2
implicitWidth: implicitHeight * 1.7 implicitWidth: implicitHeight * 1.7
radius: Appearance.rounding.full radius: Appearance.rounding.full
CustomRect { CustomRect {
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.2 : implicitHeight
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: root.checked ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1) color: root.checked && root.enabled ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1)
implicitHeight: parent.implicitHeight - 10 implicitHeight: parent.implicitHeight - Appearance.padding.extraSmall
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
radius: Appearance.rounding.full radius: Appearance.rounding.full
x: root.checked ? parent.implicitWidth - nonAnimWidth - 10 / 2 : 10 / 2 x: root.checked ? parent.implicitWidth - nonAnimWidth - Appearance.padding.extraSmall / 2 : Appearance.padding.extraSmall / 2
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {
type: Anim.FastSpatial
} }
} }
Behavior on x { Behavior on x {
Anim { Anim {
type: Anim.FastSpatial
} }
} }
CustomRect { CustomRect {
anchors.fill: parent anchors.fill: parent
color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0 opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0
radius: parent.radius radius: parent.radius
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
} }
@@ -63,14 +66,14 @@ Switch {
} }
property point end2: { property point end2: {
if (root.pressed) if (root.pressed)
return Qt.point(width, height / 2); return Qt.point(width * 0.8, height / 2);
if (root.checked) if (root.checked)
return Qt.point(width * 0.85, height * 0.2); return Qt.point(width * 0.85, height * 0.2);
return Qt.point(width * 0.85, height * 0.15); return Qt.point(width * 0.85, height * 0.15);
} }
property point start1: { property point start1: {
if (root.pressed) if (root.pressed)
return Qt.point(width * 0.1, height / 2); return Qt.point(width * 0.2, height / 2);
if (root.checked) if (root.checked)
return Qt.point(width * 0.15, height / 2); return Qt.point(width * 0.15, height / 2);
return Qt.point(width * 0.15, height * 0.15); return Qt.point(width * 0.15, height * 0.15);
@@ -88,7 +91,7 @@ Switch {
anchors.centerIn: parent anchors.centerIn: parent
asynchronous: true asynchronous: true
height: parent.implicitHeight - Appearance.padding.small * 2 height: parent.implicitHeight - Appearance.padding.larger
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
width: height width: height
@@ -110,11 +113,11 @@ Switch {
} }
ShapePath { ShapePath {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap capStyle: ShapePath.RoundCap
fillColor: "transparent" fillColor: "transparent"
startX: icon.start1.x startX: icon.start1.x
startY: icon.start1.y startY: icon.start1.y
strokeColor: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3surfaceContainerHighest strokeColor: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3surfaceContainerHighest
strokeWidth: Appearance.font.size.larger * 0.15 strokeWidth: Appearance.font.size.larger * 0.15
Behavior on strokeColor { Behavior on strokeColor {
@@ -148,8 +151,7 @@ Switch {
} }
component PropAnim: PropertyAnimation { component PropAnim: PropertyAnimation {
duration: MaterialEasing.expressiveEffectsTime duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
easing.type: Easing.BezierSpline
} }
} }
+2 -1
View File
@@ -1,6 +1,6 @@
import qs.Config
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import qs.Config
RectangularShadow { RectangularShadow {
property real dp: [0, 1, 3, 6, 8, 12][level] property real dp: [0, 1, 3, 6, 8, 12][level]
@@ -13,6 +13,7 @@ RectangularShadow {
Behavior on dp { Behavior on dp {
Anim { Anim {
type: Anim.SlowEffects
} }
} }
} }
+129 -69
View File
@@ -2,109 +2,169 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Config import qs.Config
import qs.Drawers
Elevation { MouseArea {
id: root id: root
enum Side {
Top,
Bottom,
Left,
Right
}
property MenuItem active: items[0] ?? null property MenuItem active: items[0] ?? null
property int attachSideX: Menu.Right
property int attachSideY: Menu.Bottom
required property Item attachTo
property bool expanded property bool expanded
property list<MenuItem> items property list<MenuItem> items
property real marginX
property real marginY
property int thisSideX: Menu.Right
property int thisSideY: Menu.Top
signal itemSelected(item: MenuItem) signal itemSelected(item: MenuItem)
implicitHeight: root.expanded ? column.implicitHeight + Appearance.padding.small * 2 : 0 anchors.fill: parent
implicitWidth: Math.max(200, column.implicitWidth) enabled: expanded
level: 2 layer.enabled: opacity < 1
opacity: root.expanded ? 1 : 0 opacity: expanded ? 1 : 0
radius: Appearance.rounding.normal parent: {
const win = QsWindow.window;
Behavior on implicitHeight { const contentWin = win as Windows;
Anim { return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem;
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
} }
Behavior on opacity { Behavior on opacity {
Anim { Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial type: Anim.DefaultEffects
} }
} }
CustomClippingRect { onClicked: expanded = false
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
radius: parent.radius
ColumnLayout { TransformWatcher {
id: column id: watcher
anchors.left: parent.left a: root.parent
anchors.right: parent.right b: root.attachTo
anchors.verticalCenter: parent.verticalCenter }
spacing: 5
Repeater { Elevation {
model: root.items id: menu
CustomRect { implicitHeight: column.implicitHeight + column.anchors.margins * 2
id: item implicitWidth: Math.max(200, column.implicitWidth + column.anchors.margins * 2)
level: 2
radius: Appearance.rounding.medium
x: {
watcher.transform;
const item = root.attachTo;
let off = root.attachSideX === Menu.Left ? 0 : item.width;
if (root.thisSideX === Menu.Right)
off -= width;
return item.mapToItem(root.parent, off, 0).x + root.marginX;
}
y: {
watcher.transform;
const item = root.attachTo;
let off = root.attachSideY === Menu.Top ? 0 : item.height;
if (root.thisSideY === Menu.Bottom)
off -= height;
return item.mapToItem(root.parent, 0, off).y + root.marginY;
}
readonly property bool active: modelData === root.active transform: Scale {
required property int index origin.y: root.thisSideY === Menu.Bottom ? menu.height : 0
required property MenuItem modelData yScale: root.expanded ? 1 : 0.1
Layout.fillWidth: true Behavior on yScale {
implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.normal * 2 Anim {
implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.normal * 2 }
}
}
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainerLow
radius: parent.radius
ColumnLayout {
id: column
anchors.fill: parent
anchors.margins: Appearance.padding.extraSmall
spacing: Appearance.spacing.extraSmall
Repeater {
id: repeater
model: root.items
CustomRect { CustomRect {
anchors.fill: parent id: item
anchors.leftMargin: Appearance.padding.small
anchors.rightMargin: Appearance.padding.small readonly property bool active: modelData === root.active
color: Qt.alpha(DynamicColors.palette.m3secondaryContainer, active ? 1 : 0) required property int index
radius: Appearance.rounding.normal - Appearance.padding.small required property MenuItem modelData
Layout.fillWidth: true
color: Qt.alpha(DynamicColors.palette.m3tertiaryContainer, active ? 1 : 0)
implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.larger * 2
implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.larger * 2
radius: Appearance.rounding.small
Behavior on radius {
Anim {
}
}
StateLayer { StateLayer {
function onClicked(): void { color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface
disabled: !root.expanded
onClicked: {
root.itemSelected(item.modelData); root.itemSelected(item.modelData);
root.active = item.modelData; root.active = item.modelData;
item.modelData.clicked();
root.expanded = false; root.expanded = false;
} }
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
disabled: !root.expanded
}
}
RowLayout {
id: menuOptionRow
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.icon
} }
CustomText { RowLayout {
Layout.alignment: Qt.AlignVCenter id: menuOptionRow
Layout.fillWidth: true
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.text
}
Loader { anchors.fill: parent
Layout.alignment: Qt.AlignVCenter anchors.margins: Appearance.padding.larger
active: item.modelData.trailingIcon.length > 0 spacing: Appearance.spacing.small
visible: active
sourceComponent: MaterialIcon { MaterialIcon {
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface Layout.alignment: Qt.AlignVCenter
text: item.modelData.trailingIcon color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.icon
}
CustomText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.text
}
Loader {
Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0
asynchronous: true
visible: active
sourceComponent: MaterialIcon {
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.trailingIcon
}
} }
} }
} }
+172 -67
View File
@@ -1,15 +1,53 @@
import qs.Config
import QtQuick import QtQuick
import QtQuick.Shapes
import ZShell
import ZShell.Components
import qs.Helpers
import qs.Config
MouseArea { MouseArea {
id: root id: root
property color color: DynamicColors.palette.m3onSurface property alias bottomLeftRadius: base.bottomLeftRadius
property alias bottomRightRadius: base.bottomRightRadius
property real circleRadius
property alias color: base.color
property bool disabled property bool disabled
property real radius: parent?.radius ?? 0 readonly property real endRadius: {
property alias rect: hoverLayer const d1 = distSq(0, 0);
const d2 = distSq(width, 0);
const d3 = distSq(0, height);
const d4 = distSq(width, height);
return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
}
property real endRadiusAtPress
property bool manualPressOverride
property real pressX: width / 2
property real pressY: height / 2
property alias radius: base.radius
readonly property alias rect: base
property bool shapeMorph
property bool showHoverBackground: true
property real stateOpacity: containsMouse ? 0.08 : 0
property alias topLeftRadius: base.topLeftRadius
property alias topRightRadius: base.topRightRadius
function onClicked(): void { function clamp(r: real): real {
return Math.max(0, Math.min(r, width / 2, height / 2));
}
function distSq(x: real, y: real): real {
return (pressX - x) ** 2 + (pressY - y) ** 2;
}
function press(x: real, y: real): void {
pressX = x;
pressY = y;
fadeAnim.complete();
circleRadius = 0;
circle.opacity = 0.1;
rippleAnim.restart();
endRadiusAtPress = endRadius;
} }
anchors.fill: parent anchors.fill: parent
@@ -17,79 +55,146 @@ MouseArea {
enabled: !disabled enabled: !disabled
hoverEnabled: true hoverEnabled: true
onClicked: event => !disabled && onClicked(event) Behavior on stateOpacity {
onPressed: event => { Anim {
if (disabled) type: Anim.DefaultEffects
return; }
rippleAnim.x = event.x;
rippleAnim.y = event.y;
const dist = (ox, oy) => ox * ox + oy * oy;
rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y), dist(event.x, height - event.y), dist(width - event.x, event.y), dist(width - event.x, height - event.y)));
rippleAnim.restart();
} }
SequentialAnimation { onCircleRadiusChanged: {
if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running)
fadeAnim.start();
}
onClicked: event => !disabled && onClicked(event)
onManualPressOverrideChanged: {
if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running)
fadeAnim.start();
}
onPressed: e => press(e.x, e.y)
onPressedChanged: {
if (!(pressed || manualPressOverride) && !rippleAnim.running && circle.opacity > 0)
fadeAnim.start();
}
Anim {
id: rippleAnim id: rippleAnim
property real radius alwaysRunToEnd: true
property real x duration: Appearance.anim.durations.expressiveSlowEffects * 2
property real y easing.bezierCurve: Appearance.anim.curves.standard
property: "circleRadius"
PropertyAction { target: root
property: "x" to: root.endRadius
target: ripple
value: rippleAnim.x
}
PropertyAction {
property: "y"
target: ripple
value: rippleAnim.y
}
PropertyAction {
property: "opacity"
target: ripple
value: 0.08
}
Anim {
easing.bezierCurve: MaterialEasing.standardDecel
from: 0
properties: "implicitWidth,implicitHeight"
target: ripple
to: rippleAnim.radius * 2
}
Anim {
property: "opacity"
target: ripple
to: 0
}
} }
CustomClippingRect { Anim {
id: hoverLayer id: fadeAnim
property: "opacity"
target: circle
to: 0
type: Anim.SlowEffects
}
CustomRect {
id: base
anchors.fill: parent anchors.fill: parent
border.pixelAligned: false bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
color: Qt.alpha(root.color, root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.08 : 0) bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
radius: root.radius color: DynamicColors.palette.m3onSurface
opacity: root.stateOpacity
// Pick up radius from parent if it has one (parent can be anything with radius props)
// qmllint disable missing-property
radius: root.parent?.radius ?? 0
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
// qmllint enable missing-property
}
CustomRect { Shape {
id: ripple id: circle
border.pixelAligned: false anchors.fill: parent
color: root.color opacity: 0
opacity: 0 preferredRendererType: Shape.CurveRenderer
radius: Appearance.rounding.full
transform: Translate { ShapePath {
x: -ripple.width / 2 fillColor: base.color
y: -ripple.height / 2 startX: root.clamp(base.topLeftRadius)
startY: 0
strokeColor: "transparent"
strokeWidth: 0
fillGradient: RadialGradient {
centerRadius: root.circleRadius
centerX: root.pressX
centerY: root.pressY
focalX: centerX
focalY: centerY
GradientStop {
color: Qt.alpha(base.color, 1)
position: 0
}
GradientStop {
color: Qt.alpha(base.color, 1)
position: ZUtils.clamp(1 - 0.2 * root.endRadius / root.circleRadius, 0.01, 0.99)
}
GradientStop {
color: Qt.alpha(base.color, ZUtils.clamp((root.circleRadius / root.endRadius - 0.9) / 0.1, 0, 1))
position: 1
}
}
PathLine {
x: root.width - root.clamp(base.topRightRadius)
y: 0
}
PathArc {
radiusX: root.clamp(base.topRightRadius)
radiusY: root.clamp(base.topRightRadius)
relativeX: root.clamp(base.topRightRadius)
relativeY: root.clamp(base.topRightRadius)
}
PathLine {
x: root.width
y: root.height - root.clamp(base.bottomRightRadius)
}
PathArc {
radiusX: root.clamp(base.bottomRightRadius)
radiusY: root.clamp(base.bottomRightRadius)
relativeX: -root.clamp(base.bottomRightRadius)
relativeY: root.clamp(base.bottomRightRadius)
}
PathLine {
x: root.clamp(base.bottomLeftRadius)
y: root.height
}
PathArc {
radiusX: root.clamp(base.bottomLeftRadius)
radiusY: root.clamp(base.bottomLeftRadius)
relativeX: -root.clamp(base.bottomLeftRadius)
relativeY: -root.clamp(base.bottomLeftRadius)
}
PathLine {
x: 0
y: root.clamp(base.topLeftRadius)
}
PathArc {
radiusX: root.clamp(base.topLeftRadius)
radiusY: root.clamp(base.topLeftRadius)
x: root.clamp(base.topLeftRadius)
y: 0
} }
} }
} }
-2
View File
@@ -7,8 +7,6 @@ Singleton {
readonly property AppearanceConf.Deform deform: Config.appearance.deform readonly property AppearanceConf.Deform deform: Config.appearance.deform
readonly property AppearanceConf.FontStuff font: Config.appearance.font readonly property AppearanceConf.FontStuff font: Config.appearance.font
readonly property AppearanceConf.Padding padding: Config.appearance.padding readonly property AppearanceConf.Padding padding: Config.appearance.padding
// Literally just here to shorten accessing stuff :woe:
// Also kinda so I can keep accessing it with `Appearance.xxx` instead of `Conf.appearance.xxx`
readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding
readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing
readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency
+16 -4
View File
@@ -28,9 +28,13 @@ JsonObject {
property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
property list<real> expressiveDefaultEffects: [0.34, 0.8, 0.34, 1, 1, 1]
property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
property list<real> expressiveFastEffects: [0.31, 0.94, 0.34, 1, 1, 1]
property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
property list<real> expressiveSlowEffects: [0.34, 0.88, 0.34, 1, 1, 1]
property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1]
property list<real> standard: [0.2, 0, 0, 1, 1, 1] property list<real> standard: [0.2, 0, 0, 1, 1, 1]
property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1] property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
property list<real> standardDecel: [0, 0, 0, 1, 1, 1] property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
@@ -38,7 +42,9 @@ JsonObject {
component AnimDurations: JsonObject { component AnimDurations: JsonObject {
property int expressiveDefaultSpatial: 500 * scale property int expressiveDefaultSpatial: 500 * scale
property int expressiveEffects: 200 * scale property int expressiveEffects: 200 * scale
property int expressiveFastEffects: 150 * scale
property int expressiveFastSpatial: 350 * scale property int expressiveFastSpatial: 350 * scale
property int expressiveSlowEffects: 300 * scale
property int extraLarge: 1000 * scale property int extraLarge: 1000 * scale
property int large: 600 * scale property int large: 600 * scale
property int normal: 400 * scale property int normal: 400 * scale
@@ -57,7 +63,8 @@ JsonObject {
component FontSize: JsonObject { component FontSize: JsonObject {
property int extraLarge: 28 * scale property int extraLarge: 28 * scale
property int large: 18 * scale property int large: 18 * scale
property int larger: 15 * scale property int larger: 16 * scale
property int medium: 14 * scale
property int normal: 13 * scale property int normal: 13 * scale
property real scale: 1 property real scale: 1
property int small: 11 * scale property int small: 11 * scale
@@ -70,23 +77,28 @@ JsonObject {
} }
} }
component Padding: JsonObject { component Padding: JsonObject {
property int large: 15 * scale property int extraLargeIncreased: 32 * scale
property int larger: 13 * scale property int extraSmall: 4 * scale
property int normal: 9 * scale property int large: 16 * scale
property int larger: 12 * scale
property int normal: 8 * scale
property real scale: 1 property real scale: 1
property int small: 5 * scale property int small: 5 * scale
property int smaller: 7 * scale property int smaller: 7 * scale
property int smallest: 2 * scale property int smallest: 2 * scale
} }
component Rounding: JsonObject { component Rounding: JsonObject {
property int extraSmall: 4 * scale
property int full: 1000 * scale property int full: 1000 * scale
property int large: 24 * scale property int large: 24 * scale
property int medium: 16 * scale
property int normal: 18 * scale property int normal: 18 * scale
property real scale: 1 property real scale: 1
property int small: 12 * scale property int small: 12 * scale
property int smallest: 8 * scale property int smallest: 8 * scale
} }
component Spacing: JsonObject { component Spacing: JsonObject {
property int extraSmall: 4 * scale
property int large: 20 * scale property int large: 20 * scale
property int larger: 16 * scale property int larger: 16 * scale
property int normal: 12 * scale property int normal: 12 * scale
+5
View File
@@ -59,6 +59,8 @@ JsonObject {
} }
property int rounding: 8 property int rounding: 8
property int smoothing: 32 property int smoothing: 32
property Tray tray: Tray {
}
component Popouts: JsonObject { component Popouts: JsonObject {
property bool activeWindow: true property bool activeWindow: true
@@ -69,4 +71,7 @@ JsonObject {
property bool tray: true property bool tray: true
property bool upower: true property bool upower: true
} }
component Tray: JsonObject {
property int trayIconSize: 24
}
} }
+11 -4
View File
@@ -100,6 +100,9 @@ Singleton {
border: barConfig.border, border: barConfig.border,
smoothing: barConfig.smoothing, smoothing: barConfig.smoothing,
height: barConfig.height, height: barConfig.height,
tray: {
trayIconSize: barConfig.tray.trayIconSize
},
popouts: { popouts: {
tray: barConfig.popouts.tray, tray: barConfig.popouts.tray,
audio: barConfig.popouts.audio, audio: barConfig.popouts.audio,
@@ -214,6 +217,10 @@ Singleton {
}, },
idle: { idle: {
timeouts: general.idle.timeouts timeouts: general.idle.timeouts
},
battery: {
popupThresholds: general.battery.popupThresholds,
critPerc: general.battery.critPerc
} }
}; };
} }
@@ -293,10 +300,10 @@ Singleton {
return { return {
enable_pp: screenshot.enable_pp, enable_pp: screenshot.enable_pp,
mode: screenshot.mode, mode: screenshot.mode,
corner_radius: screenshot.corner_radius, radius: screenshot.radius,
drop_shadow: screenshot.drop_shadow, shadow: screenshot.shadow,
rounded_corners: screenshot.rounded_corners, rounding: screenshot.rounding,
shadow_blur_radius: screenshot.shadow_blur_radius, shadow_blur: screenshot.shadow_blur,
shadow_color: screenshot.shadow_color, shadow_color: screenshot.shadow_color,
shadow_offset_x: screenshot.shadow_offset_x, shadow_offset_x: screenshot.shadow_offset_x,
shadow_offset_y: screenshot.shadow_offset_y shadow_offset_y: screenshot.shadow_offset_y
+4
View File
@@ -115,6 +115,10 @@ Singleton {
Config.save(); Config.save();
} }
function swapRG(c: color): color {
return Qt.rgba(c.g, c.r, c.b, c.a);
}
Component.onCompleted: debounceTimer.triggered() Component.onCompleted: debounceTimer.triggered()
Connections { Connections {
+13
View File
@@ -4,6 +4,8 @@ import Quickshell
JsonObject { JsonObject {
property Apps apps: Apps { property Apps apps: Apps {
} }
property Battery battery: Battery {
}
property Color color: Color { property Color color: Color {
} }
property string dateFormat: "ddd d MMM - hh:mm:ss" property string dateFormat: "ddd d MMM - hh:mm:ss"
@@ -19,6 +21,17 @@ JsonObject {
property list<string> playback: ["mpv"] property list<string> playback: ["mpv"]
property list<string> terminal: ["kitty"] property list<string> terminal: ["kitty"]
} }
component Battery: JsonObject {
property int critPerc: 5
property list<var> popupThresholds: [
{
perc: 20,
name: qsTr("Low battery"),
message: qsTr("Battery is low"),
icon: "battery_android_frame_2"
},
]
}
component Color: JsonObject { component Color: JsonObject {
property int hyprsunsetTemp: 5000 property int hyprsunsetTemp: 5000
property string mode: "dark" property string mode: "dark"
+4 -4
View File
@@ -1,12 +1,12 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property real corner_radius: 12.0
property bool drop_shadow: true
property bool enable_pp: true property bool enable_pp: true
property string mode: "manual" property string mode: "manual"
property bool rounded_corners: false property real radius: 12.0
property real shadow_blur_radius: 22.0 property bool rounding: false
property bool shadow: true
property real shadow_blur: 22.0
property list<int> shadow_color: [0, 0, 0, 160] property list<int> shadow_color: [0, 0, 0, 160]
property real shadow_offset_x: 5.0 property real shadow_offset_x: 5.0
property real shadow_offset_y: 5.0 property real shadow_offset_y: 5.0
+70
View File
@@ -0,0 +1,70 @@
import Quickshell
import Quickshell.Services.UPower
import QtQuick
import ZShell
import qs.Config
import qs.Components.Toast
import qs.Helpers
Scope {
id: root
readonly property list<var> popupThresholds: [...Config.general.battery.popupThresholds].sort((a, b) => b.perc - a.perc)
function nearestThresholdAbove(p: real): var {
const thresholds = [...root.popupThresholds];
for (const perc of thresholds) {
if (p < perc.perc)
return perc;
}
return null;
}
Connections {
function onOnBatteryChanged(): void {
if (!Battery.ready)
return;
if (Battery.onBattery) {
if (Config.utilities.toasts.chargingChanged)
Toaster.toast(qsTr("Charger unplugged"), qsTr("Battery is discharging"), "power_off");
const p = Battery.currentPerc * 100;
const perc = root.nearestThresholdAbove(p);
if (perc)
Toaster.toast(perc.title ?? qsTr("Battery warning"), perc.message ?? qsTr("Battery is low"), perc.icon ?? "battery_android_alert", perc.critical ? Toast.Error : Toast.Warning);
} else {
if (Config.utilities.toasts.chargingChanged)
Toaster.toast(qsTr("Charger plugged in"), qsTr("Battery is charging"), "power");
}
}
target: Battery
}
Connections {
function onCurrentPercChanged(): void {
if (!Battery.onBattery)
return;
const p = Battery.currentPerc * 100;
for (const perc of root.popupThresholds) {
if (p == perc.perc) {
Toaster.toast(perc.title ?? qsTr("Battery warning"), perc.message ?? qsTr("Battery perc is low"), perc.icon ?? "battery_android_alert", perc.critical ? Toast.Error : Toast.Warning);
}
}
}
function onReadyChanged(): void {
if (!Battery.ready)
return;
const p = Battery.currentPerc * 100;
const perc = root.nearestThresholdAbove(p);
if (perc)
Toaster.toast(perc.title ?? qsTr("Battery warning"), perc.message ?? qsTr("Battery is low"), perc.icon ?? "battery_android_alert", perc.critical ? Toast.Error : Toast.Warning);
}
target: Battery
}
}
+32 -57
View File
@@ -179,6 +179,8 @@ Singleton {
property string appIcon property string appIcon
property string appName property string appName
property string body property string body
property string cachedImageSource: ""
property bool cachingImage: false
property bool closed property bool closed
readonly property Connections conn: Connections { readonly property Connections conn: Connections {
function onActionsChanged(): void { function onActionsChanged(): void {
@@ -214,9 +216,9 @@ Singleton {
} }
function onImageChanged(): void { function onImageChanged(): void {
notif.image = notif.notification.image; notif.imageSource = notif.notification.image || "";
if (notif.notification?.image) notif.image = notif.imageSource;
notif.dummyImageLoader.active = true; notif.cacheImageIfNeeded();
} }
function onResidentChanged(): void { function onResidentChanged(): void {
@@ -233,60 +235,12 @@ Singleton {
target: notif.notification target: notif.notification
} }
readonly property LazyLoader dummyImageLoader: LazyLoader {
active: false
PanelWindow {
color: "transparent"
implicitHeight: Config.notifs.sizes.image
implicitWidth: Config.notifs.sizes.image
mask: Region {
}
Image {
function tryCache(): void {
if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
return;
const cacheKey = notif.appName + notif.summary + notif.id;
let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
for (let i = 0; i < cacheKey.length; i++) {
ch = cacheKey.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
const cache = `${Paths.notifimagecache}/${hash}.png`;
ZShellIo.saveItem(this, Qt.resolvedUrl(cache), () => {
notif.image = cache;
notif.dummyImageLoader.active = false;
});
}
anchors.fill: parent
asynchronous: true
cache: false
fillMode: Image.PreserveAspectCrop
opacity: 0
source: Qt.resolvedUrl(notif.image)
onHeightChanged: tryCache()
onStatusChanged: tryCache()
onWidthChanged: tryCache()
}
}
}
property real expireTimeout: 5 property real expireTimeout: 5
property bool hasActionIcons property bool hasActionIcons
property string id
property string image property string image
property string imageSource
property var locks: new Set() property var locks: new Set()
property string notifId
property Notification notification property Notification notification
property bool popup property bool popup
property bool resident property bool resident
@@ -329,6 +283,26 @@ Singleton {
} }
property int urgency: NotificationUrgency.Normal property int urgency: NotificationUrgency.Normal
function cacheImageIfNeeded(): void {
const source = imageSource;
if (!source || cachingImage)
return;
if (cachedImageSource === source)
return;
cachingImage = true;
ZShellIo.cacheImage(Qt.resolvedUrl(source), Paths.notifimagecache, (path, url) => {
cachedImageSource = source;
image = path;
cachingImage = false;
}, () => {
cachingImage = false;
});
}
function close(): void { function close(): void {
closed = true; closed = true;
if (locks.size === 0 && root.list.includes(this)) { if (locks.size === 0 && root.list.includes(this)) {
@@ -352,14 +326,13 @@ Singleton {
if (!notification) if (!notification)
return; return;
id = notification.id; notifId = notification.id;
summary = notification.summary; summary = notification.summary;
body = notification.body; body = notification.body;
appIcon = notification.appIcon; appIcon = notification.appIcon;
appName = notification.appName; appName = notification.appName;
image = notification.image; imageSource = notification.image || "";
if (notification?.image) image = imageSource;
dummyImageLoader.active = true;
expireTimeout = notification.expireTimeout; expireTimeout = notification.expireTimeout;
urgency = notification.urgency; urgency = notification.urgency;
resident = notification.resident; resident = notification.resident;
@@ -369,6 +342,8 @@ Singleton {
text: a.text, text: a.text,
invoke: () => a.invoke() invoke: () => a.invoke()
})); }));
cacheImageIfNeeded();
} }
} }
} }
-107
View File
@@ -1,107 +0,0 @@
import Quickshell
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
import qs.Modules as Modules
import qs.Modules.Notifications as Notifications
import qs.Modules.Notifications.Sidebar as Sidebar
import qs.Modules.Notifications.Sidebar.Utils as Utils
import qs.Modules.Dashboard as Dashboard
import qs.Modules.Osd as Osd
import qs.Modules.Launcher as Launcher
import qs.Modules.Resources as Resources
import qs.Modules.Drawing as Drawing
import qs.Modules.Settings as Settings
import qs.Modules.Dock as Dock
Shape {
id: root
required property Item bar
required property Panels panels
required property PersistentProperties visibilities
anchors.fill: parent
anchors.margins: Config.barConfig.border
anchors.topMargin: bar.implicitHeight
asynchronous: true
preferredRendererType: Shape.CurveRenderer
Drawing.Background {
startX: 0
startY: wrapper.y - rounding
wrapper: root.panels.drawing
}
Resources.Background {
startX: 0 - rounding
startY: 0
wrapper: root.panels.resources
}
Osd.Background {
startX: root.width - root.panels.sidebar.width
startY: (root.height - wrapper.height) / 2 - rounding
wrapper: root.panels.osd
}
Modules.Background {
invertBottomRounding: wrapper.x <= 0
rounding: root.panels.popouts.currentName.startsWith("updates") || root.panels.popouts.currentName.startsWith("audio") ? Appearance.rounding.normal : Appearance.rounding.smallest
startX: wrapper.x - rounding
startY: wrapper.y
wrapper: root.panels.popouts
}
Notifications.Background {
sidebar: sidebar
startX: root.width
startY: 0
wrapper: root.panels.notifications
}
Launcher.Background {
startX: (root.width - wrapper.width) / 2 - rounding
startY: root.height
wrapper: root.panels.launcher
}
Dashboard.Background {
startX: root.width - root.panels.dashboard.width - rounding
startY: 0
wrapper: root.panels.dashboard
}
Utils.Background {
sidebar: sidebar
startX: root.width
startY: root.height
wrapper: root.panels.utilities
}
Sidebar.Background {
id: sidebar
panels: root.panels
startX: root.width
startY: root.panels.notifications.height
wrapper: root.panels.sidebar
}
Settings.Background {
id: settings
startX: (root.width - wrapper.width) / 2 - rounding
startY: 0
wrapper: root.panels.settings
}
Dock.Background {
id: dock
startX: (root.width - wrapper.width) / 2 - rounding
startY: root.height
wrapper: root.panels.dock
}
}
+25
View File
@@ -0,0 +1,25 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Variants {
model: Quickshell.screens
Scope {
id: scope
required property ShellScreen modelData
Exclusions {
bar: content.bar
screen: scope.modelData
}
Windows {
id: content
screen: scope.modelData
}
}
}
+1
View File
@@ -23,6 +23,7 @@ Canvas {
ctx.save(); ctx.save();
ctx.lineWidth = root.penWidth; ctx.lineWidth = root.penWidth;
ctx.strokeStyle = root.penColor; ctx.strokeStyle = root.penColor;
ctx.lineJoin = "round";
ctx.lineCap = "round"; ctx.lineCap = "round";
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y); ctx.moveTo(points[0].x, points[0].y);
+3 -3
View File
@@ -22,20 +22,20 @@ CustomMouseArea {
} }
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: root.visibilities.isDrawing ? parent : undefined enabled: z > 0
hoverEnabled: true hoverEnabled: true
visible: root.visibilities.isDrawing visible: root.visibilities.isDrawing
onPositionChanged: event => { onPositionChanged: event => {
const x = event.x; const x = event.x;
const y = event.y; const y = event.y;
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.drawing.points.push(Qt.point(x, y)); root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint(); root.drawing.requestPaint();
return;
} }
if (root.inLeftPanel(root.popout, x, y)) { if (!(event.buttons & Qt.LeftButton) && root.inLeftPanel(root.popout, x, y)) {
root.z = -2; root.z = -2;
root.panels.drawing.expanded = true; root.panels.drawing.expanded = true;
} }
+109 -57
View File
@@ -2,20 +2,21 @@ import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Helpers
import qs.Modules as BarPopouts import qs.Modules as BarPopouts
CustomMouseArea { Item {
id: root id: root
required property Item bar required property Item bar
property bool dashboardShortcutActive property bool dashboardShortcutActive
property point dragStart
required property Drawing drawing required property Drawing drawing
required property DrawingInput input required property DrawingInput input
property bool osdShortcutActive property bool osdShortcutActive
required property Panels panels required property Panels panels
required property BarPopouts.Wrapper popouts required property BarPopouts.Wrapper popouts
required property ShellScreen screen required property ShellScreen screen
property bool singleGestureTriggered: false
property bool utilitiesShortcutActive property bool utilitiesShortcutActive
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -52,78 +53,123 @@ CustomMouseArea {
} }
anchors.fill: parent anchors.fill: parent
cursorShape: (pressed && dragStart.y < bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
hoverEnabled: true
propagateComposedEvents: false
onContainsMouseChanged: { DragHandler {
if (!containsMouse) { id: singleHandler
if (!osdShortcutActive) {
visibilities.osd = false; cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
root.panels.osd.hovered = false; dragThreshold: 0
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything
maximumPointCount: 1
minimumPointCount: 1
target: null
onActiveChanged: {
if (!active)
root.singleGestureTriggered = false;
}
onCentroidChanged: {
const x = centroid.position.x;
const y = centroid.position.y;
const dragX = x - centroid.pressPosition.x;
const dragY = y - centroid.pressPosition.y;
if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && dragY < -200)
root.visibilities.launcher = true;
if (root.singleGestureTriggered)
return;
if (centroid.pressPosition.y < root.bar.implicitHeight) {
if (dragY > 20) {
root.visibilities.settings = true;
root.singleGestureTriggered = true;
} else if (dragY < -20) {
root.visibilities.settings = false;
root.singleGestureTriggered = true;
}
} }
if (!popouts.currentName.startsWith("traymenu")) { if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight)
popouts.hasCurrent = false; if (dragY < -10) {
root.visibilities.dock = true;
root.singleGestureTriggered = true;
}
if (centroid.pressPosition.x > root.screen.width - Config.barConfig.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -20) {
root.visibilities.sidebar = true;
root.singleGestureTriggered = true;
} }
if (Config.barConfig.autoHide) if (centroid.pressPosition.x >= root.screen.width - Config.barConfig.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) {
bar.isHovered = false; Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
root.singleGestureTriggered = true;
}
if (centroid.pressPosition.x <= Config.barConfig.border && dragX > 20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
root.singleGestureTriggered = true;
}
} }
} }
onPositionChanged: event => {
const x = event.x;
const y = event.y;
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) { HoverHandler {
// root.input.z = 2; id: hoverHandler
root.panels.drawing.expanded = false;
}
if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight) onHoveredChanged: {
bar.isHovered = true; if (!hovered) {
if (!root.osdShortcutActive) {
root.visibilities.osd = false;
root.panels.osd.hovered = false;
}
if (pressed && dragStart.y < bar.implicitHeight) { if (!root.popouts.currentName.startsWith("traymenu")) {
if (dragY > 20) root.popouts.hasCurrent = false;
visibilities.settings = true; }
else if (dragY < -20)
visibilities.settings = false;
}
if (Config.dock.hoverToReveal && pressed && dragStart.y > root.screen.height - root.bar.implicitHeight) if (Config.barConfig.autoHide)
if (dragY < -10) root.bar.isHovered = false;
visibilities.dock = true;
if (panels.sidebar.width === 0) {
const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) {
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width;
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (!osdShortcutActive) {
visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
osdShortcutActive = false;
root.panels.osd.hovered = true;
} }
} }
onPointChanged: {
const x = point.position.x;
const y = point.position.y;
if (Config.dock.enable && !Config.dock.hoverToReveal && !visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y)) if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
visibilities.dock = true; root.input.z = 2;
root.panels.drawing.expanded = false;
}
if (y < root.bar.implicitHeight) { if (!root.visibilities.bar && Config.barConfig.autoHide && y < root.bar.implicitHeight)
root.bar.checkPopout(x); root.bar.isHovered = true;
if (root.panels.sidebar.width === 0) {
const showOsd = root.inRightPanel(root.panels.osdWrapper, x, y);
if (showOsd) {
root.osdShortcutActive = false;
root.panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < root.width - root.panels.sidebar.width;
const showOsd = outOfSidebar && root.inRightPanel(root.panels.osdWrapper, x, y);
if (!root.osdShortcutActive) {
root.visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
root.osdShortcutActive = false;
root.panels.osd.hovered = true;
}
}
if (Config.dock.enable && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y))
root.visibilities.dock = true;
if (y < root.bar.implicitHeight)
root.bar.checkPopout(x);
} }
} }
onPressed: event => dragStart = Qt.point(event.x, event.y)
Connections { Connections {
function onDashboardChanged() { function onDashboardChanged() {
@@ -132,6 +178,8 @@ CustomMouseArea {
if (!inDashboardArea) { if (!inDashboardArea) {
root.dashboardShortcutActive = true; root.dashboardShortcutActive = true;
} }
if (root.panels.launcher.x + root.panels.launcher.width > root.panels.dashboardWrapper.x)
root.visibilities.launcher = false;
root.visibilities.settings = false; root.visibilities.settings = false;
root.visibilities.sidebar = false; root.visibilities.sidebar = false;
@@ -161,6 +209,10 @@ CustomMouseArea {
} }
if (root.visibilities.launcher) { if (root.visibilities.launcher) {
if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) {
root.visibilities.dashboard = false;
}
root.visibilities.dock = false; root.visibilities.dock = false;
root.visibilities.settings = false; root.visibilities.settings = false;
} }
+343 -344
View File
@@ -9,383 +9,382 @@ import Quickshell.Hyprland
import ZShell.Blobs import ZShell.Blobs
import qs.Daemons import qs.Daemons
import qs.Components import qs.Components
import qs.Modules
import qs.Modules.Bar import qs.Modules.Bar
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers
import qs.Drawers import qs.Drawers
Variants { CustomWindow {
model: Quickshell.screens id: root
Scope { readonly property alias bar: bar
id: scope readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2)
readonly property alias interactionWrapper: interactions
property var root: Quickshell.shellDir
required property var modelData WlrLayershell.exclusionMode: ExclusionMode.Ignore
// WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
color: "transparent"
contentItem.focus: true
mask: visibilities.isDrawing ? null : region
name: "Bar"
Exclusions { contentItem.Keys.onEscapePressed: {
bar: bar if (Config.barConfig.autoHide)
screen: scope.modelData visibilities.bar = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
}
onHasFullscreenChanged: {
visibilities.launcher = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
}
Region {
id: region
height: root.height - bar.implicitHeight - Config.barConfig.border
intersection: Intersection.Xor
regions: popoutRegions.instances
width: root.width - Config.barConfig.border * 2
x: Config.barConfig.border
y: bar.implicitHeight
}
anchors {
bottom: true
left: true
right: true
top: true
}
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
height: modelData.height
intersection: Intersection.Subtract
width: modelData.width
x: modelData.x + Config.barConfig.border
y: modelData.y + bar.implicitHeight
}
}
HyprlandFocusGrab {
id: focusGrab
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
windows: [root]
onCleared: {
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
visibilities.dock = false;
panels.popouts.hasCurrent = false;
}
}
PersistentProperties {
id: visibilities
property bool bar
property bool dashboard
property bool dock
property bool isDrawing
property bool launcher
property bool notif: NotifServer.popups.length > 0
property bool osd
property bool resources
property bool settings
property bool sidebar
Component.onCompleted: Visibilities.load(root.screen, this)
}
IpcHandler {
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
} }
CustomWindow { target: "visibilities"
id: win }
readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2) Binding {
property var root: Quickshell.shellDir property: "bar"
target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.barConfig.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.barConfig.autoHide
}
WlrLayershell.exclusionMode: ExclusionMode.Ignore Item {
WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None anchors.fill: parent
color: "transparent" layer.enabled: true
contentItem.focus: true opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1
mask: visibilities.isDrawing ? null : region
name: "Bar"
screen: scope.modelData
contentItem.Keys.onEscapePressed: { layer.effect: MultiEffect {
if (Config.barConfig.autoHide) blurMax: 32
visibilities.bar = false; shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1)
visibilities.sidebar = false; shadowEnabled: true
visibilities.dashboard = false; }
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
}
onHasFullscreenChanged: {
visibilities.launcher = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
}
Region { BlobGroup {
id: region id: blobGroup
height: win.height - bar.implicitHeight - Config.barConfig.border color: DynamicColors.palette.m3surface
intersection: Intersection.Xor smoothing: Config.barConfig.smoothing
regions: popoutRegions.instances
width: win.width - Config.barConfig.border * 2
x: Config.barConfig.border
y: bar.implicitHeight
}
anchors { Behavior on color {
bottom: true CAnim {
left: true
right: true
top: true
}
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
height: modelData.height
intersection: Intersection.Subtract
width: modelData.width
x: modelData.x + Config.barConfig.border
y: modelData.y + bar.implicitHeight
} }
} }
}
HyprlandFocusGrab { BlobInvertedRect {
id: focusGrab anchors.fill: parent
anchors.margins: -50
borderBottom: Config.barConfig.border - anchors.margins
borderLeft: Config.barConfig.border - anchors.margins
borderRight: Config.barConfig.border - anchors.margins
borderTop: bar.implicitHeight - anchors.margins
group: blobGroup
radius: Config.barConfig.rounding
}
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu")) PanelBg {
windows: [win] id: dashBg
onCleared: { property real extraHeight: 0.2
visibilities.launcher = false;
visibilities.sidebar = false; deformAmount: 0.06
visibilities.dashboard = false; implicitHeight: panels.dashboard.height * (1 + extraHeight)
visibilities.osd = false; implicitWidth: panels.dashboard.width
visibilities.settings = false; panel: panels.dashboardWrapper
visibilities.resources = false; radius: Appearance.rounding.normal
visibilities.dock = false; x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border
panels.popouts.hasCurrent = false; y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight
}
PanelBg {
id: launcherBg
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.launcher.height * (1 + extraHeight)
panel: panels.launcher
radius: Appearance.rounding.smallest + 5
y: panels.launcher.y + bar.implicitHeight
}
PanelBg {
id: sidebarBg
bottomLeftRadius: 0
deformAmount: 0.04
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
panel: panels.sidebar
}
PanelBg {
id: osdBg
deformAmount: 0.1
implicitHeight: panels.osd.height
implicitWidth: panels.osd.width
panel: panels.osdWrapper
radius: 20
x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
}
PanelBg {
id: notifsBg
panel: panels.notifications
radius: Appearance.rounding.normal
}
PanelBg {
id: utilsBg
deformAmount: panels.sidebar.visible ? (0.1) : (0.1)
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
panel: panels.utilities
topLeftRadius: 0
}
PanelBg {
id: popoutBg
property real extraHeight: panels.popouts.isDetached ? 0 : 0.2
deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1
implicitHeight: panels.popouts.height * (1 + extraHeight)
implicitWidth: panels.popouts.width
panel: panels.popoutsWrapper
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale
x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
Behavior on extraHeight {
Anim {
} }
} }
}
PersistentProperties { PanelBg {
id: visibilities id: resourcesBg
property bool bar deformAmount: 0.05
property bool dashboard implicitHeight: panels.resources.height
property bool dock implicitWidth: panels.resources.width
property bool isDrawing panel: panels.resourcesWrapper
property bool launcher radius: Appearance.rounding.normal
property bool notif: NotifServer.popups.length > 0 x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
property bool osd y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
property bool resources }
property bool settings
property bool sidebar
Component.onCompleted: Visibilities.load(scope.modelData, this) PanelBg {
id: settingsBg
property real extraHeight: 0.2
deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width
panel: panels.settings
radius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: Appearance.rounding.large + Appearance.padding.smaller
x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
}
PanelBg {
id: dockBg
deformAmount: 0.08
panel: panels.dock
radius: Appearance.rounding.normal
}
PanelBg {
id: drawingBg
deformAmount: 0.08
panel: panels.drawing
radius: Appearance.rounding.normal
}
}
Loader {
id: drawingLoader
active: visibilities.isDrawing
anchors.fill: parent
z: 2
sourceComponent: Drawing {
id: drawing
}
}
Loader {
id: inputLoader
active: visibilities.isDrawing
anchors.fill: parent
z: 2
sourceComponent: DrawingInput {
id: input
bar: bar
drawing: drawingLoader.item
panels: panels
popout: panels.drawing
visibilities: visibilities
}
}
Interactions {
id: interactions
anchors.fill: parent
bar: bar
drawing: drawingLoader.item
enabled: true
input: inputLoader.item
panels: panels
popouts: panels.popouts
screen: root.screen
visibilities: visibilities
z: 1
Panels {
id: panels
bar: bar
drawingItem: drawingLoader.item
screen: root.screen
visibilities: visibilities
dashboard.transform: Matrix4x4 {
matrix: dashBg.deformMatrix
} }
dock.transform: Matrix4x4 {
IpcHandler { matrix: dockBg.deformMatrix
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
}
target: "visibilities"
} }
launcher.transform: Matrix4x4 {
Binding { matrix: launcherBg.deformMatrix
property: "bar"
target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.barConfig.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.barConfig.autoHide
} }
notifications.transform: Matrix4x4 {
Item { matrix: notifsBg.deformMatrix
anchors.fill: parent
layer.enabled: true
opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1
layer.effect: MultiEffect {
blurMax: 32
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1)
shadowEnabled: true
}
BlobGroup {
id: blobGroup
color: DynamicColors.palette.m3surface
smoothing: Config.barConfig.smoothing
Behavior on color {
CAnim {
}
}
}
BlobInvertedRect {
anchors.fill: parent
anchors.margins: -50
borderBottom: Config.barConfig.border - anchors.margins
borderLeft: Config.barConfig.border - anchors.margins
borderRight: Config.barConfig.border - anchors.margins
borderTop: bar.implicitHeight - anchors.margins
group: blobGroup
radius: Config.barConfig.rounding
}
PanelBg {
id: dashBg
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.dashboard.height * (1 + extraHeight)
implicitWidth: panels.dashboard.width
panel: panels.dashboardWrapper
radius: Appearance.rounding.normal
x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight
}
PanelBg {
id: launcherBg
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.launcher.height * (1 + extraHeight)
panel: panels.launcher
radius: Appearance.rounding.smallest + 5
y: panels.launcher.y + bar.implicitHeight
}
PanelBg {
id: sidebarBg
bottomLeftRadius: 0
deformAmount: 0.04
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
panel: panels.sidebar
}
PanelBg {
id: osdBg
deformAmount: 0.1
implicitHeight: panels.osd.height
implicitWidth: panels.osd.width
panel: panels.osdWrapper
radius: 20
x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
}
PanelBg {
id: notifsBg
panel: panels.notifications
}
PanelBg {
id: utilsBg
deformAmount: panels.sidebar.visible ? (0.1) : (0.1)
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
panel: panels.utilities
topLeftRadius: 0
}
PanelBg {
id: popoutBg
property real extraHeight: panels.popouts.isDetached ? 0 : 0.2
deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1
implicitHeight: panels.popouts.height * (1 + extraHeight)
implicitWidth: panels.popouts.width
panel: panels.popoutsWrapper
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale
x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
Behavior on extraHeight {
Anim {
}
}
}
PanelBg {
id: resourcesBg
deformAmount: 0.05
implicitHeight: panels.resources.height
implicitWidth: panels.resources.width
panel: panels.resourcesWrapper
radius: Appearance.rounding.normal
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
}
PanelBg {
id: settingsBg
property real extraHeight: 0.2
deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width
panel: panels.settings
radius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: Appearance.rounding.large + Appearance.padding.smaller
x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
}
PanelBg {
id: dockBg
deformAmount: 0.08
panel: panels.dock
radius: Appearance.rounding.normal
}
PanelBg {
id: drawingBg
deformAmount: 0.08
panel: panels.drawing
radius: Appearance.rounding.normal
}
} }
osd.transform: Matrix4x4 {
Drawing { matrix: osdBg.deformMatrix
id: drawing
anchors.fill: parent
z: 2
} }
popouts.transform: Matrix4x4 {
DrawingInput { matrix: popoutBg.deformMatrix
id: input
bar: bar
drawing: drawing
panels: panels
popout: panels.drawing
visibilities: visibilities
z: 2
} }
resources.transform: Matrix4x4 {
Interactions { matrix: resourcesBg.deformMatrix
id: mouseArea
anchors.fill: parent
bar: bar
drawing: drawing
input: input
panels: panels
popouts: panels.popouts
screen: scope.modelData
visibilities: visibilities
z: 1
Panels {
id: panels
bar: bar
drawingItem: drawing
screen: scope.modelData
visibilities: visibilities
dashboard.transform: Matrix4x4 {
matrix: dashBg.deformMatrix
}
dock.transform: Matrix4x4 {
matrix: dockBg.deformMatrix
}
launcher.transform: Matrix4x4 {
matrix: launcherBg.deformMatrix
}
notifications.transform: Matrix4x4 {
matrix: notifsBg.deformMatrix
}
osd.transform: Matrix4x4 {
matrix: osdBg.deformMatrix
}
popouts.transform: Matrix4x4 {
matrix: popoutBg.deformMatrix
}
resources.transform: Matrix4x4 {
matrix: resourcesBg.deformMatrix
}
settings.transform: Matrix4x4 {
matrix: settingsBg.deformMatrix
}
sidebar.transform: Matrix4x4 {
matrix: sidebarBg.deformMatrix
}
utilities.transform: Matrix4x4 {
matrix: utilsBg.deformMatrix
}
}
BarLoader {
id: bar
anchors.left: parent.left
anchors.right: parent.right
popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper
screen: scope.modelData
visibilities: visibilities
}
} }
settings.transform: Matrix4x4 {
matrix: settingsBg.deformMatrix
}
sidebar.transform: Matrix4x4 {
matrix: sidebarBg.deformMatrix
}
utilities.transform: Matrix4x4 {
matrix: utilsBg.deformMatrix
}
}
BarLoader {
id: bar
anchors.left: parent.left
anchors.right: parent.right
popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper
screen: root.screen
visibilities: visibilities
} }
} }
+32
View File
@@ -0,0 +1,32 @@
pragma Singleton
import Quickshell
import Quickshell.Services.UPower
import qs.Config
Singleton {
id: root
readonly property var colors: {
if (deviceState === UPowerDeviceState.Charging || deviceState === UPowerDeviceState.FullyCharged)
return {
fg: DynamicColors.swapRG(DynamicColors.palette.m3error),
bg: DynamicColors.swapRG(DynamicColors.palette.m3onError)
};
else if (currentPerc <= 0.2)
return {
fg: DynamicColors.palette.m3error,
bg: DynamicColors.palette.m3onError
};
else
return {
fg: DynamicColors.palette.m3onSurface,
bg: DynamicColors.palette.m3surface
};
}
readonly property real currentPerc: UPower.displayDevice.percentage
readonly property var deviceState: UPower.displayDevice.state
readonly property bool isLaptop: UPower.displayDevice.isLaptopBattery
readonly property bool onBattery: UPower.onBattery
readonly property bool ready: UPower.displayDevice.ready
}
+1 -1
View File
@@ -36,7 +36,7 @@ Singleton {
PersistentProperties { PersistentProperties {
id: props id: props
property bool enabled: Hypr.options["animations:enabled"] === 0 property bool enabled: Hypr.options.animations.enabled === 0
reloadableId: "gamemode" reloadableId: "gamemode"
} }
-1
View File
@@ -158,6 +158,5 @@ Singleton {
HyprExtras { HyprExtras {
id: extras id: extras
} }
} }
+11 -59
View File
@@ -2,12 +2,13 @@ pragma Singleton
import Quickshell import Quickshell
import QtQuick import QtQuick
import ZShell.Services
import qs.Config import qs.Config
Singleton { Singleton {
id: root id: root
property bool enabled readonly property bool enabled: service.enabled
readonly property int end: Config.general.color.scheduleHyprsunsetEnd readonly property int end: Config.general.color.scheduleHyprsunsetEnd
property bool manualToggle: false property bool manualToggle: false
readonly property int start: Config.general.color.scheduleHyprsunsetStart readonly property int start: Config.general.color.scheduleHyprsunsetStart
@@ -17,69 +18,20 @@ Singleton {
if (!Config.general.color.scheduleHyprsunset) if (!Config.general.color.scheduleHyprsunset)
return; return;
var now = new Date(); service.apply();
if (now.getHours() >= root.start || now.getHours() < root.end) {
root.startNightLight(root.temp);
} else {
root.stopNightLight();
}
}
function startNightLight(temp: int): void {
Quickshell.execDetached(["hyprctl", "hyprsunset", "temperature", `${temp}`]);
root.enabled = true;
}
function stopNightLight(): void {
Quickshell.execDetached(["hyprctl", "hyprsunset", "identity"]);
root.enabled = false;
} }
function toggleNightLight(): void { function toggleNightLight(): void {
if (enabled) service.manualToggle = true;
stopNightLight(); service.toggle();
else
startNightLight(temp);
} }
onManualToggleChanged: { HyprsunsetManager {
if (root.manualToggle) id: service
manualTimer.start();
}
Timer { activeAuto: Config.general.color.scheduleHyprsunset
id: manualTimer endTime: root.end
startTime: root.start
interval: 60000 * 60 temp: root.temp
repeat: false
running: false
onTriggered: {
root.manualToggle = false;
}
}
Timer {
interval: 5000
repeat: true
running: true
triggeredOnStart: true
onTriggered: {
if (!Config.general.color.scheduleHyprsunset)
return;
if (root.manualToggle)
return;
var now = new Date();
if (now.getHours() >= root.start || now.getHours() < root.end) {
if (!root.enabled)
root.startNightLight(root.temp);
} else {
if (root.enabled)
root.stopNightLight();
}
}
} }
} }
+17 -3
View File
@@ -26,16 +26,23 @@ MouseArea {
return (bc.pinned - ac.pinned) || ((bc.fullscreen !== 0) - (ac.fullscreen !== 0)) || (bc.floating - ac.floating); return (bc.pinned - ac.pinned) || ((bc.fullscreen !== 0) - (ac.fullscreen !== 0)) || (bc.floating - ac.floating);
}); });
} }
readonly property int cornerRadius: Hypr.options.decoration.rounding
property real ex: screen.width property real ex: screen.width
property real ey: screen.height property real ey: screen.height
required property LazyLoader loader required property LazyLoader loader
property bool onClient property bool onClient
property real realBorderWidth: onClient ? (Hypr.options["general:border_size"] ?? 1) : 2 property real realBorderWidth: onClient ? (Hypr.options.general.border_size ?? 1) : 2
property real realRounding: onClient ? (Hypr.options["decoration:rounding"] ?? 0) : 0 property real realRounding: onClient ? (Hypr.options.decoration.rounding ?? 0) : 0
property real rsx: Math.min(sx, ex) property real rsx: Math.min(sx, ex)
property real rsy: Math.min(sy, ey) property real rsy: Math.min(sy, ey)
readonly property real scaleRatio: Hypr.monitorFor(screen).scale
required property ShellScreen screen required property ShellScreen screen
property real sh: Math.abs(sy - ey) property real sh: Math.abs(sy - ey)
readonly property color shadowColor: Hypr.options.decoration.shadow.color
readonly property bool shadowEnabled: Hypr.options.decoration.shadow.enabled
readonly property var shadowOffset: Hypr.options.decoration.shadow.offset
readonly property int shadowRange: Hypr.options.decoration.shadow.range
readonly property int shadowRenderPower: Hypr.options.decoration.shadow.render_power
property real ssx property real ssx
property real ssy property real ssy
property real sw: Math.abs(sx - ex) property real sw: Math.abs(sx - ex)
@@ -66,7 +73,14 @@ MouseArea {
function save(): void { function save(): void {
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`); const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--image"] : ["swappy", "-f"]; const rounding = root.cornerRadius > 0;
const shadow_blur = root.shadowRange / root.shadowRenderPower;
const r = Math.floor(root.shadowColor.r * 256);
const g = Math.floor(root.shadowColor.g * 256);
const b = Math.floor(root.shadowColor.b * 256);
const a = Math.floor(root.shadowColor.a * 256);
const args = Config.screenshot.mode === "auto" ? ["--rounding", `${rounding}`, "--radius", root.cornerRadius, "--shadow", root.shadowEnabled, "--shadow-blur", `${shadow_blur}`, "--shadow-color", `${r},${g},${b},${a}`, "--shadow-offset-x", root.shadowOffset[0], "--shadow-offset-y", root.shadowOffset[1]] : [];
const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--scale", root.scaleRatio, ...args, "--image"] : ["swappy", "-f"];
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached([...cmd, path])); ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached([...cmd, path]));
closeAnim.start(); closeAnim.start();
} }
-1
View File
@@ -6,7 +6,6 @@ import Quickshell
Singleton { Singleton {
property var extraOpts: ({}) property var extraOpts: ({})
readonly property list<var> fuzzyPrepped: useFuzzy ? list.map(e => { readonly property list<var> fuzzyPrepped: useFuzzy ? list.map(e => {
console.log(useFuzzy);
const obj = { const obj = {
_item: e _item: e
}; };
+1
View File
@@ -17,6 +17,7 @@ Singleton {
property var disks: [] property var disks: []
property real gpuMemTotal: 0 property real gpuMemTotal: 0
property real gpuMemUsed property real gpuMemUsed
property string gpuName
property real gpuPerc property real gpuPerc
property real gpuTemp property real gpuTemp
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
-26
View File
@@ -1,26 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Services.UPower
Singleton {
id: root
readonly property real batteryPercent: UPower.displayDevice.percentage
readonly property list<UPowerDevice> devices: UPower.devices.values
readonly property UPowerDevice displayDevice: UPower.displayDevice
readonly property bool onBattery: UPower.onBattery
// property bool toastShown
//
// Connections {
// target: UPower
//
// function onPercentageChanged(): {
// if (root.batteryPercent >= 0.2 && toastShown)
// return;
//
// root.toastShown = true;
// Toaster.toast(qsTr("Battery "))
// }
// }
}
+1 -3
View File
@@ -53,14 +53,12 @@ Searcher {
}; };
root.crops = updated; root.crops = updated;
monitorCrops.writeAdapter();
monitorCrops.reload();
} }
function setWallpaper(path: string): void { function setWallpaper(path: string): void {
actualCurrent = path; actualCurrent = path;
WallpaperPath.currentWallpaperPath = path; WallpaperPath.currentWallpaperPath = path;
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 0, 0), Qt.rect(0, 0, 0, 0), 1.0)); Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), Qt.rect(0, 0, 0, 0), 1.0));
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]); Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
if (Config.general.color.schemeGeneration) if (Config.general.color.schemeGeneration)
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]);
-68
View File
@@ -1,68 +0,0 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
property real ibr: invertBottomRounding ? -1 : 1
required property bool invertBottomRounding
property real rounding: Appearance.rounding.smallest
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY - root.roundingY * root.ibr
}
PathArc {
direction: root.invertBottomRounding ? PathArc.Clockwise : PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY * root.ibr
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
color: root.visibilities.dashboard ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface color: root.visibilities.dashboard ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
font: Appearance.font.family.mono font: Appearance.font.family.mono // qmllint disable incompatible-type
text: Time.dateStr text: Time.dateStr
Behavior on color { Behavior on color {
-65
View File
@@ -1,65 +0,0 @@
import qs.Components
import qs.Config
import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ Item {
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open implicitWidth: content.implicitWidth || 854
opacity: 1 - offsetScale opacity: 1 - offsetScale
visible: offsetScale < 1 visible: offsetScale < 1
-66
View File
@@ -1,66 +0,0 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
}
+5 -6
View File
@@ -9,18 +9,17 @@ Item {
id: root id: root
property int contentHeight property int contentHeight
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
required property PersistentProperties visibilities required property PersistentProperties visibilities
readonly property bool shouldBeActive: visibilities.dock
property real offsetScale: shouldBeActive ? 0 : 1
visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 400 implicitWidth: content.implicitWidth || 400
opacity: 1 - offsetScale opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
@@ -32,10 +31,10 @@ Item {
Loader { Loader {
id: content id: content
active: root.shouldBeActive || root.visible
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
asynchronous: true
active: root.shouldBeActive || root.visible
sourceComponent: Content { sourceComponent: Content {
panels: root.panels panels: root.panels
-66
View File
@@ -1,66 +0,0 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: root.wrapper.width - root.roundingX * 2
relativeY: 0
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 2
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: -(root.wrapper.width - root.roundingX * 2)
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
}
+4 -4
View File
@@ -44,10 +44,10 @@ Item {
Loader { Loader {
id: icon id: icon
active: Qt.binding(() => root.shouldBeActive || root.visible) active: root.shouldBeActive || root.visible
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: content.contentItem.height asynchronous: true
opacity: root.expanded ? 0 : 1 opacity: root.expanded ? 0 : 1
Behavior on opacity { Behavior on opacity {
@@ -63,8 +63,10 @@ Item {
Loader { Loader {
id: content id: content
active: root.shouldBeActive || root.visible
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
asynchronous: true
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
Behavior on opacity { Behavior on opacity {
@@ -75,7 +77,5 @@ Item {
drawing: root.drawing drawing: root.drawing
visibilities: root.visibilities visibilities: root.visibilities
} }
Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible)
} }
} }
-66
View File
@@ -1,66 +0,0 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.smallest + 5
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
}
+16 -60
View File
@@ -4,6 +4,7 @@ import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Modules.Launcher.Services
Item { Item {
id: root id: root
@@ -11,34 +12,24 @@ Item {
property int contentHeight property int contentHeight
readonly property real maxHeight: { readonly property real maxHeight: {
let max = screen.height - Appearance.spacing.large * 2; let max = screen.height - Appearance.spacing.large * 2;
if (visibilities.resources) if (visibilities.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x)
max -= panels.resources.nonAnimHeight; max -= panels.resources.nonAnimHeight;
if (visibilities.dashboard && panels.dashboard.x < root.x + root.implicitWidth) if (panels.popouts.hasCurrent)
max -= panels.dashboard.nonAnimHeight; if (panels.popouts.current.x + panels.popouts.current.width > root.x && panels.popouts.current.x < root.x + root.width)
if (panels.popouts.currentName.startsWith("updates")) max -= panels.popouts.nonAnimHeight;
max -= panels.popouts.nonAnimHeight;
return max; return max;
} }
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels required property var panels
required property ShellScreen screen required property ShellScreen screen
required property PersistentProperties visibilities
readonly property bool shouldBeActive: visibilities.launcher readonly property bool shouldBeActive: visibilities.launcher
property real offsetScale: shouldBeActive ? 0 : 1 required property PersistentProperties visibilities
onShouldBeActiveChanged: {
if (shouldBeActive) {
implicitHeight = Qt.binding(() => content.implicitHeight);
timer.stop();
} else {
implicitHeight = implicitHeight;
}
}
visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 400 implicitWidth: content.implicitWidth || 400
opacity: 1 - offsetScale opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
@@ -47,61 +38,26 @@ Item {
} }
} }
onMaxHeightChanged: timer.start() Component.onCompleted: Qt.callLater(() => Apps)
onShouldBeActiveChanged: {
Connections { if (shouldBeActive)
function onEnabledChanged(): void { implicitHeight = Qt.binding(() => content.implicitHeight);
timer.start(); else
} implicitHeight = implicitHeight;
function onMaxShownChanged(): void {
timer.start();
}
target: Config.launcher
}
Connections {
function onValuesChanged(): void {
if (DesktopEntries.applications.values.length < Config.launcher.maxAppsShown)
timer.start();
}
target: DesktopEntries.applications
}
Timer {
id: timer
interval: Appearance.anim.durations.small
onRunningChanged: {
if (running && !root.shouldBeActive) {
content.visible = false;
content.active = true;
} else {
root.contentHeight = Math.min(root.maxHeight, content.implicitHeight);
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
}
}
} }
Loader { Loader {
id: content id: content
active: false active: root.shouldBeActive || root.visible
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
asynchronous: true
sourceComponent: Content { sourceComponent: Content {
maxHeight: root.maxHeight maxHeight: root.maxHeight
panels: root.panels panels: root.panels
visibilities: root.visibilities visibilities: root.visibilities
Component.onCompleted: root.contentHeight = implicitHeight
} }
Component.onCompleted: timer.start()
} }
} }
+5
View File
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import ZShell.Internal
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers
@@ -29,6 +30,10 @@ Scope {
Quickshell.execDetached(action); Quickshell.execDetached(action);
} }
LidWatcher {
onAboutToSleep: root.lock.lock.locked = true
}
Variants { Variants {
model: Config.general.idle.timeouts model: Config.general.idle.timeouts
-59
View File
@@ -1,59 +0,0 @@
import qs.Components
import qs.Config
import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: 8
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property var sidebar
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathLine {
relativeX: -(root.wrapper.width + root.rounding)
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.sidebar.notifsRoundingX
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.sidebar.notifsRoundingX
relativeY: root.roundingY
}
PathLine {
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.notifsRoundingX : root.wrapper.width
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: root.rounding
relativeX: root.rounding
relativeY: root.rounding
}
}
+3 -45
View File
@@ -8,7 +8,7 @@ import QtQuick
Item { Item {
id: root id: root
readonly property int padding: 6 readonly property int padding: Appearance.padding.smaller
required property Item panels required property Item panels
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -54,7 +54,7 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.padding anchors.margins: root.padding
color: "transparent" color: "transparent"
radius: Appearance.rounding.smallest / 2 radius: Appearance.rounding.normal - root.padding
CustomListView { CustomListView {
id: list id: list
@@ -72,7 +72,7 @@ Item {
required property NotifServer.Notif modelData required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notif.nonAnimHeight readonly property alias nonAnimHeight: notif.nonAnimHeight
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 8) implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : Appearance.spacing.small)
implicitWidth: notif.implicitWidth implicitWidth: notif.implicitWidth
ListView.onRemove: removeAnim.start() ListView.onRemove: removeAnim.start()
@@ -151,48 +151,6 @@ Item {
property: "y" property: "y"
} }
} }
ExtraIndicator {
anchors.top: parent.top
extra: {
const count = list.count;
if (count === 0)
return 0;
const scrollY = list.contentY;
let height = 0;
for (let i = 0; i < count; i++) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
if (height - 8 >= scrollY)
return i;
}
return count;
}
}
ExtraIndicator {
anchors.bottom: parent.bottom
extra: {
const count = list.count;
if (count === 0)
return 0;
const scrollY = list.contentHeight - (list.contentY + list.height);
let height = 0;
for (let i = count - 1; i >= 0; i--) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
if (height - 8 >= scrollY)
return count - i - 1;
}
return 0;
}
}
} }
} }
@@ -1,54 +0,0 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real notifsRoundingX: panels.notifications.height > 0 && notifsWidthDiff < rounding * 2 ? notifsWidthDiff / 2 : rounding
readonly property real notifsWidthDiff: panels.notifications.width - wrapper.width
required property var panels
readonly property real rounding: Config.barConfig.rounding
readonly property real utilsRoundingX: utilsWidthDiff < rounding * 2 ? utilsWidthDiff / 2 : rounding
readonly property real utilsWidthDiff: panels.utilities.width - wrapper.width
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathLine {
relativeX: -root.wrapper.width - root.notifsRoundingX
relativeY: 0
}
PathArc {
radiusX: root.notifsRoundingX
radiusY: root.rounding
relativeX: root.notifsRoundingX
relativeY: root.rounding
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 2
}
PathArc {
radiusX: root.utilsRoundingX
radiusY: root.rounding
relativeX: -root.utilsRoundingX
relativeY: root.rounding
}
PathLine {
relativeX: root.wrapper.width + root.utilsRoundingX
relativeY: 0
}
}
-66
View File
@@ -1,66 +0,0 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real rounding: 10
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: -(root.wrapper.width - root.roundingX * 3)
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding * 2, root.wrapper.width)
radiusY: root.rounding * 2
relativeX: -root.roundingX * 2
relativeY: root.rounding * 2
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 4
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding * 2, root.wrapper.width)
radiusY: root.rounding * 2
relativeX: root.roundingX * 2
relativeY: root.rounding * 2
}
PathLine {
relativeX: root.wrapper.width - root.roundingX * 3
relativeY: 0
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
}
-65
View File
@@ -1,65 +0,0 @@
import qs.Components
import qs.Config
import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+2 -3
View File
@@ -8,7 +8,7 @@ import qs.Config
Item { Item {
id: root id: root
readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0 readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
readonly property bool shouldBeActive: root.visibilities.resources readonly property bool shouldBeActive: root.visibilities.resources
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -31,8 +31,7 @@ Item {
id: content id: content
active: root.shouldBeActive || root.visible active: root.shouldBeActive || root.visible
anchors.bottom: parent.bottom anchors.centerIn: parent
anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: Content { sourceComponent: Content {
padding: Appearance.padding.normal padding: Appearance.padding.normal
-66
View File
@@ -1,66 +0,0 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.large
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.roundingY, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+15
View File
@@ -56,6 +56,21 @@ SettingsPage {
} }
} }
SettingsSection {
sectionId: "Tray"
SettingsHeader {
name: "System tray"
}
SettingSpinBox {
min: 16
name: "Tray icon size"
object: Config.barConfig.tray
setting: "trayIconSize"
}
}
SettingsSection { SettingsSection {
sectionId: "Popouts" sectionId: "Popouts"
+3 -4
View File
@@ -62,6 +62,7 @@ SettingsPage {
SettingsSection { SettingsSection {
sectionId: "Color" sectionId: "Color"
z: 1
SettingsHeader { SettingsHeader {
name: "Color" name: "Color"
@@ -69,7 +70,7 @@ SettingsPage {
CustomSplitButtonRow { CustomSplitButtonRow {
active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1] active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1]
buttonAlias.disabled: !Config.general.color.schemeGeneration enabled: Config.general.color.schemeGeneration
label: qsTr("Scheme mode") label: qsTr("Scheme mode")
menuItems: [ menuItems: [
@@ -103,9 +104,8 @@ SettingsPage {
id: schemeType id: schemeType
active: root.schemeTypeItem(menuItems, Config.colors.schemeType) active: root.schemeTypeItem(menuItems, Config.colors.schemeType)
buttonAlias.disabled: !Config.general.color.schemeGeneration enabled: Config.general.color.schemeGeneration
label: qsTr("Scheme type") label: qsTr("Scheme type")
z: 2
menuItems: [ menuItems: [
MenuItem { MenuItem {
@@ -250,7 +250,6 @@ SettingsPage {
SettingsSection { SettingsSection {
sectionId: "Default Apps" sectionId: "Default Apps"
z: -1
SettingsHeader { SettingsHeader {
name: "Default Apps" name: "Default Apps"
@@ -1,5 +1,6 @@
import qs.Modules.Settings.Categories.Lockscreen import qs.Modules.Settings.Categories.Lockscreen
import qs.Modules.Settings.Controls import qs.Modules.Settings.Controls
import qs.Helpers
import qs.Config import qs.Config
SettingsPage { SettingsPage {
+56 -36
View File
@@ -20,7 +20,8 @@ SettingsPage {
} }
CustomSplitButtonRow { CustomSplitButtonRow {
// active: true active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1]
enabled: Config.screenshot.enable_pp
label: qsTr("Effects mode") label: qsTr("Effects mode")
menuItems: [ menuItems: [
@@ -43,88 +44,107 @@ SettingsPage {
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
}
SettingSwitch {
enabled: Config.screenshot.enable_pp
name: "Enable rounded corners"
object: Config.screenshot
setting: "rounding"
shouldBeActive: Config.screenshot.mode === "manual"
}
Separator {
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.rounding
} }
SettingSpinBox { SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0 min: 0
name: "Corner radius" name: "Corner radius"
object: Config.screenshot object: Config.screenshot
setting: "corner_radius" setting: "radius"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.rounding
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
SettingSwitch { SettingSwitch {
name: "Enable drop shadow" enabled: Config.screenshot.enable_pp
name: "Enable shadow"
object: Config.screenshot object: Config.screenshot
setting: "drop_shadow" setting: "shadow"
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual"
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
}
SettingSwitch {
name: "Enable rounded corners"
object: Config.screenshot
setting: "rounded_corners"
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
} }
SettingSpinBox { SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0 min: 0
name: "Shadow blur radius" name: "Shadow blur amount"
object: Config.screenshot object: Config.screenshot
setting: "shadow_blur_radius" setting: "shadow_blur"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
} }
SettingSwitch { // SettingSwitch {
name: "Shadow color broken atm" // name: "Shadow color broken atm"
object: Config.Screenshot // object: Config.Screenshot
setting: "shadow_color" // setting: "shadow_color"
visible: Config.screenshot.mode === "manual" // shouldBeActive: Config.screenshot.mode === "manual"
} // }
//
// Separator {
// shouldBeActive: Config.screenshot.mode === "manual"
// }
Separator { // SettingSpinBox {
visible: Config.screenshot.mode === "manual" // min: 1
} // name: "Shadow passes"
// object: Config.screenshot
// setting: "shadow_blur_passes"
// shouldBeActive: Config.screenshot.mode === "manual"
// step: 1
// }
//
// Separator {
// shouldBeActive: Config.screenshot.mode === "manual"
// }
SettingSpinBox { SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0 min: 0
name: "Shadow offset X" name: "Shadow offset X"
object: Config.screenshot object: Config.screenshot
setting: "shadow_offset_x" setting: "shadow_offset_x"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
visible: Config.screenshot.mode === "manual" shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
} }
SettingSpinBox { SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0 min: 0
name: "Shadow offset Y" name: "Shadow offset Y"
object: Config.screenshot object: Config.screenshot
setting: "shadow_offset_y" setting: "shadow_offset_y"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
} }
} }
+1 -1
View File
@@ -134,7 +134,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: searchBar.bottom anchors.top: searchBar.bottom
anchors.topMargin: Appearance.spacing.smaller anchors.topMargin: Appearance.spacing.smaller
color: DynamicColors.tPalette.m3surfaceContainerLowest color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
StackView { StackView {
+100 -63
View File
@@ -80,12 +80,28 @@ Item {
required property ShellScreen modelData required property ShellScreen modelData
function applyCrop(): void { function applyCrop(): void {
const croprect = cropRect.mapToItem(scaledImg, 0, 0, cropRect.width, cropRect.height); if (!cropRectLoader.item)
const upscaledRect = Qt.rect((croprect.x - cropRect.imageX) / scaledImg.paintedWidth, (croprect.y - cropRect.imageY) / scaledImg.paintedHeight, croprect.width / scaledImg.paintedWidth, croprect.height / scaledImg.paintedHeight); return;
Wallpapers.setCrop(delegate.modelData.name, upscaledRect, croprect, cropRect.zoom); const cropRect = cropRectLoader.item;
// We need to calculate the exact percentage coordinates that map perfectly
// to our C++ backend, regardless of current display scaling
const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth;
const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight;
const cropWidthPercent = cropRect.width / scaledImg.paintedWidth;
const cropHeightPercent = cropRect.height / scaledImg.paintedHeight;
const finalRect = Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent);
// We just pass the percentages directly to the backend
Wallpapers.setCrop(delegate.modelData.name, finalRect, finalRect, cropRect.zoom);
} }
function zoomClipRect(zoom: real): void { function zoomClipRect(zoom: real): void {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let oldCenterX = cropRect.x + cropRect.width * 0.5; let oldCenterX = cropRect.x + cropRect.width * 0.5;
let oldCenterY = cropRect.y + cropRect.height * 0.5; let oldCenterY = cropRect.y + cropRect.height * 0.5;
@@ -125,13 +141,14 @@ Item {
id: zoomSlider id: zoomSlider
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 10 Layout.preferredHeight: Appearance.padding.larger * 3
from: 1.0 from: 1.0
implicitHeight: Appearance.padding.larger * 3
to: 5.0 to: 5.0
value: cropRect.zoom value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0
onMoved: { onInteraction: value => {
delegate.zoomClipRect(value); delegate.zoomClipRect(1 + (value * 4));
wrapper.changesMade = true; wrapper.changesMade = true;
} }
} }
@@ -156,15 +173,20 @@ Item {
sourceSize.width: parent.width sourceSize.width: parent.width
onPaintedWidthChanged: { onPaintedWidthChanged: {
if (paintedWidth > 0) { if (paintedWidth > 0 && cropRectLoader.item) {
scaledImg.displayData = Wallpapers.getCrop(delegate.modelData.name); cropRectLoader.item.restoreFromData();
cropRect.zoom = Wallpapers.getCrop(delegate.modelData.name).zoom; }
cropRect.restoreFromData(); }
onSourceChanged: {
if (cropRectLoader.item) {
cropRectLoader.item.restoreFromData();
}
}
onStatusChanged: {
if (scaledImg.status == Image.Ready && cropRectLoader.item) {
cropRectLoader.item.restoreFromData();
} }
} }
onSourceChanged: cropRect.clampToBounds()
onStatusChanged: if (scaledImg.status == Image.Ready)
cropRect.clampToBounds()
CustomText { CustomText {
id: monitorId id: monitorId
@@ -177,72 +199,87 @@ Item {
text: delegate.modelData.name text: delegate.modelData.name
} }
CustomRect { Loader {
id: cropRect id: cropRectLoader
property real aspectRatio: delegate.modelData.width / delegate.modelData.height active: scaledImg.paintedWidth > 0 && scaledImg.status == Image.Ready
readonly property real baseHeight: baseWidth / aspectRatio
readonly property real baseWidth: {
let fittedHeight = scaledImg.paintedHeight;
let fittedWidth = fittedHeight * aspectRatio;
if (fittedWidth > scaledImg.paintedWidth) { sourceComponent: Component {
fittedWidth = scaledImg.paintedWidth; CustomRect {
fittedHeight = fittedWidth / aspectRatio; id: cropRect
}
return fittedWidth; property real aspectRatio: delegate.modelData.width / delegate.modelData.height
} readonly property real baseHeight: baseWidth / aspectRatio
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2 readonly property real baseWidth: {
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2 let fittedHeight = scaledImg.paintedHeight;
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight let fittedWidth = fittedHeight * aspectRatio;
property real zoom: scaledImg.displayData.zoom
function centerInImage() { if (fittedWidth > scaledImg.paintedWidth) {
x = imageX + (scaledImg.paintedWidth - width) / 2; fittedWidth = scaledImg.paintedWidth;
y = imageY + (scaledImg.paintedHeight - height) / 2; fittedHeight = fittedWidth / aspectRatio;
} }
function clampToBounds() { return fittedWidth;
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width)); }
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
property real zoom: 1.0
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height)); function centerInImage() {
} x = imageX + (scaledImg.paintedWidth - width) / 2;
y = imageY + (scaledImg.paintedHeight - height) / 2;
}
function restoreFromData() { function clampToBounds() {
let data = scaledImg.displayData; x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
if (data && data.scaledX !== 0 || data.scaledY !== 0 || data.scaledWidth !== 0 || data.scaledHeight !== 0) { y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
x = data.scaledX; }
y = data.scaledY;
clampToBounds(); function restoreFromData() {
} else { let data = Wallpapers.getCrop(delegate.modelData.name);
zoom = 1.0;
centerInImage(); if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) {
zoom = data.zoom > 0 ? data.zoom : 1.0;
x = imageX + (data.x * scaledImg.paintedWidth);
y = imageY + (data.y * scaledImg.paintedHeight);
clampToBounds();
} else {
zoom = 1.0;
centerInImage();
}
}
border.color: DynamicColors.palette.m3primary
border.width: 2
height: baseHeight / zoom
opacity: 1
width: baseWidth / zoom
Behavior on opacity {
Anim {
}
}
Component.onCompleted: {
restoreFromData();
}
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
} }
} }
border.color: DynamicColors.palette.m3primary
border.width: 2
height: baseHeight / zoom
opacity: 1
width: baseWidth / zoom
Behavior on opacity {
Anim {
}
}
Component.onCompleted: clampToBounds()
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
} }
MouseArea { MouseArea {
id: mouse id: mouse
function updateCrop(mouseX, mouseY) { function updateCrop(mouseX, mouseY) {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let nx = mouseX - cropRect.width * 0.5; let nx = mouseX - cropRect.width * 0.5;
let ny = mouseY - cropRect.height * 0.5; let ny = mouseY - cropRect.height * 0.5;
+1 -2
View File
@@ -32,10 +32,9 @@ Item {
Loader { Loader {
id: content id: content
active: true active: root.shouldBeActive || root.visible
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: true
sourceComponent: Content { sourceComponent: Content {
screen: root.screen screen: root.screen
+154 -134
View File
@@ -19,7 +19,7 @@ Item {
required property var wrapper required property var wrapper
implicitHeight: vol.implicitHeight + Appearance.padding.small * 2 implicitHeight: vol.implicitHeight + Appearance.padding.small * 2
implicitWidth: 400 + Appearance.padding.small * 2 implicitWidth: 600 + Appearance.padding.small * 2
CustomRect { CustomRect {
anchors.left: parent.left anchors.left: parent.left
@@ -52,25 +52,25 @@ Item {
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2 Layout.preferredHeight: 65 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding radius: root.rounding
RowLayout { Item {
id: outputVolume id: sinkIcon
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Appearance.spacing.smaller anchors.leftMargin: Appearance.padding.larger
anchors.right: parent.right anchors.top: parent.top
anchors.verticalCenter: parent.verticalCenter implicitWidth: childrenRect.width
spacing: 15
CustomRect { CustomRect {
Layout.alignment: Qt.AlignVCenter anchors.centerIn: parent
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 54
implicitWidth: 54
radius: Appearance.rounding.full radius: Appearance.rounding.full
MaterialIcon { MaterialIcon {
@@ -78,7 +78,7 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
animate: true animate: true
color: Audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary color: Audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary
font.pointSize: 22 font.pointSize: Appearance.font.size.extraLarge
text: Audio.muted ? "volume_off" : "volume_up" text: Audio.muted ? "volume_off" : "volume_up"
} }
@@ -92,45 +92,53 @@ Item {
} }
} }
} }
}
ColumnLayout { ColumnLayout {
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.padding.smallest
anchors.left: sinkIcon.right
anchors.leftMargin: Appearance.spacing.larger
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.large
anchors.top: parent.top
anchors.topMargin: Appearance.padding.smaller
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
RowLayout { CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true Layout.fillWidth: true
text: "Output Volume"
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true
text: "Output Volume"
}
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
font.bold: true
text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`)
}
} }
CustomMouseArea { CustomText {
Layout.bottomMargin: 5 Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.fillWidth: true font.bold: true
Layout.preferredHeight: 10 text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`)
}
}
CustomSlider { CustomMouseArea {
anchors.left: parent.left Layout.bottomMargin: Appearance.padding.normal
anchors.right: parent.right Layout.fillWidth: true
color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary Layout.preferredHeight: Appearance.padding.larger * 3
implicitHeight: 10
value: Audio.volume
Behavior on value { CustomSlider {
Anim { anchors.left: parent.left
} anchors.right: parent.right
fgColor: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: parent.height
value: Audio.volume
Behavior on value {
Anim {
} }
onMoved: Audio.setVolume(value)
} }
onInteraction: value => Audio.setVolume(value)
} }
} }
} }
@@ -138,7 +146,7 @@ Item {
CustomClippingRect { CustomClippingRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2 Layout.preferredHeight: 65 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding radius: root.rounding
@@ -165,20 +173,20 @@ Item {
} }
} }
RowLayout { Item {
id: inputVolume id: sourceIcon
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Appearance.spacing.smaller anchors.leftMargin: Appearance.padding.larger
anchors.right: parent.right anchors.top: parent.top
anchors.verticalCenter: parent.verticalCenter implicitWidth: childrenRect.width
spacing: 15
CustomRect { CustomRect {
Layout.alignment: Qt.AlignVCenter anchors.centerIn: parent
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 54
implicitWidth: 54
radius: Appearance.rounding.full radius: Appearance.rounding.full
MaterialIcon { MaterialIcon {
@@ -186,7 +194,7 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
animate: true animate: true
color: Audio.sourceMuted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary color: Audio.sourceMuted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary
font.pointSize: 22 font.pointSize: Appearance.font.size.extraLarge
text: Audio.sourceMuted ? "mic_off" : "mic" text: Audio.sourceMuted ? "mic_off" : "mic"
} }
@@ -200,46 +208,53 @@ Item {
} }
} }
} }
}
ColumnLayout { ColumnLayout {
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.padding.smallest
anchors.left: sourceIcon.right
anchors.leftMargin: Appearance.spacing.larger
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.large
anchors.top: parent.top
anchors.topMargin: Appearance.padding.smaller
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
RowLayout { CustomText {
Layout.fillHeight: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true Layout.fillWidth: true
text: "Input Volume"
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true
text: "Input Volume"
}
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
font.bold: true
text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`)
}
} }
CustomMouseArea { CustomText {
Layout.bottomMargin: 5 Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.fillWidth: true font.bold: true
implicitHeight: 10 text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`)
}
}
CustomSlider { CustomMouseArea {
anchors.left: parent.left Layout.bottomMargin: Appearance.padding.normal
anchors.right: parent.right Layout.fillWidth: true
color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary Layout.preferredHeight: Appearance.padding.larger * 3
implicitHeight: 10
value: Audio.sourceVolume
Behavior on value { CustomSlider {
Anim { anchors.left: parent.left
} anchors.right: parent.right
fgColor: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: parent.height
value: Audio.sourceVolume
Behavior on value {
Anim {
} }
onMoved: Audio.setSourceVolume(value)
} }
onInteraction: value => Audio.setSourceVolume(value)
} }
} }
} }
@@ -265,7 +280,7 @@ Item {
required property var modelData required property var modelData
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2 Layout.preferredHeight: 65 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding radius: root.rounding
@@ -292,18 +307,20 @@ Item {
} }
} }
RowLayout { Item {
id: layoutVolume id: appBoxIcon
anchors.fill: parent anchors.bottom: parent.bottom
anchors.margins: Appearance.spacing.smaller anchors.left: parent.left
spacing: 15 anchors.leftMargin: Appearance.padding.larger
anchors.top: parent.top
implicitWidth: childrenRect.width
CustomRect { CustomRect {
Layout.alignment: Qt.AlignVCenter anchors.centerIn: parent
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 54
implicitWidth: 54
radius: Appearance.rounding.full radius: Appearance.rounding.full
MaterialIcon { MaterialIcon {
@@ -312,7 +329,7 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
animate: true animate: true
color: appBox.modelData.audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary color: appBox.modelData.audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary
font.pointSize: 22 font.pointSize: Appearance.font.size.extraLarge
text: appBox.modelData.audio.muted ? "volume_off" : "volume_up" text: appBox.modelData.audio.muted ? "volume_off" : "volume_up"
} }
@@ -325,55 +342,58 @@ Item {
} }
} }
} }
}
ColumnLayout { TextMetrics {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop id: metrics
elide: Text.ElideRight
elideWidth: root.width - 50
text: Audio.getStreamName(appBox.modelData)
}
ColumnLayout {
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.padding.smallest
anchors.left: appBoxIcon.right
anchors.leftMargin: Appearance.spacing.larger
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.large
anchors.top: parent.top
anchors.topMargin: Appearance.padding.smaller
RowLayout {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
TextMetrics { CustomText {
id: metrics
elide: Text.ElideRight
elideWidth: root.width - 50
text: Audio.getStreamName(appBox.modelData)
}
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillHeight: true
Layout.fillWidth: true
elide: Text.ElideRight
text: metrics.elidedText
}
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.fillHeight: true
font.bold: true
text: qsTr("%1").arg(appBox.modelData.audio.muted ? qsTr("Muted") : `${Math.round(appBox.modelData.audio.volume * 100)}%`)
}
}
CustomMouseArea {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 10 elide: Text.ElideRight
text: metrics.elidedText
}
CustomSlider { CustomText {
anchors.left: parent.left Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
anchors.right: parent.right font.bold: true
color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary text: qsTr("%1").arg(appBox.modelData.audio.muted ? qsTr("Muted") : `${Math.round(appBox.modelData.audio.volume * 100)}%`)
implicitHeight: 10 }
value: appBox.modelData.audio.volume }
onMoved: { CustomMouseArea {
Audio.setStreamVolume(appBox.modelData, value); Layout.bottomMargin: Appearance.padding.normal
} Layout.fillWidth: true
Layout.preferredHeight: Appearance.padding.larger * 3
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
fgColor: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: parent.height
value: appBox.modelData.audio.volume
onInteraction: value => {
Audio.setStreamVolume(appBox.modelData, value);
} }
} }
} }
+32 -4
View File
@@ -3,6 +3,7 @@ import QtQuick
import QtQuick.VectorImage import QtQuick.VectorImage
import Quickshell import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import qs.Helpers
import qs.Modules import qs.Modules
import qs.Components import qs.Components
import qs.Config import qs.Config
@@ -11,12 +12,36 @@ 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
property bool hasLoaded: false 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 RowLayout loader
required property Wrapper popouts required property Wrapper popouts
function resolveIcon(app: string, icon: string): string {
if (app === "chrome_status_icon_1") {
return Quickshell.iconPath("discord-tray");
} else if (app === "AyuGramDesktop") {
if (icon === Quickshell.iconPath("com.ayugram.desktop-attention-symbolic"))
return Quickshell.iconPath("telegram-attention-panel");
else if (icon === Quickshell.iconPath("com.ayugram.desktop-mute-symbolic"))
return Quickshell.iconPath("telegram-mute-panel");
else if (icon === Quickshell.iconPath("com.ayugram.desktop-symbolic"))
return Quickshell.iconPath("telegram-panel");
} else if (app === "TelegramDesktop") {
if (icon === Quickshell.iconPath("org.telegram.desktop-symbolic"))
return Quickshell.iconPath("telegram-panel");
else if (icon === Quickshell.iconPath("org.telegram.desktop-attention-symbolic"))
return Quickshell.iconPath("telegram-attention-panel");
else if (icon === Quickshell.iconPath("org.telegram.desktop-mute-symbolic"))
return Quickshell.iconPath("telegram-mute-panel");
} else if (app === "steam") {
return Quickshell.iconPath("steam_tray_mono");
}
return root.item.icon;
}
CustomRect { CustomRect {
anchors.fill: parent anchors.fill: parent
anchors.margins: 3 anchors.margins: 3
@@ -30,7 +55,8 @@ Item {
onClicked: { onClicked: {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
root.item.activate(); root.item.activate();
} else if (mouse.button === Qt.RightButton) { console.log(icon.source + "\n" + root.item.id);
} else if (mouse.button === Qt.RightButton && Config.barConfig.popouts.tray) {
root.popouts.currentName = `traymenu${root.ind}`; root.popouts.currentName = `traymenu${root.ind}`;
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x); root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
root.popouts.hasCurrent = true; root.popouts.hasCurrent = true;
@@ -48,9 +74,11 @@ Item {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
antialiasing: true
color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
implicitSize: 22 implicitSize: Config.barConfig.tray.trayIconSize * root.dpr
layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark
source: root.item.icon scale: 1 / root.dpr
source: root.resolveIcon(root.item.id, root.item.icon)
} }
} }
+2 -2
View File
@@ -23,12 +23,12 @@ RowLayout {
let modRowPos = sysTrayMod.mapToItem(sysModRow, modPos.x, modPos.y); let modRowPos = sysTrayMod.mapToItem(sysModRow, modPos.x, modPos.y);
let child = sysModRow.childAt(modRowPos.x, modRowPos.y); let child = sysModRow.childAt(modRowPos.x, modRowPos.y);
if (child) { if (child) {
if (child.objectName === "audioWidget") if (child.objectName === "audioWidget" && Config.barConfig.popouts.audio)
return { return {
id: "audio", id: "audio",
item: child item: child
}; };
if (child.objectName === "upowerWidget") if (child.objectName === "upowerWidget" && Config.barConfig.popouts.upower)
return { return {
id: "upower", id: "upower",
item: child item: child
+109 -22
View File
@@ -3,33 +3,120 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Helpers as Helpers import qs.Helpers
RowLayout { Item {
id: root id: root
MaterialIcon { implicitHeight: Battery.isLaptop ? batteryIconLoader.item.implicitHeight : upowerIconLoader.item.implicitHeight
Layout.alignment: Qt.AlignVCenter implicitWidth: Battery.isLaptop ? batteryIconLoader.item.implicitWidth : upowerIconLoader.item.implicitWidth
animate: true
color: !Helpers.UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3error Loader {
fill: 1 id: batteryIconLoader
text: {
if (!Helpers.UPower.displayDevice.isLaptopBattery) { active: Battery.isLaptop
if (PowerProfiles.profile === PowerProfile.PowerSaver) anchors.centerIn: parent
return "nest_eco_leaf";
if (PowerProfiles.profile === PowerProfile.Performance) sourceComponent: Row {
return "bolt"; id: batteryIcon
return "power_settings_new";
property real batHeight: 16
property real batWidth: 30
property real nubHeight: 6
property real nubWidth: 2
property real radius: Appearance.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: Appearance.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
}
}
}
} }
const perc = Helpers.UPower.displayDevice.percentage; CustomRect {
const charging = [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(Helpers.UPower.displayDevice.state); id: nub
if (perc === 1)
return charging ? "battery_charging_full" : "battery_full"; anchors.verticalCenter: parent.verticalCenter
let level = Math.floor(perc * 7); bottomRightRadius: 20
if (charging && (level === 4 || level === 1)) color: Battery.currentPerc < 0.99 ? track.color : fill.color
level--; height: batteryIcon.nubHeight
return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`; topRightRadius: 20
width: batteryIcon.nubWidth
}
}
}
Loader {
id: upowerIconLoader
active: !Battery.isLaptop
anchors.centerIn: parent
sourceComponent: RowLayout {
id: upowerIcon
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
fill: 1
text: {
if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "nest_eco_leaf";
if (PowerProfiles.profile === PowerProfile.Performance)
return "bolt";
return "power_settings_new";
}
}
} }
} }
} }
+122 -111
View File
@@ -11,150 +11,161 @@ import qs.Helpers
CustomClippingRect { CustomClippingRect {
id: root id: root
readonly property bool hasUpdates: Object.keys(Updates.updates)?.length > 0
readonly property int itemHeight: 50 + Appearance.padding.smaller * 2 readonly property int itemHeight: 50 + Appearance.padding.smaller * 2
required property var wrapper required property var wrapper
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: updatesList.visible ? updatesList.implicitHeight + Appearance.padding.small * 2 : noUpdates.height implicitHeight: hasUpdates ? updatesListLoader.item?.implicitHeight + Appearance.padding.small * 2 : noUpdatesLoader.item.height
implicitWidth: updatesList.visible ? updatesList.contentWidth + Appearance.padding.small * 2 : noUpdates.width implicitWidth: hasUpdates ? updatesListLoader.item?.contentWidth + Appearance.padding.small * 2 : noUpdatesLoader.item.width
radius: Appearance.rounding.small radius: Appearance.rounding.small
Item { Loader {
id: noUpdates id: noUpdatesLoader
active: !root.hasUpdates
anchors.centerIn: parent anchors.centerIn: parent
height: 200
visible: script.values.length === 0
width: 600
MaterialIcon { sourceComponent: Item {
id: noUpdatesIcon id: noUpdates
anchors.horizontalCenter: parent.horizontalCenter height: 200
anchors.top: parent.top width: 300
color: DynamicColors.tPalette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.extraLarge * 3
horizontalAlignment: Text.AlignHCenter
text: "check"
}
CustomText { MaterialIcon {
anchors.horizontalCenter: parent.horizontalCenter id: noUpdatesIcon
anchors.top: noUpdatesIcon.bottom
color: DynamicColors.tPalette.m3onSurfaceVariant anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter anchors.top: parent.top
text: qsTr("No updates available") color: DynamicColors.tPalette.m3onSurfaceVariant
verticalAlignment: Text.AlignVCenter font.pointSize: Appearance.font.size.extraLarge * 3
horizontalAlignment: Text.AlignHCenter
text: "check"
}
CustomText {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: noUpdatesIcon.bottom
color: DynamicColors.tPalette.m3onSurfaceVariant
horizontalAlignment: Text.AlignHCenter
text: qsTr("No updates available")
verticalAlignment: Text.AlignVCenter
}
} }
} }
CustomListView { Loader {
id: updatesList id: updatesListLoader
active: root.hasUpdates
anchors.centerIn: parent anchors.centerIn: parent
contentHeight: childrenRect.height
contentWidth: 600
displayMarginBeginning: root.itemHeight
displayMarginEnd: root.itemHeight
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
implicitWidth: contentWidth
spacing: Appearance.spacing.normal
visible: script.values.length > 0
delegate: CustomRect { sourceComponent: CustomListView {
id: update id: updatesList
required property var modelData contentHeight: childrenRect.height
readonly property list<string> sections: modelData.update.split(" ") contentWidth: 600
displayMarginBeginning: root.itemHeight
displayMarginEnd: root.itemHeight
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
implicitWidth: contentWidth
spacing: Appearance.spacing.normal
// anchors.left: parent.left delegate: CustomRect {
// anchors.right: parent.right id: update
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: root.itemHeight
implicitWidth: 600
radius: Appearance.rounding.small - Appearance.padding.small
RowLayout { required property var modelData
anchors.fill: parent readonly property list<string> sections: modelData.update.split(" ")
anchors.leftMargin: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.smaller
MaterialIcon { // anchors.left: parent.left
font.pointSize: Appearance.font.size.large * 2 // anchors.right: parent.right
text: "package_2" color: DynamicColors.tPalette.m3surfaceContainer
} implicitHeight: root.itemHeight
implicitWidth: 600
ColumnLayout { radius: Appearance.rounding.small - Appearance.padding.small
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.preferredHeight: 25
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: update.sections[0]
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
text: Updates.formatUpdateTime(update.modelData.timestamp)
}
}
RowLayout { RowLayout {
Layout.fillHeight: true anchors.fill: parent
Layout.preferredWidth: 300 anchors.leftMargin: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.smaller
MarqueeText {
id: versionFrom
Layout.fillHeight: true
Layout.preferredWidth: 125
animate: true
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[1]
width: 125
}
MaterialIcon { MaterialIcon {
Layout.fillHeight: true font.pointSize: Appearance.font.size.large * 2
color: DynamicColors.palette.m3secondary text: "package_2"
font.pointSize: Appearance.font.size.extraLarge
horizontalAlignment: Text.AlignHCenter
text: "arrow_right_alt"
verticalAlignment: Text.AlignVCenter
} }
MarqueeText { ColumnLayout {
id: versionTo Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.preferredHeight: 25
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: update.sections[0]
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
text: Updates.formatUpdateTime(update.modelData.timestamp)
}
}
RowLayout {
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: 120 Layout.preferredWidth: 300
animate: true
color: DynamicColors.palette.m3primary MarqueeText {
font.pointSize: Appearance.font.size.large id: versionFrom
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true Layout.fillHeight: true
pauseMs: 4000 Layout.preferredWidth: 125
text: update.sections[3] animate: true
width: 125 color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[1]
width: 125
}
MaterialIcon {
Layout.fillHeight: true
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge
horizontalAlignment: Text.AlignHCenter
text: "arrow_right_alt"
verticalAlignment: Text.AlignVCenter
}
MarqueeText {
id: versionTo
Layout.fillHeight: true
Layout.preferredWidth: 120
animate: true
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[3]
width: 125
}
} }
} }
} }
} model: ScriptModel {
model: ScriptModel { id: script
id: script
objectProp: "update" objectProp: "update"
values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({ values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({
update, update,
timestamp timestamp
})) }))
}
} }
} }
} }
-80
View File
@@ -1,80 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Components
import qs.Helpers
import qs.Config
Item {
id: root
property Image current: one
property string source: Wallpapers.current
anchors.fill: parent
Component.onCompleted: {
if (source)
Qt.callLater(() => one.update());
}
onSourceChanged: {
if (!source) {
current = null;
} else if (current === one) {
two.update();
} else {
one.update();
}
}
Img {
id: one
}
Img {
id: two
}
component Img: Image {
id: img
function update(): void {
if (source === root.source) {
root.current = this;
} else {
source = root.source;
}
}
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
opacity: 0
retainWhileLoading: true
scale: Wallpapers.showPreview ? 1 : 0.8
sourceClipRect: Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH)
states: State {
name: "visible"
when: root.current === img
PropertyChanges {
img.opacity: 1
img.scale: 1
}
}
transitions: Transition {
Anim {
duration: Config.background.wallFadeDuration
properties: "opacity,scale"
target: img
}
}
onStatusChanged: {
if (status === Image.Ready) {
root.current = this;
}
}
}
}
+37 -30
View File
@@ -6,6 +6,7 @@ import QtQuick
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
import qs.Config import qs.Config
import ZShell.Internal
Item { Item {
id: root id: root
@@ -15,58 +16,64 @@ Item {
function refreshData(): void { function refreshData(): void {
Hyprland.refreshMonitors(); Hyprland.refreshMonitors();
const scale = Hyprland.monitorFor(root.screen).scale; let scale = Hyprland.monitorFor(root.screen).scale;
if (scale > 0 && img.resScale !== scale) { if (scale <= 0)
img.resScale = scale; scale = 1.0; // Fallback to avoid zeroes on initialization
img.sourceSize.width = root.screen.width * scale;
if (root.screen.width > 0 && root.screen.height > 0) {
img.screenResolution = Qt.size(root.screen.width * scale, root.screen.height * scale);
} }
const displayData = Wallpapers.getCrop(root.screen.name); const displayData = Wallpapers.getCrop(root.screen.name);
const displayRect = Qt.rect(img.sourceSize.width * displayData.x, img.implicitHeight * displayData.y, img.sourceSize.width * displayData.width, img.implicitHeight * displayData.height);
img.anchors.fill = null; if (displayData) {
img.zoom = displayData.zoom; img.cropX = displayData.x !== undefined ? displayData.x : 0.0;
img.x = -(displayRect.x * displayData.zoom / img.resScale); img.cropY = displayData.y !== undefined ? displayData.y : 0.0;
img.y = -(displayRect.y * displayData.zoom / img.resScale); img.cropWidth = (displayData.width !== undefined && displayData.width > 0) ? displayData.width : 1.0;
img.cropHeight = (displayData.height !== undefined && displayData.height > 0) ? displayData.height : 1.0;
}
} }
anchors.fill: parent anchors.fill: parent
Image { Component.onCompleted: root.refreshData()
Connections {
function onHeightChanged() {
root.refreshData();
}
function onWidthChanged() {
root.refreshData();
}
target: root.screen
}
WallpaperImage {
id: img id: img
property int displayH anchors.fill: parent
property int displayW
property real resScale
property real zoom: 1.0
asynchronous: true
fillMode: Image.PreserveAspectCrop
height: implicitHeight * zoom / resScale
opacity: 1
retainWhileLoading: true
source: root.source source: root.source
sourceSize.width: root.screen.width * resScale
width: implicitWidth * zoom / resScale
Behavior on height { Behavior on cropHeight {
Anim { Anim {
} }
} }
Behavior on width { Behavior on cropWidth {
Anim { Anim {
} }
} }
Behavior on x { Behavior on cropX {
Anim { Anim {
} }
} }
Behavior on y { Behavior on cropY {
Anim { Anim {
} }
} }
Behavior on zoom {
onStatusChanged: { Anim {
if (img.status == Image.Ready) {
root.refreshData();
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ import qs.Modules.DesktopIcons
Loader { Loader {
active: Config.background.enabled active: Config.background.enabled
asynchronous: true asynchronous: false
sourceComponent: Variants { sourceComponent: Variants {
model: Quickshell.screens model: Quickshell.screens
+4 -49
View File
@@ -15,9 +15,8 @@ Item {
property real currentCenter property real currentCenter
property alias currentName: popoutState.currentName property alias currentName: popoutState.currentName
property string detachedMode property string detachedMode
readonly property bool isDetached: detachedMode.length > 0
property alias hasCurrent: popoutState.hasCurrent property alias hasCurrent: popoutState.hasCurrent
readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight readonly property real nonAnimHeight: content.implicitHeight || 150
readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth
required property real offsetScale required property real offsetScale
property string queuedMode property string queuedMode
@@ -28,29 +27,13 @@ Item {
detachedMode = ""; detachedMode = "";
} }
function detach(mode: string): void {
setAnims(true);
if (mode === "winfo") {
detachedMode = mode;
} else {
queuedMode = mode;
detachedMode = "any";
}
setAnims(false);
focus = true;
}
function setAnims(detach: bool): void {
const type = `expressive${detach ? "Slow" : "Default"}Spatial`;
animLength = Appearance.anim.durations[type];
animCurve = Appearance.anim.curves[type];
}
focus: hasCurrent focus: hasCurrent
implicitHeight: nonAnimHeight implicitHeight: nonAnimHeight
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: root.offsetScale < 1
Anim { Anim {
duration: root.animLength duration: root.animLength
easing.bezierCurve: root.animCurve easing.bezierCurve: root.animCurve
@@ -72,42 +55,14 @@ Item {
Comp { Comp {
id: content id: content
// anchors.horizontalCenter: parent.horizontalCenter
// anchors.top: parent.top
anchors.centerIn: parent anchors.centerIn: parent
shouldBeActive: root.hasCurrent && !root.detachedMode shouldBeActive: root.hasCurrent
sourceComponent: Content { sourceComponent: Content {
popouts: popoutState popouts: popoutState
} }
} }
// Comp {
// id: winfo
//
// anchors.centerIn: parent
// shouldBeActive: root.detachedMode === "winfo"
//
// sourceComponent: WindowInfo {
// client: Hypr.activeToplevel
// screen: root.screen
// }
// }
//
// Comp {
// id: controlCenter
//
// anchors.centerIn: parent
// shouldBeActive: root.detachedMode === "any"
//
// sourceComponent: ControlCenter {
// active: root.queuedMode
// screen: root.screen
//
// onClose: root.close()
// }
// }
component Comp: Loader { component Comp: Loader {
id: comp id: comp
+4 -4
View File
@@ -18,13 +18,13 @@ public:
explicit BlobGroup(QObject* parent = nullptr); explicit BlobGroup(QObject* parent = nullptr);
~BlobGroup() override; ~BlobGroup() override;
qreal smoothing() const { [[nodiscard]] qreal smoothing() const {
return m_smoothing; return m_smoothing;
} }
void setSmoothing(qreal s); void setSmoothing(qreal s);
QColor color() const { [[nodiscard]] QColor color() const {
return m_color; return m_color;
} }
@@ -36,11 +36,11 @@ void removeShape(BlobShape* shape);
void setInvertedRect(BlobInvertedRect* rect); void setInvertedRect(BlobInvertedRect* rect);
void clearInvertedRect(BlobInvertedRect* rect); void clearInvertedRect(BlobInvertedRect* rect);
const QList<BlobShape*>& shapes() const { [[nodiscard]] const QList<BlobShape*>& shapes() const {
return m_shapes; return m_shapes;
} }
BlobInvertedRect* invertedRect() const { [[nodiscard]] BlobInvertedRect* invertedRect() const {
return m_invertedRect; return m_invertedRect;
} }
+5 -5
View File
@@ -16,25 +16,25 @@ public:
explicit BlobInvertedRect(QQuickItem* parent = nullptr); explicit BlobInvertedRect(QQuickItem* parent = nullptr);
~BlobInvertedRect() override; ~BlobInvertedRect() override;
qreal borderLeft() const { [[nodiscard]] qreal borderLeft() const {
return m_borderLeft; return m_borderLeft;
} }
void setBorderLeft(qreal v); void setBorderLeft(qreal v);
qreal borderRight() const { [[nodiscard]] qreal borderRight() const {
return m_borderRight; return m_borderRight;
} }
void setBorderRight(qreal v); void setBorderRight(qreal v);
qreal borderTop() const { [[nodiscard]] qreal borderTop() const {
return m_borderTop; return m_borderTop;
} }
void setBorderTop(qreal v); void setBorderTop(qreal v);
qreal borderBottom() const { [[nodiscard]] qreal borderBottom() const {
return m_borderBottom; return m_borderBottom;
} }
@@ -47,7 +47,7 @@ void borderTopChanged();
void borderBottomChanged(); void borderBottomChanged();
protected: protected:
bool isInvertedRect() const override { [[nodiscard]] bool isInvertedRect() const override {
return true; return true;
} }
+2 -2
View File
@@ -21,8 +21,8 @@ struct BlobRectData {
class BlobMaterial : public QSGMaterial { class BlobMaterial : public QSGMaterial {
public: public:
QSGMaterialType* type() const override; [[nodiscard]] QSGMaterialType* type() const override;
QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; [[nodiscard]] QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override;
int compare(const QSGMaterial* other) const override; int compare(const QSGMaterial* other) const override;
float m_paddedX = 0; float m_paddedX = 0;
+23 -9
View File
@@ -14,15 +14,11 @@ BlobRect::~BlobRect() {
} }
void BlobRect::updatePolish() { void BlobRect::updatePolish() {
BlobShape::updatePolish();
if (m_physicsActive) { if (m_physicsActive) {
// Check if deformation is visually imperceptible
float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f); float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f);
float totalVel = std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11); float totalVel = std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
if (totalDelta < 0.004f && totalVel < 0.05f) { if (totalDelta < 0.004f && totalVel < 0.05f) {
// Snap to rest, no visible deformation
m_dm00 = 1.0f; m_dm00 = 1.0f;
m_dm01 = 0.0f; m_dm01 = 0.0f;
m_dm11 = 1.0f; m_dm11 = 1.0f;
@@ -31,6 +27,16 @@ void BlobRect::updatePolish() {
emit rawDeformMatrixChanged(); emit rawDeformMatrixChanged();
updateCenteredDeformMatrix(); updateCenteredDeformMatrix();
m_physicsActive = false; m_physicsActive = false;
if (m_group) {
QMetaObject::invokeMethod(
this,
[this]() {
if (m_group)
m_group->markDirty();
},
Qt::QueuedConnection);
}
} else { } else {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
@@ -41,6 +47,8 @@ void BlobRect::updatePolish() {
Qt::QueuedConnection); Qt::QueuedConnection);
} }
} }
BlobShape::updatePolish();
} }
void BlobRect::updatePhysics() { void BlobRect::updatePhysics() {
@@ -56,7 +64,6 @@ void BlobRect::updatePhysics() {
const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f; const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f;
if (dt > 0.1f || dt < 0.001f) { if (dt > 0.1f || dt < 0.001f) {
m_prevScenePos = scenePos; m_prevScenePos = scenePos;
// Still check atRest on skipped frames to avoid getting stuck
if (m_physicsActive) if (m_physicsActive)
checkAtRest(0.0f); checkAtRest(0.0f);
return; return;
@@ -74,8 +81,6 @@ void BlobRect::updatePhysics() {
m_physicsActive = true; m_physicsActive = true;
} }
// Compute target deformation matrix from velocity
// R(θ) * diag(stretch, compress) * R(θ)^T
const float kStretchFactor = static_cast<float>(m_deformScale); const float kStretchFactor = static_cast<float>(m_deformScale);
constexpr float kMaxStretch = 0.35f; constexpr float kMaxStretch = 0.35f;
@@ -98,7 +103,6 @@ void BlobRect::updatePhysics() {
target11 = targetStretch * sin2 + targetCompress * cos2; target11 = targetStretch * sin2 + targetCompress * cos2;
} }
// Underdamped spring on each matrix component
const float kStiffness = static_cast<float>(m_stiffness); const float kStiffness = static_cast<float>(m_stiffness);
const float kDamping = static_cast<float>(m_damping); const float kDamping = static_cast<float>(m_damping);
@@ -238,9 +242,19 @@ void BlobRect::checkAtRest(float speed) {
m_dmVel00 = 0.0f; m_dmVel00 = 0.0f;
m_dmVel01 = 0.0f; m_dmVel01 = 0.0f;
m_dmVel11 = 0.0f; m_dmVel11 = 0.0f;
m_deformMatrix = QMatrix4x4(); // identity m_deformMatrix = QMatrix4x4();
emit rawDeformMatrixChanged(); emit rawDeformMatrixChanged();
updateCenteredDeformMatrix(); updateCenteredDeformMatrix();
m_physicsActive = false; m_physicsActive = false;
if (m_group) {
QMetaObject::invokeMethod(
this,
[this]() {
if (m_group)
m_group->markDirty();
},
Qt::QueuedConnection);
}
} }
} }
+7 -7
View File
@@ -24,7 +24,7 @@ public:
explicit BlobRect(QQuickItem* parent = nullptr); explicit BlobRect(QQuickItem* parent = nullptr);
~BlobRect() override; ~BlobRect() override;
qreal stiffness() const { [[nodiscard]] qreal stiffness() const {
return m_stiffness; return m_stiffness;
} }
@@ -35,7 +35,7 @@ void setStiffness(qreal s) {
} }
} }
qreal damping() const { [[nodiscard]] qreal damping() const {
return m_damping; return m_damping;
} }
@@ -46,7 +46,7 @@ void setDamping(qreal d) {
} }
} }
qreal deformScale() const { [[nodiscard]] qreal deformScale() const {
return m_deformScale; return m_deformScale;
} }
@@ -62,25 +62,25 @@ QQmlListProperty<BlobRect> exclude();
bool isExcluded(const BlobShape* other) const override; bool isExcluded(const BlobShape* other) const override;
void cornerRadii(float out[4]) const override; void cornerRadii(float out[4]) const override;
qreal topLeftRadius() const { [[nodiscard]] qreal topLeftRadius() const {
return m_topLeftRadius; return m_topLeftRadius;
} }
void setTopLeftRadius(qreal r); void setTopLeftRadius(qreal r);
qreal topRightRadius() const { [[nodiscard]] qreal topRightRadius() const {
return m_topRightRadius; return m_topRightRadius;
} }
void setTopRightRadius(qreal r); void setTopRightRadius(qreal r);
qreal bottomLeftRadius() const { [[nodiscard]] qreal bottomLeftRadius() const {
return m_bottomLeftRadius; return m_bottomLeftRadius;
} }
void setBottomLeftRadius(qreal r); void setBottomLeftRadius(qreal r);
qreal bottomRightRadius() const { [[nodiscard]] qreal bottomRightRadius() const {
return m_bottomRightRadius; return m_bottomRightRadius;
} }
+7 -23
View File
@@ -9,7 +9,6 @@
#include <cmath> #include <cmath>
static float deformPadding(const QMatrix4x4& dm, float hw, float hh) { static float deformPadding(const QMatrix4x4& dm, float hw, float hh) {
// Bounding box of the deformed shape: |M * corners|
const float dm00 = dm(0, 0), dm01 = dm(0, 1); const float dm00 = dm(0, 0), dm01 = dm(0, 1);
const float dm10 = dm(1, 0), dm11 = dm(1, 1); const float dm10 = dm(1, 0), dm11 = dm(1, 1);
const float boundX = std::abs(dm00) * hw + std::abs(dm01) * hh; const float boundX = std::abs(dm00) * hw + std::abs(dm01) * hh;
@@ -69,16 +68,17 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome
QQuickItem::geometryChange(newGeometry, oldGeometry); QQuickItem::geometryChange(newGeometry, oldGeometry);
updateCenteredDeformMatrix(); updateCenteredDeformMatrix();
if (m_group) { if (m_group) {
// Accumulate sub-pixel drift so slow movements don't desync the shader
m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x()); m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x());
m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y()); m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y());
// Accumulate size delta across multiple frames so incremental size
// changes that are each below the threshold still trigger a dirty
// mark once their accumulated delta exceeds it.
m_pendingDw += static_cast<float>(newGeometry.width() - oldGeometry.width()); m_pendingDw += static_cast<float>(newGeometry.width() - oldGeometry.width());
m_pendingDh += static_cast<float>(newGeometry.height() - oldGeometry.height()); m_pendingDh += static_cast<float>(newGeometry.height() - oldGeometry.height());
if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f ||
std::abs(m_pendingDw) > 0.5f || std::abs(m_pendingDh) > 0.5f) { const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) + std::abs(m_deformMatrix(0, 1)) +
std::abs(m_deformMatrix(1, 0)) + std::abs(m_deformMatrix(1, 1) - 1.0f);
const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f;
if (std::abs(m_pendingDx) > syncThreshold || std::abs(m_pendingDy) > syncThreshold ||
std::abs(m_pendingDw) > syncThreshold || std::abs(m_pendingDh) > syncThreshold) {
m_pendingDx = 0; m_pendingDx = 0;
m_pendingDy = 0; m_pendingDy = 0;
m_pendingDw = 0; m_pendingDw = 0;
@@ -124,7 +124,6 @@ void BlobShape::updatePolish() {
if (!m_group) if (!m_group)
return; return;
// Ensure all shapes have up-to-date physics (only once per frame)
m_group->ensurePhysicsUpdated(); m_group->ensurePhysicsUpdated();
const QPointF scenePos = mapToScene(QPointF(0, 0)); const QPointF scenePos = mapToScene(QPointF(0, 0));
@@ -149,13 +148,11 @@ void BlobShape::updatePolish() {
width() + 2.0 * static_cast<double>(totalPad), height() + 2.0 * static_cast<double>(totalPad)); width() + 2.0 * static_cast<double>(totalPad), height() + 2.0 * static_cast<double>(totalPad));
} }
// Filter nearby normal rects
m_cachedRects.clear(); m_cachedRects.clear();
m_cachedMyIndex = -2; m_cachedMyIndex = -2;
const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY), const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY),
static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH)); static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH));
// Track shape pointers parallel to m_cachedRects for pairwise exclusion lookups
QVector<BlobShape*> rectShapes; QVector<BlobShape*> rectShapes;
rectShapes.reserve(m_group->shapes().size()); rectShapes.reserve(m_group->shapes().size());
@@ -163,7 +160,6 @@ void BlobShape::updatePolish() {
if (other->isInvertedRect()) if (other->isInvertedRect())
continue; continue;
// Skip zero-size rects
if (other->width() <= 0 || other->height() <= 0) if (other->width() <= 0 || other->height() <= 0)
continue; continue;
@@ -202,7 +198,6 @@ void BlobShape::updatePolish() {
r.offsetX = dm(0, 3); r.offsetX = dm(0, 3);
r.offsetY = dm(1, 3); r.offsetY = dm(1, 3);
// Pre-compute inverse deformation matrix
const float det = a * d - c * b; const float det = a * d - c * b;
const float invDet = std::abs(det) > 1e-6f ? 1.0f / det : 1.0f; const float invDet = std::abs(det) > 1e-6f ? 1.0f / det : 1.0f;
r.invDeform[0] = d * invDet; r.invDeform[0] = d * invDet;
@@ -210,12 +205,10 @@ void BlobShape::updatePolish() {
r.invDeform[2] = -c * invDet; r.invDeform[2] = -c * invDet;
r.invDeform[3] = a * invDet; r.invDeform[3] = a * invDet;
// Pre-compute minimum eigenvalue (avoids per-pixel sqrt)
const float halfTr = 0.5f * (a + d); const float halfTr = 0.5f * (a + d);
const float halfDiff = 0.5f * (a - d); const float halfDiff = 0.5f * (a - d);
r.minEig = halfTr - std::sqrt(halfDiff * halfDiff + c * c); r.minEig = halfTr - std::sqrt(halfDiff * halfDiff + c * c);
// Pre-compute screen-space AABB half-extents
r.screenHalfX = std::abs(a) * r.hw + std::abs(c) * r.hh; r.screenHalfX = std::abs(a) * r.hw + std::abs(c) * r.hh;
r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh; r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh;
@@ -227,8 +220,6 @@ void BlobShape::updatePolish() {
if (isInvertedRect()) if (isInvertedRect())
m_cachedMyIndex = -1; m_cachedMyIndex = -1;
// Compute pairwise exclude masks. Bit j in entry i is set iff rect i excludes rect j
// or rect j excludes rect i. The shader uses this to avoid smin between excluded pairs.
const auto cachedCount = m_cachedRects.size(); const auto cachedCount = m_cachedRects.size();
for (qsizetype i = 0; i < cachedCount; ++i) { for (qsizetype i = 0; i < cachedCount; ++i) {
int mask = 0; int mask = 0;
@@ -243,7 +234,6 @@ void BlobShape::updatePolish() {
m_cachedRects[i].excludeMask = mask; m_cachedRects[i].excludeMask = mask;
} }
// Cache inverted rect data
m_cachedHasInverted = false; m_cachedHasInverted = false;
m_cachedInvertedRadius = 0; m_cachedInvertedRadius = 0;
memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter)); memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter));
@@ -262,7 +252,6 @@ void BlobShape::updatePolish() {
const float innerHW = outerHW - static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0); const float innerHW = outerHW - static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0);
const float innerHH = outerHH - static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0); const float innerHH = outerHH - static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0);
// Check if this rect is near the border (within 2x smoothing of inner edge)
bool nearBorder = isInvertedRect(); bool nearBorder = isInvertedRect();
if (!nearBorder) { if (!nearBorder) {
const float margin = pad * 2.0f; const float margin = pad * 2.0f;
@@ -270,7 +259,6 @@ void BlobShape::updatePolish() {
const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f; const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f;
const float myHW = m_cachedPaddedW * 0.5f; const float myHW = m_cachedPaddedW * 0.5f;
const float myHH = m_cachedPaddedH * 0.5f; const float myHH = m_cachedPaddedH * 0.5f;
// Near border if any edge of padded rect is within margin of inner edge
nearBorder = (myCX - myHW < innerCX - innerHW + margin) || (myCX + myHW > innerCX + innerHW - margin) || nearBorder = (myCX - myHW < innerCX - innerHW + margin) || (myCX + myHW > innerCX + innerHW - margin) ||
(myCY - myHH < innerCY - innerHH + margin) || (myCY + myHH > innerCY + innerHH - margin); (myCY - myHH < innerCY - innerHH + margin) || (myCY + myHH > innerCY + innerHH - margin);
} }
@@ -291,7 +279,6 @@ void BlobShape::updatePolish() {
} }
} }
// Pre-compute effective per-corner radii (moves O(N²) work from GPU to CPU)
const float smoothFactor = pad; const float smoothFactor = pad;
constexpr float minR = 2.0f; constexpr float minR = 2.0f;
const auto rectCount = m_cachedRects.size(); const auto rectCount = m_cachedRects.size();
@@ -328,7 +315,6 @@ void BlobShape::updatePolish() {
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh))); fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh)));
} }
// Combine base radii with fill factors into effective per-corner radii
ri.radius[0] = std::max(ri.radius[0] * fTr, minR); ri.radius[0] = std::max(ri.radius[0] * fTr, minR);
ri.radius[1] = std::max(ri.radius[1] * fBr, minR); ri.radius[1] = std::max(ri.radius[1] * fBr, minR);
ri.radius[2] = std::max(ri.radius[2] * fBl, minR); ri.radius[2] = std::max(ri.radius[2] * fBl, minR);
@@ -357,7 +343,6 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
node->setFlag(QSGNode::OwnsMaterial); node->setFlag(QSGNode::OwnsMaterial);
} }
// Update geometry
auto* geometry = node->geometry(); auto* geometry = node->geometry();
auto* v = geometry->vertexDataAsTexturedPoint2D(); auto* v = geometry->vertexDataAsTexturedPoint2D();
@@ -373,7 +358,6 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
node->markDirty(QSGNode::DirtyGeometry); node->markDirty(QSGNode::DirtyGeometry);
// Update material
auto* material = static_cast<BlobMaterial*>(node->material()); auto* material = static_cast<BlobMaterial*>(node->material());
material->m_paddedX = m_cachedPaddedX; material->m_paddedX = m_cachedPaddedX;
material->m_paddedY = m_cachedPaddedY; material->m_paddedY = m_cachedPaddedY;
+10 -11
View File
@@ -21,23 +21,23 @@ public:
explicit BlobShape(QQuickItem* parent = nullptr); explicit BlobShape(QQuickItem* parent = nullptr);
~BlobShape() override = default; ~BlobShape() override = default;
BlobGroup* group() const { [[nodiscard]] BlobGroup* group() const {
return m_group; return m_group;
} }
void setGroup(BlobGroup* g); void setGroup(BlobGroup* g);
qreal radius() const { [[nodiscard]] qreal radius() const {
return m_radius; return m_radius;
} }
void setRadius(qreal r); void setRadius(qreal r);
QMatrix4x4 deformMatrix() const { [[nodiscard]] QMatrix4x4 deformMatrix() const {
return m_centeredDeformMatrix; return m_centeredDeformMatrix;
} }
QMatrix4x4 rawDeformMatrix() const { [[nodiscard]] QMatrix4x4 rawDeformMatrix() const {
return m_deformMatrix; return m_deformMatrix;
} }
@@ -53,7 +53,7 @@ void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) overri
void updatePolish() override; void updatePolish() override;
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
virtual bool isInvertedRect() const { [[nodiscard]] virtual bool isInvertedRect() const {
return false; return false;
} }
@@ -72,10 +72,9 @@ void updateCenteredDeformMatrix();
BlobGroup* m_group = nullptr; BlobGroup* m_group = nullptr;
qreal m_radius = 0; qreal m_radius = 0;
QMatrix4x4 m_deformMatrix; // identity by default QMatrix4x4 m_deformMatrix;
QMatrix4x4 m_centeredDeformMatrix; QMatrix4x4 m_centeredDeformMatrix;
// Cached data from updatePolish
float m_cachedPaddedX = 0; float m_cachedPaddedX = 0;
float m_cachedPaddedY = 0; float m_cachedPaddedY = 0;
float m_cachedPaddedW = 0; float m_cachedPaddedW = 0;
@@ -84,10 +83,10 @@ QRectF m_localPaddedRect;
QVector<BlobRectData> m_cachedRects; QVector<BlobRectData> m_cachedRects;
int m_cachedMyIndex = -2; int m_cachedMyIndex = -2;
float m_pendingDx = 0; float m_pendingDx = 0;
float m_pendingDy = 0; float m_pendingDy = 0;
float m_pendingDw = 0; float m_pendingDw = 0;
float m_pendingDh = 0; float m_pendingDh = 0;
bool m_cachedHasInverted = false; bool m_cachedHasInverted = false;
float m_cachedInvertedRadius = 0; float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {}; float m_cachedInvertedOuter[4] = {};
float m_cachedInvertedInner[4] = {}; float m_cachedInvertedInner[4] = {};
+2
View File
@@ -45,11 +45,13 @@ qml_module(ZShell
requests.hpp requests.cpp requests.hpp requests.cpp
toaster.hpp toaster.cpp toaster.hpp toaster.cpp
qalculator.hpp qalculator.cpp qalculator.hpp qalculator.cpp
zutils.hpp zutils.cpp
LIBRARIES LIBRARIES
Qt::Gui Qt::Gui
Qt::Quick Qt::Quick
Qt::Concurrent Qt::Concurrent
Qt::Sql Qt::Sql
Qt::DBus
PkgConfig::Qalculate PkgConfig::Qalculate
) )
+1
View File
@@ -2,6 +2,7 @@ qml_module(ZShell-components
URI ZShell.Components URI ZShell.Components
SOURCES SOURCES
lazylistview.hpp lazylistview.cpp lazylistview.hpp lazylistview.cpp
wavyline.hpp wavyline.cpp
LIBRARIES LIBRARIES
Qt::Quick Qt::Quick
) )
+255
View File
@@ -0,0 +1,255 @@
#include "wavyline.hpp"
#include <qpainter.h>
#include <qpainterpath.h>
namespace ZShell::controls {
WavyLine::WavyLine(QQuickItem* parent)
: QQuickPaintedItem(parent)
, m_lineWidth(4)
, m_amplitudeMultiplier(0.5)
, m_frequency(6)
, m_startX(0)
, m_fullLength(0)
, m_color(Qt::white)
, m_waveProgress(0)
, m_pathType(Linear)
, m_startAngle(0)
, m_fullAngle(360)
, m_radius(-1)
, m_value(1)
, m_startAngleRad(0)
, m_fullAngleRad(2 * M_PI) {
setAntialiasing(true);
}
int WavyLine::lineWidth() const {
return m_lineWidth;
}
void WavyLine::setLineWidth(int lineWidth) {
if (m_lineWidth != lineWidth) {
m_lineWidth = lineWidth;
emit lineWidthChanged();
update();
}
}
qreal WavyLine::amplitudeMultiplier() const {
return m_amplitudeMultiplier;
}
void WavyLine::setAmplitudeMultiplier(qreal amplitudeMultiplier) {
if (!qFuzzyCompare(m_amplitudeMultiplier + 1.0, amplitudeMultiplier + 1.0)) {
m_amplitudeMultiplier = amplitudeMultiplier;
emit amplitudeMultiplierChanged();
update();
}
}
int WavyLine::frequency() const {
return m_frequency;
}
void WavyLine::setFrequency(int frequency) {
if (m_frequency != frequency) {
m_frequency = frequency;
emit frequencyChanged();
update();
}
}
qreal WavyLine::startX() const {
return m_startX;
}
void WavyLine::setStartX(qreal startX) {
if (!qFuzzyCompare(m_startX + 1.0, startX + 1.0)) {
m_startX = startX;
emit startXChanged();
update();
}
}
qreal WavyLine::fullLength() const {
return m_fullLength;
}
void WavyLine::setFullLength(qreal fullLength) {
if (!qFuzzyCompare(m_fullLength + 1.0, fullLength + 1.0)) {
m_fullLength = fullLength;
emit fullLengthChanged();
update();
}
}
QColor WavyLine::color() const {
return m_color;
}
void WavyLine::setColor(const QColor& color) {
if (m_color != color) {
m_color = color;
emit colorChanged();
update();
}
}
qreal WavyLine::waveProgress() const {
return m_waveProgress;
}
void WavyLine::setWaveProgress(qreal progress) {
if (!qFuzzyCompare(m_waveProgress + 1.0, progress + 1.0)) {
m_waveProgress = progress;
emit waveProgressChanged();
update();
}
}
WavyLine::PathType WavyLine::pathType() const {
return m_pathType;
}
void WavyLine::setPathType(PathType pathType) {
if (m_pathType != pathType) {
m_pathType = pathType;
emit pathTypeChanged();
update();
}
}
qreal WavyLine::startAngle() const {
return m_startAngle;
}
void WavyLine::setStartAngle(qreal startAngle) {
if (!qFuzzyCompare(m_startAngle + 1.0, startAngle + 1.0)) {
m_startAngle = startAngle;
m_startAngleRad = startAngle * M_PI / 180.0;
emit startAngleChanged();
update();
}
}
qreal WavyLine::fullAngle() const {
return m_fullAngle;
}
void WavyLine::setFullAngle(qreal fullAngle) {
if (!qFuzzyCompare(m_fullAngle + 1.0, fullAngle + 1.0)) {
m_fullAngle = fullAngle;
m_fullAngleRad = fullAngle * M_PI / 180.0;
emit fullAngleChanged();
update();
}
}
qreal WavyLine::radius() const {
return m_radius;
}
void WavyLine::setRadius(qreal radius) {
if (!qFuzzyCompare(m_radius + 1.0, radius + 1.0)) {
m_radius = radius;
emit radiusChanged();
update();
}
}
qreal WavyLine::value() const {
return m_value;
}
void WavyLine::setValue(qreal value) {
if (!qFuzzyCompare(m_value + 1.0, value + 1.0)) {
m_value = value;
emit valueChanged();
update();
}
}
void WavyLine::paint(QPainter* painter) {
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(QPen(m_color, m_lineWidth, Qt::SolidLine, Qt::RoundCap));
if (m_pathType == Arc) {
paintArc(painter);
} else {
paintLinear(painter);
}
}
void WavyLine::paintLinear(QPainter* painter) {
const auto amplitude = m_lineWidth * m_amplitudeMultiplier;
const auto phase = m_waveProgress * 2 * M_PI;
const auto centerY = height() / 2;
const auto len = m_fullLength > 0 ? m_fullLength : 1;
const auto start = m_lineWidth / 2.0;
const auto fullEnd = width() - m_lineWidth / 2.0;
const auto drawEnd = start + (fullEnd - start) * m_value;
QPainterPath path;
bool first = true;
for (int x = m_lineWidth / 2; x <= drawEnd; ++x) {
const auto theta = m_frequency * 2 * M_PI * (x + m_startX) / len + phase;
const auto waveY = centerY + amplitude * qSin(theta);
if (first) {
path.moveTo(x, waveY);
first = false;
} else {
path.lineTo(x, waveY);
}
}
painter->drawPath(path);
}
void WavyLine::paintArc(QPainter* painter) {
if (m_fullAngleRad <= 0) {
return;
}
const auto amplitude = m_lineWidth * m_amplitudeMultiplier;
const auto cx = width() / 2.0;
const auto cy = height() / 2.0;
const auto radius = m_radius > 0 ? m_radius : (qMin(width(), height()) - m_lineWidth - 2 * amplitude) / 2.0;
if (radius <= 0) {
return;
}
const auto phase = m_waveProgress * 2 * M_PI;
const auto arcLen = radius * m_fullAngleRad;
const auto len = m_fullLength > 0 ? m_fullLength : arcLen;
const auto drawAngleRad = m_fullAngleRad * m_value;
if (drawAngleRad <= 0) {
return;
}
const auto N = qMax(64, qCeil(radius * drawAngleRad));
const auto dTheta = drawAngleRad / N;
QPainterPath path;
for (int i = 0; i <= N; ++i) {
const auto theta = m_startAngleRad + i * dTheta;
const auto s = i * dTheta * radius;
const auto phi = m_frequency * 2 * M_PI * (s + m_startX) / len + phase;
const auto r = radius + amplitude * qSin(phi);
const auto px = cx + r * qCos(theta);
const auto py = cy + r * qSin(theta);
if (i == 0) {
path.moveTo(px, py);
} else {
path.lineTo(px, py);
}
}
painter->drawPath(path);
}
} // namespace ZShell::controls
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include <qqmlintegration.h>
#include <qquickpainteditem.h>
namespace ZShell::controls {
class WavyLine : public QQuickPaintedItem {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged FINAL)
Q_PROPERTY(qreal amplitudeMultiplier READ amplitudeMultiplier WRITE setAmplitudeMultiplier NOTIFY
amplitudeMultiplierChanged FINAL)
Q_PROPERTY(int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged FINAL)
Q_PROPERTY(qreal startX READ startX WRITE setStartX NOTIFY startXChanged FINAL)
Q_PROPERTY(qreal fullLength READ fullLength WRITE setFullLength NOTIFY fullLengthChanged FINAL)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
Q_PROPERTY(qreal waveProgress READ waveProgress WRITE setWaveProgress NOTIFY waveProgressChanged FINAL)
Q_PROPERTY(PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged FINAL)
Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged FINAL)
Q_PROPERTY(qreal fullAngle READ fullAngle WRITE setFullAngle NOTIFY fullAngleChanged FINAL)
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL)
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL)
public:
enum PathType {
Linear,
Arc
};
Q_ENUM(PathType)
explicit WavyLine(QQuickItem* parent = nullptr);
[[nodiscard]] int lineWidth() const;
void setLineWidth(int lineWidth);
[[nodiscard]] qreal amplitudeMultiplier() const;
void setAmplitudeMultiplier(qreal amplitudeMultiplier);
[[nodiscard]] int frequency() const;
void setFrequency(int frequency);
[[nodiscard]] qreal startX() const;
void setStartX(qreal startX);
[[nodiscard]] qreal fullLength() const;
void setFullLength(qreal fullLength);
[[nodiscard]] QColor color() const;
void setColor(const QColor& color);
[[nodiscard]] qreal waveProgress() const;
void setWaveProgress(qreal progress);
[[nodiscard]] PathType pathType() const;
void setPathType(PathType pathType);
[[nodiscard]] qreal startAngle() const;
void setStartAngle(qreal startAngle);
[[nodiscard]] qreal fullAngle() const;
void setFullAngle(qreal fullAngle);
[[nodiscard]] qreal radius() const;
void setRadius(qreal radius);
[[nodiscard]] qreal value() const;
void setValue(qreal value);
void paint(QPainter* painter) override;
signals:
void lineWidthChanged();
void amplitudeMultiplierChanged();
void frequencyChanged();
void startXChanged();
void fullLengthChanged();
void colorChanged();
void waveProgressChanged();
void pathTypeChanged();
void startAngleChanged();
void fullAngleChanged();
void radiusChanged();
void valueChanged();
private:
void paintLinear(QPainter* painter);
void paintArc(QPainter* painter);
int m_lineWidth;
qreal m_amplitudeMultiplier;
int m_frequency;
qreal m_startX;
qreal m_fullLength;
QColor m_color;
qreal m_waveProgress;
PathType m_pathType;
qreal m_startAngle;
qreal m_fullAngle;
qreal m_radius;
qreal m_value;
qreal m_startAngleRad;
qreal m_fullAngleRad;
};
} // namespace ZShell::controls
+2
View File
@@ -8,6 +8,8 @@ qml_module(ZShell-internal
circularbuffer.hpp circularbuffer.cpp circularbuffer.hpp circularbuffer.cpp
sparklineitem.hpp sparklineitem.cpp sparklineitem.hpp sparklineitem.cpp
arcgauge.hpp arcgauge.cpp arcgauge.hpp arcgauge.cpp
wallpaperimage.hpp wallpaperimage.cpp
lidwatcher.hpp lidwatcher.cpp
LIBRARIES LIBRARIES
Qt::Gui Qt::Gui
Qt::Quick Qt::Quick
+2 -2
View File
@@ -4,7 +4,7 @@
#include <qpainter.h> #include <qpainter.h>
#include <qpen.h> #include <qpen.h>
namespace caelestia::internal { namespace ZShell::internal {
ArcGauge::ArcGauge(QQuickItem* parent) ArcGauge::ArcGauge(QQuickItem* parent)
: QQuickPaintedItem(parent) { : QQuickPaintedItem(parent) {
@@ -116,4 +116,4 @@ void ArcGauge::setLineWidth(qreal width) {
update(); update();
} }
} // namespace caelestia::internal } // namespace ZShell::internal
+2 -2
View File
@@ -5,7 +5,7 @@
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qquickpainteditem.h> #include <qquickpainteditem.h>
namespace caelestia::internal { namespace ZShell::internal {
class ArcGauge : public QQuickPaintedItem { class ArcGauge : public QQuickPaintedItem {
Q_OBJECT Q_OBJECT
@@ -58,4 +58,4 @@ qreal m_sweepAngle = 1.5 * M_PI;
qreal m_lineWidth = 10.0; qreal m_lineWidth = 10.0;
}; };
} // namespace caelestia::internal } // namespace ZShell::Internal
+35 -34
View File
@@ -7,59 +7,60 @@
namespace ZShell::internal { namespace ZShell::internal {
class CachingImageManager : public QObject { class CachingImageManager : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED) Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED)
Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED) Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged) Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged)
public: public:
explicit CachingImageManager(QObject* parent = nullptr) explicit CachingImageManager(QObject* parent = nullptr)
: QObject(parent) : QObject(parent)
, m_item(nullptr) {} , m_item(nullptr) {
}
[[nodiscard]] QQuickItem* item() const; [[nodiscard]] QQuickItem* item() const;
void setItem(QQuickItem* item); void setItem(QQuickItem* item);
[[nodiscard]] QUrl cacheDir() const; [[nodiscard]] QUrl cacheDir() const;
void setCacheDir(const QUrl& cacheDir); void setCacheDir(const QUrl& cacheDir);
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
void setPath(const QString& path); void setPath(const QString& path);
[[nodiscard]] QUrl cachePath() const; [[nodiscard]] QUrl cachePath() const;
Q_INVOKABLE void updateSource(); Q_INVOKABLE void updateSource();
Q_INVOKABLE void updateSource(const QString& path); Q_INVOKABLE void updateSource(const QString& path);
signals: signals:
void itemChanged(); void itemChanged();
void cacheDirChanged(); void cacheDirChanged();
void pathChanged(); void pathChanged();
void cachePathChanged(); void cachePathChanged();
void usingCacheChanged(); void usingCacheChanged();
private: private:
QString m_shaPath; QString m_shaPath;
QQuickItem* m_item; QQuickItem* m_item;
QUrl m_cacheDir; QUrl m_cacheDir;
QString m_path; QString m_path;
QUrl m_cachePath; QUrl m_cachePath;
QMetaObject::Connection m_widthConn; QMetaObject::Connection m_widthConn;
QMetaObject::Connection m_heightConn; QMetaObject::Connection m_heightConn;
[[nodiscard]] qreal effectiveScale() const; [[nodiscard]] qreal effectiveScale() const;
[[nodiscard]] QSize effectiveSize() const; [[nodiscard]] QSize effectiveSize() const;
void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const; void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const;
[[nodiscard]] static QString sha256sum(const QString& path); [[nodiscard]] static QString sha256sum(const QString& path);
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
+2 -2
View File
@@ -2,7 +2,7 @@
#include <algorithm> #include <algorithm>
namespace caelestia::internal { namespace ZShell::internal {
CircularBuffer::CircularBuffer(QObject* parent) CircularBuffer::CircularBuffer(QObject* parent)
: QObject(parent) { : QObject(parent) {
@@ -92,4 +92,4 @@ qreal CircularBuffer::at(int index) const {
return m_data[actualIndex]; return m_data[actualIndex];
} }
} // namespace caelestia::internal } // namespace ZShell::internal
+2 -2
View File
@@ -4,7 +4,7 @@
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qvector.h> #include <qvector.h>
namespace caelestia::internal { namespace ZShell::internal {
class CircularBuffer : public QObject { class CircularBuffer : public QObject {
Q_OBJECT Q_OBJECT
@@ -41,4 +41,4 @@ int m_count = 0;
int m_capacity = 0; int m_capacity = 0;
}; };
} // namespace caelestia::internal } // namespace ZShell::internal
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstdint>
#include <qeasingcurve.h> #include <qeasingcurve.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
+40 -40
View File
@@ -8,67 +8,67 @@
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
class HyprKeyboard : public QObject { class HyprKeyboard : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("HyprKeyboard instances can only be retrieved from a HyprDevices") QML_UNCREATABLE("HyprKeyboard instances can only be retrieved from a HyprDevices")
Q_PROPERTY(QVariantHash lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged) Q_PROPERTY(QVariantHash lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged)
Q_PROPERTY(QString address READ address NOTIFY addressChanged) Q_PROPERTY(QString address READ address NOTIFY addressChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged) Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged)
Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged) Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged)
Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged) Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged)
Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged) Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged)
Q_PROPERTY(bool main READ main NOTIFY mainChanged) Q_PROPERTY(bool main READ main NOTIFY mainChanged)
public: public:
explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr); explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr);
[[nodiscard]] QVariantHash lastIpcObject() const; [[nodiscard]] QVariantHash lastIpcObject() const;
[[nodiscard]] QString address() const; [[nodiscard]] QString address() const;
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] QString layout() const; [[nodiscard]] QString layout() const;
[[nodiscard]] QString activeKeymap() const; [[nodiscard]] QString activeKeymap() const;
[[nodiscard]] bool capsLock() const; [[nodiscard]] bool capsLock() const;
[[nodiscard]] bool numLock() const; [[nodiscard]] bool numLock() const;
[[nodiscard]] bool main() const; [[nodiscard]] bool main() const;
bool updateLastIpcObject(QJsonObject object); bool updateLastIpcObject(QJsonObject object);
signals: signals:
void lastIpcObjectChanged(); void lastIpcObjectChanged();
void addressChanged(); void addressChanged();
void nameChanged(); void nameChanged();
void layoutChanged(); void layoutChanged();
void activeKeymapChanged(); void activeKeymapChanged();
void capsLockChanged(); void capsLockChanged();
void numLockChanged(); void numLockChanged();
void mainChanged(); void mainChanged();
private: private:
QJsonObject m_lastIpcObject; QJsonObject m_lastIpcObject;
}; };
class HyprDevices : public QObject { class HyprDevices : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("HyprDevices instances can only be retrieved from a HyprExtras") QML_UNCREATABLE("HyprDevices instances can only be retrieved from a HyprExtras")
Q_PROPERTY( Q_PROPERTY(
QQmlListProperty<ZShell::internal::hypr::HyprKeyboard> keyboards READ keyboards NOTIFY keyboardsChanged) QQmlListProperty<ZShell::internal::hypr::HyprKeyboard> keyboards READ keyboards NOTIFY keyboardsChanged)
public: public:
explicit HyprDevices(QObject* parent = nullptr); explicit HyprDevices(QObject* parent = nullptr);
[[nodiscard]] QQmlListProperty<HyprKeyboard> keyboards(); [[nodiscard]] QQmlListProperty<HyprKeyboard> keyboards();
bool updateLastIpcObject(QJsonObject object); bool updateLastIpcObject(QJsonObject object);
signals: signals:
void keyboardsChanged(); void keyboardsChanged();
private: private:
QList<HyprKeyboard*> m_keyboards; QList<HyprKeyboard*> m_keyboards;
}; };
} // namespace ZShell::internal::hypr } // namespace ZShell::internal::hypr
+140 -19
View File
@@ -1,11 +1,18 @@
#include "hyprextras.hpp" #include "hyprextras.hpp"
#include "hyprdevices.hpp" #include "hyprdevices.hpp"
#include <functional>
#include <memory>
#include <qdir.h> #include <qdir.h>
#include <qcolor.h>
#include <qjsonarray.h> #include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qmetatype.h> #include <qmetatype.h>
#include <qobject.h>
#include <qregularexpression.h> #include <qregularexpression.h>
#include <qvariant.h> #include <qvariant.h>
@@ -163,6 +170,86 @@ static QString buildHlConfigCall(const QString& key, const QVariant& value) {
return out; return out;
} }
static QColor colorFromInt(quint32 value) {
const int a = (value >> 24) & 0xFF;
const int r = (value >> 16) & 0xFF;
const int g = (value >> 8) & 0xFF;
const int b = value & 0xFF;
return QColor(r, g, b, a);
}
static QVariant parseGetOptionValue(const QJsonObject& obj) {
if (obj.contains(QStringLiteral("bool"))) {
return obj.value(QStringLiteral("bool")).toBool();
}
if (obj.contains(QStringLiteral("int"))) {
const auto value = obj.value(QStringLiteral("int")).toInt();
const auto option = obj.value(QStringLiteral("option")).toString();
if (option.contains(QStringLiteral("color")) || option.contains(QStringLiteral("col."))) {
return colorFromInt(static_cast<quint32>(value));
}
return value;
}
if (obj.contains(QStringLiteral("float"))) {
return obj.value(QStringLiteral("float")).toDouble();
}
if (obj.contains(QStringLiteral("str"))) {
return obj.value(QStringLiteral("str")).toString();
}
if (obj.contains(QStringLiteral("current"))) {
return obj.value(QStringLiteral("current")).toVariant();
}
if (obj.contains(QStringLiteral("value"))) {
return obj.value(QStringLiteral("value")).toVariant();
}
if (obj.contains(QStringLiteral("vec2"))) {
return obj.value(QStringLiteral("vec2")).toVariant();
}
if (obj.contains(QStringLiteral("data"))) {
const auto data = obj.value(QStringLiteral("data"));
if (data.isObject()) {
const auto d = data.toObject();
if (d.contains(QStringLiteral("current"))) {
return d.value(QStringLiteral("current")).toVariant();
}
if (d.contains(QStringLiteral("value"))) {
return d.value(QStringLiteral("value")).toVariant();
}
} else {
return data.toVariant();
}
}
return {};
}
static void insertNestedValue(QVariantMap& root, const QStringList& path, const QVariant& value) {
if (path.isEmpty()) {
return;
}
if (path.size() == 1) {
root.insert(path.first(), value);
return;
}
const QString head = path.first();
QVariantMap child = root.value(head).toMap();
insertNestedValue(child, path.mid(1), value);
root.insert(head, child);
}
} // namespace } // namespace
HyprExtras::HyprExtras(QObject* parent) HyprExtras::HyprExtras(QObject* parent)
@@ -203,7 +290,7 @@ HyprExtras::HyprExtras(QObject* parent)
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly); m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
} }
QVariantHash HyprExtras::options() const { QVariantMap HyprExtras::options() const {
return m_options; return m_options;
} }
@@ -269,30 +356,64 @@ void HyprExtras::refreshOptions() {
m_optionsRefresh->close(); m_optionsRefresh->close();
} }
m_optionsRefresh = makeRequestJson(QStringLiteral("descriptions"), [this](bool success, const QJsonDocument& response) { ++m_optionsRefreshGeneration;
m_optionsRefresh.reset(); const quint64 generation = m_optionsRefreshGeneration;
if (!success) {
static const QStringList optionKeys = {
QStringLiteral("general:border_size"),
QStringLiteral("decoration:rounding"),
QStringLiteral("animations:enabled"),
QStringLiteral("decoration:shadow:enabled"),
QStringLiteral("decoration:shadow:offset"),
QStringLiteral("decoration:shadow:color"),
QStringLiteral("decoration:shadow:range"),
QStringLiteral("decoration:shadow:render_power"),
};
auto nextOptions = std::make_shared<QVariantMap>();
auto step = std::make_shared<std::function<void(int)> >();
*step = [this, generation, nextOptions, step](int index) {
if (generation != m_optionsRefreshGeneration) {
return; return;
} }
const auto options = response.array(); if (index >= optionKeys.size()) {
bool dirty = false; if (m_options != *nextOptions) {
m_options = *nextOptions;
for (const auto& o : std::as_const(options)) { emit optionsChanged();
const auto obj = o.toObject();
const auto key = obj.value(QStringLiteral("value")).toString();
const auto value = obj.value(QStringLiteral("data")).toObject().value(QStringLiteral("current")).toVariant();
if (m_options.value(key) != value) {
dirty = true;
m_options.insert(key, value);
} }
return;
} }
if (dirty) { const QString key = optionKeys.at(index);
emit optionsChanged();
} m_optionsRefresh = makeRequestJson(
}); QStringLiteral("getoption ") + key,
[this, generation, nextOptions, step, index, key](bool success, const QJsonDocument& response)
{
m_optionsRefresh.reset();
if (generation != m_optionsRefreshGeneration) {
return;
}
if (success && response.isObject()) {
const QVariant value = parseGetOptionValue(response.object());
if (value.isValid()) {
insertNestedValue(*nextOptions, key.split(QLatin1Char(':'), Qt::SkipEmptyParts), value);
} else {
qCWarning(lcHypr) << "refreshOptions: getoption returned no usable value for" << key;
}
} else if (!success) {
qCWarning(lcHypr) << "refreshOptions: getoption request error for" << key;
}
(*step)(index + 1);
});
};
(*step)(0);
} }
void HyprExtras::refreshDevices() { void HyprExtras::refreshDevices() {
+8 -3
View File
@@ -1,9 +1,13 @@
#pragma once #pragma once
#include <functional>
#include <qjsondocument.h>
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qsharedpointer.h> #include <qsharedpointer.h>
#include <qstringlist.h>
#include <qvariant.h> #include <qvariant.h>
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
@@ -15,13 +19,13 @@ Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_MOC_INCLUDE("hyprdevices.hpp") Q_MOC_INCLUDE("hyprdevices.hpp")
Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged) Q_PROPERTY(QVariantMap options READ options NOTIFY optionsChanged)
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT) Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
public: public:
explicit HyprExtras(QObject* parent = nullptr); explicit HyprExtras(QObject* parent = nullptr);
[[nodiscard]] QVariantHash options() const; [[nodiscard]] QVariantMap options() const;
[[nodiscard]] HyprDevices* devices() const; [[nodiscard]] HyprDevices* devices() const;
Q_INVOKABLE void message(const QString& message); Q_INVOKABLE void message(const QString& message);
@@ -42,11 +46,12 @@ QString m_eventSocket;
QLocalSocket* m_socket; QLocalSocket* m_socket;
bool m_socketValid; bool m_socketValid;
QVariantHash m_options; QVariantMap m_options;
HyprDevices* const m_devices; HyprDevices* const m_devices;
SocketPtr m_optionsRefresh; SocketPtr m_optionsRefresh;
SocketPtr m_devicesRefresh; SocketPtr m_devicesRefresh;
quint64 m_optionsRefreshGeneration = 0;
void socketError(QLocalSocket::LocalSocketError error) const; void socketError(QLocalSocket::LocalSocketError error) const;
void socketStateChanged(QLocalSocket::LocalSocketState state); void socketStateChanged(QLocalSocket::LocalSocketState state);
+86
View File
@@ -0,0 +1,86 @@
#include "lidwatcher.hpp"
#include <QtDBus/qdbusconnection.h>
#include <QtDBus/qdbuserror.h>
#include <QtDBus/qdbusinterface.h>
#include <QtDBus/qdbusreply.h>
#include <qloggingcategory.h>
Q_LOGGING_CATEGORY(lcLidWatcher, "ZShell.internal.logindmanager", QtInfoMsg)
namespace ZShell::internal {
LidWatcher::LidWatcher(QObject* parent) : QObject(parent) {
auto bus = QDBusConnection::systemBus();
if (!bus.isConnected()) {
qCWarning(lcLidWatcher)
<< "Failed to connect to system bus:" << bus.lastError().message();
return;
}
bool ok = bus.connect("org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"PrepareForSleep",
this,
SLOT(handlePrepareForSleep(bool)));
if (!ok) {
qCWarning(lcLidWatcher)
<< "Failed to connect to PrepareForSleep signal:"
<< bus.lastError().message();
}
QDBusInterface login1("org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
bus);
const QDBusReply<QDBusObjectPath> reply = login1.call("GetSession", "auto");
if (!reply.isValid()) {
qCWarning(lcLidWatcher) << "Failed to get session path";
return;
}
const auto sessionPath = reply.value().path();
ok = bus.connect("org.freedesktop.login1",
sessionPath,
"org.freedesktop.login1.Session",
"Lock",
this,
SLOT(handleLockRequested()));
if (!ok) {
qCWarning(lcLidWatcher)
<< "Failed to connect to Lock signal:" << bus.lastError().message();
}
ok = bus.connect("org.freedesktop.login1",
sessionPath,
"org.freedesktop.login1.Session",
"Unlock",
this,
SLOT(handleUnlockRequested()));
if (!ok) {
qCWarning(lcLidWatcher) << "Failed to connect to Unlock signal:"
<< bus.lastError().message();
}
}
void LidWatcher::handlePrepareForSleep(bool sleep) {
if (sleep) {
emit aboutToSleep();
} else {
emit resumed();
}
}
void LidWatcher::handleLockRequested() {
emit lockRequested();
}
void LidWatcher::handleUnlockRequested() {
emit unlockRequested();
}
} // namespace ZShell::internal
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <qobject.h>
#include <qqmlintegration.h>
namespace ZShell::internal {
class LidWatcher : public QObject {
Q_OBJECT
QML_ELEMENT
public:
explicit LidWatcher(QObject* parent = nullptr);
signals:
void aboutToSleep();
void resumed();
void lockRequested();
void unlockRequested();
private slots:
void handlePrepareForSleep(bool sleep);
void handleLockRequested();
void handleUnlockRequested();
};
} // namespace ZShell::internal
+2 -2
View File
@@ -4,7 +4,7 @@
#include <qpainterpath.h> #include <qpainterpath.h>
#include <qpen.h> #include <qpen.h>
namespace caelestia::internal { namespace ZShell::internal {
SparklineItem::SparklineItem(QQuickItem* parent) SparklineItem::SparklineItem(QQuickItem* parent)
: QQuickPaintedItem(parent) { : QQuickPaintedItem(parent) {
@@ -212,4 +212,4 @@ void SparklineItem::setLineWidth(qreal width) {
update(); update();
} }
} // namespace caelestia::internal } // namespace ZShell::internal
+2 -2
View File
@@ -7,7 +7,7 @@
#include "circularbuffer.hpp" #include "circularbuffer.hpp"
namespace caelestia::internal { namespace ZShell::internal {
class SparklineItem : public QQuickPaintedItem { class SparklineItem : public QQuickPaintedItem {
Q_OBJECT Q_OBJECT
@@ -87,4 +87,4 @@ int m_historyLength = 30;
qreal m_lineWidth = 2.0; qreal m_lineWidth = 2.0;
}; };
} // namespace caelestia::internal } // namespace ZShell::internal
+199
View File
@@ -0,0 +1,199 @@
#include "wallpaperimage.hpp"
#include <QStandardPaths>
#include <QCryptographicHash>
#include <QDir>
#include <QFileInfo>
#include <QtConcurrent>
#include <QSGImageNode>
#include <QQuickWindow>
namespace ZShell::internal {
WallpaperImage::WallpaperImage(QQuickItem *parent)
: QQuickItem(parent)
{
setFlag(ItemHasContents, true);
connect(&m_imageWatcher, &QFutureWatcher<QImage>::finished, this, &WallpaperImage::handleImageLoaded);
}
WallpaperImage::~WallpaperImage() {
if (m_texture) delete m_texture;
}
void WallpaperImage::setSource(const QUrl &source) {
if (m_source == source) return;
m_source = source;
emit sourceChanged();
loadImage();
}
void WallpaperImage::setScreenResolution(const QSize &screenResolution) {
if (m_screenResolution == screenResolution) return;
m_screenResolution = screenResolution;
emit screenResolutionChanged();
loadImage();
}
void WallpaperImage::setZoom(qreal zoom) {
if (qFuzzyCompare(m_zoom, zoom)) return;
m_zoom = zoom;
emit zoomChanged();
update();
}
void WallpaperImage::setCropX(qreal x) {
if (qFuzzyCompare(m_cropX, x)) return;
m_cropX = x;
emit cropXChanged();
update();
}
void WallpaperImage::setCropY(qreal y) {
if (qFuzzyCompare(m_cropY, y)) return;
m_cropY = y;
emit cropYChanged();
update();
}
void WallpaperImage::setCropWidth(qreal w) {
if (w <= 0.0) w = 1.0;
if (qFuzzyCompare(m_cropWidth, w)) return;
m_cropWidth = w;
emit cropWidthChanged();
update();
}
void WallpaperImage::setCropHeight(qreal h) {
if (h <= 0.0) h = 1.0;
if (qFuzzyCompare(m_cropHeight, h)) return;
m_cropHeight = h;
emit cropHeightChanged();
update();
}
QString WallpaperImage::getCacheFilePath() const {
if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString();
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/zshell/imagecache";
QDir().mkpath(cachePath);
// Hash the source URL + resolution
QString id = m_source.toString() + "_" + QString::number(m_screenResolution.width()) + "x" + QString::number(m_screenResolution.height());
QByteArray hash = QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex();
return cachePath + "/" + hash + ".png";
}
void WallpaperImage::loadImage() {
if (m_source.isEmpty()) return;
QString cacheFile = getCacheFilePath();
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString();
// Qt resource path correction if passed as a standard URL string
if (sourceFile.startsWith("qrc:/")) {
sourceFile = sourceFile.mid(3); // Converts "qrc:/" to ":/"
}
QSize targetRes = m_screenResolution;
// Run off the main thread to avoid blocking the UI
QFuture<QImage> future = QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage {
if (!targetRes.isEmpty() && !cacheFile.isEmpty() && QFileInfo::exists(cacheFile)) {
QImage cached(cacheFile);
if (!cached.isNull()) return cached;
}
QImage original(sourceFile);
if (original.isNull()) return QImage();
if (targetRes.isEmpty()) {
// Screen resolution not set yet by QML, return the unscaled original for now to prevent a black screen
return original;
}
// Check if original is strictly larger than screen resolution
if (original.width() > targetRes.width() || original.height() > targetRes.height()) {
QImage scaled = original.scaled(targetRes, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG");
return scaled;
}
// Otherwise just cache and return the original
if (!cacheFile.isEmpty()) original.save(cacheFile, "PNG");
return original;
});
m_imageWatcher.setFuture(future);
}
void WallpaperImage::handleImageLoaded() {
m_image = m_imageWatcher.result();
m_textureDirty = true;
update(); // Request redraw
}
QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) {
auto *node = static_cast<QSGImageNode *>(oldNode);
if (m_image.isNull()) {
delete node;
return nullptr;
}
if (!node) {
node = window()->createImageNode();
}
if (m_textureDirty) {
if (m_texture) delete m_texture;
m_texture = window()->createTextureFromImage(m_image, QQuickWindow::TextureHasAlphaChannel);
m_textureDirty = false;
}
if (m_texture) {
node->setTexture(m_texture);
node->setRect(boundingRect());
node->setFiltering(QSGTexture::Linear);
qreal cW = m_cropWidth / m_zoom;
qreal cH = m_cropHeight / m_zoom;
QRectF reqRect(
m_cropX * m_texture->textureSize().width(),
m_cropY * m_texture->textureSize().height(),
cW * m_texture->textureSize().width(),
cH * m_texture->textureSize().height()
);
QRectF bounds = boundingRect();
if (bounds.isEmpty() || reqRect.isEmpty()) return node;
qreal targetRatio = bounds.width() / bounds.height();
qreal reqRatio = reqRect.width() / reqRect.height();
QRectF sourceRect = reqRect;
// Force 'PreserveAspectCrop' behavior on the requested region
if (reqRatio > targetRatio) {
// Requested region is too wide, center-crop the sides
qreal newWidth = reqRect.height() * targetRatio;
qreal xOffset = (reqRect.width() - newWidth) / 2.0;
sourceRect.setX(reqRect.x() + xOffset);
sourceRect.setWidth(newWidth);
} else if (reqRatio < targetRatio) {
// Requested region is too tall, center-crop the top/bottom
qreal newHeight = reqRect.width() / targetRatio;
qreal yOffset = (reqRect.height() - newHeight) / 2.0;
sourceRect.setY(reqRect.y() + yOffset);
sourceRect.setHeight(newHeight);
}
node->setSourceRect(sourceRect);
}
return node;
}
} // namespace ZShell::internal
@@ -0,0 +1,95 @@
#pragma once
#include <QQuickItem>
#include <QImage>
#include <QUrl>
#include <QSGTexture>
#include <QFutureWatcher>
#include <QtQml/qqml.h>
namespace ZShell::internal {
class WallpaperImage : public QQuickItem {
Q_OBJECT
QML_NAMED_ELEMENT(WallpaperImage)
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QSize screenResolution READ screenResolution WRITE setScreenResolution NOTIFY screenResolutionChanged)
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
Q_PROPERTY(qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY cropWidthChanged)
Q_PROPERTY(qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY cropHeightChanged)
public:
explicit WallpaperImage(QQuickItem *parent = nullptr);
~WallpaperImage() override;
QUrl source() const {
return m_source;
}
void setSource(const QUrl &source);
QSize screenResolution() const {
return m_screenResolution;
}
void setScreenResolution(const QSize &screenResolution);
qreal zoom() const {
return m_zoom;
}
void setZoom(qreal zoom);
qreal cropX() const {
return m_cropX;
}
void setCropX(qreal x);
qreal cropY() const {
return m_cropY;
}
void setCropY(qreal y);
qreal cropWidth() const {
return m_cropWidth;
}
void setCropWidth(qreal w);
qreal cropHeight() const {
return m_cropHeight;
}
void setCropHeight(qreal h);
protected:
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override;
signals:
void sourceChanged();
void screenResolutionChanged();
void zoomChanged();
void cropXChanged();
void cropYChanged();
void cropWidthChanged();
void cropHeightChanged();
private:
void loadImage();
void handleImageLoaded();
QString getCacheFilePath() const;
QUrl m_source;
QSize m_screenResolution;
qreal m_zoom = 1.0;
qreal m_cropX = 0.0;
qreal m_cropY = 0.0;
qreal m_cropWidth = 1.0;
qreal m_cropHeight = 1.0;
QImage m_image;
QSGTexture *m_texture = nullptr;
bool m_textureDirty = false;
QFutureWatcher<QImage> m_imageWatcher;
};
} // namespace ZShell::internal
+96 -95
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstdint>
#include <qabstractitemmodel.h> #include <qabstractitemmodel.h>
#include <qdir.h> #include <qdir.h>
#include <qfilesystemwatcher.h> #include <qfilesystemwatcher.h>
@@ -13,136 +14,136 @@
namespace ZShell::models { namespace ZShell::models {
class FileSystemEntry : public QObject { class FileSystemEntry : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel") QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
Q_PROPERTY(QString path READ path CONSTANT) Q_PROPERTY(QString path READ path CONSTANT)
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged) Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString baseName READ baseName CONSTANT) Q_PROPERTY(QString baseName READ baseName CONSTANT)
Q_PROPERTY(QString parentDir READ parentDir CONSTANT) Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
Q_PROPERTY(QString suffix READ suffix CONSTANT) Q_PROPERTY(QString suffix READ suffix CONSTANT)
Q_PROPERTY(qint64 size READ size CONSTANT) Q_PROPERTY(qint64 size READ size CONSTANT)
Q_PROPERTY(bool isDir READ isDir CONSTANT) Q_PROPERTY(bool isDir READ isDir CONSTANT)
Q_PROPERTY(bool isImage READ isImage CONSTANT) Q_PROPERTY(bool isImage READ isImage CONSTANT)
Q_PROPERTY(QString mimeType READ mimeType CONSTANT) Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
public: public:
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr); explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr);
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
[[nodiscard]] QString relativePath() const; [[nodiscard]] QString relativePath() const;
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] QString baseName() const; [[nodiscard]] QString baseName() const;
[[nodiscard]] QString parentDir() const; [[nodiscard]] QString parentDir() const;
[[nodiscard]] QString suffix() const; [[nodiscard]] QString suffix() const;
[[nodiscard]] qint64 size() const; [[nodiscard]] qint64 size() const;
[[nodiscard]] bool isDir() const; [[nodiscard]] bool isDir() const;
[[nodiscard]] bool isImage() const; [[nodiscard]] bool isImage() const;
[[nodiscard]] QString mimeType() const; [[nodiscard]] QString mimeType() const;
void updateRelativePath(const QDir& dir); void updateRelativePath(const QDir& dir);
signals: signals:
void relativePathChanged(); void relativePathChanged();
private: private:
const QFileInfo m_fileInfo; const QFileInfo m_fileInfo;
const QString m_path; const QString m_path;
QString m_relativePath; QString m_relativePath;
mutable bool m_isImage; mutable bool m_isImage;
mutable bool m_isImageInitialised; mutable bool m_isImageInitialised;
mutable QString m_mimeType; mutable QString m_mimeType;
mutable bool m_mimeTypeInitialised; mutable bool m_mimeTypeInitialised;
}; };
class FileSystemModel : public QAbstractListModel { class FileSystemModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged) Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged) Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged) Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged) Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged) Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged) Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged) Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged)
public: public:
enum Filter { enum Filter {
NoFilter, NoFilter,
Images, Images,
Files, Files,
Dirs Dirs
}; };
Q_ENUM(Filter) Q_ENUM(Filter)
explicit FileSystemModel(QObject* parent = nullptr); explicit FileSystemModel(QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = QModelIndex()) const override; [[nodiscard]] int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override; [[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
void setPath(const QString& path); void setPath(const QString& path);
[[nodiscard]] bool recursive() const; [[nodiscard]] bool recursive() const;
void setRecursive(bool recursive); void setRecursive(bool recursive);
[[nodiscard]] bool watchChanges() const; [[nodiscard]] bool watchChanges() const;
void setWatchChanges(bool watchChanges); void setWatchChanges(bool watchChanges);
[[nodiscard]] bool showHidden() const; [[nodiscard]] bool showHidden() const;
void setShowHidden(bool showHidden); void setShowHidden(bool showHidden);
[[nodiscard]] bool sortReverse() const; [[nodiscard]] bool sortReverse() const;
void setSortReverse(bool sortReverse); void setSortReverse(bool sortReverse);
[[nodiscard]] Filter filter() const; [[nodiscard]] Filter filter() const;
void setFilter(Filter filter); void setFilter(Filter filter);
[[nodiscard]] QStringList nameFilters() const; [[nodiscard]] QStringList nameFilters() const;
void setNameFilters(const QStringList& nameFilters); void setNameFilters(const QStringList& nameFilters);
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries(); [[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
signals: signals:
void pathChanged(); void pathChanged();
void recursiveChanged(); void recursiveChanged();
void watchChangesChanged(); void watchChangesChanged();
void showHiddenChanged(); void showHiddenChanged();
void sortReverseChanged(); void sortReverseChanged();
void filterChanged(); void filterChanged();
void nameFiltersChanged(); void nameFiltersChanged();
void entriesChanged(); void entriesChanged();
private: private:
QDir m_dir; QDir m_dir;
QFileSystemWatcher m_watcher; QFileSystemWatcher m_watcher;
QList<FileSystemEntry*> m_entries; QList<FileSystemEntry*> m_entries;
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString>>>> m_futures; QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString> > > > m_futures;
QString m_path; QString m_path;
bool m_recursive; bool m_recursive;
bool m_watchChanges; bool m_watchChanges;
bool m_showHidden; bool m_showHidden;
bool m_sortReverse; bool m_sortReverse;
Filter m_filter; Filter m_filter;
QStringList m_nameFilters; QStringList m_nameFilters;
void watchDirIfRecursive(const QString& path); void watchDirIfRecursive(const QString& path);
void update(); void update();
void updateWatcher(); void updateWatcher();
void updateEntries(); void updateEntries();
void updateEntriesForDir(const QString& dir); void updateEntriesForDir(const QString& dir);
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths); void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const; [[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
}; };
} // namespace ZShell::models } // namespace ZShell::models

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