160 Commits

Author SHA1 Message Date
zach 33746fca04 Initial commit for submenu popouts, unfinished 2026-05-20 14:07:38 +02:00
zach 853b683962 Changed base deform numbers, less bouncy
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Lint & Format (Python) / lint-format (pull_request) Successful in 24s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
2026-05-20 07:45:18 +02:00
zach b1bfcb3ed0 Merge branch 'main' into hypr-plugin
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Lint & Format (Python) / lint-format (pull_request) Successful in 19s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
2026-05-20 07:35:26 +02:00
zach 68662120ba Merge pull request 'Lint/formatter workflows merged per language. Resolved errors and warnings for each workflow' (#90) from 89-lint-format-fixes into main
Reviewed-on: #90
2026-05-20 07:33:56 +02:00
zach b8af60008d fixed applying hyprland options and rules, as well as fetching
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 24s
Lint (Rust) / lint (pull_request) Failing after 1m37s
2026-05-20 07:30:38 +02:00
zach b8524ff621 experimental hyprland lua support 2026-05-20 07:17:38 +02:00
zach ffde4063a0 experimental hyprland lua support 2026-05-20 07:13:05 +02:00
zach 96bf5f3365 cleanup for hyprland lua configs 2026-05-20 06:50:14 +02:00
zach 053efb4aaf reintroduce cargo caching
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 20s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
2026-05-20 05:23:56 +02:00
AramJonghu c88aef2164 removal of cache (for now)
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 20s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m33s
2026-05-20 01:03:51 +02:00
AramJonghu 01b54ec5e1 use of newest node version available + attempt cache v3 vs v4
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 25s
Lint & Format (Python) / lint-format (pull_request) Successful in 25s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m31s
2026-05-20 00:59:41 +02:00
AramJonghu 7276ee28dc minor updates to rust workflow
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 19s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m33s
2026-05-20 00:46:59 +02:00
AramJonghu a14ebe2016 wrong cache url
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 17s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m30s
2026-05-20 00:26:50 +02:00
AramJonghu d3f6765819 changed github specific caching to forgejo's solution
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Lint & Format (Python) / lint-format (pull_request) Successful in 18s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m31s
2026-05-20 00:19:46 +02:00
AramJonghu a3d0ee18cb added cargo to be cached to avoid recompiling every time. Also added echo messages for more info
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Lint & Format (Python) / lint-format (pull_request) Successful in 19s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m35s
2026-05-20 00:11:20 +02:00
AramJonghu c9d6b95ca5 main.rs formatted
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 20s
Lint & Format (Python) / lint-format (pull_request) Successful in 26s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m36s
2026-05-20 00:06:30 +02:00
AramJonghu 794a26a3fe eslint workflow now prints a succes or fail message eslint is run succesfully or if it ran but failed
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 18s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
2026-05-20 00:05:25 +02:00
AramJonghu ca3a288eab minor changes to workflows to prevent preemptive exits/failures
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Lint & Format (Python) / lint-format (pull_request) Successful in 21s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m34s
2026-05-20 00:02:14 +02:00
AramJonghu 902863e5ba adjusted workflows -> merge of lint/formatter per lang
Lint & Format (JS/TS) / lint-format (pull_request) Failing after 10s
Lint & Format (Python) / lint-format (pull_request) Failing after 19s
Lint & Format (Rust) / lint-format (pull_request) Failing after 33s
2026-05-19 23:57:11 +02:00
AramJonghu dd49198cf7 prettier and eslint ignore valid syntax for qml specific syntax. Clippy lint resolved in .rs. unused py files commented to be ignored by ruff 2026-05-19 23:53:09 +02:00
AramJonghu ceca949535 Merge pull request 'fix blobs dirty tracking' (#87) from blob-testing into main
Reviewed-on: #87
Reviewed-by: AramJonghu <2+aramjonghu@noreply.git.zach-dev.cc>
2026-05-19 23:18:57 +02:00
zach 24d5584b98 wallpaper now uses Image (hopefully temporarily)
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m36s
2026-05-19 22:12:37 +02:00
zach 62ec1b9f33 cleanup unneeded logging
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m34s
2026-05-19 16:15:39 +02:00
zach 9c6a1ce1a4 hide notification content on lockscreen, toggleable
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 18s
Lint (Rust) / lint (pull_request) Failing after 1m32s
2026-05-19 16:10:00 +02:00
zach b6ad180b6a select part of wallpaper to show
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 12s
Lint (Python) / lint (pull_request) Failing after 20s
Lint (Rust) / lint (pull_request) Failing after 1m40s
2026-05-19 15:38:59 +02:00
zach b20767c702 hopefully increase drawing performance
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 15s
Lint (Python) / lint (pull_request) Failing after 18s
Lint (Rust) / lint (pull_request) Failing after 1m32s
2026-05-19 10:04:04 +02:00
zach 362b7bb8c2 add the drawing popout background
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m36s
2026-05-19 08:43:52 +02:00
zach 405825518a move search index file to correct place
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m30s
2026-05-19 08:30:31 +02:00
zach db7a822caf fix some backgrounds, now attaching to wrappers rather than panel itself most of the time
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 19s
Lint (Rust) / lint (pull_request) Failing after 1m35s
2026-05-19 08:24:05 +02:00
zach 3bd9444e2f fix settings background desync
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 25s
Lint (Rust) / lint (pull_request) Failing after 1m33s
2026-05-19 07:09:39 +02:00
zach d0e696c681 update blobs
Format (JS/TS) / format (pull_request) Failing after 8s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 29s
Lint (Rust) / lint (pull_request) Failing after 1m34s
2026-05-19 04:52:28 +02:00
zach 550630feaa Revert "update blobs"
This reverts commit 8c22855dd8.
2026-05-19 04:24:24 +02:00
zach 8c22855dd8 update blobs 2026-05-19 04:20:35 +02:00
zach 015ee61885 revert blobs 2026-05-19 04:06:36 +02:00
zach 8fba953f52 update blobs 2026-05-19 04:04:32 +02:00
zach 3d2fc0a3b1 update blobs 2026-05-19 04:02:46 +02:00
zach c060be79e8 update blobs 2026-05-19 03:56:56 +02:00
zach cb1df5078b revert blobs 2026-05-19 03:55:48 +02:00
zach 5eb32fc30c update blobs 2026-05-19 03:53:47 +02:00
zach 0a84c822d5 update blobs 2026-05-19 03:50:24 +02:00
zach f89236f51e update blobs 2026-05-19 03:43:08 +02:00
zach 4b1316e887 log exclusion zones sizing, debugging inconsistent border exclusions 2026-05-18 18:25:23 +02:00
zach 63f4694322 rounding scale applies to more rounded elements, slight rounding changes to tray menu items 2026-05-17 00:52:53 +02:00
zach 51a8f1d5e1 remove residue file 2026-05-17 00:09:08 +02:00
zach d57010a501 Merge pull request 'prettierrc line update to 80 from 100, and singleQuote -> false from true' (#86) from forgejo-workflows into main
Reviewed-on: #86
2026-05-16 01:46:27 +02:00
AramJonghu d506d5ad27 not fmt, clippy linter
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 13s
Lint (Python) / lint (pull_request) Failing after 20s
Lint (Rust) / lint (pull_request) Failing after 1m40s
2026-05-16 01:42:30 +02:00
AramJonghu f6119072f7 rustfmt added
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 24s
Lint (Rust) / lint (pull_request) Failing after 35s
2026-05-16 01:40:08 +02:00
AramJonghu 00063309cd cargo not installed yet in yml
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 10s
Lint (Python) / lint (pull_request) Failing after 26s
Lint (Rust) / lint (pull_request) Failing after 36s
2026-05-16 01:38:57 +02:00
AramJonghu c30891de83 rustfmt w/o project
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 15s
Lint (Python) / lint (pull_request) Failing after 17s
Lint (Rust) / lint (pull_request) Failing after 38s
2026-05-16 01:37:38 +02:00
AramJonghu 55e9c0f267 rustfmt w/o project
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 21s
Lint (Rust) / lint (pull_request) Failing after 35s
2026-05-16 01:35:54 +02:00
AramJonghu 3424df53e2 Merge branch 'main' into forgejo-workflows
Format (JS/TS) / format (pull_request) Failing after 12s
Lint (JS/TS) / lint (pull_request) Failing after 15s
Lint (Python) / lint (pull_request) Failing after 31s
Lint (Rust) / lint (pull_request) Successful in 48s
2026-05-16 01:33:03 +02:00
AramJonghu 583c50f994 prettierrc line update to 80 from 100, and singleQuote -> false from true
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 12s
Lint (Python) / lint (pull_request) Failing after 20s
Lint (Rust) / lint (pull_request) Successful in 59s
2026-05-16 01:32:25 +02:00
zach ca53152630 Merge pull request 'testing ci using external forgejo runner' (#84) from forgejo-workflows into main
Reviewed-on: #84
2026-05-16 01:26:00 +02:00
zach 0cd7df243b Merge pull request 'Screenshot tool' (#83) from screenshot-tool into main
Reviewed-on: #83
2026-05-16 01:24:48 +02:00
AramJonghu 6a8ad4dbf2 removal qml linter, not worth
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 11s
Lint (Python) / lint (pull_request) Failing after 27s
Lint (Rust) / lint (pull_request) Successful in 29s
2026-05-16 01:15:42 +02:00
AramJonghu f57577fefd aur to arch
Format (JS/TS) / format (pull_request) Failing after 6s
Lint (JS/TS) / lint (pull_request) Failing after 35s
Lint (Python) / lint (pull_request) Failing after 45s
Lint (Rust) / lint (pull_request) Successful in 1m13s
Lint (QML) / lint (pull_request) Has been cancelled
2026-05-16 01:11:17 +02:00
AramJonghu 3a05cd339d qml to arch
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 18s
Lint (Python) / lint (pull_request) Failing after 18s
Lint (Rust) / lint (pull_request) Successful in 35s
Lint (QML) / lint (pull_request) Has been cancelled
2026-05-16 01:03:26 +02:00
AramJonghu c67a498f8d qml path issue fixed
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 15s
Lint (Python) / lint (pull_request) Failing after 25s
Lint (Rust) / lint (pull_request) Successful in 53s
Lint (QML) / lint (pull_request) Failing after 2m2s
2026-05-16 00:53:49 +02:00
AramJonghu e7e772ebc6 qml needs node
Format (JS/TS) / format (pull_request) Failing after 9s
Lint (JS/TS) / lint (pull_request) Failing after 20s
Lint (Python) / lint (pull_request) Failing after 27s
Lint (Rust) / lint (pull_request) Successful in 55s
Lint (QML) / lint (pull_request) Successful in 2m3s
2026-05-16 00:50:26 +02:00
AramJonghu fb2c9c6a21 qml goes to debian:sid for simplicity
Format (JS/TS) / format (pull_request) Failing after 10s
Lint (JS/TS) / lint (pull_request) Failing after 17s
Lint (Python) / lint (pull_request) Failing after 25s
Lint (QML) / lint (pull_request) Failing after 37s
Lint (Rust) / lint (pull_request) Successful in 42s
2026-05-16 00:43:03 +02:00
AramJonghu 383671344f qml goes to Ubuntu for simplicity
Format (JS/TS) / format (pull_request) Failing after 9s
Lint (JS/TS) / lint (pull_request) Failing after 18s
Lint (Python) / lint (pull_request) Failing after 22s
Lint (Rust) / lint (pull_request) Successful in 34s
Lint (QML) / lint (pull_request) Has been cancelled
2026-05-16 00:41:11 +02:00
AramJonghu beb1d96750 added eslint config
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 9s
Lint (Python) / lint (pull_request) Failing after 15s
Lint (QML) / lint (pull_request) Failing after 1m18s
Lint (Rust) / lint (pull_request) Successful in 30s
2026-05-16 00:37:20 +02:00
AramJonghu 6f8af9028b added eslint config
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Failing after 8s
Lint (Python) / lint (pull_request) Failing after 14s
Lint (QML) / lint (pull_request) Failing after 1m19s
Lint (Rust) / lint (pull_request) Has been cancelled
2026-05-16 00:35:18 +02:00
AramJonghu e874c19ee2 now should report issue when incorrect
Format (JS/TS) / format (pull_request) Failing after 7s
Lint (JS/TS) / lint (pull_request) Successful in 5s
Lint (Python) / lint (pull_request) Failing after 14s
Lint (Rust) / lint (pull_request) Has been cancelled
Lint (QML) / lint (pull_request) Has been cancelled
2026-05-16 00:33:37 +02:00
AramJonghu af04e5d227 case sensitivity
Format (JS/TS) / format (pull_request) Successful in 7s
Lint (JS/TS) / lint (pull_request) Successful in 5s
Lint (Python) / lint (pull_request) Successful in 14s
Lint (QML) / lint (pull_request) Successful in 1m20s
Lint (Rust) / lint (pull_request) Has been cancelled
2026-05-16 00:31:35 +02:00
AramJonghu 4ab19a8e37 separate all workflows, better overview
Format (JS/TS) / format (pull_request) Successful in 6s
Lint (JS/TS) / lint (pull_request) Successful in 5s
Lint (Python) / lint (pull_request) Successful in 14s
Lint (QML) / lint (pull_request) Successful in 1m19s
Lint (Rust) / lint (pull_request) Has been cancelled
2026-05-16 00:29:31 +02:00
AramJonghu 783d05f815 test different repo apline
CI (Lint + Format Checks) / lint (pull_request) Successful in 1m48s
2026-05-16 00:25:32 +02:00
AramJonghu 17fb9c0fef apline-edge for qmllint
CI (Lint + Format Checks) / lint (pull_request) Successful in 41s
2026-05-16 00:24:06 +02:00
AramJonghu 39cbfa2c93 minor adjustments and added .prettierrc.json
CI (Lint + Format Checks) / lint (pull_request) Successful in 1m41s
2026-05-16 00:16:17 +02:00
Inorishio 5c5018033d Merge branch 'screenshot-tool' of git.zach-dev.cc:zach/z-bar-qt into screenshot-tool 2026-05-16 00:15:10 +02:00
Inorishio d9afc6c7c7 Lockscreen buttons, HoverIconButton component added 2026-05-16 00:14:58 +02:00
AramJonghu 17fef78672 fix python venv in ci
CI (Lint + Format Checks) / lint (pull_request) Failing after 38s
2026-05-16 00:06:20 +02:00
AramJonghu c120dcae41 added node as this is required
CI (Lint + Format Checks) / lint (pull_request) Failing after 41s
2026-05-16 00:04:50 +02:00
AramJonghu 64e65ca9df test
CI (Lint + Format Checks) / lint (pull_request) Failing after 4s
2026-05-16 00:01:14 +02:00
AramJonghu 22a7993c07 test
CI (Lint + Format Checks) / lint (pull_request) Failing after 2s
2026-05-15 23:59:39 +02:00
AramJonghu 24526ca2d1 runs on Linux, not alpine?
CI (Lint + Format Checks) / lint (pull_request) Has been cancelled
2026-05-15 23:56:30 +02:00
AramJonghu c5ee27bf62 testing ci using external forgejo runner
CI (Lint + Format Checks) / lint (pull_request) Failing after 7s
2026-05-15 23:52:46 +02:00
zach 7cec08d262 update category changes 2026-05-15 23:42:10 +02:00
Inorishio 97b657ce9a dpms on and off fix no need to change configs 2026-05-15 19:02:48 +02:00
zach 33f6706658 update category changes 2026-05-15 18:59:04 +02:00
zach f6c4dc8ee1 experimental update category in settings 2026-05-14 01:47:04 +02:00
zach a53a4b32eb fix shadow blur cut off in screenshots 2026-05-13 19:43:34 +02:00
zach e80ac202d0 reordering slightly, setting up for pkgbuild 2026-05-13 18:00:32 +02:00
Inorishio 6e6f6c28f6 effects tool in /plugins, bin in /scripts, edited picker.qml to work with installed bin in /usr/bin 2026-05-13 17:35:19 +02:00
Inorishio 26bfa952d7 Screenshot settings + ss search 2026-05-12 22:56:46 +02:00
Inorishio 611abdf028 Rounded corners + shadow 2026-05-12 17:57:06 +02:00
Inorishio 37a112a04b Breaking zshell: toggle-launcher globalshortcut fix 2026-05-11 19:41:56 +02:00
zach bfc09c71a8 wifi fix hopefully 2026-05-09 00:46:46 +02:00
zach c7fabf9fc5 wifi fix hopefully 2026-05-09 00:40:06 +02:00
zach 0bae21c891 wifi fix hopefully 2026-05-09 00:36:43 +02:00
zach 53733c7fe0 icon fill anim 2026-05-09 00:11:32 +02:00
zach 836b92cc5f Merge pull request 'Bar entries rework in settings' (#80) from settings-bar-entries-rework into main
Reviewed-on: #80
2026-05-08 19:31:37 +02:00
zach bcc75abc54 labels instead of icons for bar entries 2026-05-08 19:27:36 +02:00
zach 11c185baa6 ipc call for launcher 2026-05-07 23:22:53 +02:00
zach 7cc056c327 ipc call for launcher 2026-05-07 23:19:26 +02:00
zach f657741551 fix for wifi toggle 2026-05-05 23:17:40 +02:00
zach 073e1dd8b1 primitive game mode toggle for Hyprland 2026-05-05 19:04:01 +02:00
zach 8fa447c63d end of rework, uses horizontal icon buttons instead of drag handlers and vertical views 2026-05-05 12:14:06 +02:00
zach e4113994dc start of rework 2026-05-03 18:09:55 +02:00
zach 8a2eeb6c31 add support for custom date formats via config 2026-05-03 17:30:10 +02:00
zach 10340a83dd notif open and exit anim fixed 2026-05-02 19:27:06 +02:00
zach c2bd45db4a updates popout delegate creation buffer 2026-04-30 23:51:10 +02:00
zach d5256a3952 Merge pull request 'Nix flake not up to date update' (#77) from nixos-readme into main
Reviewed-on: #77
2026-04-29 21:32:56 +02:00
AramJonghu abd85388f6 removal of binary file 2026-04-29 20:32:20 +02:00
AramJonghu 2bad732592 Nix flake not up to date update 2026-04-29 20:30:58 +02:00
zach 47120db391 gitignore 2026-04-29 20:22:00 +02:00
zach d568cd34ab double clicked 2026-04-29 20:19:22 +02:00
zach a3a55ba8d1 double clicked 2026-04-29 20:16:03 +02:00
zach 8db28ec3a0 oops 2026-04-29 20:01:02 +02:00
zach 9688e99a8e Merge pull request 'changes that should be in main' (#76) from 73-colorscheme-options into main
Reviewed-on: #76
2026-04-29 19:59:12 +02:00
zach cbc81a1af8 fix for lockscreen wallpaper 2026-04-29 19:58:45 +02:00
zach 14ec888269 half-fix for notif width when sidebar open 2026-04-29 19:14:36 +02:00
zach 2eb0529e98 start 2026-04-26 22:32:57 +02:00
zach 7a2786af66 fix scheme variants 2026-04-23 14:09:18 +02:00
zach 1eb2bbda00 use mono font for clock 2026-04-23 01:06:12 +02:00
zach 6b8ae3f291 font list + search 2026-04-23 01:00:57 +02:00
zach 4a18eda37c revert wallpaper 2026-04-22 23:07:16 +02:00
zach 85c126ad1e added microphone peak monitor to audio popout 2026-04-22 19:56:18 +02:00
zach 6316e788b9 fix notif hide when other popouts visible 2026-04-22 19:23:37 +02:00
zach 3cf206ea43 fix notif background 2026-04-22 18:48:27 +02:00
zach f870bbcc52 Merge pull request 'dashboard crash fix' (#70) from dashboard-crash-fix into main
Reviewed-on: #70
2026-04-22 15:48:25 +02:00
zach 09d61dc70d restore cava in dashboard 2026-04-22 15:46:53 +02:00
zach cd86fc53a3 restore seconds in time 2026-04-22 15:38:19 +02:00
zach 47b964d9ce make dashboard unload on close 2026-04-22 15:32:52 +02:00
zach 2b83c7784c test blob performance? 2026-04-22 14:41:10 +02:00
zach 26c2315b66 dashboard crash fix 2026-04-22 13:26:27 +02:00
zach 89ae3b6074 fix drawing popout background 2026-04-22 02:02:01 +02:00
zach 4cd687afc0 Merge pull request 'Blob bounciness' (#67) from bounce-test into main
Reviewed-on: #67
2026-04-22 00:01:46 +02:00
zach 263ef66816 audio popout remove tabs 2026-04-21 21:32:53 +02:00
zach bcb0da3ab9 quick fix for resources popouts leaving leftover ghost region 2026-04-21 19:31:53 +02:00
zach c3f877c19e more fixes, clip wrappers for most popouts 2026-04-21 19:29:41 +02:00
zach ac1d19acbb settings window attached to bar again 2026-04-20 15:40:19 +02:00
zach 93d6bf536a launcher height fixes 2026-04-20 15:37:14 +02:00
zach 95a6824598 dirty fix for notif width 2026-04-19 23:10:32 +02:00
zach c1035e8a06 **bounciness** 2026-04-19 22:55:51 +02:00
zach 2fd01a7274 test blob bounciness, slight revert 2026-04-19 22:31:46 +02:00
zach 007cb32690 test blob bounciness 2026-04-19 22:21:49 +02:00
zach be6c7d4c03 Merge pull request 'Multiple fixes regarding blobs' (#66) from blob-fix into main
Reviewed-on: #66
2026-04-19 21:41:42 +02:00
zach 32c8b7311d fix blob positioning + deforming 2026-04-19 21:40:15 +02:00
zach 666bfacfcc edit blob shader 2026-04-19 20:30:43 +02:00
zach 76ddb9bdfc edit blob shader 2026-04-19 14:32:36 +02:00
zach 6043cb12a1 remove deform due to bug for now 2026-04-19 01:57:01 +02:00
zach dbc5469855 focus search box with keybind 2026-04-18 21:50:37 +02:00
zach fb315b61fb settings scrollbar 2026-04-18 17:56:04 +02:00
zach 8ed5c92e8f fix radius for settings 2026-04-18 12:31:25 +02:00
zach 4949b98cb1 Merge pull request 'fix launcher for non-uwsm' (#63) from fix-uwsm-launcher into main
Reviewed-on: #63
Reviewed-by: AramJonghu <2+aramjonghu@noreply.git.zach-dev.cc>
2026-04-18 01:01:32 +02:00
zach 7f88cbaf38 fix search entry for uwsm switch 2026-04-18 00:57:49 +02:00
zach e45ecf864a fix launcher for non-uwsm 2026-04-18 00:45:19 +02:00
zach 630a20faa7 Merge pull request 'test popouts' (#61) from test-popouts into main
Reviewed-on: #61
2026-04-18 00:17:35 +02:00
zach a7457c57c0 dock settings work now woo 2026-04-18 00:15:52 +02:00
zach 9418a92e99 test popouts 2026-04-17 19:53:57 +02:00
zach f53efea589 Merge pull request 'Now with updated quickshell version, this should work again' (#55) from uncomment-correctly-next-time into main
Reviewed-on: #55
2026-04-17 19:48:16 +02:00
AramJonghu 2da0d2a903 Merge branch 'main' into uncomment-correctly-next-time 2026-04-17 19:45:50 +02:00
AramJonghu 3ebc2befcb added current quickshell version to readme as a requirement 2026-04-17 19:45:00 +02:00
AramJonghu c1dbd387d8 Now with updated quickshell version, this should work again 2026-04-17 19:41:59 +02:00
zach 47b8d68d4b fix popout anims 2026-04-16 13:16:31 +02:00
zach 6d78a01659 add background for all popouts 2026-04-16 12:57:31 +02:00
zach 55b497f132 test popouts 2026-04-16 03:10:20 +02:00
zach 0df32b9e95 test popouts 2026-04-16 01:51:37 +02:00
zach 9a606f3e58 test popouts 2026-04-16 01:50:29 +02:00
133 changed files with 8186 additions and 3514 deletions
+42
View File
@@ -0,0 +1,42 @@
name: Lint & Format (JS/TS)
on:
pull_request:
jobs:
lint-format:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git
- name: Prettier
continue-on-error: true
run: |
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
npx --yes prettier --check "**/*.{js,jsx,ts,tsx,mjs,cjs}" --ignore-path .prettierignore
else
echo "No JS/TS files found"
fi
- name: ESLint
run: |
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
if [ -f package.json ]; then
npm install --no-audit --no-fund
fi
if [ -f eslint.config.js ] || [ -f eslint.config.mjs ] || [ -f eslint.config.cjs ] || [ -f .eslintrc ] || [ -f .eslintrc.js ] || [ -f .eslintrc.cjs ] || [ -f .eslintrc.json ] || [ -f .eslintrc.yaml ] || [ -f .eslintrc.yml ]; then
npx --yes eslint . && echo "ESLint passed" || echo "ESLint failed"
else
echo "No eslint config found"
fi
else
echo "No JS/TS files found"
fi
+34
View File
@@ -0,0 +1,34 @@
name: Lint & Format (Python)
on:
pull_request:
jobs:
lint-format:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir ruff
- name: Format check
continue-on-error: true
run: |
. .venv/bin/activate
ruff format --check .
- name: Lint
run: |
. .venv/bin/activate
ruff check .
+72
View File
@@ -0,0 +1,72 @@
name: Lint & Format (Rust)
on:
pull_request:
jobs:
lint-format:
runs-on: alpine
container: node:26-alpine
env:
CARGO_HOME: ${{ github.workspace }}/.cargo
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache cargo packages
uses: actions/cache@v4
env:
cache-name: cache-cargo-packages
with:
path: |
.cargo/registry
.cargo/git
target
key: rust-${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
rust-${{ runner.os }}-build-${{ env.cache-name }}-
rust-${{ runner.os }}-build-
rust-
- name: Install tools
run: |
apk add --no-cache \
git \
cargo \
rust \
rustfmt \
rust-clippy
- name: Format check
continue-on-error: true
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
for manifest in $(find . -name "Cargo.toml"); do
cargo fmt --manifest-path "$manifest" --check && \
echo "$manifest: formatting OK" || \
echo "$manifest: needs formatting"
done
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
else
echo "No Rust project found"
fi
- name: Clippy
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do
cargo clippy --manifest-path "$manifest" --all-targets --all-features -- -D warnings && \
echo "$manifest: Clippy passed" || \
{ echo "$manifest: Clippy failed"; status=1; }
done
exit $status
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
else
echo "No Rust project found"
fi
+2
View File
@@ -1,3 +1,4 @@
**/__pycache__/
./result/ ./result/
.pyre/ .pyre/
.cache/ .cache/
@@ -11,3 +12,4 @@ pkg/
uv.lock uv.lock
.qtcreator/ .qtcreator/
dist/ dist/
**/target/
+3
View File
@@ -0,0 +1,3 @@
.venv/
scripts/fzf.js
scripts/fuzzysort.js
+13
View File
@@ -0,0 +1,13 @@
{
"semi": true,
"singleQuote": false,
"jsxSingleQuote": false,
"tabWidth": 4,
"printWidth": 80,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf",
"proseWrap": "preserve"
}
+2 -2
View File
@@ -2,7 +2,7 @@ import QtQuick
import qs.Config import qs.Config
NumberAnimation { NumberAnimation {
duration: MaterialEasing.standardTime duration: Appearance.anim.durations.normal
easing.bezierCurve: MaterialEasing.standard easing.bezierCurve: Appearance.anim.curves.standard
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
} }
+33
View File
@@ -0,0 +1,33 @@
import QtQuick
import QtQuick.Controls
import qs.Config
IconButton {
id: root
required property bool shouldBeVisible
opacity: 0
scale: 0
visible: root.scale > 0
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
}
Behavior on scale {
Anim {
}
}
onShouldBeVisibleChanged: {
if (root.shouldBeVisible) {
root.opacity = 1;
root.scale = 1;
} else {
root.opacity = 0;
root.scale = 0;
}
}
}
+1 -2
View File
@@ -41,12 +41,11 @@ CustomRect {
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
implicitHeight: label.implicitHeight + padding * 2 implicitHeight: label.implicitHeight + padding * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: internalChecked ? 6 : implicitHeight / 2 * Math.min(1, 1) radius: internalChecked ? 6 : (implicitHeight / 2 * Math.min(1, 1)) * Appearance.rounding.scale
Behavior on radius { Behavior on radius {
Anim { Anim {
id: radiusAnim id: radiusAnim
} }
} }
+1
View File
@@ -4,6 +4,7 @@ import Quickshell
Singleton { Singleton {
readonly property AppearanceConf.Anim anim: Config.appearance.anim readonly property AppearanceConf.Anim anim: Config.appearance.anim
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: // Literally just here to shorten accessing stuff :woe:
+5
View File
@@ -3,6 +3,8 @@ import Quickshell.Io
JsonObject { JsonObject {
property Anim anim: Anim { property Anim anim: Anim {
} }
property Deform deform: Deform {
}
property FontStuff font: FontStuff { property FontStuff font: FontStuff {
} }
property Padding padding: Padding { property Padding padding: Padding {
@@ -43,6 +45,9 @@ JsonObject {
property real scale: 1 property real scale: 1
property int small: 200 * scale property int small: 200 * scale
} }
component Deform: JsonObject {
property real scale: 1
}
component FontFamily: JsonObject { component FontFamily: JsonObject {
property string clock: "Rubik" property string clock: "Rubik"
property string material: "Material Symbols Rounded" property string material: "Material Symbols Rounded"
+4
View File
@@ -7,4 +7,8 @@ JsonObject {
property real alignX: 0.5 property real alignX: 0.5
property real alignY: 0.5 property real alignY: 0.5
property real zoom: 1.0 property real zoom: 1.0
property real sourceClipX: 0
property real sourceClipY: 0
property real sourceClipW: 0
property real sourceClipH: 0
} }
+1 -12
View File
@@ -8,10 +8,6 @@ JsonObject {
id: "workspaces", id: "workspaces",
enabled: true enabled: true
}, },
{
id: "audio",
enabled: true
},
{ {
id: "media", id: "media",
enabled: true enabled: true
@@ -24,10 +20,6 @@ JsonObject {
id: "updates", id: "updates",
enabled: true enabled: true
}, },
{
id: "dash",
enabled: true
},
{ {
id: "spacer", id: "spacer",
enabled: true enabled: true
@@ -48,10 +40,6 @@ JsonObject {
id: "tray", id: "tray",
enabled: true enabled: true
}, },
{
id: "upower",
enabled: false
},
{ {
id: "network", id: "network",
enabled: false enabled: false
@@ -70,6 +58,7 @@ JsonObject {
property Popouts popouts: Popouts { property Popouts popouts: Popouts {
} }
property int rounding: 8 property int rounding: 8
property int smoothing: 32
component Popouts: JsonObject { component Popouts: JsonObject {
property bool activeWindow: true property bool activeWindow: true
+30 -1
View File
@@ -23,6 +23,7 @@ Singleton {
property alias osd: adapter.osd property alias osd: adapter.osd
property alias overview: adapter.overview property alias overview: adapter.overview
property bool recentlySaved: false property bool recentlySaved: false
property alias screenshot: adapter.screenshot
property alias services: adapter.services property alias services: adapter.services
property alias sidebar: adapter.sidebar property alias sidebar: adapter.sidebar
property alias utilities: adapter.utilities property alias utilities: adapter.utilities
@@ -48,6 +49,9 @@ Singleton {
padding: { padding: {
scale: appearance.padding.scale scale: appearance.padding.scale
}, },
deform: {
scale: appearance.deform.scale
},
font: { font: {
family: { family: {
sans: appearance.font.family.sans, sans: appearance.font.family.sans,
@@ -79,6 +83,10 @@ Singleton {
wallFadeDuration: background.wallFadeDuration, wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled, enabled: background.enabled,
alignX: background.alignX, alignX: background.alignX,
sourceClipX: background.sourceClipX,
sourceClipY: background.sourceClipY,
sourceClipW: background.sourceClipW,
sourceClipH: background.sourceClipH,
alignY: background.alignY, alignY: background.alignY,
zoom: background.zoom zoom: background.zoom
}; };
@@ -90,6 +98,7 @@ Singleton {
hideWhenNotif: barConfig.hideWhenNotif, hideWhenNotif: barConfig.hideWhenNotif,
rounding: barConfig.rounding, rounding: barConfig.rounding,
border: barConfig.border, border: barConfig.border,
smoothing: barConfig.smoothing,
height: barConfig.height, height: barConfig.height,
popouts: { popouts: {
tray: barConfig.popouts.tray, tray: barConfig.popouts.tray,
@@ -125,7 +134,8 @@ Singleton {
background: serializeBackground(), background: serializeBackground(),
launcher: serializeLauncher(), launcher: serializeLauncher(),
colors: serializeColors(), colors: serializeColors(),
dock: serializeDock() dock: serializeDock(),
screenshot: serializeScreenshot()
}; };
} }
@@ -177,6 +187,7 @@ Singleton {
logo: general.logo, logo: general.logo,
wallpaperPath: general.wallpaperPath, wallpaperPath: general.wallpaperPath,
desktopIcons: general.desktopIcons, desktopIcons: general.desktopIcons,
dateFormat: general.dateFormat,
color: { color: {
mode: general.color.mode, mode: general.color.mode,
smart: general.color.smart, smart: general.color.smart,
@@ -206,6 +217,7 @@ Singleton {
return { return {
maxAppsShown: launcher.maxAppsShown, maxAppsShown: launcher.maxAppsShown,
maxWallpapers: launcher.maxWallpapers, maxWallpapers: launcher.maxWallpapers,
uwsm: launcher.uwsm,
actionPrefix: launcher.actionPrefix, actionPrefix: launcher.actionPrefix,
specialPrefix: launcher.specialPrefix, specialPrefix: launcher.specialPrefix,
useFuzzy: { useFuzzy: {
@@ -229,6 +241,7 @@ Singleton {
return { return {
recolorLogo: lock.recolorLogo, recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint, enableFprint: lock.enableFprint,
showNotifContent: lock.showNotifContent,
maxFprintTries: lock.maxFprintTries, maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount, blurAmount: lock.blurAmount,
sizes: { sizes: {
@@ -270,6 +283,20 @@ Singleton {
}; };
} }
function serializeScreenshot(): var {
return {
enable_pp: screenshot.enable_pp,
mode: screenshot.mode,
corner_radius: screenshot.corner_radius,
drop_shadow: screenshot.drop_shadow,
rounded_corners: screenshot.rounded_corners,
shadow_blur_radius: screenshot.shadow_blur_radius,
shadow_color: screenshot.shadow_color,
shadow_offset_x: screenshot.shadow_offset_x,
shadow_offset_y: screenshot.shadow_offset_y
};
}
function serializeServices(): var { function serializeServices(): var {
return { return {
weatherLocation: services.weatherLocation, weatherLocation: services.weatherLocation,
@@ -425,6 +452,8 @@ Singleton {
} }
property Overview overview: Overview { property Overview overview: Overview {
} }
property Screenshot screenshot: Screenshot {
}
property Services services: Services { property Services services: Services {
} }
property SidebarConfig sidebar: SidebarConfig { property SidebarConfig sidebar: SidebarConfig {
+35 -4
View File
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Hyprland
import QtQuick import QtQuick
import ZShell import ZShell
import qs.Helpers import qs.Helpers
@@ -79,10 +80,32 @@ Singleton {
} }
function reloadHyprRules(): void { function reloadHyprRules(): void {
const barStr = "keyword layerrule %1 %2, match:namespace ZShell-Bar"; const blur = transparency.enabled ? 1 : 0;
const authStr = "keyword layerrule %1 %2, match:namespace ZShell-Auth"; const alpha = transparency.base - 0.03;
Hypr.extras.batchMessage([barStr.arg("blur").arg(transparency.enabled ? 1 : 0), barStr.arg("ignore_alpha").arg(transparency.base - 0.03)]);
Hypr.extras.batchMessage([authStr.arg("blur").arg(transparency.enabled ? 1 : 0), authStr.arg("ignore_alpha").arg(transparency.base - 0.03)]); const rules = `
hl.layer_rule({
match = { namespace = "ZShell-Bar" },
blur = ${blur}
})
hl.layer_rule({
match = { namespace = "ZShell-Bar" },
ignore_alpha = ${alpha}
})
hl.layer_rule({
match = { namespace = "ZShell-Auth" },
blur = ${blur}
})
hl.layer_rule({
match = { namespace = "ZShell-Auth" },
ignore_alpha = ${alpha}
})
`;
Hypr.extras.message(`eval ${rules}`);
} }
function setMode(mode: string): void { function setMode(mode: string): void {
@@ -93,6 +116,14 @@ Singleton {
Component.onCompleted: debounceTimer.triggered() Component.onCompleted: debounceTimer.triggered()
Connections {
function onUsingLuaChanged(): void {
root.reloadHyprRules();
}
target: Hyprland
}
Connections { Connections {
function onConfigReloaded(): void { function onConfigReloaded(): void {
root.reloadHyprRules(); root.reloadHyprRules();
+1
View File
@@ -6,6 +6,7 @@ JsonObject {
} }
property Color color: Color { property Color color: Color {
} }
property string dateFormat: "ddd d MMM - hh:mm:ss"
property bool desktopIcons: false property bool desktopIcons: false
property Idle idle: Idle { property Idle idle: Idle {
} }
+1
View File
@@ -91,6 +91,7 @@ JsonObject {
property string specialPrefix: "@" property string specialPrefix: "@"
property UseFuzzy useFuzzy: UseFuzzy { property UseFuzzy useFuzzy: UseFuzzy {
} }
property bool uwsm: true
component Sizes: JsonObject { component Sizes: JsonObject {
property int itemHeight: 50 property int itemHeight: 50
+1
View File
@@ -5,6 +5,7 @@ JsonObject {
property bool enableFprint: true property bool enableFprint: true
property int maxFprintTries: 3 property int maxFprintTries: 3
property bool recolorLogo: false property bool recolorLogo: false
property bool showNotifContent: false
property Sizes sizes: Sizes { property Sizes sizes: Sizes {
} }
+13
View File
@@ -0,0 +1,13 @@
import Quickshell.Io
JsonObject {
property real corner_radius: 12.0
property bool drop_shadow: true
property bool enable_pp: true
property string mode: "manual"
property bool rounded_corners: false
property real shadow_blur_radius: 22.0
property list<int> shadow_color: [0, 0, 0, 160]
property real shadow_offset_x: 5.0
property real shadow_offset_y: 5.0
}
+23 -174
View File
@@ -3,184 +3,33 @@ import QtQuick
Canvas { Canvas {
id: root id: root
property rect dirtyRect: Qt.rect(0, 0, 0, 0)
property bool frameQueued: false
property bool fullRepaintPending: true
property point lastPoint: Qt.point(0, 0)
property real minPointDistance: 2.0
property color penColor: "white" property color penColor: "white"
property real penWidth: 4 property real penWidth: 4
property var pendingSegments: [] property var points: []
property bool strokeActive: false
property var strokes: []
function appendPoint(x, y) { function clear(): void {
if (!strokeActive || strokes.length === 0) var ctx = getContext('2d');
root.points = [];
ctx.reset();
root.requestPaint();
}
renderStrategy: Canvas.Cooperative
onPaint: {
if (points.length < 2)
return; return;
const dx = x - lastPoint.x; var ctx = root.getContext('2d');
const dy = y - lastPoint.y; ctx.save();
ctx.lineWidth = root.penWidth;
if ((dx * dx + dy * dy) < (minPointDistance * minPointDistance)) ctx.strokeStyle = root.penColor;
return;
const x1 = lastPoint.x;
const y1 = lastPoint.y;
const x2 = x;
const y2 = y;
strokes[strokes.length - 1].push(Qt.point(x2, y2));
pendingSegments.push({
dot: false,
x1: x1,
y1: y1,
x2: x2,
y2: y2
});
lastPoint = Qt.point(x2, y2);
queueDirty(segmentDirtyRect(x1, y1, x2, y2));
}
function beginStroke(x, y) {
const p = Qt.point(x, y);
strokes.push([p]);
lastPoint = p;
strokeActive = true;
pendingSegments.push({
dot: true,
x: x,
y: y
});
queueDirty(pointDirtyRect(x, y));
}
function clear() {
strokes = [];
pendingSegments = [];
dirtyRect = Qt.rect(0, 0, 0, 0);
fullRepaintPending = true;
markDirty(Qt.rect(0, 0, width, height));
}
function drawDot(ctx, x, y) {
ctx.beginPath();
ctx.arc(x, y, penWidth / 2, 0, Math.PI * 2);
ctx.fill();
}
function drawSegment(ctx, x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function endStroke() {
strokeActive = false;
}
function pointDirtyRect(x, y) {
const pad = penWidth + 2;
return Qt.rect(x - pad, y - pad, pad * 2, pad * 2);
}
function queueDirty(r) {
dirtyRect = unionRects(dirtyRect, r);
if (frameQueued)
return;
frameQueued = true;
requestAnimationFrame(function () {
frameQueued = false;
if (dirtyRect.width > 0 && dirtyRect.height > 0) {
markDirty(dirtyRect);
dirtyRect = Qt.rect(0, 0, 0, 0);
}
});
}
function replayAll(ctx) {
ctx.clearRect(0, 0, width, height);
for (const stroke of strokes) {
if (!stroke || stroke.length === 0)
continue;
if (stroke.length === 1) {
const p = stroke[0];
drawDot(ctx, p.x, p.y);
continue;
}
ctx.beginPath();
ctx.moveTo(stroke[0].x, stroke[0].y);
for (let i = 1; i < stroke.length; ++i)
ctx.lineTo(stroke[i].x, stroke[i].y);
ctx.stroke();
}
}
function requestFullRepaint() {
fullRepaintPending = true;
markDirty(Qt.rect(0, 0, width, height));
}
function segmentDirtyRect(x1, y1, x2, y2) {
const pad = penWidth + 2;
const left = Math.min(x1, x2) - pad;
const top = Math.min(y1, y2) - pad;
const right = Math.max(x1, x2) + pad;
const bottom = Math.max(y1, y2) + pad;
return Qt.rect(left, top, right - left, bottom - top);
}
function unionRects(a, b) {
if (a.width <= 0 || a.height <= 0)
return b;
if (b.width <= 0 || b.height <= 0)
return a;
const left = Math.min(a.x, b.x);
const top = Math.min(a.y, b.y);
const right = Math.max(a.x + a.width, b.x + b.width);
const bottom = Math.max(a.y + a.height, b.y + b.height);
return Qt.rect(left, top, right - left, bottom - top);
}
anchors.fill: parent
contextType: "2d"
renderStrategy: Canvas.Threaded
renderTarget: Canvas.Image
onHeightChanged: requestFullRepaint()
onPaint: region => {
const ctx = getContext("2d");
ctx.lineCap = "round"; ctx.lineCap = "round";
ctx.lineJoin = "round"; ctx.beginPath();
ctx.lineWidth = penWidth; ctx.moveTo(points[0].x, points[0].y);
ctx.strokeStyle = penColor; for (var i = 1; i < points.length; i++)
ctx.fillStyle = penColor; ctx.lineTo(points[i].x, points[i].y);
ctx.stroke();
if (fullRepaintPending) { points = points.slice(points.length - 2);
fullRepaintPending = false; ctx.restore();
replayAll(ctx);
pendingSegments = [];
return;
} }
for (const seg of pendingSegments) {
if (seg.dot)
drawDot(ctx, seg.x, seg.y);
else
drawSegment(ctx, seg.x1, seg.y1, seg.x2, seg.y2);
}
pendingSegments = [];
}
onWidthChanged: requestFullRepaint()
} }
+7 -5
View File
@@ -30,8 +30,10 @@ CustomMouseArea {
const x = event.x; const x = event.x;
const y = event.y; const y = event.y;
if (event.buttons & Qt.LeftButton) if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.drawing.appendPoint(x, y); root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
}
if (root.inLeftPanel(root.popout, x, y)) { if (root.inLeftPanel(root.popout, x, y)) {
root.z = -2; root.z = -2;
@@ -44,7 +46,8 @@ CustomMouseArea {
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.panels.drawing.expanded = false; root.panels.drawing.expanded = false;
root.drawing.beginStroke(x, y); root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
return; return;
} }
@@ -52,7 +55,6 @@ CustomMouseArea {
root.drawing.clear(); root.drawing.clear();
} }
onReleased: { onReleased: {
if (root.visibilities.isDrawing) root.drawing.points = [];
root.drawing.endStroke();
} }
} }
+15
View File
@@ -12,22 +12,37 @@ Scope {
required property ShellScreen screen required property ShellScreen screen
ExclusionZone { ExclusionZone {
id: top
anchors.top: true anchors.top: true
exclusiveZone: root.bar.exclusiveZone exclusiveZone: root.bar.exclusiveZone
} }
ExclusionZone { ExclusionZone {
id: left
anchors.left: true anchors.left: true
} }
ExclusionZone { ExclusionZone {
id: right
anchors.right: true anchors.right: true
} }
ExclusionZone { ExclusionZone {
id: bottom
anchors.bottom: true anchors.bottom: true
} }
Timer {
interval: 5000
running: true
onTriggered: console.log("top height:", top.exclusiveZone, "left width:", left.exclusiveZone, "right width:", right.exclusiveZone, "bottom height:", bottom.exclusiveZone)
}
component ExclusionZone: CustomWindow { component ExclusionZone: CustomWindow {
exclusiveZone: Config.barConfig.border exclusiveZone: Config.barConfig.border
implicitHeight: 1 implicitHeight: 1
+9 -8
View File
@@ -54,7 +54,7 @@ CustomMouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: (pressed && dragStart.y < bar.implicitHeight) ? Qt.ClosedHandCursor : undefined cursorShape: (pressed && dragStart.y < bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
hoverEnabled: true hoverEnabled: true
propagateComposedEvents: true propagateComposedEvents: false
onContainsMouseChanged: { onContainsMouseChanged: {
if (!containsMouse) { if (!containsMouse) {
@@ -72,16 +72,13 @@ CustomMouseArea {
} }
} }
onPositionChanged: event => { onPositionChanged: event => {
if (popouts.isDetached)
return;
const x = event.x; const x = event.x;
const y = event.y; const y = event.y;
const dragX = x - dragStart.x; const dragX = x - dragStart.x;
const dragY = y - dragStart.y; const dragY = y - dragStart.y;
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) { if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
root.input.z = 2; // root.input.z = 2;
root.panels.drawing.expanded = false; root.panels.drawing.expanded = false;
} }
@@ -95,8 +92,12 @@ CustomMouseArea {
visibilities.settings = false; visibilities.settings = false;
} }
if (Config.dock.hoverToReveal && pressed && dragStart.y > root.screen.height - root.bar.implicitHeight)
if (dragY < -10)
visibilities.dock = true;
if (panels.sidebar.width === 0) { if (panels.sidebar.width === 0) {
const showOsd = inRightPanel(panels.osd, x, y); const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) { if (showOsd) {
osdShortcutActive = false; osdShortcutActive = false;
@@ -104,7 +105,7 @@ CustomMouseArea {
} }
} else { } else {
const outOfSidebar = x < width - panels.sidebar.width; const outOfSidebar = x < width - panels.sidebar.width;
const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y); const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (!osdShortcutActive) { if (!osdShortcutActive) {
visibilities.osd = showOsd; visibilities.osd = showOsd;
@@ -115,7 +116,7 @@ CustomMouseArea {
} }
} }
if (!visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y)) if (Config.dock.enable && !Config.dock.hoverToReveal && !visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y))
visibilities.dock = true; visibilities.dock = true;
if (y < root.bar.implicitHeight) { if (y < root.bar.implicitHeight) {
+143 -17
View File
@@ -13,6 +13,7 @@ import qs.Modules.Resources as Resources
import qs.Modules.Settings as Settings import qs.Modules.Settings as Settings
import qs.Modules.Drawing as Drawing import qs.Modules.Drawing as Drawing
import qs.Modules.Dock as Dock import qs.Modules.Dock as Dock
import qs.Modules.SysTray.Popouts as SysPopouts
import qs.Config import qs.Config
Item { Item {
@@ -20,18 +21,24 @@ Item {
required property Item bar required property Item bar
readonly property alias dashboard: dashboard readonly property alias dashboard: dashboard
readonly property alias dashboardWrapper: dashboardWrapper
readonly property alias dock: dock readonly property alias dock: dock
readonly property alias drawing: drawing readonly property alias drawing: drawing
required property Canvas drawingItem required property Canvas drawingItem
readonly property alias launcher: launcher readonly property alias launcher: launcher
readonly property alias notifications: notifications readonly property alias notifications: notifications
readonly property alias osd: osd readonly property alias osd: osd
readonly property alias popouts: popouts readonly property alias osdWrapper: osdWrapper
readonly property alias popouts: popouts.content
readonly property alias popoutsWrapper: popouts
readonly property alias resources: resources readonly property alias resources: resources
readonly property alias resourcesWrapper: resourcesWrapper
required property ShellScreen screen required property ShellScreen screen
readonly property alias settings: settings readonly property alias settings: settings
readonly property alias settingsWrapper: settingsWrapper
readonly property alias sidebar: sidebar readonly property alias sidebar: sidebar
readonly property alias toasts: toasts readonly property alias toasts: toasts
readonly property alias traySubmenus: traySubmenus
readonly property alias utilities: utilities readonly property alias utilities: utilities
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -39,6 +46,15 @@ Item {
anchors.margins: Config.barConfig.border anchors.margins: Config.barConfig.border
anchors.topMargin: bar.implicitHeight anchors.topMargin: bar.implicitHeight
Item {
id: resourcesWrapper
anchors.left: parent.left
anchors.top: parent.top
clip: true
implicitHeight: resources.implicitHeight * (1 - resources.offsetScale)
implicitWidth: resources.implicitWidth
Resources.Wrapper { Resources.Wrapper {
id: resources id: resources
@@ -46,6 +62,28 @@ Item {
anchors.top: parent.top anchors.top: parent.top
visibilities: root.visibilities visibilities: root.visibilities
} }
}
Item {
id: osdWrapper
anchors.right: parent.right
anchors.rightMargin: sidebar.width * (1 - sidebar.offsetScale)
anchors.verticalCenter: parent.verticalCenter
clip: sidebar.visible
implicitHeight: osd.implicitHeight
implicitWidth: osd.implicitWidth * (1 - osd.offsetScale)
Osd.Wrapper {
id: osd
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
screen: root.screen
sidebarOrSessionVisible: sidebar.visible
visibilities: root.visibilities
}
}
Drawing.Wrapper { Drawing.Wrapper {
id: drawing id: drawing
@@ -57,29 +95,84 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Osd.Wrapper { Item {
id: osd id: traySubmenus
anchors.right: parent.right Repeater {
anchors.rightMargin: sidebar.width model: popouts.content.state.submenus
anchors.verticalCenter: parent.verticalCenter
clip: sidebar.width > 0 CustomClippingRect {
screen: root.screen id: subMenuWrapper
visibilities: root.visibilities
required property int index
required property var modelData
property real targetX: 0
property real targetY: 0
function updatePosition() {
let sourceItem = modelData.sourceItem;
if (!sourceItem || !sourceItem.parent)
return;
let mapped = sourceItem.mapToItem(root, 0, -Appearance.padding.small);
let rightX = mapped.x + modelData.sourceWidth + Config.barConfig.border;
let leftX = mapped.x - implicitWidth - Config.barConfig.border;
if (rightX + implicitWidth > root.width) {
targetX = leftX;
} else {
targetX = rightX;
} }
Modules.Wrapper { targetY = mapped.y;
if (targetY + implicitHeight > root.height) {
targetY = root.height - implicitHeight;
}
if (targetY < 0)
targetY = 0;
}
implicitHeight: subMenuContent.implicitHeight + Appearance.padding.small * 2
implicitWidth: subMenuContent.implicitWidth + Appearance.padding.small * 2
radius: Appearance.rounding.normal
x: targetX
y: targetY
Behavior on implicitHeight {
Anim {
}
}
Behavior on implicitWidth {
Anim {
}
}
Component.onCompleted: {
updatePosition();
}
onImplicitHeightChanged: updatePosition()
onImplicitWidthChanged: updatePosition()
SysPopouts.SubMenu {
id: subMenuContent
anchors.centerIn: parent
handle: subMenuWrapper.modelData.handle
level: subMenuWrapper.index + 1
popouts: root.popouts.state
screen: root.screen
}
}
}
}
Modules.ClipWrapper {
id: popouts id: popouts
anchors.top: parent.top anchors.top: parent.top
screen: root.screen screen: root.screen
x: {
const off = currentCenter - nonAnimWidth / 2;
const diff = root.width - Math.floor(off + nonAnimWidth);
if (diff < 0)
return off + diff;
return Math.floor(Math.max(off, 0));
}
} }
Toasts.Toasts { Toasts.Toasts {
@@ -96,6 +189,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
panels: root panels: root
sidebarPanel: sidebar
visibilities: root.visibilities visibilities: root.visibilities
} }
@@ -119,13 +213,34 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Item {
id: dashboardWrapper
property real offsetScale: dashboard.shouldBeActive ? 0 : 1
anchors.right: parent.right
anchors.top: parent.top
clip: true
implicitHeight: dashboard.implicitHeight * (1 - offsetScale)
implicitWidth: dashboard.implicitWidth
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Dashboard.Wrapper { Dashboard.Wrapper {
id: dashboard id: dashboard
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: (-implicitHeight - 5) * offsetScale
offsetScale: dashboardWrapper.offsetScale
visibilities: root.visibilities visibilities: root.visibilities
} }
}
Sidebar.Wrapper { Sidebar.Wrapper {
id: sidebar id: sidebar
@@ -137,15 +252,26 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Item {
id: settingsWrapper
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
clip: true
implicitHeight: settings.implicitHeight * (1 - settings.offsetScale)
implicitWidth: settings.implicitWidth
Settings.Wrapper { Settings.Wrapper {
id: settings id: settings
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
// anchors.centerIn: parent
panels: root panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
} }
}
Dock.Wrapper { Dock.Wrapper {
id: dock id: dock
+229 -9
View File
@@ -3,8 +3,10 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
import ZShell.Blobs
import qs.Daemons import qs.Daemons
import qs.Components import qs.Components
import qs.Modules import qs.Modules
@@ -62,7 +64,7 @@ Variants {
height: win.height - bar.implicitHeight - Config.barConfig.border height: win.height - bar.implicitHeight - Config.barConfig.border
intersection: Intersection.Xor intersection: Intersection.Xor
regions: popoutRegions.instances regions: [...popoutRegions.instances, ...subMenuRegions.instances]
width: win.width - Config.barConfig.border * 2 width: win.width - Config.barConfig.border * 2
x: Config.barConfig.border x: Config.barConfig.border
y: bar.implicitHeight y: bar.implicitHeight
@@ -91,6 +93,22 @@ Variants {
} }
} }
Variants {
id: subMenuRegions
model: panels.traySubmenus.children
Region {
required property Item modelData
height: modelData.height
intersection: Intersection.Subtract
width: modelData.width
x: modelData.x + panels.traySubmenus.x + Config.barConfig.border
y: modelData.y + panels.traySubmenus.y + bar.implicitHeight
}
}
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
@@ -126,6 +144,14 @@ Variants {
Component.onCompleted: Visibilities.load(scope.modelData, this) Component.onCompleted: Visibilities.load(scope.modelData, this)
} }
IpcHandler {
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
}
target: "visibilities"
}
Binding { Binding {
property: "bar" property: "bar"
target: visibilities target: visibilities
@@ -144,16 +170,165 @@ Variants {
shadowEnabled: true shadowEnabled: true
} }
Border { BlobGroup {
bar: bar id: blobGroup
visibilities: visibilities
color: DynamicColors.palette.m3surface
smoothing: Config.barConfig.smoothing
Behavior on color {
CAnim {
}
}
} }
Backgrounds { BlobInvertedRect {
bar: bar anchors.fill: parent
panels: panels anchors.margins: -50
visibilities: visibilities borderBottom: Config.barConfig.border - anchors.margins
z: 1 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
}
Repeater {
model: panels.traySubmenus.children
PanelBg {
required property Item modelData
deformAmount: 0.1
panel: modelData
radius: 20 * Appearance.rounding.scale
}
} }
} }
@@ -195,6 +370,37 @@ Variants {
drawingItem: drawing drawingItem: drawing
screen: scope.modelData screen: scope.modelData
visibilities: visibilities 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 { BarLoader {
@@ -203,10 +409,24 @@ Variants {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
popouts: panels.popouts popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper
screen: scope.modelData screen: scope.modelData
visibilities: visibilities visibilities: visibilities
} }
} }
} }
} }
component PanelBg: BlobRect {
property real deformAmount: 0.15
required property Item panel
deformScale: (deformAmount * Config.appearance.deform.scale) / 10000
group: blobGroup
implicitHeight: panel.height
implicitWidth: panel.width
radius: Appearance.rounding.smallest
x: panel.x + Config.barConfig.border
y: panel.y + bar.implicitHeight
}
} }
-1
View File
@@ -346,7 +346,6 @@ Singleton {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
console.log("this is running");
if (root.gpuType === "GENERIC") { if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n"); const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0); const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
+20 -44
View File
@@ -7,23 +7,30 @@ import QtQuick
Singleton { Singleton {
id: root id: root
// The list of users that can log in graphically readonly property string defaultUserFile: "/etc/zshell-greeter/default-user"
// Each user object has: username, uid, home, shell, gecos (full name), face (avatar path) property int selectedIndex: 0
readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null
readonly property string selectedUsername: selectedUser ? selectedUser.username : ""
property var users: [] property var users: []
// The currently selected user index function saveDefaultUser(): void {
property int selectedIndex: 0 if (selectedUser) {
defaultUserStorage.setText(selectedUser.username);
}
}
// The currently selected user object (or null if none) function selectNext(): void {
readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null if (users.length === 0)
return;
selectedIndex = (selectedIndex + 1) % users.length;
}
// Convenience property for the selected username function selectPrevious(): void {
readonly property string selectedUsername: selectedUser ? selectedUser.username : "" if (users.length === 0)
return;
selectedIndex = (selectedIndex - 1 + users.length) % users.length;
}
// Path to store the default user preference
readonly property string defaultUserFile: "/etc/zshell-greeter/default-user"
// Select a user by username
function selectUser(username: string): bool { function selectUser(username: string): bool {
for (let i = 0; i < users.length; i++) { for (let i = 0; i < users.length; i++) {
if (users[i].username === username) { if (users[i].username === username) {
@@ -34,28 +41,6 @@ Singleton {
return false; return false;
} }
// Select the next user in the list (wraps around)
function selectNext(): void {
if (users.length === 0)
return;
selectedIndex = (selectedIndex + 1) % users.length;
}
// Select the previous user in the list (wraps around)
function selectPrevious(): void {
if (users.length === 0)
return;
selectedIndex = (selectedIndex - 1 + users.length) % users.length;
}
// Save the current user as the default for next login
function saveDefaultUser(): void {
if (selectedUser) {
defaultUserStorage.setText(selectedUser.username);
}
}
// Process to fetch the list of graphical users
Process { Process {
id: userLister id: userLister
@@ -67,13 +52,10 @@ Singleton {
try { try {
root.users = JSON.parse(text); root.users = JSON.parse(text);
// If we have users and no selection yet, try to select the default user
if (root.users.length > 0) { if (root.users.length > 0) {
// Try to load the default user
if (defaultUserStorage.loaded) { if (defaultUserStorage.loaded) {
const defaultUsername = defaultUserStorage.text().trim(); const defaultUsername = defaultUserStorage.text().trim();
if (defaultUsername && !root.selectUser(defaultUsername)) { if (defaultUsername && !root.selectUser(defaultUsername)) {
// Default user not found, select first user
root.selectedIndex = 0; root.selectedIndex = 0;
} }
} else { } else {
@@ -87,15 +69,14 @@ Singleton {
} }
} }
// FileView for persisting the default user
FileView { FileView {
id: defaultUserStorage id: defaultUserStorage
path: root.defaultUserFile path: root.defaultUserFile
preload: true preload: true
onLoadFailed: {}
onLoaded: { onLoaded: {
// If users are already loaded, try to select the default user
if (root.users.length > 0) { if (root.users.length > 0) {
const defaultUsername = text().trim(); const defaultUsername = text().trim();
if (defaultUsername) { if (defaultUsername) {
@@ -103,10 +84,5 @@ Singleton {
} }
} }
} }
onLoadFailed: {
// File doesn't exist yet, that's fine - we'll create it on first save
console.log("No default user file found, will use first user");
}
} }
} }
+72
View File
@@ -0,0 +1,72 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import ZShell
import qs.Config
Singleton {
id: root
property alias enabled: props.enabled
function setHyprConf(): void {
Hypr.extras.applyOptions({
"animations.enabled": 0,
"decoration.shadow.enabled": 0,
"decoration.blur.enabled": 0,
"general.border_size": 0,
"decoration.rounding": 0
});
}
onEnabledChanged: {
if (enabled) {
setHyprConf();
if (Config.utilities.toasts.gameModeChanged)
Toaster.toast(qsTr("Game mode enabled"), qsTr("Disabled Hyprland animations, blur, shadows and corner radius"), "gamepad");
} else {
Hypr.extras.message("reload");
if (Config.utilities.toasts.gameModeChanged)
Toaster.toast(qsTr("Game mode disabled"), qsTr("Hyprland settings restored"), "gamepad");
}
}
PersistentProperties {
id: props
property bool enabled: Hypr.options["animations:enabled"] === 0
reloadableId: "gamemode"
}
Connections {
function onConfigReloaded(): void {
if (props.enabled)
root.setHyprConf();
}
target: Hypr
}
IpcHandler {
function disable(): void {
props.enabled = false;
}
function enable(): void {
props.enabled = true;
}
function isEnabled(): bool {
return props.enabled;
}
function toggle(): void {
props.enabled = !props.enabled;
}
target: "gameMode"
}
}
-11
View File
@@ -1,11 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Networking
Singleton {
id: root
property NetworkDevice activeDevice: devices.find(d => d.connected)
property list<NetworkDevice> devices: Networking.devices.values
}
+2 -1
View File
@@ -66,7 +66,8 @@ 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`);
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached(["swappy", "-f", path])); const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--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]));
closeAnim.start(); closeAnim.start();
} }
+41
View File
@@ -0,0 +1,41 @@
// pragma Singleton
//
// import Quickshell
// import QtQuick
//
// Singleton {
// id: root
//
// function start(extraArgs = []): void {
// needsStart = true;
// startArgs = extraArgs;
// checkProc.running = true;
// }
//
// PersistentProperties {
// id: props
//
// property real elapsed: 0
// property bool paused: false
// property bool running: false
//
// reloadableId: "recorder"
// }
//
// Process {
// id: checkProc
//
// command: ["pidof", "gpu-screen-recorder"]
// running: true
//
// onExited: code => {
// props.running = code === 0;
//
// if (code === 0) {
// if (root.needsStop) {
// Quickshell.execDetached(["zshell-cli"]);
// }
// }
// }
// }
// }
+7 -2
View File
@@ -1,18 +1,23 @@
pragma Singleton pragma Singleton
import Quickshell import Quickshell
import QtQuick
import qs.Config
Singleton { Singleton {
readonly property string amPmStr: timeComponents[2] ?? "" id: root
readonly property date date: clock.date readonly property date date: clock.date
readonly property string dateStr: format(Config.general.dateFormat)
property alias enabled: clock.enabled property alias enabled: clock.enabled
readonly property string hourStr: timeComponents[0] ?? "" readonly property string hourStr: timeComponents[0] ?? ""
readonly property int hours: clock.hours readonly property int hours: clock.hours
readonly property string minuteStr: timeComponents[1] ?? "" readonly property string minuteStr: timeComponents[1] ?? ""
readonly property int minutes: clock.minutes readonly property int minutes: clock.minutes
readonly property string secondStr: timeComponents[2] ?? ""
readonly property int seconds: clock.seconds readonly property int seconds: clock.seconds
readonly property list<string> timeComponents: timeStr.split(":") readonly property list<string> timeComponents: timeStr.split(":")
readonly property string timeStr: format("hh:mm") readonly property string timeStr: format("hh:mm:ss")
function format(fmt: string): string { function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt); return Qt.formatDateTime(clock.date, fmt);
+79
View File
@@ -11,10 +11,13 @@ Singleton {
id: root id: root
property int availableUpdates: 0 property int availableUpdates: 0
property string cmd: ""
property bool commandReady property bool commandReady
property bool loaded property bool loaded
property double now: Date.now() property double now: Date.now()
property var updates: ({}) property var updates: ({})
property bool updating
property string updatingPackage: ""
function formatUpdateTime(timestamp) { function formatUpdateTime(timestamp) {
const diffMs = root.now - timestamp; const diffMs = root.now - timestamp;
@@ -34,6 +37,22 @@ Singleton {
return Qt.formatDateTime(new Date(timestamp), "dd hh:mm"); return Qt.formatDateTime(new Date(timestamp), "dd hh:mm");
} }
function performPackageUpdate(pkg: string): void {
if (root.cmd === "pacman")
pkgUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Sy", pkg];
else
pkgUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Sy", pkg];
pkgUpdateProc.running = true;
}
function performSystemUpdate(): void {
if (root.cmd === "pacman")
sysUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Syu"];
else
sysUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Syu"];
sysUpdateProc.running = true;
}
onUpdatesChanged: { onUpdatesChanged: {
if (!root.loaded) if (!root.loaded)
return; return;
@@ -92,6 +111,28 @@ Singleton {
} }
} }
Process {
id: updateCmdDetect
command: ["sh", "-c", "command -v yay || command -v paru"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const cmd = this.text.trim();
let helper;
if (cmd.length > 0) {
helper = cmd.split("/").pop();
} else {
helper = "pacman";
}
root.cmd = helper;
}
}
}
Process { Process {
id: updatesProc id: updatesProc
@@ -115,6 +156,44 @@ Singleton {
} }
} }
Process {
id: sysUpdateProc
command: []
running: false
stdout: StdioCollector {
onStreamFinished: {
root.updating = false;
}
}
onRunningChanged: {
if (running)
root.updating = true;
}
}
Process {
id: pkgUpdateProc
command: []
running: false
stdout: StdioCollector {
onStreamFinished: {
root.updating = false;
}
}
onRunningChanged: {
if (running) {
root.updatingPackage = command[command.length - 1];
root.updating = true;
}
}
}
Timer { Timer {
id: saveTimer id: saveTimer
+1
View File
@@ -14,6 +14,7 @@ Searcher {
property string actualCurrent: WallpaperPath.currentWallpaperPath property string actualCurrent: WallpaperPath.currentWallpaperPath
readonly property string current: showPreview ? previewPath : actualCurrent readonly property string current: showPreview ? previewPath : actualCurrent
property string previewPath property string previewPath
property bool recentlyChanged
property bool showPreview: false property bool showPreview: false
function preview(path: string): void { function preview(path: string): void {
+1
View File
@@ -16,6 +16,7 @@ RowLayout {
id: root id: root
required property Wrapper popouts required property Wrapper popouts
required property ClipWrapper popoutsWrapper
required property ShellScreen screen required property ShellScreen screen
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property PersistentProperties visibilities
+2
View File
@@ -20,6 +20,7 @@ Item {
property bool isHovered property bool isHovered
readonly property int padding: Math.max(Appearance.padding.smaller, Config.barConfig.border) readonly property int padding: Math.max(Appearance.padding.smaller, Config.barConfig.border)
required property Wrapper popouts required property Wrapper popouts
required property ClipWrapper popoutsWrapper
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeVisible: (!Config.barConfig.autoHide || visibilities.bar || isHovered) readonly property bool shouldBeVisible: (!Config.barConfig.autoHide || visibilities.bar || isHovered)
readonly property int vPadding: 6 readonly property int vPadding: 6
@@ -76,6 +77,7 @@ Item {
sourceComponent: Bar { sourceComponent: Bar {
height: root.contentHeight height: root.contentHeight
popouts: root.popouts popouts: root.popouts
popoutsWrapper: root.popoutsWrapper
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
} }
+61
View File
@@ -0,0 +1,61 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Components
import qs.Config
Item {
id: root
readonly property alias content: content
property real offsetScale: y > 0 || content.hasCurrent ? 0 : 1
required property ShellScreen screen
clip: true
implicitHeight: content.implicitHeight * (1 - offsetScale)
implicitWidth: content.implicitWidth
visible: width > 0 && height > 0
x: {
if (content.isDetached)
return (parent.width - content.nonAnimWidth) / 2;
const off = content.currentCenter - Config.barConfig.border - content.nonAnimWidth / 2;
const diff = parent.width - Math.floor(off + content.nonAnimWidth);
if (diff < 0)
return off + diff;
return Math.floor(Math.max(off, 0));
}
y: content.isDetached ? (parent.height - content.nonAnimHeight) / 2 : 0
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on x {
enabled: root.offsetScale < 1
Anim {
duration: content.animLength
easing.bezierCurve: content.animCurve
}
}
Behavior on y {
Anim {
duration: content.animLength
easing.bezierCurve: content.animCurve
}
}
Wrapper {
id: content
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: (-implicitHeight - 5) * root.offsetScale
offsetScale: root.offsetScale
screen: root.screen
}
}
+2 -1
View File
@@ -3,7 +3,7 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Config import qs.Config
import qs.Modules import qs.Modules
import qs.Helpers as Helpers import qs.Helpers
import qs.Components import qs.Components
CustomRect { CustomRect {
@@ -23,6 +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
text: Time.dateStr text: Time.dateStr
Behavior on color { Behavior on color {
+14 -20
View File
@@ -15,7 +15,8 @@ Item {
readonly property Item current: currentPopout?.item ?? null readonly property Item current: currentPopout?.item ?? null
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
required property Item wrapper required property PopoutState popouts
required property ShellScreen screen
implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2 implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2
implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2 implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2
@@ -49,40 +50,32 @@ Item {
Connections { Connections {
function onHasCurrentChanged(): void { function onHasCurrentChanged(): void {
if (root.wrapper.hasCurrent && trayMenu.shouldBeActive) { if (root.popouts.hasCurrent && trayMenu.shouldBeActive) {
trayMenu.sourceComponent = null; trayMenu.sourceComponent = null;
trayMenu.sourceComponent = trayMenuComponent; trayMenu.sourceComponent = trayMenuComponent;
} }
} }
target: root.wrapper target: root.popouts
} }
Component { Component {
id: trayMenuComponent id: trayMenuComponent
TrayMenuPopout { TrayMenuPopout {
popouts: root.wrapper popouts: root.popouts
screen: root.screen
trayItem: trayMenu.modelData.menu trayItem: trayMenu.modelData.menu
} }
} }
} }
} }
Popout {
name: "overview"
sourceComponent: OverviewPopout {
screen: root.wrapper.screen
wrapper: root.wrapper
}
}
Popout { Popout {
name: "upower" name: "upower"
sourceComponent: UPowerPopout { sourceComponent: UPowerPopout {
wrapper: root.wrapper wrapper: root.popouts
} }
} }
@@ -90,7 +83,7 @@ Item {
name: "network" name: "network"
sourceComponent: NetworkPopout { sourceComponent: NetworkPopout {
wrapper: root.wrapper wrapper: root.popouts
} }
} }
@@ -98,7 +91,7 @@ Item {
name: "updates" name: "updates"
sourceComponent: UpdatesPopout { sourceComponent: UpdatesPopout {
wrapper: root.wrapper wrapper: root.popouts
} }
} }
} }
@@ -107,12 +100,13 @@ Item {
id: popout id: popout
required property string name required property string name
readonly property bool shouldBeActive: root.wrapper.currentName === name readonly property bool shouldBeActive: root.popouts.currentName === name
active: false active: false
anchors.horizontalCenter: parent.horizontalCenter // anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top // anchors.top: parent.top
anchors.topMargin: 5 // anchors.topMargin: 5
anchors.centerIn: parent
opacity: 0 opacity: 0
scale: 0.8 scale: 0.8
-31
View File
@@ -1,31 +0,0 @@
import Quickshell
import QtQuick
import qs.Config
import qs.Helpers
import qs.Components
CustomRect {
id: root
required property PersistentProperties visibilities
anchors.bottom: parent.bottom
anchors.bottomMargin: 6
anchors.top: parent.top
anchors.topMargin: 6
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: 40
radius: Appearance.rounding.full
StateLayer {
onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard;
}
}
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurface
text: "widgets"
}
}
+7 -60
View File
@@ -15,78 +15,25 @@ Item {
reloadableId: "dashboardState" reloadableId: "dashboardState"
} }
readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0 readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
required property real offsetScale
readonly property bool shouldBeActive: root.visibilities.dashboard && Config.dashboard.enabled
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: 0 implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open
visible: height > 0 opacity: 1 - offsetScale
visible: offsetScale < 1
states: State {
name: "visible"
when: root.visibilities.dashboard && Config.dashboard.enabled
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
}
]
onStateChanged: {
if (state === "visible" && timer.running) {
timer.triggered();
timer.stop();
}
}
Timer {
id: timer
interval: Appearance.anim.durations.extraLarge
running: true
onTriggered: {
content.active = Qt.binding(() => (root.visibilities.dashboard && Config.dashboard.enabled) || root.visible);
content.visible = true;
}
}
CustomClippingRect {
anchors.fill: parent
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: false
sourceComponent: Content { sourceComponent: Content {
state: root.dashState state: root.dashState
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
}
} }
+13 -74
View File
@@ -11,97 +11,36 @@ Item {
property int contentHeight property int contentHeight
required property var panels required property var panels
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.dock
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: 0 readonly property bool shouldBeActive: visibilities.dock
implicitWidth: content.implicitWidth property real offsetScale: shouldBeActive ? 0 : 1
visible: height > 0
Behavior on implicitWidth { visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 400
opacity: 1 - offsetScale
Behavior on offsetScale {
Anim { Anim {
duration: Appearance.anim.durations.small duration: Appearance.anim.durations.expressiveDefaultSpatial
} easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
onShouldBeActiveChanged: {
if (shouldBeActive) {
timer.stop();
hideAnim.stop();
showAnim.start();
} else {
showAnim.stop();
hideAnim.start();
}
}
SequentialAnimation {
id: showAnim
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
property: "implicitHeight"
target: root
to: root.contentHeight
}
ScriptAction {
script: root.implicitHeight = Qt.binding(() => content.implicitHeight)
}
}
SequentialAnimation {
id: hideAnim
ScriptAction {
script: root.implicitHeight = root.implicitHeight
}
Anim {
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
property: "implicitHeight"
target: root
to: 0
}
}
Timer {
id: timer
interval: Appearance.anim.durations.small
onRunningChanged: {
if (running && !root.shouldBeActive) {
content.visible = false;
content.active = true;
} else {
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
if (showAnim.running) {
showAnim.stop();
showAnim.start();
}
}
} }
} }
Loader { Loader {
id: content id: content
active: false
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
visible: false
active: root.shouldBeActive || root.visible
sourceComponent: Content { sourceComponent: Content {
panels: root.panels panels: root.panels
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
Component.onCompleted: root.contentHeight = implicitHeight
} }
Component.onCompleted: timer.start()
} }
} }
+22 -74
View File
@@ -12,90 +12,29 @@ Item {
required property Canvas drawing required property Canvas drawing
property bool expanded: true property bool expanded: true
property real offsetScale: shouldBeActive ? 0 : 1
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.isDrawing readonly property bool shouldBeActive: visibilities.isDrawing
required property var visibilities required property var visibilities
anchors.leftMargin: (-implicitWidth - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: 0 implicitWidth: root.expanded ? content.implicitWidth : icon.implicitWidth
visible: width > 0 opacity: 1 - offsetScale
visible: offsetScale < 1
states: [ Behavior on implicitWidth {
State {
name: "hidden"
when: !root.shouldBeActive
PropertyChanges {
root.implicitWidth: 0
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "collapsed"
when: root.shouldBeActive && !root.expanded
PropertyChanges {
root.implicitWidth: icon.implicitWidth
}
PropertyChanges {
icon.opacity: 1
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "visible"
when: root.shouldBeActive && root.expanded
PropertyChanges {
root.implicitWidth: content.implicitWidth
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 1
}
}
]
transitions: [
Transition {
from: "*"
to: "*"
ParallelAnimation {
Anim { Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects duration: Appearance.anim.durations.expressiveDefaultSpatial
property: "implicitWidth" easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
target: root
} }
}
Behavior on offsetScale {
Anim { Anim {
duration: Appearance.anim.durations.small duration: Appearance.anim.durations.expressiveDefaultSpatial
property: "opacity" easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
target: icon
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: content
} }
} }
}
]
onVisibleChanged: { onVisibleChanged: {
if (!visible) if (!visible)
@@ -109,8 +48,12 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: content.contentItem.height height: content.contentItem.height
opacity: 1 opacity: root.expanded ? 0 : 1
Behavior on opacity {
Anim {
}
}
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: "arrow_forward_ios" text: "arrow_forward_ios"
@@ -122,7 +65,12 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
opacity: root.expanded ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: Content { sourceComponent: Content {
drawing: root.drawing drawing: root.drawing
visibilities: root.visibilities visibilities: root.visibilities
+4 -2
View File
@@ -9,17 +9,19 @@ import qs.Paths
Searcher { Searcher {
id: root id: root
readonly property list<string> command: Config.launcher.uwsm ? ["app2unit", "--"] : []
function launch(entry: DesktopEntry): void { function launch(entry: DesktopEntry): void {
appDb.incrementFrequency(entry.id); appDb.incrementFrequency(entry.id);
if (entry.runInTerminal) if (entry.runInTerminal)
Quickshell.execDetached({ Quickshell.execDetached({
command: ["app2unit", "--", ...Config.general.apps.terminal, `${Quickshell.shellDir}/assets/wrap_term_launch.sh`, ...entry.command], command: [...root.command, ...Config.general.apps.terminal, `${Quickshell.shellDir}/assets/wrap_term_launch.sh`, ...entry.command],
workingDirectory: entry.workingDirectory workingDirectory: entry.workingDirectory
}); });
else else
Quickshell.execDetached({ Quickshell.execDetached({
command: ["app2unit", "--", ...entry.command], command: [...root.command, ...entry.command],
workingDirectory: entry.workingDirectory workingDirectory: entry.workingDirectory
}); });
} }
+1 -1
View File
@@ -26,7 +26,7 @@ Searcher {
description: qsTr("Pastel palette with a low chroma.") description: qsTr("Pastel palette with a low chroma.")
icon: "android" icon: "android"
name: qsTr("Tonal Spot") name: qsTr("Tonal Spot")
variant: "tonalspot" variant: "tonal-spot"
}, },
Variant { Variant {
description: qsTr("Hue-shifted, artistic or playful colors.") description: qsTr("Hue-shifted, artistic or playful colors.")
+13 -40
View File
@@ -21,55 +21,33 @@ Item {
} }
required property var panels required property var panels
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.launcher
required property PersistentProperties visibilities required property PersistentProperties visibilities
readonly property bool shouldBeActive: visibilities.launcher
property real offsetScale: shouldBeActive ? 0 : 1
implicitHeight: 0
implicitWidth: content.implicitWidth
visible: height > 0
onMaxHeightChanged: timer.start()
onShouldBeActiveChanged: { onShouldBeActiveChanged: {
if (shouldBeActive) { if (shouldBeActive) {
implicitHeight = Qt.binding(() => content.implicitHeight);
timer.stop(); timer.stop();
hideAnim.stop();
showAnim.start();
} else { } else {
showAnim.stop(); implicitHeight = implicitHeight;
hideAnim.start();
} }
} }
SequentialAnimation { visible: offsetScale < 1
id: showAnim anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 400
opacity: 1 - offsetScale
Behavior on offsetScale {
Anim { Anim {
duration: Appearance.anim.durations.small duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveEffects easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "implicitHeight"
target: root
to: root.contentHeight
}
ScriptAction {
script: root.implicitHeight = Qt.binding(() => content.implicitHeight)
} }
} }
SequentialAnimation { onMaxHeightChanged: timer.start()
id: hideAnim
ScriptAction {
script: root.implicitHeight = root.implicitHeight
}
Anim {
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
property: "implicitHeight"
target: root
to: 0
}
}
Connections { Connections {
function onEnabledChanged(): void { function onEnabledChanged(): void {
@@ -105,10 +83,6 @@ Item {
root.contentHeight = Math.min(root.maxHeight, content.implicitHeight); root.contentHeight = Math.min(root.maxHeight, content.implicitHeight);
content.active = Qt.binding(() => root.shouldBeActive || root.visible); content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true; content.visible = true;
if (showAnim.running) {
showAnim.stop();
showAnim.start();
}
} }
} }
} }
@@ -119,7 +93,6 @@ Item {
active: false active: false
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
visible: false
sourceComponent: Content { sourceComponent: Content {
maxHeight: root.maxHeight maxHeight: root.maxHeight
+14 -9
View File
@@ -8,21 +8,25 @@ import qs.Helpers
Scope { Scope {
id: root id: root
readonly property bool enabled: !Players.list.some(p => p.isPlaying)
required property Lock lock required property Lock lock
readonly property bool enabled: !Players.list.some( p => p.isPlaying )
function handleIdleAction( action: var ): void { function handleIdleAction(action: var): void {
if ( !action ) if (!action)
return; return;
if ( action === "lock" ) if (action === "lock")
lock.lock.locked = true; lock.lock.locked = true;
else if ( action === "unlock" ) else if (action === "unlock")
lock.lock.locked = false; lock.lock.locked = false;
else if ( typeof action === "string" ) else if (action === "dpms on")
Hypr.dispatch( action ); Hypr.dispatch('hl.dsp.dpms({ action = "enable" })');
else if (action === "dpms off")
Hypr.dispatch('hl.dsp.dpms({ action = "disable" })');
else if (typeof action === "string")
Hypr.dispatch(action);
else else
Quickshell.execDetached( action ); Quickshell.execDetached(action);
} }
Variants { Variants {
@@ -33,7 +37,8 @@ Scope {
enabled: root.enabled && modelData.timeout > 0 ? true : false enabled: root.enabled && modelData.timeout > 0 ? true : false
timeout: modelData.timeout timeout: modelData.timeout
onIsIdleChanged: root.handleIdleAction( isIdle ? modelData.idleAction : modelData.activeAction )
onIsIdleChanged: root.handleIdleAction(isIdle ? modelData.idleAction : modelData.activeAction)
} }
} }
} }
+1
View File
@@ -150,6 +150,7 @@ WlSessionLockSurface {
id: background id: background
anchors.fill: parent anchors.fill: parent
fillMode: Image.PreserveAspectCrop
source: WallpaperPath.lockscreenBg source: WallpaperPath.lockscreenBg
} }
+2
View File
@@ -284,6 +284,8 @@ CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: { text: {
if (!Config.lock.showNotifContent)
return "Unlock to view";
const summary = modelData.summary.replace(/\n/g, " "); const summary = modelData.summary.replace(/\n/g, " ");
const body = modelData.body.replace(/\n/g, " "); const body = modelData.body.replace(/\n/g, " ");
const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline; const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline;
+5 -3
View File
@@ -25,6 +25,9 @@ Item {
height += list.itemAtIndex(i)?.nonAnimHeight ?? 0; height += list.itemAtIndex(i)?.nonAnimHeight ?? 0;
if (visibilities && panels) { if (visibilities && panels) {
if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || visibilities.dashboard)
return 0;
if (visibilities.osd) { if (visibilities.osd) {
const h = panels.osd.y - 8 * 2 - padding * 2; const h = panels.osd.y - 8 * 2 - padding * 2;
if (height > h) if (height > h)
@@ -194,8 +197,7 @@ Item {
} }
component Anim: NumberAnimation { component Anim: NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
easing.type: Easing.BezierSpline
} }
} }
@@ -78,6 +78,10 @@ LazyListView {
} }
} }
onDoubleClicked: event => {
if (event.button === Qt.LeftButton)
notifInner.toggleExpand(!notifInner.expanded);
}
onPositionChanged: event => { onPositionChanged: event => {
if (pressed) { if (pressed) {
const diffY = event.y - startY; const diffY = event.y - startY;
@@ -79,6 +79,10 @@ LazyListView {
Component.onCompleted: modelData?.lock(this) Component.onCompleted: modelData?.lock(this)
Component.onDestruction: modelData?.unlock(this) Component.onDestruction: modelData?.unlock(this)
onDoubleClicked: event => {
if (event.button === Qt.LeftButton)
root.requestToggleExpand(!root.expanded);
}
onPositionChanged: event => { onPositionChanged: event => {
if (pressed && !root.expanded) { if (pressed && !root.expanded) {
const diffY = event.y - startY; const diffY = event.y - startY;
@@ -5,6 +5,7 @@ import QtQuick.Layouts
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Modules import qs.Modules
import qs.Helpers
import qs.Daemons import qs.Daemons
CustomRect { CustomRect {
@@ -32,7 +33,7 @@ CustomRect {
Toggle { Toggle {
checked: Network.wifiEnabled checked: Network.wifiEnabled
icon: Network.wifiEnabled ? "wifi" : "wifi_off" icon: Network.wifiEnabled ? "wifi" : "wifi_off"
visible: QSNetwork.Networking.devices.values.length > 0 visible: QSNetwork.Networking.devices.values.some(n => n.type === QSNetwork.DeviceType.Wifi)
onClicked: Network.toggleWifi() onClicked: Network.toggleWifi()
} }
@@ -79,6 +80,13 @@ CustomRect {
adapter.enabled = !adapter.enabled; adapter.enabled = !adapter.enabled;
} }
} }
Toggle {
checked: GameMode.enabled
icon: GameMode.enabled ? "videogame_asset" : "videogame_asset_off"
onClicked: GameMode.enabled = !GameMode.enabled
}
} }
} }
+11 -51
View File
@@ -20,69 +20,29 @@ Item {
required property Item sidebar required property Item sidebar
required property var visibilities required property var visibilities
implicitHeight: 0 property real offsetScale: shouldBeActive ? 0 : 1
implicitWidth: sidebar.visible ? sidebar.width : Config.utilities.sizes.width
visible: height > 0
states: State { visible: offsetScale < 1
name: "visible" anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
when: root.shouldBeActive implicitHeight: content.implicitHeight + 8 * 2
implicitWidth: sidebar.width * (1 - sidebar.offsetScale)
PropertyChanges { opacity: 1 - offsetScale
root.implicitHeight: content.implicitHeight + 8 * 2
}
}
transitions: [
Transition {
from: ""
to: "visible"
Behavior on offsetScale {
Anim { Anim {
duration: MaterialEasing.expressiveEffectsTime duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
}
]
onStateChanged: {
if (state === "visible" && timer.running) {
timer.triggered();
timer.stop();
}
}
Timer {
id: timer
interval: 1000
running: true
onTriggered: {
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
} }
} }
Loader { Loader {
id: content id: content
active: true
anchors.left: parent.left anchors.left: parent.left
anchors.margins: 8 anchors.margins: 8
anchors.top: parent.top anchors.top: parent.top
visible: false
active: root.shouldBeActive || root.visible
sourceComponent: Content { sourceComponent: Content {
implicitWidth: root.implicitWidth - 8 * 2 implicitWidth: root.implicitWidth - 8 * 2
+11 -34
View File
@@ -7,50 +7,29 @@ import QtQuick
Item { Item {
id: root id: root
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels required property var panels
readonly property Props props: Props { readonly property Props props: Props {
} }
readonly property bool shouldBeActive: root.visibilities.sidebar && Config.sidebar.enabled
required property var visibilities required property var visibilities
implicitWidth: 0 anchors.rightMargin: (-implicitWidth - 5) * offsetScale
visible: width > 0 implicitWidth: Config.sidebar.sizes.width
opacity: 1 - offsetScale
states: State { visible: offsetScale < 1
name: "visible"
when: root.visibilities.sidebar
PropertyChanges {
root.implicitWidth: Config.sidebar.sizes.width
}
}
transitions: [
Transition {
from: ""
to: "visible"
Behavior on offsetScale {
Anim { Anim {
duration: MaterialEasing.expressiveEffectsTime duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "implicitWidth"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
} }
} }
]
Loader { Loader {
id: content id: content
active: true active: root.shouldBeActive || root.visible
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 0 anchors.bottomMargin: 0
anchors.left: parent.left anchors.left: parent.left
@@ -58,11 +37,9 @@ Item {
anchors.top: parent.top anchors.top: parent.top
sourceComponent: Content { sourceComponent: Content {
implicitWidth: Config.sidebar.sizes.width - 8 * 2 implicitWidth: Config.sidebar.sizes.width - Appearance.padding.smaller * 2
props: root.props props: root.props
visibilities: root.visibilities visibilities: root.visibilities
} }
Component.onCompleted: active = Qt.binding(() => (root.visibilities.sidebar && Config.sidebar.enabled) || root.visible)
} }
} }
+2 -18
View File
@@ -6,29 +6,13 @@ Item {
id: root id: root
required property Item panels required property Item panels
required property Item sidebarPanel
required property var visibilities required property var visibilities
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: Math.max(panels.sidebar.width, content.implicitWidth) implicitWidth: Math.max(sidebarPanel.width * (1 - sidebarPanel.offsetScale), content.implicitWidth)
visible: height > 0 visible: height > 0
states: State {
name: "hidden"
when: root.visibilities.sidebar || root.visibilities.dashboard || (root.panels.popouts.hasCurrent && root.panels.popouts.currentName.startsWith("traymenu"))
PropertyChanges {
root.implicitHeight: 0
}
}
transitions: Transition {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
}
Content { Content {
id: content id: content
+13 -35
View File
@@ -26,40 +26,22 @@ Item {
timer.restart(); timer.restart();
} }
property real offsetScale: shouldBeActive ? 0 : 1
required property bool sidebarOrSessionVisible
property real sidebarOffset: sidebarOrSessionVisible ? 12 : 0
visible: offsetScale < 1
anchors.rightMargin: (-implicitWidth - 5 - sidebarOffset) * offsetScale
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: 0 opacity: 1 - offsetScale
visible: width > 0
states: State {
name: "visible"
when: root.shouldBeActive
PropertyChanges {
root.implicitWidth: content.implicitWidth
}
}
transitions: [
Transition {
from: ""
to: "visible"
Behavior on offsetScale {
Anim { Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects duration: Appearance.anim.durations.expressiveDefaultSpatial
property: "implicitWidth" easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
} }
} }
]
Component.onCompleted: { Component.onCompleted: {
volume = Audio.volume; volume = Audio.volume;
@@ -113,15 +95,14 @@ Item {
} }
} }
CustomClippingRect {
anchors.fill: parent
Loader { Loader {
id: content id: content
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
active: root.shouldBeActive || root.visible
sourceComponent: Content { sourceComponent: Content {
brightness: root.brightness brightness: root.brightness
monitor: root.monitor monitor: root.monitor
@@ -131,8 +112,5 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
volume: root.volume volume: root.volume
} }
Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible)
}
} }
} }
+36
View File
@@ -0,0 +1,36 @@
import QtQuick
QtObject {
id: root
property string currentName
property bool hasCurrent
property var submenus: []
signal detachRequested(mode: string)
function clearSubmenus(): void {
submenus = [];
}
function closeSubmenus(level: int): void {
submenus = submenus.slice(0, level);
}
function pushSubmenu(level: int, handle: var, sourceItem: var, sourceWidth: int): void {
let newSubmenus = submenus.slice(0, level);
newSubmenus.push({
"handle": handle,
"sourceItem": sourceItem,
"sourceWidth": sourceWidth
});
submenus = newSubmenus;
}
onCurrentNameChanged: {
root.clearSubmenus();
}
onHasCurrentChanged: {
root.clearSubmenus();
}
}
+4 -4
View File
@@ -60,10 +60,10 @@ RowLayout {
color: root.mainColor color: root.mainColor
implicitHeight: Math.ceil(root.percentage * parent.height) implicitHeight: Math.ceil(root.percentage * parent.height)
Behavior on implicitHeight { // Behavior on implicitHeight {
Anim { // Anim {
} // }
} // }
} }
} }
} }
+1 -1
View File
@@ -17,7 +17,7 @@ CustomRect {
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2 implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2
radius: height / 2 radius: Appearance.rounding.full
StateLayer { StateLayer {
onClicked: root.visibilities.resources = !root.visibilities.resources onClicked: root.visibilities.resources = !root.visibilities.resources
+12 -56
View File
@@ -9,78 +9,34 @@ Item {
id: root id: root
readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0 readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
property real offsetScale: shouldBeActive ? 0 : 1
readonly property bool shouldBeActive: root.visibilities.resources
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: 0 anchors.topMargin: (-implicitHeight - 5) * offsetScale
implicitWidth: content.implicitWidth clip: true
visible: height > 0 implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open
states: State { opacity: 1 - offsetScale
name: "visible" visible: offsetScale < 1
when: root.visibilities.resources
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
Behavior on offsetScale {
Anim { Anim {
duration: MaterialEasing.expressiveEffectsTime duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
} }
} }
]
onStateChanged: {
if (state === "visible" && timer.running) {
timer.triggered();
timer.stop();
}
}
Timer {
id: timer
interval: Appearance.anim.durations.extraLarge
running: true
onTriggered: {
content.active = Qt.binding(() => (root.visibilities.resources) || root.visible);
content.visible = true;
}
}
CustomClippingRect {
anchors.fill: parent
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: false
sourceComponent: Content { sourceComponent: Content {
padding: Appearance.padding.normal padding: Appearance.padding.normal
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
}
} }
+19 -1
View File
@@ -104,6 +104,18 @@ Item {
key: "launcher" key: "launcher"
name: "Launcher" name: "Launcher"
} }
ListElement {
icon: "screenshot_region"
key: "screenshot"
name: "Screenshot"
}
ListElement {
icon: "cached"
key: "updates"
name: "Updates"
}
} }
CustomClippingRect { CustomClippingRect {
@@ -153,7 +165,7 @@ Item {
required property string name required property string name
implicitHeight: 42 implicitHeight: 42
implicitWidth: 200 implicitWidth: 250
radius: Appearance.rounding.normal - Appearance.padding.smaller radius: Appearance.rounding.normal - Appearance.padding.smaller
RowLayout { RowLayout {
@@ -171,9 +183,15 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: icon.contentWidth Layout.preferredWidth: icon.contentWidth
color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: categoryItem.index === clayout.currentIndex ? 1 : 0
font.pointSize: Appearance.font.size.small * 2 font.pointSize: Appearance.font.size.small * 2
text: categoryItem.icon text: categoryItem.icon
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Behavior on fill {
Anim {
}
}
} }
CustomText { CustomText {
+15 -23
View File
@@ -57,6 +57,16 @@ SettingsPage {
setting: "scale" setting: "scale"
step: 0.1 step: 0.1
} }
Separator {
}
SettingSpinBox {
name: "Deform animation scale"
object: Config.appearance.deform
setting: "scale"
step: 0.1
}
} }
SettingsSection { SettingsSection {
@@ -66,7 +76,7 @@ SettingsPage {
name: "Fonts" name: "Fonts"
} }
SettingInput { SettingListView {
name: "Sans family" name: "Sans family"
object: Config.appearance.font.family object: Config.appearance.font.family
setting: "sans" setting: "sans"
@@ -75,29 +85,11 @@ SettingsPage {
Separator { Separator {
} }
SettingInput { SettingListView {
name: "Monospace family" name: "Monospace family"
object: Config.appearance.font.family object: Config.appearance.font.family
setting: "mono" setting: "mono"
} }
Separator {
}
SettingInput {
name: "Material family"
object: Config.appearance.font.family
setting: "material"
}
Separator {
}
SettingInput {
name: "Clock family"
object: Config.appearance.font.family
setting: "clock"
}
} }
SettingsSection { SettingsSection {
@@ -118,9 +110,9 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Session GIF speed"
max: 5 max: 5
min: 0 min: 0
name: "Session GIF speed"
object: Config.appearance.anim object: Config.appearance.anim
setting: "sessionGifSpeed" setting: "sessionGifSpeed"
step: 0.1 step: 0.1
@@ -144,9 +136,9 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Base opacity"
max: 1 max: 1
min: 0 min: 0
name: "Base opacity"
object: Config.appearance.transparency object: Config.appearance.transparency
setting: "base" setting: "base"
step: 0.05 step: 0.05
@@ -156,9 +148,9 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Layer opacity"
max: 1 max: 1
min: 0 min: 0
name: "Layer opacity"
object: Config.appearance.transparency object: Config.appearance.transparency
setting: "layers" setting: "layers"
step: 0.05 step: 0.05
+7 -7
View File
@@ -29,13 +29,13 @@ SettingsPage {
step: 50 step: 50
} }
// Separator { Separator {
// } }
//
// WallpaperCropper { WallpaperCropper {
// Layout.fillWidth: true Layout.fillWidth: true
// Layout.preferredHeight: 300 Layout.preferredHeight: 600
// } }
} }
SettingsSection { SettingsSection {
+16 -6
View File
@@ -19,8 +19,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Height"
min: 1 min: 1
name: "Height"
object: Config.barConfig object: Config.barConfig
setting: "height" setting: "height"
} }
@@ -29,8 +29,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Rounding"
min: 0 min: 0
name: "Rounding"
object: Config.barConfig object: Config.barConfig
setting: "rounding" setting: "rounding"
} }
@@ -39,11 +39,21 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Border"
min: 0 min: 0
name: "Border"
object: Config.barConfig object: Config.barConfig
setting: "border" setting: "border"
} }
Separator {
}
SettingSpinBox {
min: 0
name: "Smoothing"
object: Config.barConfig
setting: "smoothing"
}
} }
SettingsSection { SettingsSection {
@@ -145,8 +155,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Dock height"
min: 1 min: 1
name: "Dock height"
object: Config.dock object: Config.dock
setting: "height" setting: "height"
} }
@@ -173,8 +183,8 @@ SettingsPage {
} }
SettingStringList { SettingStringList {
name: "Pinned apps"
addLabel: qsTr("Add pinned app") addLabel: qsTr("Add pinned app")
name: "Pinned apps"
object: Config.dock object: Config.dock
setting: "pinnedApps" setting: "pinnedApps"
} }
@@ -183,8 +193,8 @@ SettingsPage {
} }
SettingStringList { SettingStringList {
name: "Ignored app regexes"
addLabel: qsTr("Add ignored regex") addLabel: qsTr("Add ignored regex")
name: "Ignored app regexes"
object: Config.dock object: Config.dock
setting: "ignoredAppRegexes" setting: "ignoredAppRegexes"
} }
+11 -7
View File
@@ -47,6 +47,15 @@ SettingsPage {
object: Config.general object: Config.general
setting: "desktopIcons" setting: "desktopIcons"
} }
Separator {
}
SettingInput {
name: "Date format"
object: Config.general
setting: "dateFormat"
}
} }
SettingsSection { SettingsSection {
@@ -118,7 +127,7 @@ SettingsPage {
MenuItem { MenuItem {
icon: "gradient" icon: "gradient"
text: qsTr("Tonal spot") text: qsTr("Tonal spot")
value: "tonalSpot" value: "tonal-spot"
}, },
MenuItem { MenuItem {
icon: "target" icon: "target"
@@ -138,7 +147,7 @@ SettingsPage {
MenuItem { MenuItem {
icon: "nutrition" icon: "nutrition"
text: qsTr("Fruit salad") text: qsTr("Fruit salad")
value: "fruitSalad" value: "fruit-salad"
} }
] ]
@@ -146,11 +155,6 @@ SettingsPage {
Config.colors.schemeType = item.value; Config.colors.schemeType = item.value;
Config.save(); Config.save();
if (item.value === "tonalSpot")
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", "tonal-spot"]);
else if (item.value === "fruitSalad")
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", "fruit-salad"]);
else
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", item.value]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", item.value]);
} }
} }
+15 -6
View File
@@ -10,8 +10,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Max apps shown"
min: 1 min: 1
name: "Max apps shown"
object: Config.launcher object: Config.launcher
setting: "maxAppsShown" setting: "maxAppsShown"
} }
@@ -20,8 +20,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Max wallpapers shown"
min: 1 min: 1
name: "Max wallpapers shown"
object: Config.launcher object: Config.launcher
setting: "maxWallpapers" setting: "maxWallpapers"
} }
@@ -43,6 +43,15 @@ SettingsPage {
object: Config.launcher object: Config.launcher
setting: "specialPrefix" setting: "specialPrefix"
} }
Separator {
}
SettingSwitch {
name: "Use UWSM launch command"
object: Config.launcher
setting: "uwsm"
}
} }
SettingsSection { SettingsSection {
@@ -103,8 +112,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Item width"
min: 1 min: 1
name: "Item width"
object: Config.launcher.sizes object: Config.launcher.sizes
setting: "itemWidth" setting: "itemWidth"
} }
@@ -113,8 +122,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Item height"
min: 1 min: 1
name: "Item height"
object: Config.launcher.sizes object: Config.launcher.sizes
setting: "itemHeight" setting: "itemHeight"
} }
@@ -123,8 +132,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Wallpaper width"
min: 1 min: 1
name: "Wallpaper width"
object: Config.launcher.sizes object: Config.launcher.sizes
setting: "wallpaperWidth" setting: "wallpaperWidth"
} }
@@ -133,8 +142,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Wallpaper height"
min: 1 min: 1
name: "Wallpaper height"
object: Config.launcher.sizes object: Config.launcher.sizes
setting: "wallpaperHeight" setting: "wallpaperHeight"
} }
+14 -5
View File
@@ -31,8 +31,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Max fingerprint tries"
min: 1 min: 1
name: "Max fingerprint tries"
object: Config.lock object: Config.lock
setting: "maxFprintTries" setting: "maxFprintTries"
step: 1 step: 1
@@ -41,9 +41,18 @@ SettingsPage {
Separator { Separator {
} }
SettingSwitch {
name: "Show notification details"
object: Config.lock
setting: "showNotifContent"
}
Separator {
}
SettingSpinBox { SettingSpinBox {
name: "Blur amount"
min: 0 min: 0
name: "Blur amount"
object: Config.lock object: Config.lock
setting: "blurAmount" setting: "blurAmount"
step: 1 step: 1
@@ -53,9 +62,9 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Height multiplier"
max: 2 max: 2
min: 0.1 min: 0.1
name: "Height multiplier"
object: Config.lock.sizes object: Config.lock.sizes
setting: "heightMult" setting: "heightMult"
step: 0.05 step: 0.05
@@ -65,9 +74,9 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Aspect ratio"
max: 4 max: 4
min: 0.5 min: 0.5
name: "Aspect ratio"
object: Config.lock.sizes object: Config.lock.sizes
setting: "ratio" setting: "ratio"
step: 0.05 step: 0.05
@@ -77,8 +86,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Center width"
min: 100 min: 100
name: "Center width"
object: Config.lock.sizes object: Config.lock.sizes
setting: "centerWidth" setting: "centerWidth"
step: 10 step: 10
@@ -22,6 +22,13 @@ ColumnLayout {
Config.save(); Config.save();
} }
function deleteTimeoutEntry(index) {
let list = [...Config.general.idle.timeouts];
list.splice(index, 1);
Config.general.idle.timeouts = list;
Config.save();
}
function updateTimeoutEntry(i, key, value) { function updateTimeoutEntry(i, key, value) {
const list = [...Config.general.idle.timeouts]; const list = [...Config.general.idle.timeouts];
let entry = list[i]; let entry = list[i];
@@ -49,6 +56,9 @@ ColumnLayout {
onAddActiveActionRequested: { onAddActiveActionRequested: {
root.updateTimeoutEntry(index, "activeAction", ""); root.updateTimeoutEntry(index, "activeAction", "");
} }
onDeleteRequested: function (index) {
root.deleteTimeoutEntry(index);
}
onFieldEdited: function (key, value) { onFieldEdited: function (key, value) {
root.updateTimeoutEntry(index, key, value); root.updateTimeoutEntry(index, key, value);
} }
+130
View File
@@ -0,0 +1,130 @@
import qs.Modules.Settings.Controls
import qs.Config
import qs.Components
SettingsPage {
SettingsSection {
sectionId: "Screenshot"
SettingsHeader {
name: "Screenshot"
}
SettingSwitch {
name: "Enable effects"
object: Config.screenshot
setting: "enable_pp"
}
Separator {
}
CustomSplitButtonRow {
// active: true
label: qsTr("Effects mode")
menuItems: [
MenuItem {
icon: "build"
text: qsTr("Manual")
value: "manual"
},
MenuItem {
icon: "rotate_auto"
text: qsTr("Auto")
value: "auto"
}
]
onSelected: item => {
Config.screenshot.mode = item.value;
Config.save();
}
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSpinBox {
min: 0
name: "Corner radius"
object: Config.screenshot
setting: "corner_radius"
step: 1
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSwitch {
name: "Enable drop shadow"
object: Config.screenshot
setting: "drop_shadow"
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSwitch {
name: "Enable rounded corners"
object: Config.screenshot
setting: "rounded_corners"
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSpinBox {
min: 0
name: "Shadow blur radius"
object: Config.screenshot
setting: "shadow_blur_radius"
step: 1
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSwitch {
name: "Shadow color broken atm"
object: Config.Screenshot
setting: "shadow_color"
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSpinBox {
min: 0
name: "Shadow offset X"
object: Config.screenshot
setting: "shadow_offset_x"
step: 1
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
}
SettingSpinBox {
min: 0
name: "Shadow offset Y"
object: Config.screenshot
setting: "shadow_offset_y"
step: 1
visible: Config.screenshot.mode === "manual"
}
}
}
@@ -0,0 +1,197 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick.Layouts
import QtQuick
import qs.Config
import qs.Helpers
import qs.Components
import qs.Modules.Settings.Controls
CustomClippingRect {
id: root
radius: Appearance.rounding.normal - Appearance.padding.smaller
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
Layout.margins: Appearance.padding.large
spacing: Appearance.spacing.large
MaterialIcon {
font.pointSize: Appearance.font.size.larger * 4
text: "update"
}
ColumnLayout {
CustomText {
font.pointSize: Appearance.font.size.large * 2
text: "System updates"
}
RowLayout {
id: row
Layout.fillWidth: true
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: `${Updates.availableUpdates} available updates`
}
CustomRect {
Layout.preferredHeight: 40
Layout.preferredWidth: 150
color: Updates.updating ? DynamicColors.layer(DynamicColors.palette.m3outline, 2) : DynamicColors.palette.m3primary
radius: Appearance.rounding.full
RowLayout {
anchors.centerIn: parent
MaterialIcon {
animate: true
color: DynamicColors.palette.m3onPrimary
font.pointSize: Appearance.font.size.large
text: Updates.updating ? "update" : "download"
}
CustomText {
color: Updates.updating ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onPrimary
text: "Update all"
}
}
StateLayer {
color: DynamicColors.palette.m3onPrimary
disabled: Updates.updating
onClicked: Updates.performSystemUpdate()
}
}
}
}
}
CustomListView {
id: view
readonly property int itemHeight: 50 + Appearance.padding.smaller * 2
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
contentHeight: height
spacing: Appearance.spacing.normal
delegate: CustomRect {
id: update
required property var modelData
readonly property list<string> sections: modelData.update.split(" ")
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: view.itemHeight
implicitWidth: parent.width
radius: Appearance.rounding.small - Appearance.padding.small
RowLayout {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.smaller
MaterialIcon {
font.pointSize: Appearance.font.size.large * 2
text: "package_2"
}
ColumnLayout {
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.preferredWidth: 500
MarqueeText {
id: versionFrom
Layout.fillHeight: true
Layout.preferredWidth: 225
animate: true
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[1]
width: 225
}
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: 225
animate: true
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[3]
width: 225
}
}
IconButton {
Layout.preferredHeight: width
icon: "download"
onClicked: {
Updates.performPackageUpdate(update.sections[0]);
}
}
}
}
model: ScriptModel {
id: script
objectProp: "update"
values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({
update,
timestamp
}))
}
}
}
}
+18
View File
@@ -74,6 +74,10 @@ Item {
stack.push(osd); stack.push(osd);
else if (currentCategory === "launcher") else if (currentCategory === "launcher")
stack.push(launcher); stack.push(launcher);
else if (currentCategory === "screenshot")
stack.push(screenshot);
else if (currentCategory === "updates")
stack.push(updates);
} }
target: root target: root
@@ -225,4 +229,18 @@ Item {
Cat.Launcher { Cat.Launcher {
} }
} }
Component {
id: screenshot
Cat.Screenshot {
}
}
Component {
id: updates
Cat.SystemUpdates {
}
}
} }
+29 -346
View File
@@ -28,72 +28,11 @@ Item {
property int uidCounter: 0 property int uidCounter: 0
property var visualEntries: [] property var visualEntries: []
function beginVisualDrag(uid, modelData, item) {
const pos = item.mapToItem(root, 0, 0);
root.draggedUid = uid;
root.draggedModelData = modelData;
root.dragHeight = item.height;
root.dragStartX = pos.x;
root.dragStartY = pos.y;
root.dragX = pos.x;
root.dragY = pos.y;
root.dragActive = true;
root.dropAnimating = false;
root.pendingCommitEntries = [];
}
function commitVisualOrder(entries) {
const list = [];
for (let i = 0; i < entries.length; i++)
list.push(entries[i].entry);
root.object[root.setting] = list;
Config.save();
root.rebuildVisualEntries();
}
function endVisualDrag() {
const entries = root.visualEntries.slice();
const finalIndex = root.indexForUid(root.draggedUid);
const finalItem = listView.itemAtIndex(finalIndex);
root.dragActive = false;
if (!finalItem) {
root.pendingCommitEntries = entries;
root.finishVisualDrag();
return;
}
const pos = finalItem.mapToItem(root, 0, 0);
root.pendingCommitEntries = entries;
root.dropAnimating = true;
settleX.to = pos.x;
settleY.to = pos.y;
settleAnim.start();
}
function ensureVisualEntries() { function ensureVisualEntries() {
if (!root.dragActive && !root.dropAnimating) if (!root.dragActive && !root.dropAnimating)
root.rebuildVisualEntries(); root.rebuildVisualEntries();
} }
function finishVisualDrag() {
const entries = root.pendingCommitEntries.slice();
root.dragActive = false;
root.dropAnimating = false;
root.draggedUid = "";
root.draggedModelData = null;
root.pendingCommitEntries = [];
root.dragHeight = 0;
root.commitVisualOrder(entries);
}
function iconForId(id) { function iconForId(id) {
switch (id) { switch (id) {
case "workspaces": case "workspaces":
@@ -153,7 +92,7 @@ Item {
case "spacer": case "spacer":
return qsTr("Spacer"); return qsTr("Spacer");
case "activeWindow": case "activeWindow":
return qsTr("Active window"); return qsTr("Title");
case "tray": case "tray":
return qsTr("Tray"); return qsTr("Tray");
case "upower": case "upower":
@@ -163,34 +102,14 @@ Item {
case "clock": case "clock":
return qsTr("Clock"); return qsTr("Clock");
case "notifBell": case "notifBell":
return qsTr("Notification bell"); return qsTr("Notifs");
case "hyprsunset":
return qsTr("Night light");
default: default:
return id; return id;
} }
} }
function moveArrayItem(list, from, to) {
const next = list.slice();
const [item] = next.splice(from, 1);
next.splice(to, 0, item);
return next;
}
function previewVisualMove(from, hovered, before) {
let to = hovered + (before ? 0 : 1);
if (to > from)
to -= 1;
to = Math.max(0, Math.min(visualModel.items.count - 1, to));
if (from === to)
return;
visualModel.items.move(from, to);
root.visualEntries = root.moveArrayItem(root.visualEntries, from, to);
}
function rebuildVisualEntries() { function rebuildVisualEntries() {
const entries = root.object[root.setting] ?? []; const entries = root.object[root.setting] ?? [];
const next = []; const next = [];
@@ -225,7 +144,6 @@ Item {
list[index] = entry; list[index] = entry;
root.object[root.setting] = list; root.object[root.setting] = list;
Config.save(); Config.save();
root.ensureVisualEntries();
} }
Layout.fillWidth: true Layout.fillWidth: true
@@ -233,7 +151,7 @@ Item {
Component.onCompleted: root.rebuildVisualEntries() Component.onCompleted: root.rebuildVisualEntries()
Rectangle { CustomRect {
anchors.fill: parent anchors.fill: parent
anchors.margins: -Appearance.padding.smaller anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer color: DynamicColors.palette.m3primaryContainer
@@ -248,39 +166,12 @@ Item {
} }
} }
ParallelAnimation { RowLayout {
id: settleAnim
onFinished: root.finishVisualDrag()
Anim {
id: settleX
duration: Appearance.anim.durations.normal
property: "dragX"
target: root
}
Anim {
id: settleY
duration: Appearance.anim.durations.normal
property: "dragY"
target: root
}
}
ColumnLayout {
id: layout id: layout
anchors.fill: parent anchors.fill: parent
spacing: Appearance.spacing.smaller
CustomText { // spacing: Appearance.spacing.smaller
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
DelegateModel { DelegateModel {
id: visualModel id: visualModel
@@ -293,248 +184,40 @@ Item {
} }
} }
ListView { Repeater {
id: listView delegate: entryDelegate
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
boundsBehavior: Flickable.StopAtBounds
clip: false
implicitHeight: contentHeight
implicitWidth: width
interactive: !(root.dragActive || root.dropAnimating)
model: visualModel model: visualModel
spacing: Appearance.spacing.small
add: Transition {
Anim {
properties: "opacity,scale"
to: 1
}
}
addDisplaced: Transition {
Anim {
duration: Appearance.anim.durations.normal
property: "y"
}
}
displaced: Transition {
Anim {
duration: Appearance.anim.durations.normal
property: "y"
}
}
move: Transition {
Anim {
duration: Appearance.anim.durations.normal
property: "y"
}
}
removeDisplaced: Transition {
Anim {
duration: Appearance.anim.durations.normal
property: "y"
}
}
}
}
Loader {
active: root.dragActive || root.dropAnimating
asynchronous: false
sourceComponent: Item {
Drag.active: root.dragActive
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
height: proxyRect.implicitHeight
implicitHeight: proxyRect.implicitHeight
implicitWidth: listView.width
visible: root.draggedModelData !== null
width: listView.width
x: root.dragX
y: root.dragY
z: 100
Drag.source: QtObject {
property string uid: root.draggedUid
property int visualIndex: root.indexForUid(root.draggedUid)
}
CustomRect {
id: proxyRect
color: DynamicColors.tPalette.m3surface
implicitHeight: proxyRow.implicitHeight + Appearance.padding.small * 2
implicitWidth: parent.width
opacity: 0.95
radius: Appearance.rounding.normal
width: parent.width
RowLayout {
id: proxyRow
anchors.fill: parent
anchors.margins: Appearance.padding.small
spacing: Appearance.spacing.normal
CustomRect {
color: Qt.alpha(DynamicColors.palette.m3onSurface, 0.12)
implicitHeight: 32
implicitWidth: implicitHeight
radius: Appearance.rounding.small
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
text: "drag_indicator"
}
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
text: root.iconForId(root.draggedModelData?.entry?.id ?? "")
}
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.labelForId(root.draggedModelData?.entry?.id ?? "")
}
CustomSwitch {
checked: root.draggedModelData?.entry?.enabled ?? true
enabled: false
}
}
}
} }
} }
Component { Component {
id: entryDelegate id: entryDelegate
DropArea { IconButton {
id: slot required property int index
readonly property var entryData: modelData.entry
required property var modelData required property var modelData
readonly property string uid: modelData.uid
function previewReorder(drag) {
const source = drag.source;
if (!source || !source.uid || source.uid === slot.uid)
return;
const from = source.visualIndex;
const hovered = slot.DelegateModel.itemsIndex;
if (from < 0 || hovered < 0)
return;
root.previewVisualMove(from, hovered, drag.y < height / 2);
}
height: entryRow.implicitHeight
implicitHeight: entryRow.implicitHeight
implicitWidth: listView.width
width: ListView.view ? ListView.view.width : listView.width
onEntered: drag => previewReorder(drag)
onPositionChanged: drag => previewReorder(drag)
CustomRect {
id: entryRow
anchors.fill: parent
color: DynamicColors.tPalette.m3surface
implicitHeight: entryLayout.implicitHeight + Appearance.padding.small * 2
implicitWidth: parent.width
opacity: root.draggedUid === slot.uid ? 0 : 1
radius: Appearance.rounding.full
Behavior on opacity {
Anim {
}
}
RowLayout {
id: entryLayout
anchors.fill: parent
anchors.margins: Appearance.padding.small
spacing: Appearance.spacing.normal
CustomRect {
id: handle
color: Qt.alpha(DynamicColors.palette.m3onSurface, handleDrag.active ? 0.12 : handleHover.hovered ? 0.09 : 0.06)
implicitHeight: 32
implicitWidth: implicitHeight
radius: Appearance.rounding.full
Behavior on color {
CAnim {
}
}
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
text: "drag_indicator"
}
HoverHandler {
id: handleHover
cursorShape: handleDrag.active ? Qt.ClosedHandCursor : Qt.OpenHandCursor
}
DragHandler {
id: handleDrag
enabled: true
grabPermissions: PointerHandler.CanTakeOverFromAnything
target: null
xAxis.enabled: false
yAxis.enabled: true
onActiveChanged: {
if (active) {
root.beginVisualDrag(slot.uid, slot.modelData, entryRow);
} else if (root.draggedUid === slot.uid) {
root.endVisualDrag();
}
}
onActiveTranslationChanged: {
if (!active || root.draggedUid !== slot.uid)
return;
root.dragX = root.dragStartX;
root.dragY = root.dragStartY + activeTranslation.y;
}
}
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
text: root.iconForId(slot.entryData.id)
}
CustomText {
Layout.fillWidth: true Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0)
text: root.labelForId(slot.entryData.id) checked: modelData.entry.enabled ?? true
font: Appearance.font.family.sans
// icon: root.iconForId(modelData.entry.id)
icon: root.labelForId(modelData.entry.id)
inactiveColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
toggle: true
visible: !["spacer", "upower", "dash", "audio"].some(prefix => modelData.entry.id.startsWith(prefix))
Behavior on Layout.preferredWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
} }
CustomSwitch { onClicked: root.updateEntry(index, internalChecked)
Layout.rightMargin: Appearance.padding.small
checked: slot.entryData.enabled ?? true
onToggled: root.updateEntry(slot.DelegateModel.itemsIndex, checked)
}
}
}
} }
} }
} }
+2 -2
View File
@@ -59,7 +59,7 @@ Item {
id: rect id: rect
Layout.preferredHeight: 33 Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.normal * 2, 550), 50) Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.large * 2, 550), 50)
color: DynamicColors.tPalette.m3surfaceContainerHigh color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -68,7 +68,7 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth, 550) implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
text: root.formattedValue() text: root.formattedValue()
onEditingFinished: { onEditingFinished: {
+31 -8
View File
@@ -10,6 +10,7 @@ Item {
required property var modelData required property var modelData
signal addActiveActionRequested signal addActiveActionRequested
signal deleteRequested(int index)
signal fieldEdited(string key, var value) signal fieldEdited(string key, var value)
Layout.fillWidth: true Layout.fillWidth: true
@@ -65,42 +66,64 @@ Item {
HoverHandler { HoverHandler {
id: nameHover id: nameHover
}
HoverIconButton {
id: editButton
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large
icon: "edit"
shouldBeVisible: nameHover.hovered && !nameCell.editing
onClicked: nameCell.beginEdit()
} }
CustomText { CustomText {
anchors.left: parent.left anchors.left: parent.left
anchors.right: editButton.left anchors.leftMargin: nameHover.hovered ? editButton.width + Appearance.spacing.smaller * 2 : 0
anchors.right: deleteButton.left
anchors.rightMargin: Appearance.spacing.small anchors.rightMargin: Appearance.spacing.small
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight // enable if CustomText supports it elide: Text.ElideRight // enable if CustomText supports it
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: root.modelData.name text: root.modelData.name
visible: !nameCell.editing visible: !nameCell.editing
Behavior on anchors.leftMargin {
Anim {
}
}
} }
IconButton { HoverIconButton {
id: editButton id: deleteButton
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large font.pointSize: Appearance.font.size.large
icon: "edit" icon: "delete"
visible: nameHover.hovered && !nameCell.editing shouldBeVisible: nameHover.hovered && !nameCell.editing
onClicked: nameCell.beginEdit() onClicked: root.deleteRequested(root.index)
} }
CustomRect { CustomRect {
anchors.fill: parent anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.tPalette.m3surface color: DynamicColors.tPalette.m3surface
implicitHeight: nameEditor.implicitHeight + (Appearance.padding.normal * 2)
implicitWidth: Math.min(nameEditor.contentWidth + (Appearance.padding.normal * 2), parent.width - Appearance.padding.normal)
radius: Appearance.rounding.small radius: Appearance.rounding.small
visible: nameCell.editing visible: nameCell.editing
CustomTextField { CustomTextField {
id: nameEditor id: nameEditor
anchors.fill: parent anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, nameCell.width - Appearance.padding.normal)
text: nameCell.draftName text: nameCell.draftName
Keys.onEscapePressed: { Keys.onEscapePressed: {
@@ -0,0 +1,140 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
Item {
id: root
required property string name
required property var object
required property string setting
Layout.fillWidth: true
Layout.preferredHeight: row.height
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomClippingRect {
id: fontArea
Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5
Layout.preferredWidth: 500
color: DynamicColors.tPalette.m3surfaceContainer
radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale
CustomRect {
id: searchBox
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 42
radius: Appearance.rounding.full
MaterialIcon {
id: searchIcon
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.large
anchors.top: parent.top
font.pointSize: Appearance.font.size.large
text: "search"
verticalAlignment: Text.AlignVCenter
}
CustomTextField {
id: fontSearch
anchors.left: searchIcon.right
anchors.leftMargin: Appearance.spacing.small
anchors.right: parent.right
anchors.rightMargin: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
placeholderText: "Search..."
}
}
CustomClippingRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: searchBox.bottom
bottomLeftRadius: 21
bottomRightRadius: 21
CustomListView {
anchors.fill: parent
clip: true
spacing: Appearance.spacing.small
delegate: CustomRect {
id: fontDelegate
required property string modelData
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 42
radius: Appearance.rounding.smallest
CustomText {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
text: modelData
verticalAlignment: Text.AlignVCenter
}
MaterialIcon {
anchors.fill: parent
anchors.rightMargin: Appearance.padding.normal
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignRight
text: "check_circle"
verticalAlignment: Text.AlignVCenter
visible: root.object[root.setting] === fontDelegate.modelData
}
StateLayer {
onClicked: {
root.object[root.setting] = fontDelegate.modelData;
Config.save();
}
}
}
model: ScriptModel {
values: {
const fonts = Qt.fontFamilies();
const search = fontSearch.text;
var regex = new RegExp(search, "i");
return fonts.filter(n => regex.test(n));
}
}
}
}
}
}
}
@@ -36,6 +36,10 @@ CustomClippingRect {
clip: true clip: true
contentHeight: clayout.implicitHeight contentHeight: clayout.implicitHeight
CustomScrollBar.vertical: CustomScrollBar {
flickable: flickable
}
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
+81 -69
View File
@@ -6,97 +6,109 @@ import qs.Config
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
ColumnLayout { Item {
id: root id: root
width: Math.min(parent ? parent.width : 600, 600)
spacing: 15
Rectangle {
id: previewContainer
Layout.fillWidth: true
Layout.preferredHeight: width * (Quickshell.screens.length > 0 ? (Quickshell.screens[0].height / Math.max(1, Quickshell.screens[0].width)) : 9/16)
clip: true
color: DynamicColors.surfaceContainer
radius: Config.appearance.rounding.scale * 10
Image { Image {
id: img id: imageView
property real displayH: paintedHeight
property real displayW: paintedWidth
property real displayX: (width - paintedWidth) * 0.5
property real displayY: (height - paintedHeight) * 0.5
property real scaleX: sourceW / displayW
property real scaleY: sourceH / displayH
property real sourceH: Quickshell.screens[0].height
property real sourceW: Quickshell.screens[0].width
anchors.fill: parent anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true
source: Wallpapers.current source: Wallpapers.current
}
Rectangle { Item {
id: overlay
clip: true
height: imageView.displayH
width: imageView.displayW
x: imageView.displayX
y: imageView.displayY
CustomRect {
id: cropRect id: cropRect
property real paintedWidth: img.paintedWidth > 0 ? img.paintedWidth : img.width property real aspectRatio: Quickshell.screens[0].width / Quickshell.screens[0].height
property real paintedHeight: img.paintedHeight > 0 ? img.paintedHeight : img.height readonly property rect sourceRect: Qt.rect(x * imageView.scaleX, y * imageView.scaleY, width * imageView.scaleX, height * imageView.scaleY)
property real paintedX: (img.width - paintedWidth) / 2 property real zoom: Config.background.zoom
property real paintedY: (img.height - paintedHeight) / 2
property real screenAspect: Quickshell.screens.length > 0 ? (Quickshell.screens[0].width / Math.max(1, Quickshell.screens[0].height)) : 16/9 function clampToBounds() {
property real imageAspect: Math.max(1, paintedWidth) / Math.max(1, paintedHeight) x = Math.max(0, Math.min(x, overlay.width - width));
property real cropWidth: (imageAspect > screenAspect ? paintedHeight * screenAspect : paintedWidth) / Config.background.zoom y = Math.max(0, Math.min(y, overlay.height - height));
property real cropHeight: (imageAspect > screenAspect ? paintedHeight : paintedWidth / screenAspect) / Config.background.zoom }
border.color: DynamicColors.primary border.color: DynamicColors.palette.m3primary
border.width: 2 border.width: 2
color: DynamicColors.primaryContainer.withAlpha(0.3) color: DynamicColors.tPalette.m3primary
height: width / aspectRatio
width: cropWidth radius: Appearance.rounding.small
height: cropHeight visible: imageView.status === Image.Ready
width: Math.min(overlay.width / zoom, overlay.height * aspectRatio / zoom)
x: paintedX + (paintedWidth - width) * Config.background.alignX x: Config.background.sourceClipX / imageView.scaleX
y: paintedY + (paintedHeight - height) * Config.background.alignY y: Config.background.sourceClipY / imageView.scaleY
DragHandler {
target: null
onActiveTranslationChanged: {
if (active) {
let newX = cropRect.x - cropRect.paintedX + translation.x;
let newY = cropRect.y - cropRect.paintedY + translation.y;
let rangeX = cropRect.paintedWidth - cropRect.width;
let rangeY = cropRect.paintedHeight - cropRect.height;
if (rangeX > 0) {
let valX = newX / rangeX;
Config.background.alignX = Math.max(0.0, Math.min(1.0, valX));
} }
if (rangeY > 0) { MouseArea {
let valY = newY / rangeY; function updateCrop(mouseX, mouseY) {
Config.background.alignY = Math.max(0.0, Math.min(1.0, valY)); let nx = mouseX - cropRect.width * 0.5;
} let ny = mouseY - cropRect.height * 0.5;
}
} nx = Math.max(0, Math.min(nx, overlay.width - cropRect.width));
ny = Math.max(0, Math.min(ny, overlay.height - cropRect.height));
cropRect.x = nx;
cropRect.y = ny;
} }
PinchHandler { anchors.fill: parent
maximumScale: 5.0 hoverEnabled: true
minimumScale: 1.0 preventStealing: true
target: null
onActiveScaleChanged: { onPositionChanged: mouse => {
if (active) { if (pressed)
let newZoom = Config.background.zoom * (1 / (1 + (activeScale - 1) * 0.1)); updateCrop(mouse.x, mouse.y);
Config.background.zoom = Math.max(1.0, Math.min(newZoom, 5.0));
}
}
}
} }
onPressed: mouse => {
updateCrop(mouse.x, mouse.y);
} }
onReleased: {
Wallpapers.recentlyChanged = false;
Config.background.sourceClipX = cropRect.sourceRect.x;
Config.background.sourceClipY = cropRect.sourceRect.y;
Config.background.sourceClipW = cropRect.sourceRect.width;
Config.background.sourceClipH = cropRect.sourceRect.height;
Config.save();
} }
onWheel: wheel => {
let oldCenterX = cropRect.x + cropRect.width * 0.5;
let oldCenterY = cropRect.y + cropRect.height * 0.5;
SettingSpinBox { if (wheel.angleDelta.y > 0)
name: "Zoom" cropRect.zoom *= 1.1;
object: Config.background else
setting: "zoom" cropRect.zoom /= 1.1;
min: 1.0
max: 5.0 cropRect.zoom = Math.max(1.0, Math.min(cropRect.zoom, 10.0));
step: 0.1 Config.background.zoom = cropRect.zoom;
cropRect.x = oldCenterX - cropRect.width * 0.5;
cropRect.y = oldCenterY - cropRect.height * 0.5;
cropRect.clampToBounds();
}
}
} }
} }
File diff suppressed because it is too large Load Diff
+7 -2
View File
@@ -6,7 +6,7 @@ import QtQuick.Controls
import qs.Components import qs.Components
import qs.Config import qs.Config
import "../../scripts/fuzzysort.js" as Fuzzy import "../../scripts/fuzzysort.js" as Fuzzy
import "./SettingsIndex.mjs" as SettingsIndex import "../../scripts/SettingsIndex.mjs" as SettingsIndex
Item { Item {
id: root id: root
@@ -51,9 +51,14 @@ Item {
implicitHeight: searchContainer.implicitHeight implicitHeight: searchContainer.implicitHeight
implicitWidth: 200 implicitWidth: 200
Shortcut {
sequence: "/"
onActivated: searchField.forceActiveFocus()
}
ListModel { ListModel {
id: resultsModel id: resultsModel
} }
CustomRect { CustomRect {
+9 -30
View File
@@ -7,45 +7,24 @@ import qs.Helpers
Item { Item {
id: root id: root
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.settings
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: 0 anchors.topMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
visible: height > 0 opacity: 1 - offsetScale
visible: offsetScale < 1
states: State {
name: "visible"
when: root.visibilities.settings
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
Behavior on offsetScale {
Anim { Anim {
duration: MaterialEasing.expressiveEffectsTime duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
} }
} }
]
CustomClippingRect { CustomClippingRect {
anchors.fill: parent anchors.fill: parent
+2 -2
View File
@@ -14,8 +14,8 @@ Scope {
description: "Toggle launcher" description: "Toggle launcher"
name: "toggle-launcher" name: "toggle-launcher"
onPressed: root.launcherInterrupted = false onPressed: {
onReleased: { root.launcherInterrupted = false;
if (!root.launcherInterrupted && !root.hasFullscreen) { if (!root.launcherInterrupted && !root.hasFullscreen) {
const visibilities = Visibilities.getForActive(); const visibilities = Visibilities.getForActive();
visibilities.launcher = !visibilities.launcher; visibilities.launcher = !visibilities.launcher;
+32 -168
View File
@@ -18,193 +18,35 @@ Item {
readonly property int topMargin: 0 readonly property int topMargin: 0
required property var wrapper required property var wrapper
implicitHeight: layout.implicitHeight + Appearance.padding.small * 2 implicitHeight: vol.implicitHeight + Appearance.padding.small * 2
implicitWidth: layout.implicitWidth + Appearance.padding.small * 2 implicitWidth: 400 + Appearance.padding.small * 2
CustomRect { CustomRect {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: parent.implicitHeight implicitHeight: parent.implicitHeight
radius: Appearance.rounding.small radius: Appearance.rounding.small
y: parent.y y: parent.y
Behavior on implicitHeight { Behavior on implicitHeight {
Anim {
duration: MaterialEasing.emphasizedDecelTime
easing.bezierCurve: MaterialEasing.emphasized
}
}
}
ColumnLayout {
id: layout
anchors.centerIn: parent
implicitWidth: stack.currentItem ? stack.currentItem.childrenRect.height : 0
spacing: 12
RowLayout {
id: tabBar
property int tabHeight: 36
Layout.fillWidth: true
spacing: 6
CustomClippingRect {
Layout.fillWidth: true
Layout.preferredHeight: tabBar.tabHeight
color: stack.currentIndex === 0 ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
StateLayer {
function onClicked(): void {
stack.currentIndex = 0;
}
CustomText {
anchors.centerIn: parent
color: stack.currentIndex === 0 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
text: qsTr("Volumes")
}
}
}
CustomClippingRect {
Layout.fillWidth: true
Layout.preferredHeight: tabBar.tabHeight
color: stack.currentIndex === 1 ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
StateLayer {
function onClicked(): void {
stack.currentIndex = 1;
}
CustomText {
anchors.centerIn: parent
color: stack.currentIndex === 1 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
text: qsTr("Devices")
}
}
}
}
StackLayout {
id: stack
Layout.fillWidth: true
Layout.preferredHeight: currentIndex === 0 ? vol.childrenRect.height : dev.childrenRect.height
currentIndex: 0
Behavior on currentIndex {
SequentialAnimation {
ParallelAnimation {
Anim { Anim {
duration: MaterialEasing.expressiveEffectsTime duration: MaterialEasing.expressiveEffectsTime
property: "opacity" easing.bezierCurve: MaterialEasing.expressiveEffects
target: stack
to: 0
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
property: "scale"
target: stack
to: 0.9
}
}
PropertyAction {
}
ParallelAnimation {
Anim {
duration: MaterialEasing.expressiveEffectsTime
property: "opacity"
target: stack
to: 1
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
property: "scale"
target: stack
to: 1
}
} }
} }
} }
VolumesTab { VolumesTab {
id: vol id: vol
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
} }
DevicesTab {
id: dev
}
}
}
component DevicesTab: Item {
implicitHeight: deviceColumn.height + Appearance.padding.normal
implicitWidth: deviceColumn.width + Appearance.padding.normal
ColumnLayout {
id: deviceColumn
anchors.centerIn: parent
spacing: 12
ButtonGroup {
id: sinks
}
ButtonGroup {
id: sources
}
CustomText {
font.weight: 500
text: qsTr("Output device")
}
Repeater {
model: Audio.sinks
CustomRadioButton {
required property PwNode modelData
ButtonGroup.group: sinks
checked: Audio.sink?.id === modelData.id
text: modelData.description
onClicked: Audio.setAudioSink(modelData)
}
}
CustomText {
Layout.topMargin: 10
font.weight: 500
text: qsTr("Input device")
}
Repeater {
model: Audio.sources
CustomRadioButton {
required property PwNode modelData
ButtonGroup.group: sources
checked: Audio.source?.id === modelData.id
text: modelData.description
onClicked: Audio.setAudioSource(modelData)
}
}
}
}
component VolumesTab: ColumnLayout { component VolumesTab: ColumnLayout {
spacing: 12 spacing: 12
@@ -294,13 +136,35 @@ Item {
} }
} }
CustomRect { CustomClippingRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2 Layout.preferredHeight: 42 + 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
PwNodePeakMonitor {
id: sourcePeak
node: Audio.source
}
CustomRect {
id: sourcePeakFill
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
color: Qt.alpha(DynamicColors.palette.m3primary, 0.15)
implicitWidth: parent.width * sourcePeak.peak
Behavior on implicitWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
}
}
}
RowLayout { RowLayout {
id: inputVolume id: inputVolume
+158
View File
@@ -0,0 +1,158 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import qs.Components
import qs.Modules
import qs.Config
Column {
id: menu
property int biggestWidth: 0
required property QsMenuHandle handle
required property int level
required property PopoutState popouts
required property ShellScreen screen
property bool shown: true
height: childrenRect.height
opacity: shown ? 1 : 0
padding: 0
scale: shown ? 1 : 0.8
spacing: 4
width: biggestWidth
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
QsMenuOpener {
id: menuOpener
menu: menu.handle
}
Repeater {
model: menuOpener.children
CustomRect {
id: item
required property int index
required property QsMenuEntry modelData
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
implicitWidth: menu.biggestWidth
radius: Appearance.rounding.full
visible: index !== (menuOpener.children.values.length - 1) ? true : (modelData.isSeparator ? false : true)
Loader {
id: children
active: !item.modelData.isSeparator
anchors.left: parent.left
anchors.right: parent.right
asynchronous: true
sourceComponent: Item {
implicitHeight: 30
StateLayer {
function onClicked(): void {
const entry = item.modelData;
if (entry.hasChildren) {
menu.popouts.pushSubmenu(menu.level, entry, item, menu.biggestWidth);
} else {
entry.triggered();
menu.popouts.hasCurrent = false;
}
}
disabled: !item.modelData.enabled
radius: item.radius
}
Loader {
id: icon
active: item.modelData.icon !== ""
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: Item {
implicitHeight: label.implicitHeight
implicitWidth: label.implicitHeight
IconImage {
id: iconImage
implicitSize: parent.implicitHeight
source: item.modelData.icon
visible: false
}
MultiEffect {
anchors.fill: iconImage
colorization: 1.0
colorizationColor: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
source: iconImage
}
}
}
CustomText {
id: label
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: labelMetrics.elidedText
}
TextMetrics {
id: labelMetrics
font.family: label.font.family
font.pointSize: label.font.pointSize
text: item.modelData.text
Component.onCompleted: {
var biggestWidth = menu.biggestWidth;
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
if (currentWidth > biggestWidth) {
menu.biggestWidth = currentWidth;
}
}
}
Loader {
id: expand
active: item.modelData.hasChildren
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: MaterialIcon {
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: "chevron_right"
}
}
}
}
}
}
}
+7 -239
View File
@@ -1,248 +1,16 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import qs.Components
import qs.Config
import Quickshell import Quickshell
import Quickshell.Widgets
import QtQuick import QtQuick
import QtQuick.Controls import qs.Components
import QtQuick.Effects import qs.Modules
import qs.Config
StackView { SubMenu {
id: root id: root
property int biggestWidth: 0 handle: trayItem
required property Item popouts level: 0
property int rootWidth: 0
required property QsMenuHandle trayItem required property QsMenuHandle trayItem
implicitHeight: currentItem.implicitHeight
implicitWidth: currentItem.implicitWidth
initialItem: SubMenu {
handle: root.trayItem
}
popEnter: NoAnim {
}
popExit: NoAnim {
}
pushEnter: NoAnim {
}
pushExit: NoAnim {
}
Component {
id: subMenuComp
SubMenu {
}
}
component NoAnim: Transition {
NumberAnimation {
duration: 0
}
}
component SubMenu: Column {
id: menu
required property QsMenuHandle handle
property bool isSubMenu
property bool shown
opacity: shown ? 1 : 0
padding: 0
scale: shown ? 1 : 0.8
spacing: 4
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Component.onCompleted: shown = true
StackView.onActivating: shown = true
StackView.onDeactivating: shown = false
StackView.onRemoved: destroy()
QsMenuOpener {
id: menuOpener
menu: menu.handle
}
Repeater {
model: menuOpener.children
CustomRect {
id: item
required property QsMenuEntry modelData
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
implicitWidth: root.biggestWidth
radius: Appearance.rounding.smallest / 2
Loader {
id: children
active: !item.modelData.isSeparator
anchors.left: parent.left
anchors.right: parent.right
asynchronous: true
sourceComponent: Item {
implicitHeight: 30
StateLayer {
function onClicked(): void {
const entry = item.modelData;
if (entry.hasChildren) {
root.rootWidth = root.biggestWidth;
root.biggestWidth = 0;
root.push(subMenuComp.createObject(null, {
handle: entry,
isSubMenu: true
}));
} else {
item.modelData.triggered();
root.popouts.hasCurrent = false;
}
}
disabled: !item.modelData.enabled
radius: item.radius
}
Loader {
id: icon
active: item.modelData.icon !== ""
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: Item {
implicitHeight: label.implicitHeight
implicitWidth: label.implicitHeight
IconImage {
id: iconImage
implicitSize: parent.implicitHeight
source: item.modelData.icon
visible: false
}
MultiEffect {
anchors.fill: iconImage
colorization: 1.0
colorizationColor: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
source: iconImage
}
}
}
CustomText {
id: label
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: labelMetrics.elidedText
}
TextMetrics {
id: labelMetrics
font.family: label.font.family
font.pointSize: label.font.pointSize
text: item.modelData.text
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
if (currentWidth > biggestWidth) {
root.biggestWidth = currentWidth;
}
}
}
Loader {
id: expand
active: item.modelData.hasChildren
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: MaterialIcon {
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: "chevron_right"
}
}
}
}
}
}
Loader {
id: loader
active: menu.isSubMenu
asynchronous: true
sourceComponent: Item {
implicitHeight: back.implicitHeight + 2 / 2
implicitWidth: back.implicitWidth
Item {
anchors.bottom: parent.bottom
implicitHeight: back.implicitHeight
implicitWidth: back.implicitWidth + 10
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3secondaryContainer
radius: Appearance.rounding.smallest / 2
StateLayer {
function onClicked(): void {
root.pop();
root.biggestWidth = root.rootWidth;
}
color: DynamicColors.palette.m3onSecondaryContainer
radius: parent.radius
}
}
Row {
id: back
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSecondaryContainer
text: "chevron_left"
}
CustomText {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSecondaryContainer
text: qsTr("Back")
}
}
}
}
}
}
} }
+1 -1
View File
@@ -31,7 +31,7 @@ Item {
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
// color: "transparent" // color: "transparent"
radius: 6 radius: (20 - Appearance.padding.small) * Appearance.rounding.scale
CustomRect { CustomRect {
id: indicator id: indicator
-30
View File
@@ -1,30 +0,0 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
readonly property date date: clock.date
readonly property string dateStr: format("ddd d MMM - hh:mm:ss")
property alias enabled: clock.enabled
readonly property string hourStr: timeComponents[0] ?? ""
readonly property int hours: clock.hours
readonly property string minuteStr: timeComponents[1] ?? ""
readonly property int minutes: clock.minutes
readonly property string secondStr: timeComponents[2] ?? ""
readonly property int seconds: clock.seconds
readonly property list<string> timeComponents: timeStr.split(":")
readonly property string timeStr: format("hh:mm:ss")
function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt);
}
SystemClock {
id: clock
precision: SystemClock.Seconds
}
}
+6 -3
View File
@@ -25,7 +25,7 @@ CustomClippingRect {
anchors.centerIn: parent anchors.centerIn: parent
height: 200 height: 200
visible: script.values.length === 0 visible: script.values.length === 0
width: 300 width: 600
MaterialIcon { MaterialIcon {
id: noUpdatesIcon id: noUpdatesIcon
@@ -54,6 +54,8 @@ CustomClippingRect {
anchors.centerIn: parent anchors.centerIn: parent
contentHeight: childrenRect.height contentHeight: childrenRect.height
contentWidth: 600 contentWidth: 600
displayMarginBeginning: root.itemHeight
displayMarginEnd: root.itemHeight
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing) implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
implicitWidth: contentWidth implicitWidth: contentWidth
spacing: Appearance.spacing.normal spacing: Appearance.spacing.normal
@@ -65,10 +67,11 @@ CustomClippingRect {
required property var modelData required property var modelData
readonly property list<string> sections: modelData.update.split(" ") readonly property list<string> sections: modelData.update.split(" ")
anchors.left: parent.left // anchors.left: parent.left
anchors.right: parent.right // anchors.right: parent.right
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: root.itemHeight implicitHeight: root.itemHeight
implicitWidth: 600
radius: Appearance.rounding.small - Appearance.padding.small radius: Appearance.rounding.small - Appearance.padding.small
RowLayout { RowLayout {
+1 -1
View File
@@ -14,7 +14,7 @@ CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2 implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2
radius: height / 2 radius: Appearance.rounding.full
RowLayout { RowLayout {
id: contentRow id: contentRow
+80
View File
@@ -0,0 +1,80 @@
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;
}
}
}
}
+20 -70
View File
@@ -1,5 +1,6 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
@@ -8,85 +9,34 @@ import qs.Config
Item { Item {
id: root id: root
property Image current: one required property ShellScreen screen
property string source: Wallpapers.current property string source: Wallpapers.current
anchors.fill: parent anchors.fill: parent
Component.onCompleted: { Image {
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: CachingImage {
id: img id: img
function update(): void { anchors.fill: parent
if (path === root.source) {
root.current = this;
} else {
path = root.source;
}
}
anchors.fill: undefined
asynchronous: true asynchronous: true
opacity: 0 fillMode: Image.PreserveAspectCrop
fillMode: Image.Stretch opacity: 1
retainWhileLoading: true
source: root.source
sourceClipRect: Wallpapers.recentlyChanged ? null : Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH)
sourceSize.height: root.screen.height
sourceSize.width: root.screen.width
property real windowRatio: root.width / Math.max(1, root.height) onSourceChanged: {
property real imageRatio: Math.max(1, sourceSize.width) / Math.max(1, sourceSize.height) if (Wallpapers.recentlyChanged) {
Config.background.sourceClipH = 0;
property bool isValid: sourceSize.width > 0 && sourceSize.height > 0 && root.width > 0 && root.height > 0 Config.background.sourceClipW = 0;
Config.background.sourceClipY = 0;
width: isValid ? (imageRatio > windowRatio ? root.height * imageRatio : root.width) * Config.background.zoom : root.width Config.background.sourceClipX = 0;
height: isValid ? (imageRatio > windowRatio ? root.height : root.width / imageRatio) * Config.background.zoom : root.height Config.background.zoom = 1.0;
Config.save();
x: isValid ? (root.width - width) * Config.background.alignX : 0
y: isValid ? (root.height - height) * Config.background.alignY : 0
scale: Wallpapers.showPreview ? 1 : 0.8
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;
} }
Wallpapers.recentlyChanged = true;
} }
} }
} }
+1
View File
@@ -30,6 +30,7 @@ Loader {
} }
WallBackground { WallBackground {
screen: root.screen
} }
Loader { Loader {
+32 -90
View File
@@ -21,7 +21,7 @@ Item {
readonly property int workspacesShown: workspaces.length readonly property int workspacesShown: workspaces.length
height: implicitHeight height: implicitHeight
implicitHeight: Config.barConfig.height + Math.max(Appearance.padding.smaller, Config.barConfig.border) * 2 implicitHeight: Config.barConfig.height + Appearance.padding.smaller * 2
implicitWidth: (root.workspaceButtonWidth * root.workspacesShown) + root.activeWorkspaceMargin * 2 implicitWidth: (root.workspaceButtonWidth * root.workspacesShown) + root.activeWorkspaceMargin * 2
Behavior on implicitWidth { Behavior on implicitWidth {
@@ -39,36 +39,15 @@ Item {
implicitHeight: root.implicitHeight - ((Appearance.padding.small - 1) * 2) implicitHeight: root.implicitHeight - ((Appearance.padding.small - 1) * 2)
radius: height / 2 radius: height / 2
CustomRect {
id: indicator
property real indicatorLength: (Math.abs(idxPair.idx1 - idxPair.idx2) + 1) * root.workspaceButtonWidth
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorThickness: root.workspaceButtonWidth
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
implicitHeight: indicatorThickness
implicitWidth: indicatorLength
radius: Appearance.rounding.full
x: indicatorPosition
z: 2
AnimatedTabIndexPair {
id: idxPair
index: root.workspaces.findIndex(w => w.active)
}
}
Grid { Grid {
id: grid
anchors.fill: parent anchors.fill: parent
anchors.margins: root.activeWorkspaceMargin anchors.margins: root.activeWorkspaceMargin
columnSpacing: 0 columnSpacing: 0
columns: root.workspacesShown columns: root.workspacesShown
rowSpacing: 0 rowSpacing: 0
rows: 1 rows: 1
z: 3
Repeater { Repeater {
model: root.workspaces model: root.workspaces
@@ -91,88 +70,51 @@ Item {
CustomText { CustomText {
anchors.centerIn: parent anchors.centerIn: parent
color: button.modelData.active ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer color: DynamicColors.palette.m3onSecondaryContainer
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: button.modelData.name text: button.modelData.name
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
z: 3
} }
} }
onPressed: { onPressed: {
Hyprland.dispatch(`workspace ${button.modelData.name}`); const ws = button.modelData.name;
Hyprland.dispatch(Hyprland.usingLua ? `hl.dsp.focus({ workspace= "${ws}"})` : `workspace ${ws}`);
} }
} }
} }
} }
Item {
id: activeTextSource
anchors.fill: parent
anchors.margins: root.activeWorkspaceMargin
layer.enabled: true
visible: false
z: 4
Grid {
anchors.fill: parent
columnSpacing: 0
columns: root.workspacesShown
rowSpacing: 0
rows: 1
Repeater {
model: root.workspaces
Item {
id: activeWorkspace
required property int index
required property HyprlandWorkspace modelData
implicitHeight: indicator.indicatorThickness
implicitWidth: indicator.indicatorThickness
width: root.workspaceButtonWidth
CustomText {
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
text: activeWorkspace.modelData.name
verticalAlignment: Text.AlignVCenter
}
}
}
}
}
Item {
id: indicatorMask
anchors.fill: bgRect
layer.enabled: true
visible: false
CustomRect { CustomRect {
color: "white" id: indicator
height: indicator.height
radius: indicator.radius property real indicatorLength: (Math.abs(idxPair.idx1 - idxPair.idx2) + 1) * root.workspaceButtonWidth
width: indicator.width property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin
x: indicator.x property real indicatorThickness: root.workspaceButtonWidth
y: indicator.y
} anchors.verticalCenter: parent.verticalCenter
clip: true
color: DynamicColors.palette.m3primary
implicitHeight: indicatorThickness
implicitWidth: indicatorLength
radius: Appearance.rounding.full
x: indicatorPosition
AnimatedTabIndexPair {
id: idxPair
index: root.workspaces.findIndex(w => w.active)
} }
MultiEffect { Coloriser {
anchors.fill: activeTextSource colorizationColor: DynamicColors.palette.m3onPrimary
maskEnabled: true implicitHeight: grid.height
maskInverted: false implicitWidth: grid.width
maskSource: indicatorMask source: grid
source: activeTextSource sourceColor: DynamicColors.palette.m3onSurface
z: 5 x: -indicator.x + 3
}
} }
} }
} }
+55 -75
View File
@@ -8,42 +8,48 @@ import qs.Config
Item { Item {
id: root id: root
property list<real> animCurve: MaterialEasing.emphasized property list<real> animCurve: Appearance.anim.curves.expressiveDefaultSpatial
property int animLength: MaterialEasing.emphasizedDecelTime property int animLength: Appearance.anim.durations.expressiveDefaultSpatial
readonly property Item current: content.item?.current ?? null readonly property alias content: content
readonly property Item current: (content.item as Content)?.current ?? null
property real currentCenter property real currentCenter
property string currentName property alias currentName: popoutState.currentName
property string detachedMode property string detachedMode
property bool hasCurrent property alias hasCurrent: popoutState.hasCurrent
readonly property bool isDetached: detachedMode.length > 0 readonly property bool isDetached: detachedMode.length > 0
readonly property real nonAnimHeight: hasCurrent ? children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight : 0 readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
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
property string queuedMode property string queuedMode
required property ShellScreen screen required property ShellScreen screen
property alias state: popoutState
function close(): void { function close(): void {
hasCurrent = false; hasCurrent = false;
animCurve = MaterialEasing.emphasizedDecel;
animLength = MaterialEasing.emphasizedDecelTime;
detachedMode = ""; detachedMode = "";
animCurve = MaterialEasing.emphasized;
} }
function detach(mode: string): void { function detach(mode: string): void {
animLength = 600; setAnims(true);
if (mode === "winfo") { if (mode === "winfo") {
detachedMode = mode; detachedMode = mode;
} else { } else {
detachedMode = "any";
queuedMode = mode; queuedMode = mode;
detachedMode = "any";
} }
setAnims(false);
focus = true; focus = true;
} }
clip: 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
implicitHeight: nonAnimHeight implicitHeight: nonAnimHeight
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
visible: width > 0 && height > 0
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {
@@ -52,7 +58,7 @@ Item {
} }
} }
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: root.implicitHeight > 0 enabled: root.offsetScale < 1
Anim { Anim {
duration: root.animLength duration: root.animLength
@@ -60,85 +66,59 @@ Item {
} }
} }
// Comp { PopoutState {
// shouldBeActive: root.detachedMode === "winfo" id: popoutState
// asynchronous: true
// anchors.centerIn: parent
//
// sourceComponent: WindowInfo {
// screen: root.screen
// client: Hypr.activeToplevel
// }
// }
// Comp {
// shouldBeActive: root.detachedMode === "any"
// asynchronous: true
// anchors.centerIn: parent
//
// sourceComponent: ControlCenter {
// screen: root.screen
// active: root.queuedMode
//
// function close(): void {
// root.close();
// }
// }
// }
Behavior on x {
enabled: root.implicitHeight > 0
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
Behavior on y {
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
Keys.onEscapePressed: close()
HyprlandFocusGrab {
active: root.isDetached
windows: [QsWindow.window]
onCleared: root.close()
}
Binding {
property: "WlrLayershell.keyboardFocus"
target: QsWindow.window
value: WlrKeyboardFocus.OnDemand
when: root.isDetached
} }
Comp { Comp {
id: content id: content
anchors.horizontalCenter: parent.horizontalCenter // anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top // anchors.top: parent.top
asynchronous: true anchors.centerIn: parent
shouldBeActive: root.hasCurrent shouldBeActive: root.hasCurrent && !root.detachedMode
sourceComponent: Content { sourceComponent: Content {
wrapper: root popouts: popoutState
screen: root.screen
} }
} }
// 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
property bool shouldBeActive property bool shouldBeActive
active: false active: false
asynchronous: true
opacity: 0 opacity: 0
// Makes the loader load on the same frame shouldBeActive becomes true, which ensures size is set
states: State { states: State {
name: "active" name: "active"
when: comp.shouldBeActive when: comp.shouldBeActive
+20
View File
@@ -0,0 +1,20 @@
qml_module(ZShell-blobs
URI ZShell.Blobs
SOURCES
blobgroup.cpp
blobshape.cpp
blobrect.cpp
blobinvertedrect.cpp
blobmaterial.cpp
LIBRARIES
Qt::Quick
)
qt_add_shaders(ZShell-blobs "blob_shaders"
BATCHABLE OPTIMIZED NOHLSL NOMSL
GLSL "300es,330"
PREFIX "/"
FILES
shaders/blob.frag
shaders/blob.vert
)
+105
View File
@@ -0,0 +1,105 @@
#include "blobgroup.hpp"
#include "blobinvertedrect.hpp"
#include "blobshape.hpp"
BlobGroup::BlobGroup(QObject* parent)
: QObject(parent) {
}
BlobGroup::~BlobGroup() {
for (auto* shape : std::as_const(m_shapes))
shape->m_group = nullptr;
if (m_invertedRect)
static_cast<BlobShape*>(m_invertedRect)->m_group = nullptr;
}
void BlobGroup::setSmoothing(qreal s) {
if (qFuzzyCompare(m_smoothing, s))
return;
m_smoothing = s;
emit smoothingChanged();
markDirty();
}
void BlobGroup::setColor(const QColor& c) {
if (m_color == c)
return;
m_color = c;
emit colorChanged();
markDirty();
}
void BlobGroup::addShape(BlobShape* shape) {
if (!shape || m_shapes.contains(shape))
return;
m_shapes.append(shape);
markDirty();
}
void BlobGroup::removeShape(BlobShape* shape) {
m_shapes.removeOne(shape);
markDirty();
}
void BlobGroup::setInvertedRect(BlobInvertedRect* rect) {
if (m_invertedRect == rect)
return;
m_invertedRect = rect;
markDirty();
}
void BlobGroup::clearInvertedRect(BlobInvertedRect* rect) {
if (m_invertedRect != rect)
return;
m_invertedRect = nullptr;
markDirty();
}
void BlobGroup::markDirty() {
m_physicsUpdated = false;
for (auto* shape : std::as_const(m_shapes)) {
shape->polish();
shape->update();
}
if (m_invertedRect) {
static_cast<BlobShape*>(m_invertedRect)->polish();
static_cast<BlobShape*>(m_invertedRect)->update();
}
}
void BlobGroup::markShapeDirty(BlobShape* source) {
m_physicsUpdated = false;
source->polish();
source->update();
// Use cached padded rects to find spatial neighbors
const float pad = static_cast<float>(m_smoothing) * 2.0f;
const QRectF srcRect(static_cast<double>(source->m_cachedPaddedX - pad),
static_cast<double>(source->m_cachedPaddedY - pad), static_cast<double>(source->m_cachedPaddedW + pad * 2.0f),
static_cast<double>(source->m_cachedPaddedH + pad * 2.0f));
for (auto* shape : std::as_const(m_shapes)) {
if (shape == source)
continue;
const QRectF otherRect(static_cast<double>(shape->m_cachedPaddedX), static_cast<double>(shape->m_cachedPaddedY),
static_cast<double>(shape->m_cachedPaddedW), static_cast<double>(shape->m_cachedPaddedH));
if (srcRect.intersects(otherRect)) {
shape->polish();
shape->update();
}
}
if (m_invertedRect && static_cast<BlobShape*>(m_invertedRect) != source) {
static_cast<BlobShape*>(m_invertedRect)->polish();
static_cast<BlobShape*>(m_invertedRect)->update();
}
}
void BlobGroup::ensurePhysicsUpdated() {
if (m_physicsUpdated)
return;
m_physicsUpdated = true;
for (auto* shape : std::as_const(m_shapes))
shape->updatePhysics();
}
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include <qcolor.h>
#include <qlist.h>
#include <qobject.h>
#include <qqmlengine.h>
class BlobShape;
class BlobInvertedRect;
class BlobGroup : public QObject {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
explicit BlobGroup(QObject* parent = nullptr);
~BlobGroup() override;
qreal smoothing() const {
return m_smoothing;
}
void setSmoothing(qreal s);
QColor color() const {
return m_color;
}
void setColor(const QColor& c);
void addShape(BlobShape* shape);
void removeShape(BlobShape* shape);
void setInvertedRect(BlobInvertedRect* rect);
void clearInvertedRect(BlobInvertedRect* rect);
const QList<BlobShape*>& shapes() const {
return m_shapes;
}
BlobInvertedRect* invertedRect() const {
return m_invertedRect;
}
void markDirty();
void markShapeDirty(BlobShape* source);
void ensurePhysicsUpdated();
signals:
void smoothingChanged();
void colorChanged();
private:
qreal m_smoothing = 32.0;
QColor m_color{ 0x44, 0x88, 0xff };
QList<BlobShape*> m_shapes;
BlobInvertedRect* m_invertedRect = nullptr;
bool m_physicsUpdated = false;
};
+185
View File
@@ -0,0 +1,185 @@
#include "blobinvertedrect.hpp"
#include "blobgroup.hpp"
#include "blobmaterial.hpp"
#include <qsggeometry.h>
#include <qsgnode.h>
#include <algorithm>
#include <cstring>
BlobInvertedRect::BlobInvertedRect(QQuickItem* parent)
: BlobShape(parent) {
}
static void setFrameIndices(quint16* idx) {
// Top strip: 0-1-4, 1-5-4
idx[0] = 0;
idx[1] = 1;
idx[2] = 4;
idx[3] = 1;
idx[4] = 5;
idx[5] = 4;
// Right strip: 1-2-5, 2-6-5
idx[6] = 1;
idx[7] = 2;
idx[8] = 5;
idx[9] = 2;
idx[10] = 6;
idx[11] = 5;
// Bottom strip: 2-3-6, 3-7-6
idx[12] = 2;
idx[13] = 3;
idx[14] = 6;
idx[15] = 3;
idx[16] = 7;
idx[17] = 6;
// Left strip: 3-0-7, 0-4-7
idx[18] = 3;
idx[19] = 0;
idx[20] = 7;
idx[21] = 0;
idx[22] = 4;
idx[23] = 7;
}
QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
if (!m_group) {
delete oldNode;
return nullptr;
}
const float pad = static_cast<float>(m_group->smoothing());
// Compute inner hole boundary in local coords
// Inset past the inner border edge by 2x smoothing to cover the blend zone
const float inset = pad * 2.0f;
const float holeLeft = static_cast<float>(m_borderLeft) + inset;
const float holeTop = static_cast<float>(m_borderTop) + inset;
const float holeRight = static_cast<float>(width() - m_borderRight) - inset;
const float holeBot = static_cast<float>(height() - m_borderBottom) - inset;
// If the hole is too small or invalid, fall back to full quad
if (holeLeft >= holeRight || holeTop >= holeBot)
return BlobShape::updatePaintNode(oldNode, nullptr);
auto* node = static_cast<QSGGeometryNode*>(oldNode);
const bool needsRebuild = !node || node->geometry()->vertexCount() != 8;
if (needsRebuild) {
delete oldNode;
node = new QSGGeometryNode;
auto* geometry =
new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 8, 24, QSGGeometry::UnsignedShortType);
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
setFrameIndices(geometry->indexDataAsUShort());
auto* material = new BlobMaterial;
material->setFlag(QSGMaterial::Blending);
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);
}
// Outer bounds (local coords)
const float x0 = static_cast<float>(m_localPaddedRect.x());
const float y0 = static_cast<float>(m_localPaddedRect.y());
const float x1 = x0 + static_cast<float>(m_localPaddedRect.width());
const float y1 = y0 + static_cast<float>(m_localPaddedRect.height());
const float w = x1 - x0;
const float h = y1 - y0;
// Update vertex positions and texture coordinates
auto* v = node->geometry()->vertexDataAsTexturedPoint2D();
// Outer corners
v[0].set(x0, y0, 0.0f, 0.0f);
v[1].set(x1, y0, 1.0f, 0.0f);
v[2].set(x1, y1, 1.0f, 1.0f);
v[3].set(x0, y1, 0.0f, 1.0f);
// Inner corners (hole)
v[4].set(holeLeft, holeTop, (holeLeft - x0) / w, (holeTop - y0) / h);
v[5].set(holeRight, holeTop, (holeRight - x0) / w, (holeTop - y0) / h);
v[6].set(holeRight, holeBot, (holeRight - x0) / w, (holeBot - y0) / h);
v[7].set(holeLeft, holeBot, (holeLeft - x0) / w, (holeBot - y0) / h);
node->markDirty(QSGNode::DirtyGeometry);
// Update material uniforms
auto* material = static_cast<BlobMaterial*>(node->material());
material->m_paddedX = m_cachedPaddedX;
material->m_paddedY = m_cachedPaddedY;
material->m_paddedW = m_cachedPaddedW;
material->m_paddedH = m_cachedPaddedH;
material->m_smoothFactor = pad;
material->m_myIndex = m_cachedMyIndex;
material->m_color = m_group->color();
material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
material->m_invertedRadius = m_cachedInvertedRadius;
memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter));
memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner));
const int count = static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
material->m_rectCount = count;
for (int i = 0; i < count; ++i)
material->m_rects[i] = m_cachedRects[i];
node->markDirty(QSGNode::DirtyMaterial);
return node;
}
BlobInvertedRect::~BlobInvertedRect() {
if (m_group)
m_group->clearInvertedRect(this);
}
void BlobInvertedRect::setBorderLeft(qreal v) {
if (qFuzzyCompare(m_borderLeft, v))
return;
m_borderLeft = v;
emit borderLeftChanged();
if (m_group)
m_group->markDirty();
}
void BlobInvertedRect::setBorderRight(qreal v) {
if (qFuzzyCompare(m_borderRight, v))
return;
m_borderRight = v;
emit borderRightChanged();
if (m_group)
m_group->markDirty();
}
void BlobInvertedRect::setBorderTop(qreal v) {
if (qFuzzyCompare(m_borderTop, v))
return;
m_borderTop = v;
emit borderTopChanged();
if (m_group)
m_group->markDirty();
}
void BlobInvertedRect::setBorderBottom(qreal v) {
if (qFuzzyCompare(m_borderBottom, v))
return;
m_borderBottom = v;
emit borderBottomChanged();
if (m_group)
m_group->markDirty();
}
void BlobInvertedRect::registerWithGroup() {
if (m_group)
m_group->setInvertedRect(this);
}
void BlobInvertedRect::unregisterFromGroup() {
if (m_group)
m_group->clearInvertedRect(this);
}
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include "blobshape.hpp"
#include <qqmlengine.h>
class BlobInvertedRect : public BlobShape {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY borderLeftChanged)
Q_PROPERTY(qreal borderRight READ borderRight WRITE setBorderRight NOTIFY borderRightChanged)
Q_PROPERTY(qreal borderTop READ borderTop WRITE setBorderTop NOTIFY borderTopChanged)
Q_PROPERTY(qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY borderBottomChanged)
public:
explicit BlobInvertedRect(QQuickItem* parent = nullptr);
~BlobInvertedRect() override;
qreal borderLeft() const {
return m_borderLeft;
}
void setBorderLeft(qreal v);
qreal borderRight() const {
return m_borderRight;
}
void setBorderRight(qreal v);
qreal borderTop() const {
return m_borderTop;
}
void setBorderTop(qreal v);
qreal borderBottom() const {
return m_borderBottom;
}
void setBorderBottom(qreal v);
signals:
void borderLeftChanged();
void borderRightChanged();
void borderTopChanged();
void borderBottomChanged();
protected:
bool isInvertedRect() const override {
return true;
}
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
void registerWithGroup() override;
void unregisterFromGroup() override;
private:
qreal m_borderLeft = 0;
qreal m_borderRight = 0;
qreal m_borderTop = 0;
qreal m_borderBottom = 0;
};
+102
View File
@@ -0,0 +1,102 @@
#include "blobmaterial.hpp"
#include <cstring>
static_assert(sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float),
"BlobMaterial packs excludeMask into a float slot via memcpy");
QSGMaterialType* BlobMaterial::type() const {
static QSGMaterialType s_type;
return &s_type;
}
QSGMaterialShader* BlobMaterial::createShader(QSGRendererInterface::RenderMode) const {
return new BlobMaterialShader;
}
int BlobMaterial::compare(const QSGMaterial* other) const {
if (this < other)
return -1;
if (this > other)
return 1;
return 0;
}
BlobMaterialShader::BlobMaterialShader() {
setShaderFileName(VertexStage, QStringLiteral(":/shaders/blob.vert.qsb"));
setShaderFileName(FragmentStage, QStringLiteral(":/shaders/blob.frag.qsb"));
}
bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) {
Q_UNUSED(oldMaterial);
auto* mat = static_cast<BlobMaterial*>(newMaterial);
QByteArray* buf = state.uniformData();
Q_ASSERT(buf->size() >= 1440);
if (state.isMatrixDirty()) {
const QMatrix4x4 m = state.combinedMatrix();
memcpy(buf->data(), m.constData(), 64);
}
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
memcpy(buf->data() + 64, &opacity, 4);
}
// Padded rect (offset 68)
memcpy(buf->data() + 68, &mat->m_paddedX, 4);
memcpy(buf->data() + 72, &mat->m_paddedY, 4);
memcpy(buf->data() + 76, &mat->m_paddedW, 4);
memcpy(buf->data() + 80, &mat->m_paddedH, 4);
// Smooth factor (offset 84)
memcpy(buf->data() + 84, &mat->m_smoothFactor, 4);
// Rect count (offset 88)
memcpy(buf->data() + 88, &mat->m_rectCount, 4);
// My index (offset 92)
memcpy(buf->data() + 92, &mat->m_myIndex, 4);
// Color as vec4 (offset 96, 16 bytes)
const float color[4] = {
static_cast<float>(mat->m_color.redF()),
static_cast<float>(mat->m_color.greenF()),
static_cast<float>(mat->m_color.blueF()),
static_cast<float>(mat->m_color.alphaF()),
};
memcpy(buf->data() + 96, color, 16);
// Has inverted (offset 112)
memcpy(buf->data() + 112, &mat->m_hasInverted, 4);
// Inverted radius (offset 116)
memcpy(buf->data() + 116, &mat->m_invertedRadius, 4);
// Padding at 120-127 (skip)
// Inverted outer (offset 128, 16 bytes)
memcpy(buf->data() + 128, mat->m_invertedOuter, 16);
// Inverted inner (offset 144, 16 bytes)
memcpy(buf->data() + 144, mat->m_invertedInner, 16);
// Rect data (offset 160, each rect = 5 vec4s = 80 bytes)
const int count = qMin(mat->m_rectCount, 16);
for (int i = 0; i < count; ++i) {
const auto& r = mat->m_rects[i];
const int base = 160 + i * 80;
// Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt)
float maskAsFloat;
memcpy(&maskAsFloat, &r.excludeMask, sizeof(float));
const float d0[4] = { r.cx, r.cy, r.hw, r.hh };
const float d1[4] = { maskAsFloat, r.offsetX, r.offsetY, r.minEig };
const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f };
memcpy(buf->data() + base, d0, 16);
memcpy(buf->data() + base + 16, d1, 16);
memcpy(buf->data() + base + 32, r.invDeform, 16);
memcpy(buf->data() + base + 48, d3, 16);
memcpy(buf->data() + base + 64, r.radius, 16);
}
return true;
}

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