162 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
zach 40b2e46b25 Merge pull request 'Temporary comment of DropExpensiveFonts' (#53) from uncomment-correctly-next-time into main
Reviewed-on: #53
2026-04-15 18:44:52 +02:00
AramJonghu 7ac158d9ea Temporary comment of DropExpensiveFonts 2026-04-15 18:26:19 +02:00
133 changed files with 8185 additions and 3513 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/
.pyre/
.cache/
@@ -11,3 +12,4 @@ pkg/
uv.lock
.qtcreator/
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
NumberAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
duration: Appearance.anim.durations.normal
easing.bezierCurve: Appearance.anim.curves.standard
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
implicitHeight: label.implicitHeight + padding * 2
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 {
Anim {
id: radiusAnim
}
}
+1
View File
@@ -4,6 +4,7 @@ import Quickshell
Singleton {
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.Padding padding: Config.appearance.padding
// Literally just here to shorten accessing stuff :woe:
+5
View File
@@ -3,6 +3,8 @@ import Quickshell.Io
JsonObject {
property Anim anim: Anim {
}
property Deform deform: Deform {
}
property FontStuff font: FontStuff {
}
property Padding padding: Padding {
@@ -43,6 +45,9 @@ JsonObject {
property real scale: 1
property int small: 200 * scale
}
component Deform: JsonObject {
property real scale: 1
}
component FontFamily: JsonObject {
property string clock: "Rubik"
property string material: "Material Symbols Rounded"
+4
View File
@@ -7,4 +7,8 @@ JsonObject {
property real alignX: 0.5
property real alignY: 0.5
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",
enabled: true
},
{
id: "audio",
enabled: true
},
{
id: "media",
enabled: true
@@ -24,10 +20,6 @@ JsonObject {
id: "updates",
enabled: true
},
{
id: "dash",
enabled: true
},
{
id: "spacer",
enabled: true
@@ -48,10 +40,6 @@ JsonObject {
id: "tray",
enabled: true
},
{
id: "upower",
enabled: false
},
{
id: "network",
enabled: false
@@ -70,6 +58,7 @@ JsonObject {
property Popouts popouts: Popouts {
}
property int rounding: 8
property int smoothing: 32
component Popouts: JsonObject {
property bool activeWindow: true
+30 -1
View File
@@ -23,6 +23,7 @@ Singleton {
property alias osd: adapter.osd
property alias overview: adapter.overview
property bool recentlySaved: false
property alias screenshot: adapter.screenshot
property alias services: adapter.services
property alias sidebar: adapter.sidebar
property alias utilities: adapter.utilities
@@ -48,6 +49,9 @@ Singleton {
padding: {
scale: appearance.padding.scale
},
deform: {
scale: appearance.deform.scale
},
font: {
family: {
sans: appearance.font.family.sans,
@@ -79,6 +83,10 @@ Singleton {
wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled,
alignX: background.alignX,
sourceClipX: background.sourceClipX,
sourceClipY: background.sourceClipY,
sourceClipW: background.sourceClipW,
sourceClipH: background.sourceClipH,
alignY: background.alignY,
zoom: background.zoom
};
@@ -90,6 +98,7 @@ Singleton {
hideWhenNotif: barConfig.hideWhenNotif,
rounding: barConfig.rounding,
border: barConfig.border,
smoothing: barConfig.smoothing,
height: barConfig.height,
popouts: {
tray: barConfig.popouts.tray,
@@ -125,7 +134,8 @@ Singleton {
background: serializeBackground(),
launcher: serializeLauncher(),
colors: serializeColors(),
dock: serializeDock()
dock: serializeDock(),
screenshot: serializeScreenshot()
};
}
@@ -177,6 +187,7 @@ Singleton {
logo: general.logo,
wallpaperPath: general.wallpaperPath,
desktopIcons: general.desktopIcons,
dateFormat: general.dateFormat,
color: {
mode: general.color.mode,
smart: general.color.smart,
@@ -206,6 +217,7 @@ Singleton {
return {
maxAppsShown: launcher.maxAppsShown,
maxWallpapers: launcher.maxWallpapers,
uwsm: launcher.uwsm,
actionPrefix: launcher.actionPrefix,
specialPrefix: launcher.specialPrefix,
useFuzzy: {
@@ -229,6 +241,7 @@ Singleton {
return {
recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint,
showNotifContent: lock.showNotifContent,
maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount,
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 {
return {
weatherLocation: services.weatherLocation,
@@ -425,6 +452,8 @@ Singleton {
}
property Overview overview: Overview {
}
property Screenshot screenshot: Screenshot {
}
property Services services: Services {
}
property SidebarConfig sidebar: SidebarConfig {
+35 -4
View File
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import QtQuick
import ZShell
import qs.Helpers
@@ -79,10 +80,32 @@ Singleton {
}
function reloadHyprRules(): void {
const barStr = "keyword layerrule %1 %2, match:namespace ZShell-Bar";
const authStr = "keyword layerrule %1 %2, match:namespace ZShell-Auth";
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 blur = transparency.enabled ? 1 : 0;
const alpha = 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 {
@@ -93,6 +116,14 @@ Singleton {
Component.onCompleted: debounceTimer.triggered()
Connections {
function onUsingLuaChanged(): void {
root.reloadHyprRules();
}
target: Hyprland
}
Connections {
function onConfigReloaded(): void {
root.reloadHyprRules();
+1
View File
@@ -6,6 +6,7 @@ JsonObject {
}
property Color color: Color {
}
property string dateFormat: "ddd d MMM - hh:mm:ss"
property bool desktopIcons: false
property Idle idle: Idle {
}
+1
View File
@@ -91,6 +91,7 @@ JsonObject {
property string specialPrefix: "@"
property UseFuzzy useFuzzy: UseFuzzy {
}
property bool uwsm: true
component Sizes: JsonObject {
property int itemHeight: 50
+1
View File
@@ -5,6 +5,7 @@ JsonObject {
property bool enableFprint: true
property int maxFprintTries: 3
property bool recolorLogo: false
property bool showNotifContent: false
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 {
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 real penWidth: 4
property var pendingSegments: []
property bool strokeActive: false
property var strokes: []
property var points: []
function appendPoint(x, y) {
if (!strokeActive || strokes.length === 0)
function clear(): void {
var ctx = getContext('2d');
root.points = [];
ctx.reset();
root.requestPaint();
}
renderStrategy: Canvas.Cooperative
onPaint: {
if (points.length < 2)
return;
const dx = x - lastPoint.x;
const dy = y - lastPoint.y;
if ((dx * dx + dy * dy) < (minPointDistance * minPointDistance))
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");
var ctx = root.getContext('2d');
ctx.save();
ctx.lineWidth = root.penWidth;
ctx.strokeStyle = root.penColor;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = penWidth;
ctx.strokeStyle = penColor;
ctx.fillStyle = penColor;
if (fullRepaintPending) {
fullRepaintPending = false;
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 = [];
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (var i = 1; i < points.length; i++)
ctx.lineTo(points[i].x, points[i].y);
ctx.stroke();
points = points.slice(points.length - 2);
ctx.restore();
}
onWidthChanged: requestFullRepaint()
}
+7 -5
View File
@@ -30,8 +30,10 @@ CustomMouseArea {
const x = event.x;
const y = event.y;
if (event.buttons & Qt.LeftButton)
root.drawing.appendPoint(x, y);
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
}
if (root.inLeftPanel(root.popout, x, y)) {
root.z = -2;
@@ -44,7 +46,8 @@ CustomMouseArea {
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.panels.drawing.expanded = false;
root.drawing.beginStroke(x, y);
root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
return;
}
@@ -52,7 +55,6 @@ CustomMouseArea {
root.drawing.clear();
}
onReleased: {
if (root.visibilities.isDrawing)
root.drawing.endStroke();
root.drawing.points = [];
}
}
+15
View File
@@ -12,22 +12,37 @@ Scope {
required property ShellScreen screen
ExclusionZone {
id: top
anchors.top: true
exclusiveZone: root.bar.exclusiveZone
}
ExclusionZone {
id: left
anchors.left: true
}
ExclusionZone {
id: right
anchors.right: true
}
ExclusionZone {
id: bottom
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 {
exclusiveZone: Config.barConfig.border
implicitHeight: 1
+9 -8
View File
@@ -54,7 +54,7 @@ CustomMouseArea {
anchors.fill: parent
cursorShape: (pressed && dragStart.y < bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
hoverEnabled: true
propagateComposedEvents: true
propagateComposedEvents: false
onContainsMouseChanged: {
if (!containsMouse) {
@@ -72,16 +72,13 @@ CustomMouseArea {
}
}
onPositionChanged: event => {
if (popouts.isDetached)
return;
const x = event.x;
const y = event.y;
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
root.input.z = 2;
// root.input.z = 2;
root.panels.drawing.expanded = false;
}
@@ -95,8 +92,12 @@ CustomMouseArea {
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) {
const showOsd = inRightPanel(panels.osd, x, y);
const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) {
osdShortcutActive = false;
@@ -104,7 +105,7 @@ CustomMouseArea {
}
} else {
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) {
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;
if (y < root.bar.implicitHeight) {
+154 -28
View File
@@ -13,6 +13,7 @@ import qs.Modules.Resources as Resources
import qs.Modules.Settings as Settings
import qs.Modules.Drawing as Drawing
import qs.Modules.Dock as Dock
import qs.Modules.SysTray.Popouts as SysPopouts
import qs.Config
Item {
@@ -20,18 +21,24 @@ Item {
required property Item bar
readonly property alias dashboard: dashboard
readonly property alias dashboardWrapper: dashboardWrapper
readonly property alias dock: dock
readonly property alias drawing: drawing
required property Canvas drawingItem
readonly property alias launcher: launcher
readonly property alias notifications: notifications
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 resourcesWrapper: resourcesWrapper
required property ShellScreen screen
readonly property alias settings: settings
readonly property alias settingsWrapper: settingsWrapper
readonly property alias sidebar: sidebar
readonly property alias toasts: toasts
readonly property alias traySubmenus: traySubmenus
readonly property alias utilities: utilities
required property PersistentProperties visibilities
@@ -39,12 +46,43 @@ Item {
anchors.margins: Config.barConfig.border
anchors.topMargin: bar.implicitHeight
Resources.Wrapper {
id: resources
Item {
id: resourcesWrapper
anchors.left: parent.left
anchors.top: parent.top
visibilities: root.visibilities
clip: true
implicitHeight: resources.implicitHeight * (1 - resources.offsetScale)
implicitWidth: resources.implicitWidth
Resources.Wrapper {
id: resources
anchors.left: parent.left
anchors.top: parent.top
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 {
@@ -57,29 +95,84 @@ Item {
visibilities: root.visibilities
}
Osd.Wrapper {
id: osd
Item {
id: traySubmenus
anchors.right: parent.right
anchors.rightMargin: sidebar.width
anchors.verticalCenter: parent.verticalCenter
clip: sidebar.width > 0
screen: root.screen
visibilities: root.visibilities
Repeater {
model: popouts.content.state.submenus
CustomClippingRect {
id: subMenuWrapper
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;
}
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.Wrapper {
Modules.ClipWrapper {
id: popouts
anchors.top: parent.top
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 {
@@ -96,6 +189,7 @@ Item {
anchors.right: parent.right
anchors.top: parent.top
panels: root
sidebarPanel: sidebar
visibilities: root.visibilities
}
@@ -119,12 +213,33 @@ Item {
visibilities: root.visibilities
}
Dashboard.Wrapper {
id: dashboard
Item {
id: dashboardWrapper
property real offsetScale: dashboard.shouldBeActive ? 0 : 1
anchors.right: parent.right
anchors.top: parent.top
visibilities: root.visibilities
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 {
id: dashboard
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: (-implicitHeight - 5) * offsetScale
offsetScale: dashboardWrapper.offsetScale
visibilities: root.visibilities
}
}
Sidebar.Wrapper {
@@ -137,14 +252,25 @@ Item {
visibilities: root.visibilities
}
Settings.Wrapper {
id: settings
Item {
id: settingsWrapper
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
panels: root
screen: root.screen
visibilities: root.visibilities
clip: true
implicitHeight: settings.implicitHeight * (1 - settings.offsetScale)
implicitWidth: settings.implicitWidth
Settings.Wrapper {
id: settings
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
// anchors.centerIn: parent
panels: root
screen: root.screen
visibilities: root.visibilities
}
}
Dock.Wrapper {
+229 -9
View File
@@ -3,8 +3,10 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import ZShell.Blobs
import qs.Daemons
import qs.Components
import qs.Modules
@@ -62,7 +64,7 @@ Variants {
height: win.height - bar.implicitHeight - Config.barConfig.border
intersection: Intersection.Xor
regions: popoutRegions.instances
regions: [...popoutRegions.instances, ...subMenuRegions.instances]
width: win.width - Config.barConfig.border * 2
x: Config.barConfig.border
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 {
id: focusGrab
@@ -126,6 +144,14 @@ Variants {
Component.onCompleted: Visibilities.load(scope.modelData, this)
}
IpcHandler {
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
}
target: "visibilities"
}
Binding {
property: "bar"
target: visibilities
@@ -144,16 +170,165 @@ Variants {
shadowEnabled: true
}
Border {
bar: bar
visibilities: visibilities
BlobGroup {
id: blobGroup
color: DynamicColors.palette.m3surface
smoothing: Config.barConfig.smoothing
Behavior on color {
CAnim {
}
}
}
Backgrounds {
bar: bar
panels: panels
visibilities: visibilities
z: 1
BlobInvertedRect {
anchors.fill: parent
anchors.margins: -50
borderBottom: Config.barConfig.border - anchors.margins
borderLeft: Config.barConfig.border - anchors.margins
borderRight: Config.barConfig.border - anchors.margins
borderTop: bar.implicitHeight - anchors.margins
group: blobGroup
radius: Config.barConfig.rounding
}
PanelBg {
id: dashBg
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.dashboard.height * (1 + extraHeight)
implicitWidth: panels.dashboard.width
panel: panels.dashboardWrapper
radius: Appearance.rounding.normal
x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight
}
PanelBg {
id: launcherBg
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.launcher.height * (1 + extraHeight)
panel: panels.launcher
radius: Appearance.rounding.smallest + 5
y: panels.launcher.y + bar.implicitHeight
}
PanelBg {
id: sidebarBg
bottomLeftRadius: 0
deformAmount: 0.04
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
panel: panels.sidebar
}
PanelBg {
id: osdBg
deformAmount: 0.1
implicitHeight: panels.osd.height
implicitWidth: panels.osd.width
panel: panels.osdWrapper
radius: 20
x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
}
PanelBg {
id: notifsBg
panel: panels.notifications
}
PanelBg {
id: utilsBg
deformAmount: panels.sidebar.visible ? (0.1) : (0.1)
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
panel: panels.utilities
topLeftRadius: 0
}
PanelBg {
id: popoutBg
property real extraHeight: panels.popouts.isDetached ? 0 : 0.2
deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1
implicitHeight: panels.popouts.height * (1 + extraHeight)
implicitWidth: panels.popouts.width
panel: panels.popoutsWrapper
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale
x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
Behavior on extraHeight {
Anim {
}
}
}
PanelBg {
id: resourcesBg
deformAmount: 0.05
implicitHeight: panels.resources.height
implicitWidth: panels.resources.width
panel: panels.resourcesWrapper
radius: Appearance.rounding.normal
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
}
PanelBg {
id: settingsBg
property real extraHeight: 0.2
deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width
panel: panels.settings
radius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: Appearance.rounding.large + Appearance.padding.smaller
x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
}
PanelBg {
id: dockBg
deformAmount: 0.08
panel: panels.dock
radius: Appearance.rounding.normal
}
PanelBg {
id: drawingBg
deformAmount: 0.08
panel: panels.drawing
radius: Appearance.rounding.normal
}
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
screen: scope.modelData
visibilities: visibilities
dashboard.transform: Matrix4x4 {
matrix: dashBg.deformMatrix
}
dock.transform: Matrix4x4 {
matrix: dockBg.deformMatrix
}
launcher.transform: Matrix4x4 {
matrix: launcherBg.deformMatrix
}
notifications.transform: Matrix4x4 {
matrix: notifsBg.deformMatrix
}
osd.transform: Matrix4x4 {
matrix: osdBg.deformMatrix
}
popouts.transform: Matrix4x4 {
matrix: popoutBg.deformMatrix
}
resources.transform: Matrix4x4 {
matrix: resourcesBg.deformMatrix
}
settings.transform: Matrix4x4 {
matrix: settingsBg.deformMatrix
}
sidebar.transform: Matrix4x4 {
matrix: sidebarBg.deformMatrix
}
utilities.transform: Matrix4x4 {
matrix: utilsBg.deformMatrix
}
}
BarLoader {
@@ -203,10 +409,24 @@ Variants {
anchors.left: parent.left
anchors.right: parent.right
popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper
screen: scope.modelData
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 {
onStreamFinished: {
console.log("this is running");
if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
+20 -44
View File
@@ -7,23 +7,30 @@ import QtQuick
Singleton {
id: root
// The list of users that can log in graphically
// Each user object has: username, uid, home, shell, gecos (full name), face (avatar path)
readonly property string defaultUserFile: "/etc/zshell-greeter/default-user"
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: []
// The currently selected user index
property int selectedIndex: 0
function saveDefaultUser(): void {
if (selectedUser) {
defaultUserStorage.setText(selectedUser.username);
}
}
// The currently selected user object (or null if none)
readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null
function selectNext(): void {
if (users.length === 0)
return;
selectedIndex = (selectedIndex + 1) % users.length;
}
// Convenience property for the selected username
readonly property string selectedUsername: selectedUser ? selectedUser.username : ""
function selectPrevious(): void {
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 {
for (let i = 0; i < users.length; i++) {
if (users[i].username === username) {
@@ -34,28 +41,6 @@ Singleton {
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 {
id: userLister
@@ -67,13 +52,10 @@ Singleton {
try {
root.users = JSON.parse(text);
// If we have users and no selection yet, try to select the default user
if (root.users.length > 0) {
// Try to load the default user
if (defaultUserStorage.loaded) {
const defaultUsername = defaultUserStorage.text().trim();
if (defaultUsername && !root.selectUser(defaultUsername)) {
// Default user not found, select first user
root.selectedIndex = 0;
}
} else {
@@ -87,15 +69,14 @@ Singleton {
}
}
// FileView for persisting the default user
FileView {
id: defaultUserStorage
path: root.defaultUserFile
preload: true
onLoadFailed: {}
onLoaded: {
// If users are already loaded, try to select the default user
if (root.users.length > 0) {
const defaultUsername = text().trim();
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 {
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();
}
+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
import Quickshell
import QtQuick
import qs.Config
Singleton {
readonly property string amPmStr: timeComponents[2] ?? ""
id: root
readonly property date date: clock.date
readonly property string dateStr: format(Config.general.dateFormat)
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")
readonly property string timeStr: format("hh:mm:ss")
function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt);
+79
View File
@@ -11,10 +11,13 @@ Singleton {
id: root
property int availableUpdates: 0
property string cmd: ""
property bool commandReady
property bool loaded
property double now: Date.now()
property var updates: ({})
property bool updating
property string updatingPackage: ""
function formatUpdateTime(timestamp) {
const diffMs = root.now - timestamp;
@@ -34,6 +37,22 @@ Singleton {
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: {
if (!root.loaded)
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 {
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 {
id: saveTimer
+1
View File
@@ -14,6 +14,7 @@ Searcher {
property string actualCurrent: WallpaperPath.currentWallpaperPath
readonly property string current: showPreview ? previewPath : actualCurrent
property string previewPath
property bool recentlyChanged
property bool showPreview: false
function preview(path: string): void {
+1
View File
@@ -16,6 +16,7 @@ RowLayout {
id: root
required property Wrapper popouts
required property ClipWrapper popoutsWrapper
required property ShellScreen screen
readonly property int vPadding: 6
required property PersistentProperties visibilities
+2
View File
@@ -20,6 +20,7 @@ Item {
property bool isHovered
readonly property int padding: Math.max(Appearance.padding.smaller, Config.barConfig.border)
required property Wrapper popouts
required property ClipWrapper popoutsWrapper
required property ShellScreen screen
readonly property bool shouldBeVisible: (!Config.barConfig.autoHide || visibilities.bar || isHovered)
readonly property int vPadding: 6
@@ -76,6 +77,7 @@ Item {
sourceComponent: Bar {
height: root.contentHeight
popouts: root.popouts
popoutsWrapper: root.popoutsWrapper
screen: root.screen
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 qs.Config
import qs.Modules
import qs.Helpers as Helpers
import qs.Helpers
import qs.Components
CustomRect {
@@ -23,6 +23,7 @@ CustomRect {
anchors.centerIn: parent
color: root.visibilities.dashboard ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
font: Appearance.font.family.mono
text: Time.dateStr
Behavior on color {
+14 -20
View File
@@ -15,7 +15,8 @@ Item {
readonly property Item current: currentPopout?.item ?? 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
implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2
@@ -49,40 +50,32 @@ Item {
Connections {
function onHasCurrentChanged(): void {
if (root.wrapper.hasCurrent && trayMenu.shouldBeActive) {
if (root.popouts.hasCurrent && trayMenu.shouldBeActive) {
trayMenu.sourceComponent = null;
trayMenu.sourceComponent = trayMenuComponent;
}
}
target: root.wrapper
target: root.popouts
}
Component {
id: trayMenuComponent
TrayMenuPopout {
popouts: root.wrapper
popouts: root.popouts
screen: root.screen
trayItem: trayMenu.modelData.menu
}
}
}
}
Popout {
name: "overview"
sourceComponent: OverviewPopout {
screen: root.wrapper.screen
wrapper: root.wrapper
}
}
Popout {
name: "upower"
sourceComponent: UPowerPopout {
wrapper: root.wrapper
wrapper: root.popouts
}
}
@@ -90,7 +83,7 @@ Item {
name: "network"
sourceComponent: NetworkPopout {
wrapper: root.wrapper
wrapper: root.popouts
}
}
@@ -98,7 +91,7 @@ Item {
name: "updates"
sourceComponent: UpdatesPopout {
wrapper: root.wrapper
wrapper: root.popouts
}
}
}
@@ -107,12 +100,13 @@ Item {
id: popout
required property string name
readonly property bool shouldBeActive: root.wrapper.currentName === name
readonly property bool shouldBeActive: root.popouts.currentName === name
active: false
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 5
// anchors.horizontalCenter: parent.horizontalCenter
// anchors.top: parent.top
// anchors.topMargin: 5
anchors.centerIn: parent
opacity: 0
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"
}
}
+14 -67
View File
@@ -15,78 +15,25 @@ Item {
reloadableId: "dashboardState"
}
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
implicitHeight: 0
implicitWidth: content.implicitWidth
visible: height > 0
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open
opacity: 1 - offsetScale
visible: offsetScale < 1
states: State {
name: "visible"
when: root.visibilities.dashboard && Config.dashboard.enabled
Loader {
id: content
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
active: root.shouldBeActive || root.visible
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
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 {
id: content
active: true
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
visible: false
sourceComponent: Content {
state: root.dashState
visibilities: root.visibilities
}
sourceComponent: Content {
state: root.dashState
visibilities: root.visibilities
}
}
}
+13 -74
View File
@@ -11,97 +11,36 @@ Item {
property int contentHeight
required property var panels
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.dock
required property PersistentProperties visibilities
implicitHeight: 0
implicitWidth: content.implicitWidth
visible: height > 0
readonly property bool shouldBeActive: visibilities.dock
property real offsetScale: shouldBeActive ? 0 : 1
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 {
duration: Appearance.anim.durations.small
}
}
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();
}
}
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Loader {
id: content
active: false
anchors.left: parent.left
anchors.top: parent.top
visible: false
active: root.shouldBeActive || root.visible
sourceComponent: Content {
panels: root.panels
screen: root.screen
visibilities: root.visibilities
Component.onCompleted: root.contentHeight = implicitHeight
}
Component.onCompleted: timer.start()
}
}
+25 -77
View File
@@ -12,90 +12,29 @@ Item {
required property Canvas drawing
property bool expanded: true
property real offsetScale: shouldBeActive ? 0 : 1
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.isDrawing
required property var visibilities
anchors.leftMargin: (-implicitWidth - 5) * offsetScale
implicitHeight: content.implicitHeight
implicitWidth: 0
visible: width > 0
implicitWidth: root.expanded ? content.implicitWidth : icon.implicitWidth
opacity: 1 - offsetScale
visible: offsetScale < 1
states: [
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
}
Behavior on implicitWidth {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
]
transitions: [
Transition {
from: "*"
to: "*"
ParallelAnimation {
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: icon
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: content
}
}
}
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
]
}
onVisibleChanged: {
if (!visible)
@@ -109,8 +48,12 @@ Item {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: content.contentItem.height
opacity: 1
opacity: root.expanded ? 0 : 1
Behavior on opacity {
Anim {
}
}
sourceComponent: MaterialIcon {
font.pointSize: Appearance.font.size.larger
text: "arrow_forward_ios"
@@ -122,7 +65,12 @@ Item {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
opacity: root.expanded ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: Content {
drawing: root.drawing
visibilities: root.visibilities
+4 -2
View File
@@ -9,17 +9,19 @@ import qs.Paths
Searcher {
id: root
readonly property list<string> command: Config.launcher.uwsm ? ["app2unit", "--"] : []
function launch(entry: DesktopEntry): void {
appDb.incrementFrequency(entry.id);
if (entry.runInTerminal)
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
});
else
Quickshell.execDetached({
command: ["app2unit", "--", ...entry.command],
command: [...root.command, ...entry.command],
workingDirectory: entry.workingDirectory
});
}
+1 -1
View File
@@ -26,7 +26,7 @@ Searcher {
description: qsTr("Pastel palette with a low chroma.")
icon: "android"
name: qsTr("Tonal Spot")
variant: "tonalspot"
variant: "tonal-spot"
},
Variant {
description: qsTr("Hue-shifted, artistic or playful colors.")
+13 -40
View File
@@ -21,55 +21,33 @@ Item {
}
required property var panels
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.launcher
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: {
if (shouldBeActive) {
implicitHeight = Qt.binding(() => content.implicitHeight);
timer.stop();
hideAnim.stop();
showAnim.start();
} else {
showAnim.stop();
hideAnim.start();
implicitHeight = implicitHeight;
}
}
SequentialAnimation {
id: showAnim
visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 400
opacity: 1 - offsetScale
Behavior on offsetScale {
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)
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
SequentialAnimation {
id: hideAnim
ScriptAction {
script: root.implicitHeight = root.implicitHeight
}
Anim {
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
property: "implicitHeight"
target: root
to: 0
}
}
onMaxHeightChanged: timer.start()
Connections {
function onEnabledChanged(): void {
@@ -105,10 +83,6 @@ Item {
root.contentHeight = Math.min(root.maxHeight, content.implicitHeight);
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
if (showAnim.running) {
showAnim.stop();
showAnim.start();
}
}
}
}
@@ -119,7 +93,6 @@ Item {
active: false
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
visible: false
sourceComponent: Content {
maxHeight: root.maxHeight
+14 -9
View File
@@ -8,21 +8,25 @@ import qs.Helpers
Scope {
id: root
readonly property bool enabled: !Players.list.some(p => p.isPlaying)
required property Lock lock
readonly property bool enabled: !Players.list.some( p => p.isPlaying )
function handleIdleAction( action: var ): void {
if ( !action )
function handleIdleAction(action: var): void {
if (!action)
return;
if ( action === "lock" )
if (action === "lock")
lock.lock.locked = true;
else if ( action === "unlock" )
else if (action === "unlock")
lock.lock.locked = false;
else if ( typeof action === "string" )
Hypr.dispatch( action );
else if (action === "dpms on")
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
Quickshell.execDetached( action );
Quickshell.execDetached(action);
}
Variants {
@@ -33,7 +37,8 @@ Scope {
enabled: root.enabled && modelData.timeout > 0 ? true : false
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
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
source: WallpaperPath.lockscreenBg
}
+2
View File
@@ -284,6 +284,8 @@ CustomRect {
Layout.fillWidth: true
color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: {
if (!Config.lock.showNotifContent)
return "Unlock to view";
const summary = modelData.summary.replace(/\n/g, " ");
const body = modelData.body.replace(/\n/g, " ");
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;
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) {
const h = panels.osd.y - 8 * 2 - padding * 2;
if (height > h)
@@ -194,8 +197,7 @@ Item {
}
component Anim: NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
@@ -78,6 +78,10 @@ LazyListView {
}
}
onDoubleClicked: event => {
if (event.button === Qt.LeftButton)
notifInner.toggleExpand(!notifInner.expanded);
}
onPositionChanged: event => {
if (pressed) {
const diffY = event.y - startY;
@@ -79,6 +79,10 @@ LazyListView {
Component.onCompleted: modelData?.lock(this)
Component.onDestruction: modelData?.unlock(this)
onDoubleClicked: event => {
if (event.button === Qt.LeftButton)
root.requestToggleExpand(!root.expanded);
}
onPositionChanged: event => {
if (pressed && !root.expanded) {
const diffY = event.y - startY;
@@ -5,6 +5,7 @@ import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Modules
import qs.Helpers
import qs.Daemons
CustomRect {
@@ -32,7 +33,7 @@ CustomRect {
Toggle {
checked: Network.wifiEnabled
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()
}
@@ -79,6 +80,13 @@ CustomRect {
adapter.enabled = !adapter.enabled;
}
}
Toggle {
checked: GameMode.enabled
icon: GameMode.enabled ? "videogame_asset" : "videogame_asset_off"
onClicked: GameMode.enabled = !GameMode.enabled
}
}
}
+12 -52
View File
@@ -20,69 +20,29 @@ Item {
required property Item sidebar
required property var visibilities
implicitHeight: 0
implicitWidth: sidebar.visible ? sidebar.width : Config.utilities.sizes.width
visible: height > 0
property real offsetScale: shouldBeActive ? 0 : 1
states: State {
name: "visible"
when: root.shouldBeActive
visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight + 8 * 2
implicitWidth: sidebar.width * (1 - sidebar.offsetScale)
opacity: 1 - offsetScale
PropertyChanges {
root.implicitHeight: content.implicitHeight + 8 * 2
}
}
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: 1000
running: true
onTriggered: {
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Loader {
id: content
active: true
anchors.left: parent.left
anchors.margins: 8
anchors.top: parent.top
visible: false
active: root.shouldBeActive || root.visible
sourceComponent: Content {
implicitWidth: root.implicitWidth - 8 * 2
+12 -35
View File
@@ -7,50 +7,29 @@ import QtQuick
Item {
id: root
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels
readonly property Props props: Props {
}
readonly property bool shouldBeActive: root.visibilities.sidebar && Config.sidebar.enabled
required property var visibilities
implicitWidth: 0
visible: width > 0
anchors.rightMargin: (-implicitWidth - 5) * offsetScale
implicitWidth: Config.sidebar.sizes.width
opacity: 1 - offsetScale
visible: offsetScale < 1
states: State {
name: "visible"
when: root.visibilities.sidebar
PropertyChanges {
root.implicitWidth: Config.sidebar.sizes.width
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
transitions: [
Transition {
from: ""
to: "visible"
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
}
]
Loader {
id: content
active: true
active: root.shouldBeActive || root.visible
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.left: parent.left
@@ -58,11 +37,9 @@ Item {
anchors.top: parent.top
sourceComponent: Content {
implicitWidth: Config.sidebar.sizes.width - 8 * 2
implicitWidth: Config.sidebar.sizes.width - Appearance.padding.smaller * 2
props: root.props
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
required property Item panels
required property Item sidebarPanel
required property var visibilities
implicitHeight: content.implicitHeight
implicitWidth: Math.max(panels.sidebar.width, content.implicitWidth)
implicitWidth: Math.max(sidebarPanel.width * (1 - sidebarPanel.offsetScale), content.implicitWidth)
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 {
id: content
+25 -47
View File
@@ -26,40 +26,22 @@ Item {
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
implicitWidth: 0
visible: width > 0
opacity: 1 - offsetScale
states: State {
name: "visible"
when: root.shouldBeActive
PropertyChanges {
root.implicitWidth: content.implicitWidth
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
transitions: [
Transition {
from: ""
to: "visible"
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
}
]
Component.onCompleted: {
volume = Audio.volume;
@@ -113,26 +95,22 @@ Item {
}
}
CustomClippingRect {
anchors.fill: parent
Loader {
id: content
Loader {
id: content
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
active: root.shouldBeActive || root.visible
sourceComponent: Content {
brightness: root.brightness
monitor: root.monitor
muted: root.muted
sourceMuted: root.sourceMuted
sourceVolume: root.sourceVolume
visibilities: root.visibilities
volume: root.volume
}
Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible)
sourceComponent: Content {
brightness: root.brightness
monitor: root.monitor
muted: root.muted
sourceMuted: root.sourceMuted
sourceVolume: root.sourceVolume
visibilities: root.visibilities
volume: root.volume
}
}
}
+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
implicitHeight: Math.ceil(root.percentage * parent.height)
Behavior on implicitHeight {
Anim {
}
}
// Behavior on implicitHeight {
// Anim {
// }
// }
}
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ CustomRect {
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2
radius: height / 2
radius: Appearance.rounding.full
StateLayer {
onClicked: root.visibilities.resources = !root.visibilities.resources
+20 -64
View File
@@ -9,78 +9,34 @@ Item {
id: root
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
implicitHeight: 0
implicitWidth: content.implicitWidth
visible: height > 0
anchors.topMargin: (-implicitHeight - 5) * offsetScale
clip: true
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open
opacity: 1 - offsetScale
visible: offsetScale < 1
states: State {
name: "visible"
when: root.visibilities.resources
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();
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Timer {
id: timer
Loader {
id: content
interval: Appearance.anim.durations.extraLarge
running: true
active: root.shouldBeActive || root.visible
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
onTriggered: {
content.active = Qt.binding(() => (root.visibilities.resources) || root.visible);
content.visible = true;
}
}
CustomClippingRect {
anchors.fill: parent
Loader {
id: content
active: true
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
visible: false
sourceComponent: Content {
padding: Appearance.padding.normal
visibilities: root.visibilities
}
sourceComponent: Content {
padding: Appearance.padding.normal
visibilities: root.visibilities
}
}
}
+19 -1
View File
@@ -104,6 +104,18 @@ Item {
key: "launcher"
name: "Launcher"
}
ListElement {
icon: "screenshot_region"
key: "screenshot"
name: "Screenshot"
}
ListElement {
icon: "cached"
key: "updates"
name: "Updates"
}
}
CustomClippingRect {
@@ -153,7 +165,7 @@ Item {
required property string name
implicitHeight: 42
implicitWidth: 200
implicitWidth: 250
radius: Appearance.rounding.normal - Appearance.padding.smaller
RowLayout {
@@ -171,9 +183,15 @@ Item {
Layout.fillHeight: true
Layout.preferredWidth: icon.contentWidth
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
text: categoryItem.icon
verticalAlignment: Text.AlignVCenter
Behavior on fill {
Anim {
}
}
}
CustomText {
+15 -23
View File
@@ -57,6 +57,16 @@ SettingsPage {
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Deform animation scale"
object: Config.appearance.deform
setting: "scale"
step: 0.1
}
}
SettingsSection {
@@ -66,7 +76,7 @@ SettingsPage {
name: "Fonts"
}
SettingInput {
SettingListView {
name: "Sans family"
object: Config.appearance.font.family
setting: "sans"
@@ -75,29 +85,11 @@ SettingsPage {
Separator {
}
SettingInput {
SettingListView {
name: "Monospace family"
object: Config.appearance.font.family
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 {
@@ -118,9 +110,9 @@ SettingsPage {
}
SettingSpinBox {
name: "Session GIF speed"
max: 5
min: 0
name: "Session GIF speed"
object: Config.appearance.anim
setting: "sessionGifSpeed"
step: 0.1
@@ -144,9 +136,9 @@ SettingsPage {
}
SettingSpinBox {
name: "Base opacity"
max: 1
min: 0
name: "Base opacity"
object: Config.appearance.transparency
setting: "base"
step: 0.05
@@ -156,9 +148,9 @@ SettingsPage {
}
SettingSpinBox {
name: "Layer opacity"
max: 1
min: 0
name: "Layer opacity"
object: Config.appearance.transparency
setting: "layers"
step: 0.05
+7 -7
View File
@@ -29,13 +29,13 @@ SettingsPage {
step: 50
}
// Separator {
// }
//
// WallpaperCropper {
// Layout.fillWidth: true
// Layout.preferredHeight: 300
// }
Separator {
}
WallpaperCropper {
Layout.fillWidth: true
Layout.preferredHeight: 600
}
}
SettingsSection {
+16 -6
View File
@@ -19,8 +19,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Height"
min: 1
name: "Height"
object: Config.barConfig
setting: "height"
}
@@ -29,8 +29,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Rounding"
min: 0
name: "Rounding"
object: Config.barConfig
setting: "rounding"
}
@@ -39,11 +39,21 @@ SettingsPage {
}
SettingSpinBox {
name: "Border"
min: 0
name: "Border"
object: Config.barConfig
setting: "border"
}
Separator {
}
SettingSpinBox {
min: 0
name: "Smoothing"
object: Config.barConfig
setting: "smoothing"
}
}
SettingsSection {
@@ -145,8 +155,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Dock height"
min: 1
name: "Dock height"
object: Config.dock
setting: "height"
}
@@ -173,8 +183,8 @@ SettingsPage {
}
SettingStringList {
name: "Pinned apps"
addLabel: qsTr("Add pinned app")
name: "Pinned apps"
object: Config.dock
setting: "pinnedApps"
}
@@ -183,8 +193,8 @@ SettingsPage {
}
SettingStringList {
name: "Ignored app regexes"
addLabel: qsTr("Add ignored regex")
name: "Ignored app regexes"
object: Config.dock
setting: "ignoredAppRegexes"
}
+12 -8
View File
@@ -47,6 +47,15 @@ SettingsPage {
object: Config.general
setting: "desktopIcons"
}
Separator {
}
SettingInput {
name: "Date format"
object: Config.general
setting: "dateFormat"
}
}
SettingsSection {
@@ -118,7 +127,7 @@ SettingsPage {
MenuItem {
icon: "gradient"
text: qsTr("Tonal spot")
value: "tonalSpot"
value: "tonal-spot"
},
MenuItem {
icon: "target"
@@ -138,7 +147,7 @@ SettingsPage {
MenuItem {
icon: "nutrition"
text: qsTr("Fruit salad")
value: "fruitSalad"
value: "fruit-salad"
}
]
@@ -146,12 +155,7 @@ SettingsPage {
Config.colors.schemeType = item.value;
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 {
name: "Max apps shown"
min: 1
name: "Max apps shown"
object: Config.launcher
setting: "maxAppsShown"
}
@@ -20,8 +20,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Max wallpapers shown"
min: 1
name: "Max wallpapers shown"
object: Config.launcher
setting: "maxWallpapers"
}
@@ -43,6 +43,15 @@ SettingsPage {
object: Config.launcher
setting: "specialPrefix"
}
Separator {
}
SettingSwitch {
name: "Use UWSM launch command"
object: Config.launcher
setting: "uwsm"
}
}
SettingsSection {
@@ -103,8 +112,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Item width"
min: 1
name: "Item width"
object: Config.launcher.sizes
setting: "itemWidth"
}
@@ -113,8 +122,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Item height"
min: 1
name: "Item height"
object: Config.launcher.sizes
setting: "itemHeight"
}
@@ -123,8 +132,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Wallpaper width"
min: 1
name: "Wallpaper width"
object: Config.launcher.sizes
setting: "wallpaperWidth"
}
@@ -133,8 +142,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Wallpaper height"
min: 1
name: "Wallpaper height"
object: Config.launcher.sizes
setting: "wallpaperHeight"
}
+14 -5
View File
@@ -31,8 +31,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Max fingerprint tries"
min: 1
name: "Max fingerprint tries"
object: Config.lock
setting: "maxFprintTries"
step: 1
@@ -41,9 +41,18 @@ SettingsPage {
Separator {
}
SettingSwitch {
name: "Show notification details"
object: Config.lock
setting: "showNotifContent"
}
Separator {
}
SettingSpinBox {
name: "Blur amount"
min: 0
name: "Blur amount"
object: Config.lock
setting: "blurAmount"
step: 1
@@ -53,9 +62,9 @@ SettingsPage {
}
SettingSpinBox {
name: "Height multiplier"
max: 2
min: 0.1
name: "Height multiplier"
object: Config.lock.sizes
setting: "heightMult"
step: 0.05
@@ -65,9 +74,9 @@ SettingsPage {
}
SettingSpinBox {
name: "Aspect ratio"
max: 4
min: 0.5
name: "Aspect ratio"
object: Config.lock.sizes
setting: "ratio"
step: 0.05
@@ -77,8 +86,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Center width"
min: 100
name: "Center width"
object: Config.lock.sizes
setting: "centerWidth"
step: 10
@@ -22,6 +22,13 @@ ColumnLayout {
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) {
const list = [...Config.general.idle.timeouts];
let entry = list[i];
@@ -49,6 +56,9 @@ ColumnLayout {
onAddActiveActionRequested: {
root.updateTimeoutEntry(index, "activeAction", "");
}
onDeleteRequested: function (index) {
root.deleteTimeoutEntry(index);
}
onFieldEdited: function (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);
else if (currentCategory === "launcher")
stack.push(launcher);
else if (currentCategory === "screenshot")
stack.push(screenshot);
else if (currentCategory === "updates")
stack.push(updates);
}
target: root
@@ -225,4 +229,18 @@ Item {
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 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() {
if (!root.dragActive && !root.dropAnimating)
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) {
switch (id) {
case "workspaces":
@@ -153,7 +92,7 @@ Item {
case "spacer":
return qsTr("Spacer");
case "activeWindow":
return qsTr("Active window");
return qsTr("Title");
case "tray":
return qsTr("Tray");
case "upower":
@@ -163,34 +102,14 @@ Item {
case "clock":
return qsTr("Clock");
case "notifBell":
return qsTr("Notification bell");
return qsTr("Notifs");
case "hyprsunset":
return qsTr("Night light");
default:
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() {
const entries = root.object[root.setting] ?? [];
const next = [];
@@ -225,7 +144,6 @@ Item {
list[index] = entry;
root.object[root.setting] = list;
Config.save();
root.ensureVisualEntries();
}
Layout.fillWidth: true
@@ -233,7 +151,7 @@ Item {
Component.onCompleted: root.rebuildVisualEntries()
Rectangle {
CustomRect {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
@@ -248,39 +166,12 @@ Item {
}
}
ParallelAnimation {
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 {
RowLayout {
id: layout
anchors.fill: parent
spacing: Appearance.spacing.smaller
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
// spacing: Appearance.spacing.smaller
DelegateModel {
id: visualModel
@@ -293,248 +184,40 @@ Item {
}
}
ListView {
id: listView
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
boundsBehavior: Flickable.StopAtBounds
clip: false
implicitHeight: contentHeight
implicitWidth: width
interactive: !(root.dragActive || root.dropAnimating)
Repeater {
delegate: entryDelegate
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 {
id: entryDelegate
DropArea {
id: slot
readonly property var entryData: modelData.entry
IconButton {
required property int index
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;
Layout.fillWidth: true
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0)
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))
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
font.pointSize: Appearance.font.size.larger
text: root.labelForId(slot.entryData.id)
}
CustomSwitch {
Layout.rightMargin: Appearance.padding.small
checked: slot.entryData.enabled ?? true
onToggled: root.updateEntry(slot.DelegateModel.itemsIndex, checked)
}
Behavior on Layout.preferredWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
onClicked: root.updateEntry(index, internalChecked)
}
}
}
+2 -2
View File
@@ -59,7 +59,7 @@ Item {
id: rect
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
radius: Appearance.rounding.full
@@ -68,7 +68,7 @@ Item {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth, 550)
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
text: root.formattedValue()
onEditingFinished: {
+31 -8
View File
@@ -10,6 +10,7 @@ Item {
required property var modelData
signal addActiveActionRequested
signal deleteRequested(int index)
signal fieldEdited(string key, var value)
Layout.fillWidth: true
@@ -65,42 +66,64 @@ Item {
HoverHandler {
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 {
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.verticalCenter: parent.verticalCenter
elide: Text.ElideRight // enable if CustomText supports it
font.pointSize: Appearance.font.size.larger
text: root.modelData.name
visible: !nameCell.editing
Behavior on anchors.leftMargin {
Anim {
}
}
}
IconButton {
id: editButton
HoverIconButton {
id: deleteButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large
icon: "edit"
visible: nameHover.hovered && !nameCell.editing
icon: "delete"
shouldBeVisible: nameHover.hovered && !nameCell.editing
onClicked: nameCell.beginEdit()
onClicked: root.deleteRequested(root.index)
}
CustomRect {
anchors.fill: parent
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
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
visible: nameCell.editing
CustomTextField {
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
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
contentHeight: clayout.implicitHeight
CustomScrollBar.vertical: CustomScrollBar {
flickable: flickable
}
TapHandler {
acceptedButtons: Qt.LeftButton
+90 -78
View File
@@ -6,97 +6,109 @@ import qs.Config
import qs.Components
import qs.Helpers
ColumnLayout {
Item {
id: root
width: Math.min(parent ? parent.width : 600, 600)
spacing: 15
Image {
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
fillMode: Image.PreserveAspectFit
smooth: true
source: Wallpapers.current
}
Item {
id: overlay
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
height: imageView.displayH
width: imageView.displayW
x: imageView.displayX
y: imageView.displayY
Image {
id: img
CustomRect {
id: cropRect
property real aspectRatio: Quickshell.screens[0].width / Quickshell.screens[0].height
readonly property rect sourceRect: Qt.rect(x * imageView.scaleX, y * imageView.scaleY, width * imageView.scaleX, height * imageView.scaleY)
property real zoom: Config.background.zoom
function clampToBounds() {
x = Math.max(0, Math.min(x, overlay.width - width));
y = Math.max(0, Math.min(y, overlay.height - height));
}
border.color: DynamicColors.palette.m3primary
border.width: 2
color: DynamicColors.tPalette.m3primary
height: width / aspectRatio
radius: Appearance.rounding.small
visible: imageView.status === Image.Ready
width: Math.min(overlay.width / zoom, overlay.height * aspectRatio / zoom)
x: Config.background.sourceClipX / imageView.scaleX
y: Config.background.sourceClipY / imageView.scaleY
}
MouseArea {
function updateCrop(mouseX, mouseY) {
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;
}
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectFit
source: Wallpapers.current
hoverEnabled: true
preventStealing: true
Rectangle {
id: cropRect
onPositionChanged: mouse => {
if (pressed)
updateCrop(mouse.x, mouse.y);
}
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;
property real paintedWidth: img.paintedWidth > 0 ? img.paintedWidth : img.width
property real paintedHeight: img.paintedHeight > 0 ? img.paintedHeight : img.height
property real paintedX: (img.width - paintedWidth) / 2
property real paintedY: (img.height - paintedHeight) / 2
if (wheel.angleDelta.y > 0)
cropRect.zoom *= 1.1;
else
cropRect.zoom /= 1.1;
property real screenAspect: Quickshell.screens.length > 0 ? (Quickshell.screens[0].width / Math.max(1, Quickshell.screens[0].height)) : 16/9
property real imageAspect: Math.max(1, paintedWidth) / Math.max(1, paintedHeight)
cropRect.zoom = Math.max(1.0, Math.min(cropRect.zoom, 10.0));
Config.background.zoom = cropRect.zoom;
property real cropWidth: (imageAspect > screenAspect ? paintedHeight * screenAspect : paintedWidth) / Config.background.zoom
property real cropHeight: (imageAspect > screenAspect ? paintedHeight : paintedWidth / screenAspect) / Config.background.zoom
cropRect.x = oldCenterX - cropRect.width * 0.5;
cropRect.y = oldCenterY - cropRect.height * 0.5;
border.color: DynamicColors.primary
border.width: 2
color: DynamicColors.primaryContainer.withAlpha(0.3)
width: cropWidth
height: cropHeight
x: paintedX + (paintedWidth - width) * Config.background.alignX
y: paintedY + (paintedHeight - height) * Config.background.alignY
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) {
let valY = newY / rangeY;
Config.background.alignY = Math.max(0.0, Math.min(1.0, valY));
}
}
}
}
PinchHandler {
maximumScale: 5.0
minimumScale: 1.0
target: null
onActiveScaleChanged: {
if (active) {
let newZoom = Config.background.zoom * (1 / (1 + (activeScale - 1) * 0.1));
Config.background.zoom = Math.max(1.0, Math.min(newZoom, 5.0));
}
}
}
cropRect.clampToBounds();
}
}
}
SettingSpinBox {
name: "Zoom"
object: Config.background
setting: "zoom"
min: 1.0
max: 5.0
step: 0.1
}
}
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.Config
import "../../scripts/fuzzysort.js" as Fuzzy
import "./SettingsIndex.mjs" as SettingsIndex
import "../../scripts/SettingsIndex.mjs" as SettingsIndex
Item {
id: root
@@ -51,9 +51,14 @@ Item {
implicitHeight: searchContainer.implicitHeight
implicitWidth: 200
Shortcut {
sequence: "/"
onActivated: searchField.forceActiveFocus()
}
ListModel {
id: resultsModel
}
CustomRect {
+10 -31
View File
@@ -7,45 +7,24 @@ import qs.Helpers
Item {
id: root
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.settings
required property PersistentProperties visibilities
implicitHeight: 0
anchors.topMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight
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
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
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
}
}
]
CustomClippingRect {
anchors.fill: parent
+2 -2
View File
@@ -14,8 +14,8 @@ Scope {
description: "Toggle launcher"
name: "toggle-launcher"
onPressed: root.launcherInterrupted = false
onReleased: {
onPressed: {
root.launcherInterrupted = false;
if (!root.launcherInterrupted && !root.hasFullscreen) {
const visibilities = Visibilities.getForActive();
visibilities.launcher = !visibilities.launcher;
+34 -170
View File
@@ -18,12 +18,13 @@ Item {
readonly property int topMargin: 0
required property var wrapper
implicitHeight: layout.implicitHeight + Appearance.padding.small * 2
implicitWidth: layout.implicitWidth + Appearance.padding.small * 2
implicitHeight: vol.implicitHeight + Appearance.padding.small * 2
implicitWidth: 400 + Appearance.padding.small * 2
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: parent.implicitHeight
radius: Appearance.rounding.small
@@ -31,180 +32,21 @@ Item {
Behavior on implicitHeight {
Anim {
duration: MaterialEasing.emphasizedDecelTime
easing.bezierCurve: MaterialEasing.emphasized
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
ColumnLayout {
id: layout
VolumesTab {
id: vol
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 {
duration: MaterialEasing.expressiveEffectsTime
property: "opacity"
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 {
id: vol
}
DevicesTab {
id: dev
}
}
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
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 {
spacing: 12
@@ -294,13 +136,35 @@ Item {
}
}
CustomRect {
CustomClippingRect {
Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer
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 {
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
import qs.Components
import qs.Config
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import qs.Components
import qs.Modules
import qs.Config
StackView {
SubMenu {
id: root
property int biggestWidth: 0
required property Item popouts
property int rootWidth: 0
handle: trayItem
level: 0
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
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
// color: "transparent"
radius: 6
radius: (20 - Appearance.padding.small) * Appearance.rounding.scale
CustomRect {
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
height: 200
visible: script.values.length === 0
width: 300
width: 600
MaterialIcon {
id: noUpdatesIcon
@@ -54,6 +54,8 @@ CustomClippingRect {
anchors.centerIn: parent
contentHeight: childrenRect.height
contentWidth: 600
displayMarginBeginning: root.itemHeight
displayMarginEnd: root.itemHeight
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
implicitWidth: contentWidth
spacing: Appearance.spacing.normal
@@ -65,10 +67,11 @@ CustomClippingRect {
required property var modelData
readonly property list<string> sections: modelData.update.split(" ")
anchors.left: parent.left
anchors.right: parent.right
// anchors.left: parent.left
// anchors.right: parent.right
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: root.itemHeight
implicitWidth: 600
radius: Appearance.rounding.small - Appearance.padding.small
RowLayout {
+1 -1
View File
@@ -14,7 +14,7 @@ CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2
radius: height / 2
radius: Appearance.rounding.full
RowLayout {
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
import Quickshell
import QtQuick
import qs.Components
import qs.Helpers
@@ -8,85 +9,34 @@ import qs.Config
Item {
id: root
property Image current: one
required property ShellScreen screen
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: CachingImage {
Image {
id: img
function update(): void {
if (path === root.source) {
root.current = this;
} else {
path = root.source;
}
}
anchors.fill: undefined
anchors.fill: parent
asynchronous: true
opacity: 0
fillMode: Image.Stretch
fillMode: Image.PreserveAspectCrop
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)
property real imageRatio: Math.max(1, sourceSize.width) / Math.max(1, sourceSize.height)
property bool isValid: sourceSize.width > 0 && sourceSize.height > 0 && root.width > 0 && root.height > 0
width: isValid ? (imageRatio > windowRatio ? root.height * imageRatio : root.width) * Config.background.zoom : root.width
height: isValid ? (imageRatio > windowRatio ? root.height : root.width / imageRatio) * Config.background.zoom : root.height
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;
onSourceChanged: {
if (Wallpapers.recentlyChanged) {
Config.background.sourceClipH = 0;
Config.background.sourceClipW = 0;
Config.background.sourceClipY = 0;
Config.background.sourceClipX = 0;
Config.background.zoom = 1.0;
Config.save();
}
Wallpapers.recentlyChanged = true;
}
}
}
+1
View File
@@ -30,6 +30,7 @@ Loader {
}
WallBackground {
screen: root.screen
}
Loader {
+28 -86
View File
@@ -21,7 +21,7 @@ Item {
readonly property int workspacesShown: workspaces.length
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
Behavior on implicitWidth {
@@ -39,36 +39,15 @@ Item {
implicitHeight: root.implicitHeight - ((Appearance.padding.small - 1) * 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 {
id: grid
anchors.fill: parent
anchors.margins: root.activeWorkspaceMargin
columnSpacing: 0
columns: root.workspacesShown
rowSpacing: 0
rows: 1
z: 3
Repeater {
model: root.workspaces
@@ -91,88 +70,51 @@ Item {
CustomText {
anchors.centerIn: parent
color: button.modelData.active ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
color: DynamicColors.palette.m3onSecondaryContainer
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
text: button.modelData.name
verticalAlignment: Text.AlignVCenter
z: 3
}
}
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
CustomRect {
id: indicator
anchors.fill: parent
anchors.margins: root.activeWorkspaceMargin
layer.enabled: true
visible: false
z: 4
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
Grid {
anchors.fill: parent
columnSpacing: 0
columns: root.workspacesShown
rowSpacing: 0
rows: 1
anchors.verticalCenter: parent.verticalCenter
clip: true
color: DynamicColors.palette.m3primary
implicitHeight: indicatorThickness
implicitWidth: indicatorLength
radius: Appearance.rounding.full
x: indicatorPosition
Repeater {
model: root.workspaces
AnimatedTabIndexPair {
id: idxPair
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
}
}
}
index: root.workspaces.findIndex(w => w.active)
}
}
Item {
id: indicatorMask
anchors.fill: bgRect
layer.enabled: true
visible: false
CustomRect {
color: "white"
height: indicator.height
radius: indicator.radius
width: indicator.width
x: indicator.x
y: indicator.y
Coloriser {
colorizationColor: DynamicColors.palette.m3onPrimary
implicitHeight: grid.height
implicitWidth: grid.width
source: grid
sourceColor: DynamicColors.palette.m3onSurface
x: -indicator.x + 3
}
}
MultiEffect {
anchors.fill: activeTextSource
maskEnabled: true
maskInverted: false
maskSource: indicatorMask
source: activeTextSource
z: 5
}
}
}
+55 -75
View File
@@ -8,42 +8,48 @@ import qs.Config
Item {
id: root
property list<real> animCurve: MaterialEasing.emphasized
property int animLength: MaterialEasing.emphasizedDecelTime
readonly property Item current: content.item?.current ?? null
property list<real> animCurve: Appearance.anim.curves.expressiveDefaultSpatial
property int animLength: Appearance.anim.durations.expressiveDefaultSpatial
readonly property alias content: content
readonly property Item current: (content.item as Content)?.current ?? null
property real currentCenter
property string currentName
property alias currentName: popoutState.currentName
property string detachedMode
property bool hasCurrent
property alias hasCurrent: popoutState.hasCurrent
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
required property real offsetScale
property string queuedMode
required property ShellScreen screen
property alias state: popoutState
function close(): void {
hasCurrent = false;
animCurve = MaterialEasing.emphasizedDecel;
animLength = MaterialEasing.emphasizedDecelTime;
detachedMode = "";
animCurve = MaterialEasing.emphasized;
}
function detach(mode: string): void {
animLength = 600;
setAnims(true);
if (mode === "winfo") {
detachedMode = mode;
} else {
detachedMode = "any";
queuedMode = mode;
detachedMode = "any";
}
setAnims(false);
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
implicitWidth: nonAnimWidth
visible: width > 0 && height > 0
Behavior on implicitHeight {
Anim {
@@ -52,7 +58,7 @@ Item {
}
}
Behavior on implicitWidth {
enabled: root.implicitHeight > 0
enabled: root.offsetScale < 1
Anim {
duration: root.animLength
@@ -60,85 +66,59 @@ Item {
}
}
// Comp {
// shouldBeActive: root.detachedMode === "winfo"
// 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
PopoutState {
id: popoutState
}
Comp {
id: content
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
asynchronous: true
shouldBeActive: root.hasCurrent
// anchors.horizontalCenter: parent.horizontalCenter
// anchors.top: parent.top
anchors.centerIn: parent
shouldBeActive: root.hasCurrent && !root.detachedMode
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 {
id: comp
property bool shouldBeActive
active: false
asynchronous: true
opacity: 0
// Makes the loader load on the same frame shouldBeActive becomes true, which ensures size is set
states: State {
name: "active"
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