88 Commits
Author SHA1 Message Date
AramJonghu 951c31ab3d Merge remote-tracking branch 'origin/feat/settings-revamp' 2026-06-30 15:29:27 +02:00
zach 60831de7c5 Merge pull request 'tidy, format, and resolving tidy warnings' (#132) from tidy-warning-fixes into main
Reviewed-on: #132
2026-06-30 14:46:39 +02:00
zach 38b9362515 remove old Settings, rename new
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
C++ / build (pull_request) Successful in 2m26s
2026-06-30 13:43:34 +02:00
zach deaeac4dbd section headers for screenshot page + rename
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 45s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
C++ / build (pull_request) Successful in 2m23s
2026-06-30 13:31:23 +02:00
zach 157bdd8fd9 new spin box, textfield and searchbar
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 30s
Python / test (pull_request) Successful in 43s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m38s
C++ / build (pull_request) Successful in 2m18s
2026-06-30 13:25:12 +02:00
AramJonghu 45350210cc fix&revert(qalculator): unassigned this to calculator pointer fixed, revert of succesful test
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 20s
Python / lint-format (pull_request) Successful in 41s
Python / test (pull_request) Successful in 1m51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 2m0s
C++ / build (pull_request) Successful in 3m41s
2026-06-30 04:49:55 +02:00
AramJonghu 85944ec966 force harmless lint warning to check ci/cd
Python / lint-format (pull_request) Successful in 26s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 30s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m43s
C++ / build (pull_request) Successful in 2m25s
2026-06-30 04:42:39 +02:00
AramJonghu 3c9df82594 CI/CD: attempt only user tidy warnings
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 16s
Python / lint-format (pull_request) Successful in 29s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
C++ / build (pull_request) Successful in 2m35s
2026-06-30 04:36:22 +02:00
AramJonghu 1cdcecae68 CI/CD: revert
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 25s
Python / lint-format (pull_request) Successful in 29s
Python / test (pull_request) Successful in 41s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
C++ / build (pull_request) Successful in 2m28s
2026-06-30 04:27:44 +02:00
AramJonghu d2368c8c10 CI/CD: testing warnings as errors for CI/CD only
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 20s
Python / lint-format (pull_request) Successful in 49s
Python / test (pull_request) Successful in 1m11s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m32s
C++ / build (pull_request) Failing after 2m26s
2026-06-30 04:17:43 +02:00
AramJonghu b8f79c51ae CI/CD: updated, researched other CI/CD implementations
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 16s
Python / lint-format (pull_request) Successful in 31s
Python / test (pull_request) Successful in 44s
C++ / build (pull_request) Successful in 2m18s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m47s
2026-06-30 04:02:22 +02:00
AramJonghu 25e1610e2b CI/CD: updated, should not stop the check now due to diagnostic demotion
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 14s
Python / test (pull_request) Successful in 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
C++ / build (pull_request) Successful in 2m26s
2026-06-30 03:54:47 +02:00
AramJonghu 49a65becff CI/CD: updated cpp to reduce false flags, focus on dev tidy check
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 30s
Python / test (pull_request) Successful in 42s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m37s
C++ / build (pull_request) Successful in 2m23s
2026-06-30 03:44:35 +02:00
AramJonghu 87c9f85ac7 fix(hyprextras): false flag recursion
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m53s
C++ / build (pull_request) Successful in 2m22s
2026-06-30 03:38:39 +02:00
AramJonghu 532e5a0a03 clang-tidy: reordering 2026-06-30 03:38:16 +02:00
AramJonghu dcc535d213 unused nolint
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 15s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
C++ / build (pull_request) Successful in 2m26s
2026-06-30 03:27:01 +02:00
AramJonghu e347671edd missing commas clang-tidy 2026-06-30 03:23:36 +02:00
AramJonghu d4e4152bdc leftover format file
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 19s
Python / lint-format (pull_request) Successful in 25s
Python / test (pull_request) Successful in 46s
C++ / build (pull_request) Successful in 3m19s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m9s
2026-06-30 03:18:42 +02:00
AramJonghu 728bdc85bc fix(clang-tidy/analyzer): resolving false positives
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 26s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m54s
C++ / build (pull_request) Successful in 3m25s
2026-06-30 03:17:52 +02:00
AramJonghu 944694966b tidy(blobmaterial&circularindicatormanager): warning fixes, readability and modernize headers.
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m41s
C++ / build (pull_request) Successful in 2m29s
2026-06-30 03:08:32 +02:00
AramJonghu 1e31e14de8 format(remainder): clang-format remainder of files
Python / lint-format (pull_request) Successful in 27s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 48s
Python / test (pull_request) Successful in 1m2s
C++ / build (pull_request) Successful in 2m15s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m25s
2026-06-30 02:10:37 +02:00
AramJonghu 658be09da2 fix(buttonrow): restore private specifier removed by clang-format 2026-06-30 02:10:17 +02:00
AramJonghu 467e44bb9f tidy&format(all): all files formatted and relevant warnings resolved
C++ / build (pull_request) Failing after 15s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 28s
Python / lint-format (pull_request) Successful in 45s
Python / test (pull_request) Successful in 38s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m45s
2026-06-30 02:04:24 +02:00
AramJonghu fed9372b35 refactor(tickingservice): flatten nested if blocks with early returns 2026-06-30 01:54:22 +02:00
AramJonghu b2cddea636 fix(gpu): use epsilon comparison instead of == for floating-point guards 2026-06-30 01:36:40 +02:00
AramJonghu 396825d785 fix(desktopmodel): cast qsizetype to int to suppress -Wshorten-64-to-32 2026-06-30 01:36:14 +02:00
AramJonghu f439456ff4 fix(strokecanvasrenderer): use qreal instead of float in drawHoverCursor
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 27s
Python / lint-format (pull_request) Successful in 31s
Python / test (pull_request) Successful in 1m0s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
C++ / build (pull_request) Successful in 2m17s
2026-06-30 01:26:19 +02:00
AramJonghu 4a18edfc78 fix(strokecanvasitem): use qreal instead of float to suppress -Wdouble-promotion
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 16s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 48s
C++ / build (pull_request) Successful in 2m20s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m59s
2026-06-30 01:22:40 +02:00
AramJonghu 3eecbc16bc perf(lazylistview): collect pending indices and hoist layoutSize cast in updatePolishdbus workaround for session 2026-06-30 00:23:23 +02:00
zach 437f935f73 add about page in settings
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 13s
Python / test (pull_request) Successful in 53s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m35s
C++ / build (pull_request) Successful in 2m32s
2026-06-29 23:54:54 +02:00
AramJonghu b939224e9d action secret added so we have push permissions for docker
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 25s
Python / lint-format (pull_request) Successful in 28s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m53s
C++ / build (pull_request) Successful in 2m31s
2026-06-29 23:01:54 +02:00
AramJonghu 644a1e0c78 workflow update; node and docker deps added
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 52s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m40s
C++ / build (pull_request) Successful in 2m43s
2026-06-29 22:40:28 +02:00
zach 3f7bbc01aa Merge branch 'main' into feat/settings-revamp
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 18s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 1m13s
Lint & Format (Rust) / lint-format (pull_request) Successful in 2m21s
C++ / build (pull_request) Successful in 4m35s
2026-06-29 22:10:39 +02:00
AramJonghuandzach bc9d8af0fe update to clang-tidy + workflow improvement cpp build & lint check (#130)
### Goal

Idea is to reduce the annoyances and research which clang-tidy rules make sense and actually have benefits in our project.

Also considering immediately resolving most if not all tidy warnings.

### Current Changes

- `.clang-tidy` has changes to follow the guidelines we have been following silently. Some rules were different from how we program.
    - Note that naming is completely removed and won't be checked. In a future PR (where we go over the code to fix linting issues), we can reintroduce them and make sure they fit our codebase consistently.
- tidy rules are not considered errors anymore (stopped compile).
- CI/CD addition to build check and clang-tidy check. Tried to get clang-tidy check to show when failed, but that causes complications in its checks; so now it is simply silent when failing.
- Dockerfile that creates an arch container as cache for the CI/CD, prevents redownload of dependencies each run. Instead, it runs weekly (or optionally a different interval).

### Current complications

- To push the docker container build, it would require this webserver to allow larger filesize pushes (need ~2GB). Current workaround is arguably better where docker build is pushed to runner server.
- Figuring out which clang-tidy rules make sense.

Reviewed-on: #130
Co-authored-by: AramJonghu <aramjonghu@tutamail.com>
Co-committed-by: AramJonghu <aramjonghu@tutamail.com>
2026-06-29 22:10:26 +02:00
zach 01d521b42d Merge branch 'main' into feat/settings-revamp
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 30s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m7s
2026-06-29 19:37:24 +02:00
zach 7d65578a5f proper VERSION var 2026-06-29 19:37:11 +02:00
zach 77ec1c87f8 Merge branch 'main' into feat/settings-revamp
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-29 18:59:24 +02:00
zach f883b3d7fe versioning 2026-06-29 18:57:32 +02:00
zach e685500698 revert certain changes for blobs
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 30s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m14s
2026-06-28 22:49:15 +02:00
zach 04481ae7e1 sidebar menu on top
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-27 00:11:18 +02:00
zach 82a660229b remove network-dev folder
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m7s
2026-06-26 17:25:25 +02:00
zach ce6e314fde chore: regions now manually set, moved to separate file
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-26 17:23:56 +02:00
zach 0b92cbb3b4 add corner fill
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 27s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-25 18:58:21 +02:00
zach 6a15be7b15 sys tray spacing
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m17s
2026-06-25 12:57:11 +02:00
zach 01c63ebead blobpopup positioning and radius changes
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m43s
2026-06-24 23:06:29 +02:00
zach ecfa1115cd wonky fix for cropping rect desync
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 14s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m11s
2026-06-24 20:21:36 +02:00
zach d08b2b7ac6 correct index for subpages
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m7s
2026-06-24 15:42:50 +02:00
zach 4df31d7564 added services page + rename barConfig json property to bar, migration helper function
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-24 15:30:33 +02:00
zach 5c6882d2a8 added panels page
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 28s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-23 23:06:30 +02:00
zach 6a80961b2b added audio and apps page in settings
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-23 22:32:56 +02:00
zach 2366901b24 Merge branch 'main' into feat/settings-revamp
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 18s
Python / lint-format (pull_request) Successful in 29s
Python / test (pull_request) Successful in 55s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m24s
2026-06-23 16:24:01 +02:00
zach ea4d5376f6 initial commit for settings revamp
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 15s
Python / lint-format (pull_request) Successful in 29s
Python / test (pull_request) Successful in 54s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m34s
2026-06-23 16:22:56 +02:00
zach e6f7742bd8 Merge pull request 'updated components' (#122) from feat/updated-components into main
Reviewed-on: #122
Reviewed-by: AramJonghu <2+aramjonghu@noreply.git.zach-dev.cc>
2026-06-23 11:33:20 +02:00
zach a37f6075d1 GPU plugin
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-22 23:13:18 +02:00
zach 80d5f13663 cursor crosshair + outline of pen width
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 14s
Python / test (pull_request) Successful in 32s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m7s
2026-06-22 11:52:11 +02:00
zach 2f3f41cedd fill icons when relevant
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 30s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-06-22 09:27:13 +02:00
zach faad2dbd09 sidebar toggle buttons
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 15s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m7s
2026-06-21 22:31:10 +02:00
zach 164b48ab6a better spacing of action buttons in notification popups
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m14s
2026-06-21 17:44:58 +02:00
zach e55d4aa36b clear and exit buttons on drawing popout + pin button
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m11s
2026-06-21 16:14:36 +02:00
zach 91b4466773 early return in hover handler if drawing
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-06-20 20:57:41 +02:00
zach 7be81e584e GPU caching for better performance after many strokes
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-20 20:55:15 +02:00
zach 8a87f86758 apply curve smoothing to drawn lines
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m10s
2026-06-20 20:09:03 +02:00
zach e619632077 show hover dot instead of cursor while drawing
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m11s
2026-06-20 19:00:58 +02:00
zach 81eb56e1d0 fixes for drawing
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 14s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-20 14:23:15 +02:00
zach a1c148bf70 current color rectangle centered in arc
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 14s
Python / test (pull_request) Successful in 27s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-20 13:37:00 +02:00
zach fc9be754fd preset color buttons rework + curve smoothing
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 30s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-20 13:24:47 +02:00
zach ca1243f9c7 disable interactible elements when drawing
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 25s
Python / test (pull_request) Successful in 32s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m28s
2026-06-19 23:21:01 +02:00
zach 697058146e notif actions rework
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m25s
2026-06-19 22:50:52 +02:00
zach d516dd8be4 fix text input
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 30s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m15s
2026-06-19 14:06:31 +02:00
zach b17bc61d80 remove logging
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 15s
Python / test (pull_request) Successful in 30s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m9s
2026-06-18 22:26:52 +02:00
zach e37332fe6b add right click clear
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m14s
2026-06-18 20:36:28 +02:00
zach c102d0e38f drawing reworked into c++ plugin, moved input handler to Interactions.qml as a PointHandler
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m17s
2026-06-18 13:48:58 +02:00
zach fbadcbb717 sidebar doesn't reduce height when new notifs come in
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m14s
2026-06-17 21:49:52 +02:00
zach 2cb4d9b739 revert sensors process removal
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 32s
Python / lint-format (pull_request) Successful in 38s
Python / test (pull_request) Successful in 49s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m24s
2026-06-17 18:53:23 +02:00
zach a98a7944b9 show notification in fullscreen
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 27s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m11s
2026-06-17 18:44:50 +02:00
zach 5fb137699f visualizer wave graph instead of lines
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 24s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m13s
2026-06-17 13:19:37 +02:00
zach 866373be48 remove accidental log files
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m7s
2026-06-17 12:02:22 +02:00
zach b034400df9 hacky english month and day names while using system locale
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-17 11:59:29 +02:00
zach c0b62dfb6c rounded sparkline graph, fixed network usage accumulation spikes
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-16 13:52:43 +02:00
zach ea82b8cf22 calendar month change animation
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-06-16 10:55:30 +02:00
zach be8b7ae436 config option for min brightness + clipboard config options now in settings
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m5s
2026-06-16 10:15:57 +02:00
zach e3d3daf807 fix function call in wallpaper cropper
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 27s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-16 09:46:36 +02:00
zach 0057830fbc resource popout spacing and larger percent text size
Python / lint-format (pull_request) Successful in 30s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 52s
Python / test (pull_request) Successful in 1m26s
Lint & Format (Rust) / lint-format (pull_request) Successful in 2m20s
2026-06-15 19:27:16 +02:00
zach eafffdcc8f fix slider inset icons initial position
Python / lint-format (pull_request) Failing after 2m25s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 3m14s
Python / test (pull_request) Successful in 3m55s
Lint & Format (Rust) / lint-format (pull_request) Successful in 4m49s
2026-06-15 19:20:02 +02:00
zach 578a10c950 clipboard preview leading tab trimming while preserving indentation + proper width calculations
Python / lint-format (pull_request) Successful in 27s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 30s
Python / test (pull_request) Successful in 32s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-15 18:23:56 +02:00
zach 2034f3a8db fixed applying crop being overwritten by multiple write calls, added wallpaper fade anim
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 14s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-15 15:52:55 +02:00
zach 7e38f3f428 slight changes to clipboard history preview and item delegate
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 32s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m10s
2026-06-14 23:33:12 +02:00
zach 2a50732bc2 resources popout refresh + new components & reskinned old components
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m12s
2026-06-14 23:09:10 +02:00
299 changed files with 16695 additions and 14172 deletions
+3 -3
View File
@@ -1,15 +1,15 @@
--- ---
BasedOnStyle: LLVM BasedOnStyle: LLVM
AccessModifierOffset: 0 AccessModifierOffset: 0
AlignAfterOpenBracket: Align AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false AlignConsecutiveDeclarations: false
AlignOperands: Align AlignOperands: Align
AllowAllParametersOfDeclarationOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true
AllowShortFunctionsOnASingleLine: Inline AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never AllowShortIfStatementsOnASingleLine: WithoutElse
AlwaysBreakBeforeMultilineStrings: false AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: false BinPackArguments: false
BinPackParameters: false BinPackParameters: false
BreakBeforeBraces: Attach BreakBeforeBraces: Attach
+15 -19
View File
@@ -2,29 +2,25 @@
Checks: > Checks: >
-*, -*,
bugprone-*, bugprone-*,
-bugprone-throwing-static-initialization,
-bugprone-invalid-enum-default-initialization,
-bugprone-suspicious-include,
-bugprone-branch-clone,
-bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions,
-bugprone-implicit-widening-of-multiplication-result,
clang-analyzer-*, clang-analyzer-*,
modernize-*, cppcoreguidelines-init-variables,
-modernize-use-trailing-return-type, misc-no-recursion,
performance-*, concurrency-mt-unsafe,
readability-braces-around-statements,
readability-else-after-return, readability-else-after-return,
readability-identifier-naming, readability-make-member-function-const,
readability-redundant-*, readability-redundant-*,
-readability-redundant-inline-specifier,
readability-simplify-*, readability-simplify-*,
CheckOptions: modernize-use-override,
readability-identifier-naming.ClassCase: CamelCase modernize-use-nullptr,
readability-identifier-naming.EnumCase: CamelCase modernize-deprecated-headers,
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.MemberCase: camelBack
readability-identifier-naming.MemberPrefix: m_
readability-identifier-naming.MethodCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.ParameterCase: camelBack
readability-identifier-naming.PrivateMemberPrefix: m_
readability-identifier-naming.StaticConstantCase: UPPER_CASE
readability-identifier-naming.StaticConstantPrefix: k
readability-identifier-naming.VariableCase: camelBack
WarningsAsErrors: "*"
HeaderFilterRegex: ".*" HeaderFilterRegex: ".*"
FormatStyle: file FormatStyle: file
+30
View File
@@ -0,0 +1,30 @@
name: Rebuild CI Image
on:
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
jobs:
build:
runs-on: alpine
container:
image: node:26-alpine
env:
IMAGE: git.aramjonghu.nl/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Docker CLI
run: apk add --no-cache docker-cli
- name: Login to registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.nl --username aramjonghu --password-stdin
- name: Build image
run: docker build -t "$IMAGE" -f ci/Dockerfile .
- name: Push image
run: docker push "$IMAGE"
+29
View File
@@ -0,0 +1,29 @@
name: C++
on:
pull_request:
jobs:
build:
runs-on: alpine
container:
image: git.aramjonghu.nl/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
- name: Build
run: ninja -C build
- name: clang-tidy
run: |
sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json
set -o pipefail
run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \
grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error\|warnings generated\.\|^Use -header-filter'
+37 -8
View File
@@ -1,6 +1,35 @@
cmake_minimum_required(VERSION 3.19) cmake_minimum_required(VERSION 3.19)
project(ZShell) if(NOT DEFINED VERSION)
execute_process(
COMMAND git describe --tags --abbrev=0
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_LAST_TAG
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if (GIT_LAST_TAG)
string(REGEX REPLACE "^v" "" GIT_LAST_TAG "${GIT_LAST_TAG}")
string(REPLACE "." ";" VERSION_LIST "${GIT_LAST_TAG}")
list(GET VERSION_LIST 0 VERSION_MAJOR)
list(GET VERSION_LIST 1 VERSION_MINOR)
execute_process(
COMMAND git rev-list v${VERSION_MAJOR}.${VERSION_MINOR}.0..HEAD --count
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE VERSION_PATCH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
endif()
endif()
set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
project(ZShell VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
message(STATUS "ZShell version: ${VERSION}")
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -15,11 +44,11 @@ set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell confi
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir") set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
add_compile_options( add_compile_options(
-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wold-style-cast -Wnull-dereference -Wdouble-promotion -Wold-style-cast -Wnull-dereference -Wdouble-promotion
-Wformat=2 -Wfloat-equal -Woverloaded-virtual -Wformat=2 -Wfloat-equal -Woverloaded-virtual
-Wsign-conversion -Wredundant-decls -Wswitch -Wsign-conversion -Wredundant-decls -Wswitch
-Wunreachable-code -Wunreachable-code
) )
@@ -30,8 +59,8 @@ endif()
if("shell" IN_LIST ENABLE_MODULES) if("shell" IN_LIST ENABLE_MODULES)
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths) foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}") install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
endforeach() endforeach()
# Disable watching for changes # Disable watching for changes
file(READ shell.qml SHELL_QML) file(READ shell.qml SHELL_QML)
-3
View File
@@ -12,11 +12,8 @@ Slider {
readonly property bool isVertical: orientation === Qt.Vertical readonly property bool isVertical: orientation === Qt.Vertical
property real multiplier: 100 property real multiplier: 100
property real oldValue property real oldValue
// Wrapper components can inject their own track visuals here.
property Component trackContent property Component trackContent
// Keep current behavior for existing usages.
orientation: Qt.Vertical orientation: Qt.Vertical
background: CustomRect { background: CustomRect {
+6 -1
View File
@@ -18,6 +18,11 @@ CustomRect {
property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledOnColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) property color disabledOnColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
property bool fillWidth property bool fillWidth
property font font: ({
family: Appearance.font.family.sans,
pointSize: Appearance.font.size.larger,
bold: false
})
property real horizontalPadding: padding property real horizontalPadding: padding
readonly property alias hovered: stateLayer.containsMouse readonly property alias hovered: stateLayer.containsMouse
required implicitHeight required implicitHeight
@@ -71,7 +76,7 @@ CustomRect {
id: stateLayer id: stateLayer
color: root.internalChecked ? root.activeOnColor : root.inactiveOnColor color: root.internalChecked ? root.activeOnColor : root.inactiveOnColor
enabled: enabled enabled: root.enabled
onClicked: { onClicked: {
if (root.isToggle) if (root.isToggle)
+4 -5
View File
@@ -17,8 +17,8 @@ BusyIndicator {
} }
property int animState property int animState
property color bgColour: DynamicColors.palette.m3secondaryContainer property color bgColor: DynamicColors.palette.m3secondaryContainer
property color fgColour: DynamicColors.palette.m3primary property color fgColor: DynamicColors.palette.m3primary
property real implicitSize: Appearance.font.size.normal * 3 property real implicitSize: Appearance.font.size.normal * 3
property real internalStrokeWidth: strokeWidth property real internalStrokeWidth: strokeWidth
readonly property alias progress: manager.progress readonly property alias progress: manager.progress
@@ -31,8 +31,8 @@ BusyIndicator {
contentItem: CircularProgress { contentItem: CircularProgress {
anchors.fill: parent anchors.fill: parent
bgColour: root.bgColour bgColor: root.bgColor
fgColour: root.fgColour fgColor: root.fgColor
padding: root.padding padding: root.padding
rotation: manager.rotation rotation: manager.rotation
startAngle: manager.startFraction * 360 startAngle: manager.startFraction * 360
@@ -73,7 +73,6 @@ BusyIndicator {
CircularIndicatorManager { CircularIndicatorManager {
id: manager id: manager
} }
NumberAnimation { NumberAnimation {
+89 -39
View File
@@ -1,66 +1,116 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import ZShell.Components
import qs.Config import qs.Config
Shape { Item {
id: root id: root
readonly property real arcRadius: (size - padding - strokeWidth) / 2 readonly property real arcRadius: (size - padding - strokeWidth * (1 + waveAmplitude * 2)) / 2
property color bgColour: DynamicColors.palette.m3secondaryContainer property color bgColor: DynamicColors.palette.m3secondaryContainer
property color fgColour: DynamicColors.palette.m3primary property real clampedVal: Math.max(1 / 360, Math.min(1, isNaN(value) ? 0 : value))
readonly property real dotAngleRad: (startAngle + sweepAngle - gapAngle * (sweepAngle < 360 ? 0 : 1)) * Math.PI / 180
property color fgColor: DynamicColors.palette.m3primary
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI) readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
property alias hasEndIndicator: dot.active
property real implicitSize
property int padding: 0 property int padding: 0
readonly property real size: Math.min(width, height) readonly property real size: Math.min(width, height)
property int spacing: Appearance.spacing.small property int spacing: Appearance.spacing.small
property int startAngle: -90 property int startAngle: -90
property int strokeWidth: Appearance.padding.smaller property int strokeWidth: Appearance.padding.small
readonly property real vValue: value || 1 / 360 property int sweepAngle: 360
readonly property real thickness: strokeWidth * (1 + waveAmplitude) * 2
property real value property real value
property alias waveAmplitude: wave.amplitudeMultiplier
property alias waveDuration: waveProgAnim.duration
property alias waveFrequency: wave.frequency
property bool wavePaused
property bool wavy: false
asynchronous: true implicitHeight: implicitSize
preferredRendererType: Shape.CurveRenderer implicitWidth: implicitSize
ShapePath { Shape {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap asynchronous: true
fillColor: "transparent" opacity: Math.min(1, remainingArc.sweepAngle)
strokeColor: root.bgColour preferredRendererType: Shape.CurveRenderer
strokeWidth: root.strokeWidth
Behavior on strokeColor { ShapePath {
CAnim { capStyle: ShapePath.RoundCap
duration: Appearance.anim.durations.large fillColor: "transparent"
strokeColor: root.bgColor
strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.strokeWidth
Behavior on strokeColor {
CAnim {
}
} }
}
PathAngleArc { PathAngleArc {
centerX: root.size / 2 id: remainingArc
centerY: root.size / 2
radiusX: root.arcRadius centerX: root.size / 2
radiusY: root.arcRadius centerY: root.size / 2
startAngle: root.startAngle + 360 * root.vValue + root.gapAngle radiusX: root.arcRadius
sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2) radiusY: root.arcRadius
startAngle: root.startAngle + root.clampedVal * root.sweepAngle + root.gapAngle
sweepAngle: Math.max(1 / 360, root.sweepAngle * (1 - root.clampedVal) - root.gapAngle * (root.sweepAngle < 360 ? 1 : 2))
}
} }
} }
ShapePath { WavyLine {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap id: wave
fillColor: "transparent"
strokeColor: root.fgColour
strokeWidth: root.strokeWidth
Behavior on strokeColor { amplitudeMultiplier: root.wavy ? 0.5 : 0
CAnim { anchors.fill: parent
duration: Appearance.anim.durations.large anchors.margins: -lineWidth * amplitudeMultiplier
color: root.fgColor
frequency: 8
fullAngle: root.sweepAngle
lineWidth: root.strokeWidth
pathType: WavyLine.Arc
radius: root.arcRadius
startAngle: root.startAngle
value: root.clampedVal
Behavior on amplitudeMultiplier {
Anim {
type: Anim.DefaultEffects
} }
} }
Behavior on color {
CAnim {
}
}
Anim on waveProgress {
id: waveProgAnim
PathAngleArc { duration: 2000
centerX: root.size / 2 easing.type: Easing.Linear
centerY: root.size / 2 from: 0
radiusX: root.arcRadius loops: Animation.Infinite
radiusY: root.arcRadius paused: root.wavePaused || wave.amplitudeMultiplier === 0
startAngle: root.startAngle running: true
sweepAngle: 360 * root.vValue to: 1
}
}
Loader {
id: dot
x: root.size / 2 + root.arcRadius * Math.cos(root.dotAngleRad) - width / 2
y: root.size / 2 + root.arcRadius * Math.sin(root.dotAngleRad) - height / 2
sourceComponent: CustomRect {
color: root.fgColor
implicitHeight: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth)
implicitWidth: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth)
opacity: Math.min(1, remainingArc.sweepAngle)
radius: Appearance.rounding.full
} }
} }
} }
+28 -5
View File
@@ -45,10 +45,10 @@ Item {
} }
function syncFromPenColor() { function syncFromPenColor() {
if (!drawing) if (!drawing.drawingState)
return; return;
const c = drawing.penColor; const c = drawing.drawingState.penColor;
if (c.hsvSaturation > 0) { if (c.hsvSaturation > 0) {
currentHue = c.hsvHue; currentHue = c.hsvHue;
@@ -85,12 +85,20 @@ Item {
currentHue = relative / arcSweep; currentHue = relative / arcSweep;
lastChromaticHue = currentHue; lastChromaticHue = currentHue;
drawing.penColor = Qt.hsva(currentHue, drawing.penColor.hsvSaturation, drawing.penColor.hsvValue, drawing.penColor.a); drawing.drawingState.penColor = Qt.hsva(currentHue, drawing.drawingState.penColor.hsvSaturation, drawing.drawingState.penColor.hsvValue, drawing.drawingState.penColor.a);
} }
implicitHeight: 180 implicitHeight: 180
implicitWidth: 220 implicitWidth: 220
Behavior on currentHue {
enabled: !root.dragActive
Anim {
type: Anim.StandardLarge
}
}
Component.onCompleted: syncFromPenColor() Component.onCompleted: syncFromPenColor()
onCurrentHueChanged: canvas.requestPaint() onCurrentHueChanged: canvas.requestPaint()
onDrawingChanged: syncFromPenColor() onDrawingChanged: syncFromPenColor()
@@ -103,7 +111,7 @@ Item {
root.syncFromPenColor(); root.syncFromPenColor();
} }
target: root.drawing target: root.drawing.drawingState
} }
Canvas { Canvas {
@@ -141,6 +149,21 @@ Item {
} }
} }
CustomRect {
anchors.centerIn: parent
color: root.drawing?.drawingState.penColor
implicitHeight: implicitWidth
implicitWidth: canvas.height - root.handleSize - Appearance.padding.extraLarge * 2
radius: Appearance.rounding.full
Behavior on color {
enabled: false
CAnim {
}
}
}
Item { Item {
id: handle id: handle
@@ -174,7 +197,7 @@ Item {
Rectangle { Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
color: root.drawing ? root.drawing.penColor : Qt.hsla(root.currentHue, 1.0, 0.5, 1.0) color: Qt.hsla(root.currentHue, 1.0, 0.5, 1.0)
height: width height: width
radius: width / 2 radius: width / 2
width: parent.width - 12 width: parent.width - 12
+20
View File
@@ -1,13 +1,33 @@
import QtQuick import QtQuick
import qs.Helpers
Flickable { Flickable {
id: root id: root
property bool doneFakeFlick
interactive: !Visibilities.getForActive().isDrawing
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
onRunningChanged: {
if (!running && !root.doneFakeFlick) {
root.doneFakeFlick = true;
root.flick(1, 1);
root.flick(-1, -1);
Qt.callLater(() => root.cancelFlick());
}
}
Anim { Anim {
properties: "x,y" properties: "x,y"
} }
} }
Timer {
interval: 10
running: root.doneFakeFlick
onTriggered: root.doneFakeFlick = false
}
} }
+2
View File
@@ -1,10 +1,12 @@
import QtQuick import QtQuick
import qs.Helpers
ListView { ListView {
id: root id: root
property bool doneFakeFlick property bool doneFakeFlick
interactive: !Visibilities.getForActive().isDrawing
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
+237
View File
@@ -0,0 +1,237 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Templates
import ZShell
import ZShell.Components
import ZShell.Internal
import qs.Config
ProgressBar {
id: root
enum IndeterminateAnimState {
Running,
Completing,
Stopped
}
property color bgColor: DynamicColors.palette.m3secondaryContainer
property color fgColor: DynamicColors.palette.m3primary
property int indeterminateAnimState: CustomProgressBar.Stopped
property real waveAmplitude: 0.5
property int waveDuration: 1000
property int waveFrequency: 6
property bool wavePaused
property bool wavy
function toBounds(startFrac: real, endFrac: real, gapSize: real): point {
startFrac = ZUtils.clamp(startFrac, 0, 1);
endFrac = ZUtils.clamp(endFrac, 0, 1);
// Ramp down gap size
const GAP_RAMP_DOWN_THRESHOLD = 0.01;
gapSize += height / 2;
const startGapSize = (gapSize * ZUtils.clamp(startFrac, 0, GAP_RAMP_DOWN_THRESHOLD) / GAP_RAMP_DOWN_THRESHOLD);
const endGapSize = (gapSize * (1 - ZUtils.clamp(endFrac, 1 - GAP_RAMP_DOWN_THRESHOLD, 1)) / GAP_RAMP_DOWN_THRESHOLD);
const start = width * startFrac + startGapSize;
const end = width * endFrac - endGapSize;
return start >= end ? Qt.point(0, 0) : Qt.point(start, end);
}
function updateIAnimState(): void {
if (indeterminate) {
manager.completeEndProgress = 0;
indeterminateAnimState = CustomProgressBar.Running;
} else if (indeterminateAnimState === CustomProgressBar.Running) {
indeterminateAnimState = CustomProgressBar.Completing;
}
}
implicitHeight: 4
implicitWidth: 200
contentItem: Loader {
anchors.fill: parent
asynchronous: true
sourceComponent: root.indeterminate || root.indeterminateAnimState !== CustomProgressBar.Stopped ? indeterminateComp : determinateComp
}
Component.onCompleted: updateIAnimState()
onIndeterminateChanged: updateIAnimState()
LinearIndicatorManager {
id: manager
gap: Appearance.spacing.extraSmall
Anim on completeEndProgress {
duration: manager.completeEndDuration
running: root.indeterminateAnimState === CustomProgressBar.Completing
to: 1
onFinished: {
if (root.indeterminateAnimState === CustomProgressBar.Completing)
root.indeterminateAnimState = CustomProgressBar.Stopped;
}
}
Anim on progress {
duration: manager.duration
easing.type: Easing.Linear
from: 0
loops: Animation.Infinite
running: root.indeterminateAnimState !== CustomProgressBar.Stopped
to: 1
}
}
Component {
id: determinateComp
Item {
Line {
id: remaining
anchors.right: parent.right
implicitWidth: parent.width - wave.implicitWidth - Appearance.spacing.extraSmall
}
Line {
property real implicitSize
anchors.right: parent.right
anchors.rightMargin: (parent.height - implicitHeight) / 2
anchors.verticalCenter: parent.verticalCenter
color: root.fgColor
implicitHeight: implicitSize
implicitWidth: implicitSize
radius: Appearance.rounding.full
Behavior on implicitSize {
Anim {
type: Anim.FastSpatial
}
}
Component.onCompleted: implicitSize = Qt.binding(() => parent.width - wave.implicitWidth < parent.height ? parent.height : 4)
}
Wave {
id: wave
anchors.left: parent.left
Behavior on implicitWidth {
Anim {
}
}
Component.onCompleted: implicitWidth = Qt.binding(() => parent.width * root.visualPosition)
}
}
}
Component {
id: indeterminateComp
Item {
id: content
Line {
bounds: {
const i = manager.activeIndicators[0]; // qmllint disable unresolved-type
return i ? root.toBounds(0, i.startFraction, i.gapSize / 2) : Qt.point(0, 0);
}
}
Line {
bounds: {
const i = manager.activeIndicators[manager.activeIndicators.length - 1]; // qmllint disable unresolved-type
return i ? root.toBounds(i.endFraction, 1, i.gapSize / 2) : Qt.point(0, 0);
}
}
Instantiator {
model: Math.max(manager.activeIndicators.length, 1) - 1 // qmllint disable unresolved-type
delegate: Line {
readonly property LinearIndicatorSegment cur: manager.activeIndicators[index] // qmllint disable unresolved-type
required property int index
readonly property LinearIndicatorSegment next: manager.activeIndicators[index + 1 % manager.activeIndicators.length] // qmllint disable unresolved-type
bounds: root.toBounds(cur.endFraction, next.startFraction, cur.gapSize / 2)
}
onObjectAdded: (_, obj) => content.data.push(obj)
onObjectRemoved: (_, obj) => {
const idx = content.data.indexOf(obj);
if (idx !== -1)
content.data.splice(idx, 1);
}
}
Instantiator {
model: manager.activeIndicators // qmllint disable unresolved-type
delegate: Wave {
readonly property point bounds: root.toBounds(modelData.startFraction, modelData.endFraction, modelData.gapSize / 2)
required property LinearIndicatorSegment modelData
color: root.fgColor
implicitWidth: bounds.y - bounds.x
x: bounds.x
}
onObjectAdded: (_, obj) => content.data.push(obj)
onObjectRemoved: (_, obj) => {
const idx = content.data.indexOf(obj);
if (idx !== -1)
content.data.splice(idx, 1);
}
}
}
}
component Line: CustomRect {
property point bounds
anchors.verticalCenter: parent.verticalCenter
color: root.bgColor
implicitHeight: parent.height
implicitWidth: bounds.y - bounds.x
radius: Appearance.rounding.full
x: bounds.x
}
component Wave: WavyLine {
id: wave
amplitudeMultiplier: root.wavy ? root.waveAmplitude : 0
anchors.verticalCenter: parent.verticalCenter
color: root.fgColor
frequency: root.waveFrequency
fullLength: parent.width
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
lineWidth: parent.height
startX: x
Behavior on amplitudeMultiplier {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on color {
CAnim {
}
}
Anim on waveProgress {
duration: root.waveDuration
easing.type: Easing.Linear
from: 0
loops: Animation.Infinite
paused: wave.amplitudeMultiplier === 0 || root.wavePaused
running: true
to: 1
}
}
}
+5 -1
View File
@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Helpers
import qs.Config import qs.Config
ScrollBar { ScrollBar {
@@ -15,6 +16,7 @@ ScrollBar {
property bool shouldBeActive property bool shouldBeActive
readonly property real travelScale: root.rawTravel > 0 ? root.effectiveTravel / root.rawTravel : 0 readonly property real travelScale: root.rawTravel > 0 ? root.effectiveTravel / root.rawTravel : 0
enabled: !Visibilities.getForActive().isDrawing
implicitWidth: Appearance.padding.extraSmall * 2 implicitWidth: Appearance.padding.extraSmall * 2
contentItem: Item { contentItem: Item {
@@ -59,11 +61,13 @@ ScrollBar {
implicitHeight: root.height * root.effectiveSize implicitHeight: root.height * root.effectiveSize
implicitWidth: fullMouse.pressed || fullMouse.containsMouse ? Appearance.padding.extraSmall * 2 : Appearance.padding.extraSmall implicitWidth: fullMouse.pressed || fullMouse.containsMouse ? Appearance.padding.extraSmall * 2 : Appearance.padding.extraSmall
opacity: { opacity: {
if (!root.enabled)
return 0;
if (root.size === 1) if (root.size === 1)
return 0; return 0;
if (fullMouse.pressed) if (fullMouse.pressed)
return 1; return 1;
if (mouse.containsMouse) if (fullMouse.containsMouse)
return 0.8; return 0.8;
if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive) if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive)
return 0.6; return 0.6;
+4 -2
View File
@@ -91,7 +91,7 @@ Slider {
MaterialIcon { MaterialIcon {
id: inset id: inset
readonly property bool attached: root.pos < 0.1 readonly property bool attached: root.width ? (root.width - handle.implicitWidth - handle.anchors.leftMargin) * root.pos < inset.paintedWidth + Appearance.spacing.extraSmall * 2 : false
property real dockT: attached ? 1 : 0 property real dockT: attached ? 1 : 0
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@@ -157,7 +157,9 @@ Slider {
} }
} }
Component.onCompleted: filledWidth = Qt.binding(() => (width - handle.implicitWidth - handle.anchors.leftMargin) * pos) Component.onCompleted: {
filledWidth = Qt.binding(() => (width - handle.implicitWidth - handle.anchors.leftMargin) * pos);
}
Binding { Binding {
id: posBinding id: posBinding
+97 -133
View File
@@ -1,168 +1,132 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Templates
import qs.Config import qs.Config
RowLayout { DoubleSpinBox {
id: root id: root
property string displayText: root.value.toString() property int cLayer: 1
property bool isEditing: false property int repeatDecay: 50
property real max: Infinity property int repeatRate: 400
property real min: -Infinity
property alias repeatRate: timer.interval
property real step: 1
property real value
signal valueModified(value: real) function decrease(): void {
let newValue = Math.max(from, value - stepSize);
const decimals = stepSize < 1 ? Math.max(1, Math.ceil(-Math.log10(stepSize))) : 0;
newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
value = newValue;
valueModified();
}
function increase(): void {
let newValue = Math.min(to, value + stepSize);
const decimals = stepSize < 1 ? Math.max(1, Math.ceil(-Math.log10(stepSize))) : 0;
newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
value = newValue;
valueModified();
}
decimals: stepSize < 1 ? Math.max(1, Math.ceil(-Math.log10(stepSize))) : 0
editable: true
implicitHeight: Math.max(up.indicator.implicitHeight, down.indicator.implicitHeight, contentItem.implicitHeight) + topPadding + bottomPadding
implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding
leftPadding: up.indicator.implicitWidth + Appearance.spacing.extraSmall / 2
rightPadding: down.indicator.implicitWidth + Appearance.spacing.extraSmall / 2
spacing: Appearance.spacing.small spacing: Appearance.spacing.small
onValueChanged: { contentItem: TextFieldBase {
if (!root.isEditing) { horizontalAlignment: TextField.AlignHCenter
root.displayText = root.value.toString(); implicitWidth: 65
}
}
CustomTextField {
id: textField
color: root.enabled ? DynamicColors.palette.m3onSurface : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5)
implicitHeight: upButton.implicitHeight
inputMethodHints: Qt.ImhFormattedNumbersOnly inputMethodHints: Qt.ImhFormattedNumbersOnly
leftPadding: Appearance.padding.normal leftPadding: Appearance.padding.larger
padding: Appearance.padding.small readOnly: !root.editable
rightPadding: Appearance.padding.normal rightPadding: Appearance.padding.larger
text: root.isEditing ? text : root.displayText text: root.textFromValue(root.value, root.locale)
validator: root.validator
background: CustomRect { background: CustomRect {
color: root.enabled ? DynamicColors.tPalette.m3surfaceContainerHigh : DynamicColors.tPalette.m3surfaceContainerLow color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer)
implicitWidth: 100 radius: Appearance.rounding.extraSmall
radius: Appearance.rounding.full
}
validator: DoubleValidator {
bottom: root.min
decimals: root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0
top: root.max
}
onAccepted: {
const numValue = parseFloat(text);
if (!isNaN(numValue)) {
const clampedValue = Math.max(root.min, Math.min(root.max, numValue));
root.value = clampedValue;
root.displayText = clampedValue.toString();
root.valueModified(clampedValue);
} else {
text = root.displayText;
}
root.isEditing = false;
}
onActiveFocusChanged: {
if (activeFocus) {
root.isEditing = true;
} else {
root.isEditing = false;
root.displayText = root.value.toString();
}
}
onEditingFinished: {
if (text !== root.displayText) {
const numValue = parseFloat(text);
if (!isNaN(numValue)) {
const clampedValue = Math.max(root.min, Math.min(root.max, numValue));
root.value = clampedValue;
root.displayText = clampedValue.toString();
root.valueModified(clampedValue);
} else {
text = root.displayText;
}
}
root.isEditing = false;
} }
} }
down.indicator: IconButton {
id: downButton
CustomRect { bottomRightRadius: pressed ? Appearance.rounding.small : Appearance.rounding.extraSmall
color: enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) : disabledColor
disabledColor: Qt.alpha(DynamicColors.palette.m3surfaceContainerHighest, 0.4)
icon: "remove"
isRound: true
label.anchors.horizontalCenterOffset: pressed ? 0 : 2
padding: Appearance.padding.extraSmall
topRightRadius: pressed ? Appearance.rounding.small : Appearance.rounding.extraSmall
type: IconButton.Text
Behavior on bottomRightRadius {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on label.anchors.horizontalCenterOffset {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on topRightRadius {
Anim {
type: Anim.DefaultEffects
}
}
}
up.indicator: IconButton {
id: upButton id: upButton
color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) anchors.right: parent.right
implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2 bottomLeftRadius: pressed ? Appearance.rounding.small : Appearance.rounding.extraSmall
implicitWidth: implicitHeight color: enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) : disabledColor
radius: Appearance.rounding.full disabledColor: Qt.alpha(DynamicColors.palette.m3surfaceContainerHighest, 0.4)
icon: "add"
isRound: true
label.anchors.horizontalCenterOffset: pressed ? 0 : -2
padding: Appearance.padding.extraSmall
topLeftRadius: pressed ? Appearance.rounding.small : Appearance.rounding.extraSmall
type: IconButton.Text
StateLayer { Behavior on bottomLeftRadius {
id: upState Anim {
type: Anim.DefaultEffects
color: DynamicColors.palette.m3onPrimary
onClicked: {
let newValue = Math.min(root.max, root.value + root.step);
// Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
root.value = newValue;
root.displayText = newValue.toString();
root.valueModified(newValue);
} }
onPressAndHold: timer.start()
onReleased: timer.stop()
} }
Behavior on label.anchors.horizontalCenterOffset {
MaterialIcon { Anim {
id: upIcon type: Anim.DefaultEffects
anchors.centerIn: parent
color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5)
text: "keyboard_arrow_up"
}
}
CustomRect {
color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1)
implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
StateLayer {
id: downState
color: DynamicColors.palette.m3onPrimary
onClicked: {
let newValue = Math.max(root.min, root.value - root.step);
// Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
root.value = newValue;
root.displayText = newValue.toString();
root.valueModified(newValue);
} }
onPressAndHold: timer.start()
onReleased: timer.stop()
} }
Behavior on topLeftRadius {
MaterialIcon { Anim {
id: downIcon type: Anim.DefaultEffects
}
anchors.centerIn: parent
color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5)
text: "keyboard_arrow_down"
} }
} }
Timer { Timer {
id: timer id: timer
interval: 100 interval: root.repeatRate
repeat: true repeat: true
running: upButton.pressed || downButton.pressed
triggeredOnStart: true triggeredOnStart: true
onRunningChanged: {
if (!running)
interval = root.repeatRate;
}
onTriggered: { onTriggered: {
if (upState.pressed) if (upButton.pressed)
upState.onClicked(); root.increase();
else if (downState.pressed) else if (downButton.pressed)
downState.onClicked(); root.decrease();
if (interval > root.repeatDecay)
interval -= root.repeatDecay;
} }
} }
} }
+9 -4
View File
@@ -27,8 +27,7 @@ Text {
enabled: root.animate enabled: root.animate
SequentialAnimation { SequentialAnimation {
Anim { TAnim {
property: root.animateProp
target: root target: root
to: root.animateFrom to: root.animateFrom
type: Anim.FastEffects type: Anim.FastEffects
@@ -37,12 +36,18 @@ Text {
PropertyAction { PropertyAction {
} }
Anim { TAnim {
property: root.animateProp
target: root target: root
to: root.animateTo to: root.animateTo
type: Anim.DefaultEffects type: Anim.DefaultEffects
} }
} }
} }
component TAnim: Anim {
duration: root.animateDuration / 2
properties: root.animateProp.split(",").length > 1 ? root.animateProp : ""
property: root.animateProp.split(",").length === 1 ? root.animateProp : ""
target: root
}
} }
+267 -50
View File
@@ -1,78 +1,295 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Shapes
import qs.Config import qs.Config
TextField { TextFieldBase {
id: root id: root
property int cursorHeight: root.height enum TextFieldType {
Outlined,
Filled
}
background: null readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius)
color: DynamicColors.palette.m3onSurface readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText
cursorVisible: !readOnly property bool emptyIsValid: true
font.family: Appearance.font.family.sans property string errorText
font.pointSize: Appearance.font.size.smaller readonly property int filledOffset: type === CustomTextField.Filled ? Appearance.spacing.small : 0
placeholderTextColor: DynamicColors.palette.m3outline readonly property int horizontalPadding: Appearance.padding.large
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering property bool isError
property string leadingIcon
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
readonly property real outlineGap: placeholder.width * root.smallFontScale + Appearance.spacing.extraSmall * 2
property real outlineGapScale: activeFocus || text ? 1 : 0
property int radius: Appearance.rounding.small
readonly property real smallFontScale: smallFontSize / font.pointSize
property int smallFontSize: Appearance.font.size.small
property string supportingText
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Appearance.spacing.extraSmall : 0
property string trailingIcon
readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
property int type: CustomTextField.Outlined
readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
property var validate // Regex or function
Behavior on color { bottomPadding: Appearance.padding.large + supportingTextOffset - filledOffset
CAnim { leftPadding: horizontalPadding + leadingOffset
rightPadding: horizontalPadding + trailingOffset
topPadding: Appearance.padding.large + filledOffset
background: Loader {
anchors.bottomMargin: root.supportingTextOffset
anchors.fill: parent
sourceComponent: root.type === CustomTextField.Filled ? filledComp : outlineComp
StateLayer {
id: stateLayer
cursorShape: Qt.IBeamCursor
enabled: !root.activeFocus
manualPressOverride: tapHandler.pressed
radius: root.type === CustomTextField.Outlined ? root.clampedRadius : 0
topLeftRadius: root.clampedRadius
topRightRadius: root.clampedRadius
onClicked: root.focus = true
} }
} }
cursorDelegate: CustomRect { Behavior on outlineGapScale {
id: cursor Anim {
type: Anim.DefaultEffects
property bool disableBlink
color: DynamicColors.palette.m3primary
height: root.cursorHeight
implicitWidth: 2
radius: Appearance.rounding.normal
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
} }
}
Connections { onEditingFinished: {
function onCursorPositionChanged(): void { if (!valid)
if (root.activeFocus && root.cursorVisible) { isError = true;
cursor.opacity = 1; }
cursor.disableBlink = true; onPressed: {
enableBlink.restart(); if (stateLayer.enabled)
stateLayer.press(stateLayer.mouseX, stateLayer.mouseY);
}
onTextEdited: {
if (isError)
isError = false;
}
Item {
id: contentWrapper
anchors.bottomMargin: root.supportingTextOffset
anchors.fill: parent
CustomText {
id: placeholder
anchors.left: parent.left
anchors.leftMargin: root.leftPadding
anchors.topMargin: Appearance.padding.extraSmall
anchors.verticalCenter: parent.verticalCenter
color: root.isError ? DynamicColors.palette.m3error : (root.activeFocus ? DynamicColors.palette.m3primary : root.text ? DynamicColors.palette.m3outline : root.placeholderTextColor)
font.family: root.font.family
font.pointSize: root.font.pointSize
font.variableAxes: root.font.variableAxes
font.weight: root.font.weight
renderType: Text.QtRendering
text: root.placeholderText
states: [
State {
name: "smallOutlined"
when: root.type === CustomTextField.Outlined && (root.activeFocus || root.text)
PropertyChanges {
placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding + -Appearance.spacing.extraSmall
placeholder.scale: root.smallFontScale
}
AnchorChanges {
anchors.verticalCenter: contentWrapper.top
target: placeholder
}
},
State {
name: "smallFilled"
when: root.type === CustomTextField.Filled && (root.activeFocus || root.text)
PropertyChanges {
placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding + root.leadingOffset
placeholder.scale: root.smallFontScale
}
AnchorChanges {
anchors.top: contentWrapper.top
anchors.verticalCenter: undefined
target: placeholder
}
}
]
transitions: Transition {
Anim {
properties: "scale,leftMargin"
type: Anim.DefaultEffects
}
AnchorAnim {
duration: Appearance.anim.durations.expressiveEffects
easing: Appearance.anim.curves.expressiveDefaultEffects
} }
} }
target: root
} }
Timer { Loader {
id: enableBlink id: leadingIconLoader
interval: 100 active: root.leadingIcon
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.larger
anchors.verticalCenter: parent.verticalCenter
onTriggered: cursor.disableBlink = false sourceComponent: MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
text: root.leadingIcon
}
} }
Timer { Loader {
interval: 500 id: trailingIconLoader
repeat: true
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
triggeredOnStart: true
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1 active: root.trailingIcon
} anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
anchors.verticalCenter: parent.verticalCenter
Binding { sourceComponent: MaterialIcon {
cursor.opacity: 0 color: DynamicColors.palette.m3onSurfaceVariant
when: !root.activeFocus || !root.cursorVisible font.pointSize: Appearance.font.size.large
text: root.trailingIcon
}
} }
} }
Behavior on placeholderTextColor {
CAnim { Loader {
id: supportingTextLoader
active: root.effectiveSupportingText
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: root.horizontalPadding
sourceComponent: CustomText {
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: root.effectiveSupportingText
}
}
TapHandler {
id: tapHandler
}
Component {
id: outlineComp
Shape {
id: bg
asynchronous: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: root.horizontalPadding - root.clampedRadius + root.outlineGap * (1 - root.outlineGapScale) / 2 + root.outlineGap * root.outlineGapScale
strokeColor: root.isError ? DynamicColors.palette.m3error : (root.activeFocus ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline)
strokeWidth: root.activeFocus ? 2 : 1
Behavior on strokeColor {
CAnim {
}
}
Behavior on strokeWidth {
Anim {
}
}
PathLine {
x: bg.width - root.clampedRadius
}
PathArc {
radiusX: root.clampedRadius
radiusY: root.clampedRadius
x: bg.width
y: root.clampedRadius
}
PathLine {
x: bg.width
y: bg.height - root.clampedRadius
}
PathArc {
radiusX: root.clampedRadius
radiusY: root.clampedRadius
x: bg.width - root.clampedRadius
y: bg.height
}
PathLine {
x: root.clampedRadius
y: bg.height
}
PathArc {
radiusX: root.clampedRadius
radiusY: root.clampedRadius
x: 0
y: bg.height - root.clampedRadius
}
PathLine {
x: 0
y: root.clampedRadius
}
PathArc {
radiusX: root.clampedRadius
radiusY: root.clampedRadius
x: root.clampedRadius
y: 0
}
PathLine {
x: root.horizontalPadding - root.clampedRadius + root.outlineGap * (1 - root.outlineGapScale) / 2
}
}
}
}
Component {
id: filledComp
CustomRect {
color: root.activeFocus ? DynamicColors.tPalette.m3surfaceContainerHighest : DynamicColors.tPalette.m3surfaceContainerHigh
topLeftRadius: root.clampedRadius
topRightRadius: root.clampedRadius
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: root.isError ? DynamicColors.palette.m3error : (root.activeFocus ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline)
implicitHeight: root.activeFocus ? 2 : 1
Behavior on implicitHeight {
Anim {
}
}
}
} }
} }
} }
+79
View File
@@ -0,0 +1,79 @@
import QtQuick
import Quickshell
import qs.Config
Image {
id: root
property int fadeInAnim: Anim.DefaultEffects
property int fadeInLargeAnim: Anim.StandardLarge
property int fadeOutAnim: Anim.FastEffects
property bool fadingOut
property bool hadPrevious
property bool preventInit
function maybeStartInAnim(): void {
if (!preventInit && !opacityInAnim.running && status === Image.Ready) {
opacityInAnim.type = hadPrevious ? fadeInAnim : fadeInLargeAnim;
opacityInAnim.start();
}
}
asynchronous: true
fillMode: Image.PreserveAspectCrop
opacity: 0
retainWhileLoading: true
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
Anim on opacity {
id: opacityInAnim
running: false
to: 1
}
Behavior on source {
SequentialAnimation {
ScriptAction {
script: opacityInAnim.stop()
}
PropertyAction {
property: "fadingOut"
target: root
value: true
}
Anim {
property: "opacity"
target: root
to: 0
type: root.fadeOutAnim
}
PropertyAction {
property: "fadingOut"
target: root
value: false
}
PropertyAction {
property: "hadPrevious"
target: root
value: root.source
}
PropertyAction {
}
ScriptAction {
script: root.maybeStartInAnim()
}
}
}
onPreventInitChanged: maybeStartInAnim()
onStatusChanged: maybeStartInAnim()
}
+1 -1
View File
@@ -4,7 +4,6 @@ import qs.Config
ButtonBase { ButtonBase {
id: root id: root
property alias font: label.font
property alias icon: label.text property alias icon: label.text
readonly property alias label: label readonly property alias label: label
@@ -36,6 +35,7 @@ ButtonBase {
anchors.verticalCenterOffset: 1 anchors.verticalCenterOffset: 1
color: root.onColor color: root.onColor
fill: !root.isToggle || root.internalChecked ? 1 : 0 fill: !root.isToggle || root.internalChecked ? 1 : 0
font.pointSize: root.font.pointSize
Behavior on fill { Behavior on fill {
Anim { Anim {
+71
View File
@@ -0,0 +1,71 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
ButtonBase {
id: root
property alias icon: iconLabel.text
readonly property alias iconLabel: iconLabel
readonly property alias label: label
property alias text: label.text
activeColor: type === TextButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
activeOnColor: {
if (type === TextButton.Text)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondary;
}
horizontalPadding: Appearance.padding.larger
implicitHeight: row.implicitHeight + verticalPadding * 2
implicitWidth: row.implicitWidth + horizontalPadding * 2
inactiveColor: {
if (!isToggle && type === TextButton.Filled)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
}
inactiveOnColor: {
if (!isToggle && type === TextButton.Filled)
return DynamicColors.palette.m3onPrimary;
if (type === TextButton.Text)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer;
}
verticalPadding: Appearance.padding.small
RowLayout {
id: row
anchors.centerIn: parent
spacing: Appearance.spacing.small
MaterialIcon {
id: iconLabel
Layout.alignment: Qt.AlignVCenter
color: root.onColor
fill: root.internalChecked ? 1 : 0
font: {
const f = Qt.font(root.font);
f.pointSize = Math.round(root.font.pointSize * 1.2);
f.family = "Material Symbols Rounded";
return f;
}
Behavior on fill {
Anim {
type: Anim.DefaultEffects
}
}
}
CustomText {
id: label
Layout.alignment: Qt.AlignVCenter
Layout.topMargin: -1
color: root.onColor
font: root.font
}
}
}
+309
View File
@@ -0,0 +1,309 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Config
Item {
id: root
enum Variant {
Outlined,
Filled
}
readonly property color accent: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline))
readonly property real disabledOpacity: 0.38
readonly property string effectiveTrailingIcon: root.trailingIcon.length > 0 ? root.trailingIcon : (root.password ? (field.echoMode === TextInput.Password ? "visibility" : "visibility_off") : (root.isError ? "error" : ""))
property string errorText: ""
readonly property alias field: field
readonly property bool floating: focused || hasContent || prefixText.length > 0
readonly property bool focused: field.activeFocus
readonly property bool hasContent: field.text.length > 0
readonly property color iconColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
property int inputMethodHints: Qt.ImhNone
property bool isError: false
property string label: ""
readonly property color labelColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
property string leadingIcon: ""
property int maxLength: 0
property color notchColor: DynamicColors.palette.m3surface
readonly property bool outlined: variant === M3TextField.Variant.Outlined
property bool password: false
property string placeholder: ""
property string prefixText: ""
property string suffixText: ""
readonly property color supportColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant)
property string supportingText: ""
property alias text: field.text
property string trailingIcon: ""
readonly property bool trailingInteractive: root.password && root.trailingIcon.length === 0
property var validator: null
property int variant: M3TextField.Variant.Outlined
signal accepted
function forceFieldFocus(): void {
field.forceActiveFocus();
}
implicitHeight: 56 + (supportRow.visible ? supportRow.implicitHeight + Appearance.spacing.extraSmall : 0)
implicitWidth: 240
CustomRect {
id: filledBg
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: root.enabled ? (root.focused ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) : DynamicColors.palette.m3surfaceContainerHigh) : DynamicColors.palette.m3onSurface
implicitHeight: 56
opacity: root.enabled ? 1 : 0.04 // M3 filled disabled container @ 4%
topLeftRadius: Appearance.rounding.small
topRightRadius: Appearance.rounding.small
visible: !root.outlined
Behavior on color {
CAnim {
}
}
StateLayer {
anchors.fill: parent
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
enabled: root.enabled
topLeftRadius: Appearance.rounding.small
topRightRadius: Appearance.rounding.small
onClicked: field.forceActiveFocus()
}
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: root.accent
implicitHeight: root.focused || root.isError ? 2 : 1
opacity: root.enabled ? 1 : root.disabledOpacity
Behavior on color {
CAnim {
}
}
Behavior on implicitHeight {
Anim {
type: Anim.StandardSmall
}
}
}
}
CustomRect {
id: outline
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
border.color: root.accent
border.width: root.focused || root.isError ? 2 : 1
color: "transparent"
implicitHeight: 56
opacity: root.enabled ? 1 : root.disabledOpacity
radius: Appearance.rounding.small
visible: root.outlined
Behavior on border.color {
CAnim {
}
}
Behavior on border.width {
Anim {
type: Anim.StandardSmall
}
}
StateLayer {
anchors.fill: parent
anchors.margins: outline.border.width
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
enabled: root.enabled
radius: Appearance.rounding.small
onClicked: field.forceActiveFocus()
}
}
Rectangle {
color: root.notchColor
height: outline.border.width
opacity: root.enabled ? 1 : root.disabledOpacity
visible: root.outlined && root.floating && root.label.length > 0
width: labelText.width + Appearance.spacing.extraSmall
x: labelText.x - Appearance.spacing.extraSmall / 2
y: 0
}
CustomText {
id: labelText
color: root.labelColor
font.pointSize: root.floating ? Appearance.font.size.small : Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.label
visible: root.label.length > 0
x: root.floating ? (Appearance.padding.larger + Appearance.spacing.extraSmall / 2) : (leading.visible ? leading.x + leading.width + Appearance.spacing.small : Appearance.padding.larger)
y: root.floating ? (root.outlined ? -height / 2 : Appearance.padding.extraSmall) : (56 - height) / 2
Behavior on color {
CAnim {
}
}
Behavior on x {
Anim {
type: Anim.StandardSmall
}
}
Behavior on y {
Anim {
type: Anim.StandardSmall
}
}
}
MaterialIcon {
id: leading
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: (56 - height) / 2
color: root.iconColor
font.pointSize: Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.leadingIcon
visible: root.leadingIcon.length > 0
Behavior on color {
CAnim {
}
}
}
MaterialIcon {
id: trailing
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: (56 - height) / 2
color: root.isError ? DynamicColors.palette.m3error : (root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface)
font.pointSize: Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.effectiveTrailingIcon
visible: root.effectiveTrailingIcon.length > 0
MouseArea {
anchors.fill: parent
anchors.margins: -Appearance.padding.small
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: root.trailingInteractive && root.enabled
onClicked: field.echoMode = field.echoMode === TextInput.Password ? TextInput.Normal : TextInput.Password
}
}
CustomText {
id: prefix
anchors.left: leading.visible ? leading.right : parent.left
anchors.leftMargin: leading.visible ? Appearance.spacing.small : Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.medium
height: 56
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.prefixText
verticalAlignment: Text.AlignVCenter
visible: root.prefixText.length > 0 && root.floating
}
CustomText {
id: suffix
anchors.right: trailing.visible ? trailing.left : parent.right
anchors.rightMargin: trailing.visible ? Appearance.spacing.small : Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.medium
height: 56
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.suffixText
verticalAlignment: Text.AlignVCenter
visible: root.suffixText.length > 0 && root.floating
}
CustomTextField {
id: field
anchors.left: prefix.visible ? prefix.right : (leading.visible ? leading.right : parent.left)
anchors.leftMargin: prefix.visible ? Appearance.spacing.extraSmall : (leading.visible ? Appearance.spacing.small : Appearance.padding.larger)
anchors.right: suffix.visible ? suffix.left : (trailing.visible ? trailing.left : parent.right)
anchors.rightMargin: suffix.visible ? Appearance.spacing.extraSmall : (trailing.visible ? Appearance.spacing.small : Appearance.padding.larger)
anchors.top: parent.top
color: DynamicColors.palette.m3onSurface
echoMode: root.password ? TextInput.Password : TextInput.Normal
enabled: root.enabled
font.pointSize: Appearance.font.size.medium
height: 56
inputMethodHints: root.inputMethodHints
maximumLength: root.maxLength > 0 ? root.maxLength : 32767
opacity: root.enabled ? 1 : root.disabledOpacity
placeholderText: root.focused ? root.placeholder : ""
topPadding: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
validator: root.validator
verticalAlignment: TextInput.AlignVCenter
onAccepted: root.accepted()
onTextEdited: if (root.isError)
root.isError = false
}
Item {
id: supportRow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 56 + Appearance.spacing.extraSmall
implicitHeight: Math.max(supportText.implicitHeight, counter.implicitHeight)
visible: (root.isError && root.errorText.length > 0) || root.supportingText.length > 0 || root.maxLength > 0
CustomText {
id: supportText
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.larger
anchors.right: counter.visible ? counter.left : parent.right
anchors.rightMargin: Appearance.spacing.small
color: root.supportColor
font.pointSize: Appearance.font.size.small
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.isError && root.errorText.length > 0 ? root.errorText : root.supportingText
wrapMode: Text.WordWrap
}
CustomText {
id: counter
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
color: root.supportColor
font.pointSize: Appearance.font.size.small
opacity: root.enabled ? 1 : root.disabledOpacity
text: `${root.text.length}/${root.maxLength}`
visible: root.maxLength > 0
}
}
}
+2 -1
View File
@@ -30,8 +30,9 @@ MouseArea {
signal itemSelected(item: MenuItem) signal itemSelected(item: MenuItem)
anchors.fill: parent anchors.fill: parent
cursorShape: undefined cursorShape: expanded ? Qt.ArrowCursor : undefined
enabled: expanded enabled: expanded
hoverEnabled: expanded
layer.enabled: opacity < 1 layer.enabled: opacity < 1
opacity: expanded ? 1 : 0 opacity: expanded ? 1 : 0
parent: { parent: {
+94
View File
@@ -0,0 +1,94 @@
import QtQuick
import qs.Config
TextFieldBase {
id: root
readonly property alias bg: bg
readonly property alias clearIcon: clearIcon
readonly property alias searchIcon: searchIcon
bottomPadding: Appearance.padding.large
leftPadding: searchIcon.width + searchIcon.anchors.leftMargin + Appearance.spacing.small
rightPadding: clearIcon.width + clearIcon.anchors.rightMargin + Appearance.spacing.small
topPadding: Appearance.padding.large
background: CustomRect {
id: bg
anchors.fill: parent
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.full
StateLayer {
id: stateLayer
cursorShape: Qt.IBeamCursor
enabled: !root.activeFocus
manualPressOverride: tapHandler.pressed
onClicked: root.focus = true
}
}
onPressed: {
if (stateLayer.enabled)
stateLayer.press(stateLayer.mouseX, stateLayer.mouseY);
}
CustomText {
id: placeholder
anchors.left: parent.left
anchors.leftMargin: root.leftPadding
anchors.verticalCenter: parent.verticalCenter
color: root.placeholderTextColor
font: root.font
opacity: root.text ? 0 : 1
text: root.placeholderText
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
MaterialIcon {
id: searchIcon
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.large
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
text: "search"
}
IconButton {
id: clearIcon
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
anchors.verticalCenter: parent.verticalCenter
enabled: root.text
icon: "clear"
opacity: root.text ? 1 : 0
radius: Appearance.rounding.full
radiusMorph: false
stateLayer.hoverEnabled: enabled
type: IconButton.Text
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
onClicked: root.clear()
}
TapHandler {
id: tapHandler
}
}
+3 -1
View File
@@ -20,6 +20,7 @@ MouseArea {
return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3; return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
} }
property real endRadiusAtPress property real endRadiusAtPress
property bool manualHoverOverride
property bool manualPressOverride property bool manualPressOverride
property real pressX: width / 2 property real pressX: width / 2
property real pressY: height / 2 property real pressY: height / 2
@@ -27,7 +28,7 @@ MouseArea {
readonly property alias rect: base readonly property alias rect: base
property bool shapeMorph property bool shapeMorph
property bool showHoverBackground: true property bool showHoverBackground: true
property real stateOpacity: containsMouse ? 0.08 : 0 property real stateOpacity: containsMouse || manualHoverOverride ? 0.08 : 0
property alias topLeftRadius: base.topLeftRadius property alias topLeftRadius: base.topLeftRadius
property alias topRightRadius: base.topRightRadius property alias topRightRadius: base.topRightRadius
@@ -51,6 +52,7 @@ MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: !enabled ? undefined : Qt.PointingHandCursor cursorShape: !enabled ? undefined : Qt.PointingHandCursor
enabled: parent.enabled && !Visibilities.getForActive().isDrawing
hoverEnabled: true hoverEnabled: true
Behavior on stateOpacity { Behavior on stateOpacity {
+40
View File
@@ -0,0 +1,40 @@
import QtQuick
import qs.Config
ButtonBase {
id: root
readonly property alias label: label
property alias text: label.text
activeColor: type === TextButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
activeOnColor: {
if (type === TextButton.Text)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondary;
}
horizontalPadding: Appearance.padding.normal
implicitHeight: label.implicitHeight + verticalPadding * 2
implicitWidth: label.implicitWidth + horizontalPadding * 2
inactiveColor: {
if (!isToggle && type === TextButton.Filled)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
}
inactiveOnColor: {
if (!isToggle && type === TextButton.Filled)
return DynamicColors.palette.m3onPrimary;
if (type === TextButton.Text)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer;
}
verticalPadding: Appearance.padding.small
CustomText {
id: label
anchors.centerIn: parent
color: root.onColor
font: root.font
}
}
+88
View File
@@ -0,0 +1,88 @@
import QtQuick
import QtQuick.Templates
import qs.Config
TextField {
id: root
color: DynamicColors.palette.m3onSurface
cursorVisible: !readOnly
font.pointSize: Appearance.font.size.small
implicitHeight: contentHeight + topPadding + bottomPadding
implicitWidth: contentWidth + leftPadding + rightPadding
placeholderTextColor: DynamicColors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
selectedTextColor: color
selectionColor: Qt.alpha(DynamicColors.palette.m3primary, 0.4)
verticalAlignment: TextInput.AlignVCenter
Behavior on color {
CAnim {
}
}
cursorDelegate: Item {
}
Behavior on selectionColor {
CAnim {
}
}
CustomRect {
id: cursor
property bool disableBlink
color: DynamicColors.palette.m3primary
implicitHeight: root.cursorRectangle.height
implicitWidth: 1.5
radius: Appearance.rounding.large
x: root.cursorRectangle.x
y: root.cursorRectangle.y
Behavior on opacity {
Anim {
type: Anim.StandardSmall
}
}
Behavior on x {
Anim {
duration: Appearance.anim.durations.expressiveFastEffects
easing.bezierCurve: [0.2, 1, 0.21, 1, 1, 1]
}
}
Connections {
function onCursorPositionChanged(): void {
if (root.activeFocus && root.cursorVisible) {
cursor.opacity = 1;
cursor.disableBlink = true;
enableBlink.restart();
}
}
target: root
}
Timer {
id: enableBlink
interval: 500
onTriggered: cursor.disableBlink = false
}
Timer {
interval: 500
repeat: true
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
triggeredOnStart: true
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
}
Binding {
cursor.opacity: 0
when: !root.activeFocus || !root.cursorVisible
}
}
}
+71
View File
@@ -0,0 +1,71 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Effects
CustomFlickable {
id: root
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property real fadeAmount: 0.1
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts
if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0)))
return false;
if (isStart)
return visibleArea.yPosition > 0;
return visibleArea.yPosition + visibleArea.heightRatio < 1;
}
flickableDirection: Flickable.VerticalFlick
layer.enabled: true
Behavior on bottomFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
layer.effect: Mask {
maskSource: mask
Rectangle {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop {
color: Qt.rgba(0, 0, 0, root.topFadeOpacity)
position: 0
}
GradientStop {
color: Qt.rgba(0, 0, 0, 1)
position: root.fadeAmount
}
GradientStop {
color: Qt.rgba(0, 0, 0, 1)
position: 1 - root.fadeAmount
}
GradientStop {
color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity)
position: 1
}
}
}
}
Behavior on topFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
}
+72
View File
@@ -0,0 +1,72 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Effects
CustomListView {
id: root
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property real fadeAmount: 0.1
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts
if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0)))
return false;
if (isStart)
return visibleArea.yPosition > 0;
return visibleArea.yPosition + visibleArea.heightRatio < 1;
}
flickableDirection: Flickable.VerticalFlick
layer.enabled: true
orientation: ListView.Vertical
Behavior on bottomFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
layer.effect: Mask {
maskSource: mask
Rectangle {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop {
color: Qt.rgba(0, 0, 0, root.topFadeOpacity)
position: 0
}
GradientStop {
color: Qt.rgba(0, 0, 0, 1)
position: root.fadeAmount
}
GradientStop {
color: Qt.rgba(0, 0, 0, 1)
position: 1 - root.fadeAmount
}
GradientStop {
color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity)
position: 1
}
}
}
}
Behavior on topFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
}
+2
View File
@@ -77,9 +77,11 @@ JsonObject {
} }
} }
component Padding: JsonObject { component Padding: JsonObject {
property int extraLarge: 28 * scale
property int extraLargeIncreased: 32 * scale property int extraLargeIncreased: 32 * scale
property int extraSmall: 4 * scale property int extraSmall: 4 * scale
property int large: 16 * scale property int large: 16 * scale
property int largeIncreased: 20 * scale
property int larger: 12 * scale property int larger: 12 * scale
property int normal: 8 * scale property int normal: 8 * scale
property real scale: 1 property real scale: 1
+6
View File
@@ -72,6 +72,12 @@ JsonObject {
property bool upower: true property bool upower: true
} }
component Tray: JsonObject { component Tray: JsonObject {
property bool showAudio: true
property bool showBluetooth: false
property bool showMicrophone: true
property bool showNetwork: false
property bool showPower: true
property bool showWifi: false
property int trayIconSize: 24 property int trayIconSize: 24
} }
} }
+48 -357
View File
@@ -12,7 +12,7 @@ Singleton {
property alias appearance: adapter.appearance property alias appearance: adapter.appearance
property alias background: adapter.background property alias background: adapter.background
property alias barConfig: adapter.barConfig property alias bar: adapter.bar
property alias clipboard: adapter.clipboard property alias clipboard: adapter.clipboard
property alias colors: adapter.colors property alias colors: adapter.colors
property alias dashboard: adapter.dashboard property alias dashboard: adapter.dashboard
@@ -20,6 +20,7 @@ Singleton {
property alias general: adapter.general property alias general: adapter.general
property alias launcher: adapter.launcher property alias launcher: adapter.launcher
property alias lock: adapter.lock property alias lock: adapter.lock
property bool migratingLegacyBar: false
property alias notifs: adapter.notifs property alias notifs: adapter.notifs
property alias osd: adapter.osd property alias osd: adapter.osd
property alias overview: adapter.overview property alias overview: adapter.overview
@@ -29,6 +30,41 @@ Singleton {
property alias sidebar: adapter.sidebar property alias sidebar: adapter.sidebar
property alias utilities: adapter.utilities property alias utilities: adapter.utilities
function applyLegacyBarConfig(legacy): void {
const tray = legacy.tray ?? {};
const popouts = legacy.popouts ?? {};
bar.autoHide = pick(legacy.autoHide, bar.autoHide);
bar.hideWhenNotif = pick(legacy.hideWhenNotif, bar.hideWhenNotif);
bar.rounding = pick(legacy.rounding, bar.rounding);
bar.border = pick(legacy.border, bar.border);
bar.smoothing = pick(legacy.smoothing, bar.smoothing);
bar.height = pick(legacy.height, bar.height);
bar.tray.trayIconSize = pick(tray.trayIconSize, bar.tray.trayIconSize);
bar.popouts.tray = pick(popouts.tray, bar.popouts.tray);
bar.popouts.audio = pick(popouts.audio, bar.popouts.audio);
bar.popouts.activeWindow = pick(popouts.activeWindow, bar.popouts.activeWindow);
bar.popouts.resources = pick(popouts.resources, bar.popouts.resources);
bar.popouts.clock = pick(popouts.clock, bar.popouts.clock);
bar.popouts.network = pick(popouts.network, bar.popouts.network);
bar.popouts.upower = pick(popouts.upower, bar.popouts.upower);
bar.entries = pick(legacy.entries, bar.entries);
}
function migrateLegacyBarConfig(raw): bool {
if (raw.bar !== undefined || raw.barConfig === undefined)
return false;
migratingLegacyBar = true;
applyLegacyBarConfig(raw.barConfig);
migratingLegacyBar = false;
return true;
}
function pick(value, fallback) {
return value === undefined ? fallback : value;
}
function save(): void { function save(): void {
saveTimer.restart(); saveTimer.restart();
recentlySaved = true; recentlySaved = true;
@@ -39,346 +75,6 @@ Singleton {
saveTimer.restart(); saveTimer.restart();
} }
function serializeAppearance(): var {
return {
rounding: {
scale: appearance.rounding.scale
},
spacing: {
scale: appearance.spacing.scale
},
padding: {
scale: appearance.padding.scale
},
deform: {
scale: appearance.deform.scale
},
font: {
family: {
sans: appearance.font.family.sans,
mono: appearance.font.family.mono,
material: appearance.font.family.material,
clock: appearance.font.family.clock
},
size: {
scale: appearance.font.size.scale
}
},
anim: {
mediaGifSpeedAdjustment: appearance.anim.mediaGifSpeedAdjustment,
sessionGifSpeed: appearance.anim.sessionGifSpeed,
durations: {
scale: appearance.anim.durations.scale
}
},
transparency: {
enabled: appearance.transparency.enabled,
base: appearance.transparency.base,
layers: appearance.transparency.layers
}
};
}
function serializeBackground(): var {
return {
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
};
}
function serializeBar(): var {
return {
autoHide: barConfig.autoHide,
hideWhenNotif: barConfig.hideWhenNotif,
rounding: barConfig.rounding,
border: barConfig.border,
smoothing: barConfig.smoothing,
height: barConfig.height,
tray: {
trayIconSize: barConfig.tray.trayIconSize
},
popouts: {
tray: barConfig.popouts.tray,
audio: barConfig.popouts.audio,
activeWindow: barConfig.popouts.activeWindow,
resources: barConfig.popouts.resources,
clock: barConfig.popouts.clock,
network: barConfig.popouts.network,
upower: barConfig.popouts.upower
},
entries: barConfig.entries
};
}
function serializeClipboard(): var {
return {
enabled: clipboard.enabled,
maxEntriesShown: clipboard.maxEntriesShown,
sizes: {
width: clipboard.sizes.width,
previewWidth: clipboard.sizes.previewWidth,
minPreviewWidth: clipboard.sizes.minPreviewWidth,
itemHeight: clipboard.sizes.itemHeight
}
};
}
function serializeColors(): var {
return {
schemeType: colors.schemeType,
presets: {
name: colors.presets.name,
variant: colors.presets.variant,
accent: colors.presets.accent
}
};
}
function serializeConfig(): var {
return {
barConfig: serializeBar(),
lock: serializeLock(),
general: serializeGeneral(),
services: serializeServices(),
notifs: serializeNotifs(),
sidebar: serializeSidebar(),
utilities: serializeUtilities(),
dashboard: serializeDashboard(),
appearance: serializeAppearance(),
osd: serializeOsd(),
background: serializeBackground(),
launcher: serializeLauncher(),
colors: serializeColors(),
dock: serializeDock(),
screenshot: serializeScreenshot(),
clipboard: serializeClipboard()
};
}
function serializeDashboard(): var {
return {
enabled: dashboard.enabled,
mediaUpdateInterval: dashboard.mediaUpdateInterval,
resourceUpdateInterval: dashboard.resourceUpdateInterval,
dragThreshold: dashboard.dragThreshold,
performance: {
showBattery: dashboard.performance.showBattery,
showGpu: dashboard.performance.showGpu,
showCpu: dashboard.performance.showCpu,
showMemory: dashboard.performance.showMemory,
showStorage: dashboard.performance.showStorage,
showNetwork: dashboard.performance.showNetwork
},
sizes: {
tabIndicatorHeight: dashboard.sizes.tabIndicatorHeight,
tabIndicatorSpacing: dashboard.sizes.tabIndicatorSpacing,
infoWidth: dashboard.sizes.infoWidth,
infoIconSize: dashboard.sizes.infoIconSize,
dateTimeWidth: dashboard.sizes.dateTimeWidth,
mediaWidth: dashboard.sizes.mediaWidth,
mediaProgressSweep: dashboard.sizes.mediaProgressSweep,
mediaProgressThickness: dashboard.sizes.mediaProgressThickness,
resourceProgessThickness: dashboard.sizes.resourceProgessThickness,
weatherWidth: dashboard.sizes.weatherWidth,
mediaCoverArtSize: dashboard.sizes.mediaCoverArtSize,
mediaVisualiserSize: dashboard.sizes.mediaVisualiserSize,
resourceSize: dashboard.sizes.resourceSize
}
};
}
function serializeDock(): var {
return {
enable: dock.enable,
height: dock.height,
hoverToReveal: dock.hoverToReveal,
pinnedApps: dock.pinnedApps,
pinnedOnStartup: dock.pinnedOnStartup,
ignoredAppRegexes: dock.ignoredAppRegexes
};
}
function serializeGeneral(): var {
return {
logo: general.logo,
wallpaperPath: general.wallpaperPath,
desktopIcons: general.desktopIcons,
dateFormat: general.dateFormat,
color: {
mode: general.color.mode,
smart: general.color.smart,
scheduleDark: general.color.scheduleDark,
scheduleHyprsunset: general.color.scheduleHyprsunset,
scheduleHyprsunsetStart: general.color.scheduleHyprsunsetStart,
hyprsunsetTemp: general.color.hyprsunsetTemp,
scheduleHyprsunsetEnd: general.color.scheduleHyprsunsetEnd,
schemeGeneration: general.color.schemeGeneration,
scheduleDarkStart: general.color.scheduleDarkStart,
scheduleDarkEnd: general.color.scheduleDarkEnd,
neovimColors: general.color.neovimColors
},
apps: {
terminal: general.apps.terminal,
audio: general.apps.audio,
playback: general.apps.playback,
explorer: general.apps.explorer
},
idle: {
timeouts: general.idle.timeouts
},
battery: {
popupThresholds: general.battery.popupThresholds,
critPerc: general.battery.critPerc
}
};
}
function serializeLauncher(): var {
return {
maxAppsShown: launcher.maxAppsShown,
maxWallpapers: launcher.maxWallpapers,
uwsm: launcher.uwsm,
actionPrefix: launcher.actionPrefix,
specialPrefix: launcher.specialPrefix,
useFuzzy: {
apps: launcher.useFuzzy.apps,
actions: launcher.useFuzzy.actions,
schemes: launcher.useFuzzy.schemes,
variants: launcher.useFuzzy.variants,
wallpapers: launcher.useFuzzy.wallpapers
},
sizes: {
itemWidth: launcher.sizes.itemWidth,
itemHeight: launcher.sizes.itemHeight,
wallpaperWidth: launcher.sizes.wallpaperWidth,
wallpaperHeight: launcher.sizes.wallpaperHeight
},
actions: launcher.actions
};
}
function serializeLock(): var {
return {
recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint,
showNotifContent: lock.showNotifContent,
showNotifIcon: lock.showNotifIcon,
maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount,
sizes: {
heightMult: lock.sizes.heightMult,
ratio: lock.sizes.ratio,
centerWidth: lock.sizes.centerWidth
}
};
}
function serializeNotifs(): var {
return {
expire: notifs.expire,
defaultExpireTimeout: notifs.defaultExpireTimeout,
appNotifCooldown: notifs.appNotifCooldown,
clearThreshold: notifs.clearThreshold,
expandThreshold: notifs.expandThreshold,
actionOnClick: notifs.actionOnClick,
groupPreviewNum: notifs.groupPreviewNum,
sizes: {
width: notifs.sizes.width,
image: notifs.sizes.image,
badge: notifs.sizes.badge
}
};
}
function serializeOsd(): var {
return {
enabled: osd.enabled,
hideDelay: osd.hideDelay,
enableBrightness: osd.enableBrightness,
enableMicrophone: osd.enableMicrophone,
allMonBrightness: osd.allMonBrightness,
sizes: {
sliderWidth: osd.sizes.sliderWidth,
sliderHeight: osd.sizes.sliderHeight
}
};
}
function serializeScreenshot(): var {
return {
enable_pp: screenshot.enable_pp,
mode: screenshot.mode,
radius: screenshot.radius,
shadow: screenshot.shadow,
rounding: screenshot.rounding,
shadow_blur: screenshot.shadow_blur,
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,
updates: services.updates,
useFahrenheit: services.useFahrenheit,
ddcutilService: services.ddcutilService,
useTwelveHourClock: services.useTwelveHourClock,
gpuType: services.gpuType,
audioIncrement: services.audioIncrement,
brightnessIncrement: services.brightnessIncrement,
maxVolume: services.maxVolume,
defaultPlayer: services.defaultPlayer,
playerAliases: services.playerAliases,
visualizerBars: services.visualizerBars
};
}
function serializeSidebar(): var {
return {
enabled: sidebar.enabled,
sizes: {
width: sidebar.sizes.width
}
};
}
function serializeUtilities(): var {
return {
enabled: utilities.enabled,
maxToasts: utilities.maxToasts,
sizes: {
width: utilities.sizes.width,
toastWidth: utilities.sizes.toastWidth
},
toasts: {
configLoaded: utilities.toasts.configLoaded,
chargingChanged: utilities.toasts.chargingChanged,
gameModeChanged: utilities.toasts.gameModeChanged,
dndChanged: utilities.toasts.dndChanged,
audioOutputChanged: utilities.toasts.audioOutputChanged,
audioInputChanged: utilities.toasts.audioInputChanged,
capsLockChanged: utilities.toasts.capsLockChanged,
numLockChanged: utilities.toasts.numLockChanged,
kbLayoutChanged: utilities.toasts.kbLayoutChanged,
vpnChanged: utilities.toasts.vpnChanged,
nowPlaying: utilities.toasts.nowPlaying
},
vpn: {
enabled: utilities.vpn.enabled,
provider: utilities.vpn.provider
}
};
}
ElapsedTimer { ElapsedTimer {
id: timer id: timer
} }
@@ -390,20 +86,7 @@ Singleton {
onTriggered: { onTriggered: {
timer.restart(); timer.restart();
try { fileView.writeAdapter();
let config = {};
try {
config = JSON.parse(fileView.text());
} catch (e) {
config = {};
}
config = root.serializeConfig();
fileView.setText(JSON.stringify(config, null, 4));
} catch (e) {
Toaster.toast(qsTr("Failed to serialize config"), e.message, "settings_alert", Toast.Error);
}
} }
} }
@@ -423,6 +106,10 @@ Singleton {
path: `${Paths.config}/config.json` path: `${Paths.config}/config.json`
watchChanges: true watchChanges: true
onAdapterUpdated: {
if (!root.migratingLegacyBar)
root.save();
}
onFileChanged: { onFileChanged: {
if (!root.recentlySaved) { if (!root.recentlySaved) {
timer.restart(); timer.restart();
@@ -439,7 +126,11 @@ Singleton {
ModeScheduler.checkStartup(); ModeScheduler.checkStartup();
Hyprsunset.checkStartup(); Hyprsunset.checkStartup();
try { try {
JSON.parse(text()); const raw = JSON.parse(text());
const migrated = root.migrateLegacyBarConfig(raw);
if (migrated)
root.save();
const elapsed = timer.elapsedMs(); const elapsed = timer.elapsedMs();
if (adapter.utilities.toasts.configLoaded && !root.recentlySaved) { if (adapter.utilities.toasts.configLoaded && !root.recentlySaved) {
@@ -460,7 +151,7 @@ Singleton {
} }
property BackgroundConfig background: BackgroundConfig { property BackgroundConfig background: BackgroundConfig {
} }
property BarConfig barConfig: BarConfig { property BarConfig bar: BarConfig {
} }
property ClipboardConfig clipboard: ClipboardConfig { property ClipboardConfig clipboard: ClipboardConfig {
} }
+2 -2
View File
@@ -1,7 +1,6 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property int dragThreshold: 50
property bool enabled: true property bool enabled: true
property int mediaUpdateInterval: 500 property int mediaUpdateInterval: 500
property Performance performance: Performance { property Performance performance: Performance {
@@ -11,6 +10,7 @@ JsonObject {
} }
component Performance: JsonObject { component Performance: JsonObject {
property bool enabled: true
property bool showBattery: true property bool showBattery: true
property bool showCpu: true property bool showCpu: true
property bool showGpu: true property bool showGpu: true
@@ -25,7 +25,7 @@ JsonObject {
readonly property int mediaCoverArtSize: 150 readonly property int mediaCoverArtSize: 150
readonly property int mediaProgressSweep: 180 readonly property int mediaProgressSweep: 180
readonly property int mediaProgressThickness: 8 readonly property int mediaProgressThickness: 8
readonly property int mediaVisualiserSize: 80 readonly property int mediaVisualizerSize: 200
readonly property int mediaWidth: 200 readonly property int mediaWidth: 200
readonly property int resourceProgessThickness: 10 readonly property int resourceProgessThickness: 10
readonly property int resourceSize: 200 readonly property int resourceSize: 200
+1
View File
@@ -13,6 +13,7 @@ JsonObject {
property Idle idle: Idle { property Idle idle: Idle {
} }
property string logo: "" property string logo: ""
property bool showOverFullscreen: true
property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers" property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers"
component Apps: JsonObject { component Apps: JsonObject {
+5
View File
@@ -84,6 +84,11 @@ JsonObject {
dangerous: false dangerous: false
}, },
] ]
property int dragThreshold: 50
property bool enableDangerousActions: false
property bool enabled: true
property list<string> favoriteApps: []
property list<string> hiddenApps: []
property int maxAppsShown: 10 property int maxAppsShown: 10
property int maxWallpapers: 7 property int maxWallpapers: 7
property Sizes sizes: Sizes { property Sizes sizes: Sizes {
+1
View File
@@ -9,6 +9,7 @@ JsonObject {
property bool expire: true property bool expire: true
property int groupPreviewNum: 3 property int groupPreviewNum: 3
property bool openExpanded: false property bool openExpanded: false
property bool showInFullscreen: true
property Sizes sizes: Sizes { property Sizes sizes: Sizes {
} }
+1
View File
@@ -8,6 +8,7 @@ JsonObject {
property string defaultPlayer: "Spotify" property string defaultPlayer: "Spotify"
property string gpuType: "" property string gpuType: ""
property real maxVolume: 1.0 property real maxVolume: 1.0
property real minBrightness: 0.01
property list<var> playerAliases: [ property list<var> playerAliases: [
{ {
"from": "com.github.th_ch.youtube_music", "from": "com.github.th_ch.youtube_music",
+1
View File
@@ -1,6 +1,7 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property int dragThreshold: 30
property bool enabled: true property bool enabled: true
property Sizes sizes: Sizes { property Sizes sizes: Sizes {
} }
+7 -6
View File
@@ -25,7 +25,7 @@ Singleton {
property alias server: server property alias server: server
function shouldThrottle(appName: string): bool { function shouldThrottle(appName: string): bool {
if (props.dnd) if (props.dnd || [...Visibilities.screens.values()].some(v => v.sidebar))
return false; return false;
const key = (appName || "unknown").trim().toLowerCase(); const key = (appName || "unknown").trim().toLowerCase();
@@ -49,11 +49,6 @@ Singleton {
if (loaded) { if (loaded) {
saveTimer.restart(); saveTimer.restart();
} }
if (root.list.length > 0) {
HasNotifications.hasNotifications = true;
} else {
HasNotifications.hasNotifications = false;
}
} }
Timer { Timer {
@@ -215,6 +210,10 @@ Singleton {
notif.hasActionIcons = notif.notification.hasActionIcons; notif.hasActionIcons = notif.notification.hasActionIcons;
} }
function onHintsChanged(): void {
notif.hints = notif.notification.hints;
}
function onImageChanged(): void { function onImageChanged(): void {
notif.imageSource = notif.notification.image || ""; notif.imageSource = notif.notification.image || "";
notif.image = notif.imageSource; notif.image = notif.imageSource;
@@ -237,6 +236,7 @@ Singleton {
} }
property real expireTimeout: 5 property real expireTimeout: 5
property bool hasActionIcons property bool hasActionIcons
property var hints
property string image property string image
property string imageSource property string imageSource
property var locks: new Set() property var locks: new Set()
@@ -333,6 +333,7 @@ Singleton {
appName = notification.appName; appName = notification.appName;
imageSource = notification.image || ""; imageSource = notification.image || "";
image = imageSource; image = imageSource;
hints = notification.hints;
expireTimeout = notification.expireTimeout; expireTimeout = notification.expireTimeout;
urgency = notification.urgency; urgency = notification.urgency;
resident = notification.resident; resident = notification.resident;
+21 -27
View File
@@ -1,36 +1,30 @@
import QtQuick pragma ComponentBehavior: Bound
Canvas { import Quickshell
import QtQuick
import ZShell.Internal
Item {
id: root id: root
property color penColor: "white" readonly property alias content: contentLoader.item
property real penWidth: 4 readonly property PersistentProperties drawingState: PersistentProperties {
property var points: [] property color penColor: "white"
property int penWidth: 4
function clear(): void { reloadableId: "drawingState"
var ctx = getContext('2d');
root.points = [];
ctx.reset();
root.requestPaint();
} }
required property PersistentProperties visibilities
renderStrategy: Canvas.Cooperative Loader {
id: contentLoader
onPaint: { active: root.visibilities.isDrawing
if (points.length < 2) anchors.fill: parent
return;
var ctx = root.getContext('2d'); sourceComponent: StrokeCanvas {
ctx.save(); penColor: root.drawingState.penColor
ctx.lineWidth = root.penWidth; penWidth: root.drawingState.penWidth
ctx.strokeStyle = root.penColor; }
ctx.lineJoin = "round";
ctx.lineCap = "round";
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();
} }
} }
-60
View File
@@ -1,60 +0,0 @@
import Quickshell
import QtQuick
import qs.Components
import qs.Config
CustomMouseArea {
id: root
required property var bar
required property Drawing drawing
required property Panels panels
required property var popout
required property PersistentProperties visibilities
function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width + Config.barConfig.border && withinPanelHeight(panel, x, y);
}
function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelY = panel.y + bar.implicitHeight;
return y >= panelY && y <= panelY + panel.height;
}
acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: z > 0
hoverEnabled: true
visible: root.visibilities.isDrawing
onPositionChanged: event => {
const x = event.x;
const y = event.y;
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
return;
}
if (!(event.buttons & Qt.LeftButton) && root.inLeftPanel(root.popout, x, y)) {
root.z = -2;
root.panels.drawing.expanded = true;
}
}
onPressed: event => {
const x = event.x;
const y = event.y;
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.panels.drawing.expanded = false;
root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
return;
}
if (event.buttons & Qt.RightButton)
root.drawing.clear();
}
onReleased: {
root.drawing.points = [];
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ Scope {
} }
component ExclusionZone: CustomWindow { component ExclusionZone: CustomWindow {
exclusiveZone: Config.barConfig.border exclusiveZone: Config.bar.border
implicitHeight: 1 implicitHeight: 1
implicitWidth: 1 implicitWidth: 1
name: "Bar-Exclusion" name: "Bar-Exclusion"
+68 -16
View File
@@ -9,9 +9,9 @@ Item {
id: root id: root
required property Item bar required property Item bar
required property real borderThickness
property bool dashboardShortcutActive property bool dashboardShortcutActive
required property Drawing drawing required property Drawing drawing
required property DrawingInput input
property bool osdShortcutActive property bool osdShortcutActive
required property Panels panels required property Panels panels
required property BarPopouts.Wrapper popouts required property BarPopouts.Wrapper popouts
@@ -21,15 +21,15 @@ Item {
required property PersistentProperties visibilities required property PersistentProperties visibilities
function inBottomPanel(panel: Item, x: real, y: real): bool { function inBottomPanel(panel: Item, x: real, y: real): bool {
return y > root.height - panel.height - Config.barConfig.border && withinPanelWidth(panel, x, y); return y > root.height - panel.height - Config.bar.border && withinPanelWidth(panel, x, y);
} }
function inLeftPanel(panel: Item, x: real, y: real): bool { function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width + Config.barConfig.border && withinPanelHeight(panel, x, y); return x < panel.x + panel.width + Config.bar.border && withinPanelHeight(panel, x, y);
} }
function inRightPanel(panel: Item, x: real, y: real): bool { function inRightPanel(panel: Item, x: real, y: real): bool {
return x > panel.x - Config.barConfig.border && withinPanelHeight(panel, x, y); return x > panel.x - Config.bar.border && withinPanelHeight(panel, x, y);
} }
function inTopPanel(panel: Item, x: real, y: real): bool { function inTopPanel(panel: Item, x: real, y: real): bool {
@@ -48,7 +48,7 @@ Item {
} }
function withinPanelWidth(panel: Item, x: real, y: real): bool { function withinPanelWidth(panel: Item, x: real, y: real): bool {
const panelX = panel.x; const panelX = panel.x + root.borderThickness;
return x >= panelX && x <= panelX + panel.width; return x >= panelX && x <= panelX + panel.width;
} }
@@ -59,6 +59,7 @@ Item {
cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
dragThreshold: 0 dragThreshold: 0
enabled: !root.visibilities.isDrawing
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything
maximumPointCount: 1 maximumPointCount: 1
minimumPointCount: 1 minimumPointCount: 1
@@ -74,7 +75,7 @@ Item {
const dragX = x - centroid.pressPosition.x; const dragX = x - centroid.pressPosition.x;
const dragY = y - centroid.pressPosition.y; const dragY = y - centroid.pressPosition.y;
if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -200) if (centroid.pressPosition.y >= root.screen.height - Config.bar.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold)
root.visibilities.launcher = true; root.visibilities.launcher = true;
if (root.singleGestureTriggered) if (root.singleGestureTriggered)
@@ -90,35 +91,78 @@ Item {
} }
} }
if (centroid.pressPosition.y > root.screen.height - Config.barConfig.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50) if (centroid.pressPosition.y > root.screen.height - Config.bar.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50)
root.visibilities.clipboard = true; root.visibilities.clipboard = true;
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight && centroid.pressPosition.x > root.screen.width / 5) if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight && centroid.pressPosition.x > root.screen.width / 5 && !root.visibilities.launcher)
if (dragY < -10) { if (dragY < -10) {
root.visibilities.dock = true; root.visibilities.dock = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
if (centroid.pressPosition.x > root.screen.width - Config.barConfig.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -20) { if (centroid.pressPosition.x > root.screen.width - Config.bar.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) {
root.visibilities.sidebar = true; root.visibilities.sidebar = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
if (centroid.pressPosition.x >= root.screen.width - Config.barConfig.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) { if (centroid.pressPosition.x >= root.screen.width - Config.bar.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`); Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
if (centroid.pressPosition.x <= Config.barConfig.border && dragX > 20) { if (centroid.pressPosition.x <= Config.bar.border && dragX > 20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`); Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
} }
} }
PointHandler {
id: drawingHandler
property bool setInitialPoint: false
acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: root.visibilities.isDrawing && (!root.inLeftPanel(root.panels.drawing, hoverHandler.point.position.x, hoverHandler.point.position.y) || !root.panels.drawing.expanded)
onActiveChanged: {
if (!active) {
setInitialPoint = false;
root.drawing.content.endStroke();
} else {
root.panels.drawing.collapse();
}
}
onPointChanged: {
if (!active)
return;
const x = point.position.x;
const y = point.position.y;
const origX = point.pressPosition.x;
const origY = point.pressPosition.y;
if (point.pressedButtons & Qt.RightButton) {
root.drawing.content.clear();
return;
}
if (x === 0 && y === 0 && origX === 0 && origY === 0)
return;
if (!setInitialPoint) {
setInitialPoint = true;
root.drawing.content.beginStroke(origX, origY);
return;
}
root.drawing.content.appendPoint(x, y);
}
}
HoverHandler { HoverHandler {
id: hoverHandler id: hoverHandler
cursorShape: root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, point.position.x, point.position.y) ? Qt.BlankCursor : undefined
onHoveredChanged: { onHoveredChanged: {
if (!hovered) { if (!hovered) {
if (!root.osdShortcutActive) { if (!root.osdShortcutActive) {
@@ -130,7 +174,7 @@ Item {
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} }
if (Config.barConfig.autoHide) if (Config.bar.autoHide)
root.bar.isHovered = false; root.bar.isHovered = false;
} }
} }
@@ -138,12 +182,18 @@ Item {
const x = point.position.x; const x = point.position.x;
const y = point.position.y; const y = point.position.y;
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) { if (root.visibilities.isDrawing) {
root.input.z = 2; if (root.inLeftPanel(root.panels.drawing, x, y) && !(drawingHandler.point.pressedButtons & Qt.LeftButton)) {
root.panels.drawing.expanded = false; root.panels.drawing.expand();
root.drawing.content.hideHover();
return;
}
root.drawing.content.showHover(x, y);
return;
} }
if (!root.visibilities.bar && Config.barConfig.autoHide && y < root.bar.implicitHeight) if (!root.visibilities.bar && Config.bar.autoHide && y < root.bar.implicitHeight)
root.bar.isHovered = true; root.bar.isHovered = true;
if (root.panels.sidebar.width === 0) { if (root.panels.sidebar.width === 0) {
@@ -248,6 +298,8 @@ Item {
root.visibilities.sidebar = false; root.visibilities.sidebar = false;
root.panels.popouts.hasCurrent = false; root.panels.popouts.hasCurrent = false;
root.visibilities.launcher = false; root.visibilities.launcher = false;
} else {
Config.save();
} }
} }
+7 -5
View File
@@ -20,12 +20,13 @@ Item {
id: root id: root
required property Item bar required property Item bar
required property real borderThickness
readonly property alias clipboard: clipboard readonly property alias clipboard: clipboard
readonly property alias dashboard: dashboard readonly property alias dashboard: dashboard
readonly property alias dashboardWrapper: dashboardWrapper readonly property alias dashboardWrapper: dashboardWrapper
readonly property alias dock: dock readonly property alias dock: dock
readonly property alias drawing: drawing readonly property alias drawing: drawing
required property Canvas drawingItem required property var drawingItem
readonly property alias launcher: launcher readonly property alias launcher: launcher
readonly property alias notifications: notifications readonly property alias notifications: notifications
readonly property alias osd: osd readonly property alias osd: osd
@@ -43,7 +44,7 @@ Item {
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.fill: parent anchors.fill: parent
anchors.margins: Config.barConfig.border anchors.margins: borderThickness
anchors.topMargin: bar.implicitHeight anchors.topMargin: bar.implicitHeight
Item { Item {
@@ -99,6 +100,7 @@ Item {
id: popouts id: popouts
anchors.top: parent.top anchors.top: parent.top
borderThickness: root.borderThickness
screen: root.screen screen: root.screen
} }
@@ -182,11 +184,11 @@ Item {
Item { Item {
id: settingsWrapper id: settingsWrapper
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
clip: true clip: true
implicitHeight: settings.implicitHeight * (1 - settings.offsetScale) implicitHeight: settings.implicitHeight
implicitWidth: settings.implicitWidth implicitWidth: settings.implicitWidth
x: (root.width - settings.implicitWidth) / 2
y: (settings.implicitHeight + (root.height - root.bar.implicitHeight - settings.implicitHeight) / 2) * (1 - settings.offsetScale) - settings.implicitHeight - 5
Settings.Wrapper { Settings.Wrapper {
id: settings id: settings
+89
View File
@@ -0,0 +1,89 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Modules.Bar as Bar
Region {
id: root
required property Bar.BarLoader bar
readonly property real borderThickness: win.borderThickness
readonly property alias menuPopoutRegion: menuPopoutRegion
required property Panels panels
required property var win
height: win.height - bar.implicitHeight - win.borderThickness - win.dragMaskPadding * 2
intersection: Intersection.Xor
width: win.width - win.borderThickness * 2 - win.dragMaskPadding * 2
x: win.borderThickness + win.dragMaskPadding
y: bar.implicitHeight + win.dragMaskPadding
R {
panel: root.panels.dashboardWrapper
}
R {
panel: root.panels.launcher
}
R {
id: sidebarRegion
panel: root.panels.sidebar
width: panel.width * (1 - root.panels.sidebar.offsetScale) + root.borderThickness
x: root.win.width - width
}
R {
panel: root.panels.osdWrapper
width: panel.width * (1 - root.panels.osd.offsetScale) + root.borderThickness
x: root.win.width - width
}
R {
panel: root.panels.notifications
}
R {
height: panel.height * (1 - root.panels.utilities.offsetScale) + root.borderThickness
panel: root.panels.utilities
y: root.win.height - height
}
R {
panel: root.panels.popoutsWrapper
}
R {
panel: root.panels.resourcesWrapper
}
R {
panel: root.panels.settingsWrapper
}
R {
panel: root.panels.dock
}
R {
panel: root.panels.clipboard
}
Region {
id: menuPopoutRegion
intersection: Intersection.Subtract
}
component R: Region {
required property Item panel
height: panel.height
intersection: Intersection.Subtract
width: panel.width
x: panel.x + root.borderThickness
y: panel.y + root.bar.implicitHeight
}
}
+111 -79
View File
@@ -18,20 +18,58 @@ CustomWindow {
id: root id: root
readonly property alias bar: bar readonly property alias bar: bar
readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2) readonly property real borderLayoutThickness: hasFullscreen ? 0 : Config.bar.border
readonly property real borderRounding: Config.bar.rounding * (1 - fsTransitionProg)
readonly property real borderThickness: Config.bar.border * (1 - fsTransitionProg)
readonly property int dragMaskPadding: {
if (focusGrab.active)
return 0;
if (monitor?.lastIpcObject.specialWorkspace?.name || monitor?.activeWorkspace.lastIpcObject.windows > 0)
return 0;
return 100;
}
property real fsTransitionProg: hasFullscreen ? 1 : 0
readonly property bool hasFullscreen: {
if (hasSpecialWorkspace) {
const specialName = monitor?.lastIpcObject.specialWorkspace?.name;
if (!specialName)
return false;
const specialWs = Hypr.workspaces.values.find(ws => ws.name === specialName);
return specialWs?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false;
}
return hasFullscreenOnNormalWs;
}
readonly property bool hasFullscreenOnNormalWs: monitor?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false
readonly property bool hasSpecialWorkspace: (monitor?.lastIpcObject.specialWorkspace?.name.length ?? 0) > 0
readonly property alias interactionWrapper: interactions readonly property alias interactionWrapper: interactions
readonly property alias menuRegion: menuPopoutRegion readonly property alias menuRegion: regions.menuPopoutRegion
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
property var root: Quickshell.shellDir property var root: Quickshell.shellDir
readonly property real sdfBorderOffset: 2 * fsTransitionProg
readonly property real shadowOpacity: 0.7 * (1 - fsTransitionProg)
property color surfaceColor: DynamicColors.tPalette.m3surface
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
// WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.settings ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
color: "transparent" color: "transparent"
contentItem.focus: true contentItem.focus: true
mask: visibilities.isDrawing ? null : region mask: visibilities.isDrawing ? null : (hasFullscreen ? emptyRegion : regions)
name: "Bar" name: "Bar"
Behavior on fsTransitionProg {
Anim {
}
}
Behavior on surfaceColor {
CAnim {
}
}
contentItem.Keys.onEscapePressed: { contentItem.Keys.onEscapePressed: {
if (Config.barConfig.autoHide) if (Config.bar.autoHide)
visibilities.bar = false; visibilities.bar = false;
visibilities.launcher = false; visibilities.launcher = false;
visibilities.sidebar = false; visibilities.sidebar = false;
@@ -56,20 +94,27 @@ CustomWindow {
} }
Region { Region {
id: menuPopoutRegion id: emptyRegion
intersection: Intersection.Subtract height: panels.notifications.height
width: panels.notifications.width
x: panels.notifications.x + root.borderThickness
y: panels.notifications.y + bar.implicitHeight
Region {
height: panels.osd.height
width: panels.osdWrapper.width * (1 - panels.osd.offsetScale) + root.borderThickness
x: root.width - width
y: panels.osdWrapper.y + bar.implicitHeight
}
} }
Region { Regions {
id: region id: regions
height: root.height - bar.implicitHeight - Config.barConfig.border bar: bar
intersection: Intersection.Xor panels: panels
regions: [...popoutRegions.instances, menuPopoutRegion] win: root
width: root.width - Config.barConfig.border * 2
x: Config.barConfig.border
y: bar.implicitHeight
} }
anchors { anchors {
@@ -79,22 +124,6 @@ CustomWindow {
top: true top: true
} }
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
height: modelData.height
intersection: Intersection.Subtract
width: modelData.width
x: modelData.x + Config.barConfig.border
y: modelData.y + bar.implicitHeight
}
}
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
@@ -143,42 +172,39 @@ CustomWindow {
Binding { Binding {
property: "bar" property: "bar"
target: visibilities target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.barConfig.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.bar.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.barConfig.autoHide when: Config.bar.autoHide
} }
Item { Item {
id: surface
anchors.fill: parent anchors.fill: parent
layer.enabled: true layer.enabled: true
opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1 opacity: root.surfaceColor.a
layer.effect: MultiEffect { layer.effect: MultiEffect {
blurMax: 32 blurMax: 32
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1) shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, Math.max(0, root.shadowOpacity))
shadowEnabled: true shadowEnabled: true
} }
BlobGroup { BlobGroup {
id: blobGroup id: blobGroup
color: DynamicColors.palette.m3surface color: root.surfaceColor
smoothing: Config.barConfig.smoothing smoothing: Config.bar.smoothing
Behavior on color {
CAnim {
}
}
} }
BlobInvertedRect { BlobInvertedRect {
anchors.fill: parent anchors.fill: parent
anchors.margins: -50 anchors.margins: -50
borderBottom: Config.barConfig.border - anchors.margins borderBottom: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderLeft: Config.barConfig.border - anchors.margins borderLeft: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderRight: Config.barConfig.border - anchors.margins borderRight: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderTop: bar.implicitHeight - anchors.margins borderTop: bar.implicitHeight - anchors.margins - root.sdfBorderOffset
group: blobGroup group: blobGroup
radius: Config.barConfig.rounding radius: root.borderRounding
} }
PanelBg { PanelBg {
@@ -191,7 +217,7 @@ CustomWindow {
implicitWidth: panels.dashboard.width implicitWidth: panels.dashboard.width
panel: panels.dashboardWrapper panel: panels.dashboardWrapper
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border x: panels.dashboardWrapper.x + panels.dashboard.x + root.borderThickness
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight
} }
@@ -225,7 +251,7 @@ CustomWindow {
implicitWidth: panels.osd.width implicitWidth: panels.osd.width
panel: panels.osdWrapper panel: panels.osdWrapper
radius: 20 radius: 20
x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border x: panels.osdWrapper.x + panels.osd.x + root.borderThickness
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
} }
@@ -255,7 +281,7 @@ CustomWindow {
implicitWidth: panels.popouts.width implicitWidth: panels.popouts.width
panel: panels.popoutsWrapper panel: panels.popoutsWrapper
radius: panels.popouts.current?.panelRadius ?? Appearance.rounding.normal radius: panels.popouts.current?.panelRadius ?? Appearance.rounding.normal
x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border x: panels.popoutsWrapper.x + panels.popouts.x + root.borderThickness
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
Behavior on extraHeight { Behavior on extraHeight {
@@ -271,24 +297,22 @@ CustomWindow {
implicitHeight: panels.resources.height implicitHeight: panels.resources.height
implicitWidth: panels.resources.width implicitWidth: panels.resources.width
panel: panels.resourcesWrapper panel: panels.resourcesWrapper
radius: Appearance.rounding.normal radius: Appearance.rounding.large
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border x: panels.resourcesWrapper.x + panels.resources.x + root.borderThickness
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
} }
PanelBg { PanelBg {
id: settingsBg id: settingsBg
property real extraHeight: 0.2 property real extraHeight: 0
deformAmount: 0.03 deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight) implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width implicitWidth: panels.settings.width
panel: panels.settings panel: panels.settingsWrapper
radius: Appearance.rounding.large radius: Appearance.rounding.large + Appearance.padding.normal
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller x: panels.settingsWrapper.x + panels.settings.x + root.borderThickness
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 y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
} }
@@ -317,32 +341,37 @@ CustomWindow {
} }
} }
Loader { Drawing {
id: drawingLoader id: drawing
active: visibilities.isDrawing
anchors.fill: parent anchors.fill: parent
layer.enabled: true
visibilities: visibilities
z: 2
sourceComponent: Drawing { layer.effect: MultiEffect {
id: drawing maskEnabled: true
maskInverted: true
maskSource: maskSource
} }
} }
Loader { Item {
id: inputLoader id: maskSource
active: visibilities.isDrawing
anchors.fill: parent anchors.fill: parent
z: 2 layer.enabled: true
visible: false
sourceComponent: DrawingInput { CustomRect {
id: input readonly property int extraWidth: radius
bar: bar color: "white"
drawing: drawingLoader.item implicitHeight: panels.drawing.height
panels: panels implicitWidth: panels.drawing.width + extraWidth
popout: panels.drawing radius: drawingBg.radius
visibilities: visibilities x: -extraWidth + root.borderThickness + panels.drawing.x
y: panels.drawing.y + bar.implicitHeight
} }
} }
@@ -351,9 +380,9 @@ CustomWindow {
anchors.fill: parent anchors.fill: parent
bar: bar bar: bar
drawing: drawingLoader.item borderThickness: root.borderLayoutThickness
drawing: drawing
enabled: true enabled: true
input: inputLoader.item
panels: panels panels: panels
popouts: panels.popouts popouts: panels.popouts
screen: root.screen screen: root.screen
@@ -363,7 +392,8 @@ CustomWindow {
id: panels id: panels
bar: bar bar: bar
drawingItem: drawingLoader.item borderThickness: root.borderThickness
drawingItem: drawing
screen: root.screen screen: root.screen
visibilities: visibilities visibilities: visibilities
@@ -391,7 +421,7 @@ CustomWindow {
resources.transform: Matrix4x4 { resources.transform: Matrix4x4 {
matrix: resourcesBg.deformMatrix matrix: resourcesBg.deformMatrix
} }
settings.transform: Matrix4x4 { settingsWrapper.transform: Matrix4x4 {
matrix: settingsBg.deformMatrix matrix: settingsBg.deformMatrix
} }
sidebar.transform: Matrix4x4 { sidebar.transform: Matrix4x4 {
@@ -407,6 +437,8 @@ CustomWindow {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
enabled: !visibilities.isDrawing
fullscreen: root.hasFullscreen
popouts: panels.popouts popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper popoutsWrapper: panels.popoutsWrapper
screen: root.screen screen: root.screen
@@ -423,7 +455,7 @@ CustomWindow {
implicitHeight: panel.height implicitHeight: panel.height
implicitWidth: panel.width implicitWidth: panel.width
radius: Appearance.rounding.smallest radius: Appearance.rounding.smallest
x: panel.x + Config.barConfig.border x: panel.x + root.borderThickness
y: panel.y + bar.implicitHeight y: panel.y + bar.implicitHeight
} }
} }
+7
View File
@@ -0,0 +1,7 @@
import QtQuick.Effects
MultiEffect {
maskEnabled: true
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
+1 -1
View File
@@ -232,7 +232,7 @@ Singleton {
} }
function setBrightness(value: real): void { function setBrightness(value: real): void {
value = Math.max(0, Math.min(1, value)); value = Math.max(Config.services.minBrightness, Math.min(1, value));
const rounded = Math.round(value * 100); const rounded = Math.round(value * 100);
if (Math.round(brightness * 100) === rounded) if (Math.round(brightness * 100) === rounded)
return; return;
+30 -114
View File
@@ -20,10 +20,8 @@ Singleton {
})) }))
property string previewImageFile: "/tmp/qs-cliphist-preview.img" property string previewImageFile: "/tmp/qs-cliphist-preview.img"
property string previewImageSource: "" property string previewImageSource: ""
property bool previewIsCode: looksLikeCode(previewText)
property bool previewIsImage: false property bool previewIsImage: false
readonly property string previewMarkup: previewIsCode ? generateHighlightedMarkup(previewText) : previewText property list<var> previewText: []
property string previewText: ""
property int previewToken: 0 property int previewToken: 0
property real scoreThreshold: 0.2 property real scoreThreshold: 0.2
@@ -59,119 +57,37 @@ Singleton {
}); });
} }
function generateHighlightedMarkup(text): string {
const raw = String(text ?? "");
const lines = raw.split("\n").map(line => highlightCodeLine(line));
return `<div style="font-family:monospace; white-space:pre-wrap;">${lines.join("<br>")}</div>`;
}
function highlightCodeLine(rawLine): string {
const line = String(rawLine ?? "");
const kwColor = DynamicColors.palette.m3primary;
const strColor = DynamicColors.palette.m3tertiary;
const comColor = Qt.alpha(DynamicColors.palette.m3onSurface, 0.55);
const keywordRe = /\b(function|class|import|const|let|var|if|else|for|while|return|switch|case|break|continue|try|catch|throw|async|await|new|null|true|false|public|private|protected|static|extends|struct|enum)\b/g;
let out = "";
let i = 0;
while (i < line.length) {
const ch = line[i];
// Line comment
if (line.slice(i, i + 2) === "//") {
out += `<span style="color:${comColor};">${escapeHtml(line.slice(i))}</span>`;
break;
}
// Shell/Python-style comment
if (ch === "#" && i === 0) {
out += `<span style="color:${comColor};">${escapeHtml(line.slice(i))}</span>`;
break;
}
// Quoted string
if (ch === "'" || ch === '"' || ch === "`") {
const quote = ch;
let j = i + 1;
while (j < line.length) {
if (line[j] === "\\") {
j += 2;
continue;
}
if (line[j] === quote) {
j += 1;
break;
}
j += 1;
}
out += `<span style="color:${strColor};">${escapeHtml(line.slice(i, j))}</span>`;
i = j;
continue;
}
// Plain code text until next special token
let j = i;
while (j < line.length) {
const two = line.slice(j, j + 2);
const c = line[j];
if (two === "//")
break;
if (c === "'" || c === '"' || c === "`")
break;
if (c === "#" && j === 0)
break;
j += 1;
}
let segment = escapeHtml(line.slice(i, j));
segment = segment.replace(keywordRe, `<span style="color:${kwColor}; font-weight:600;">$1</span>`);
out += segment;
i = j;
}
return out === "" ? "&nbsp;" : out;
}
function looksLikeCode(text): bool {
const t = String(text ?? "").trim();
if (t === "")
return false;
const lines = t.split("\n");
if (lines.length < 2)
return false;
let score = 0;
for (const line of lines) {
if (/^\s{4,}|\t/.test(line))
score += 2;
if (/[{}()[\];]/.test(line))
score += 1;
if (/\b(function|class|import|const|let|var|if|else|for|while|return|switch|case|try|catch|async|await)\b/.test(line))
score += 2;
if (/=>|:=/.test(line))
score += 1;
}
return score >= 4;
}
function paste(entry): void { function paste(entry): void {
Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]); Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]);
} }
function processPreviewLines(text: string): var {
if (text === "")
return [];
const lines = text.split("\n");
let minIndent = Infinity;
for (let i = 0; i < lines.length; i++) {
const m = lines[i].match(/^(\s*)/);
const indent = m ? m[1].length : 0;
if (lines[i].trim().length > 0 && indent < minIndent)
minIndent = indent;
}
if (minIndent === Infinity)
minIndent = 0;
const result = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const indentMatch = line.match(/^(\s*)/);
const indentLen = indentMatch ? indentMatch[1].length : 0;
const stripped = line.slice(Math.min(minIndent, indentLen));
result.push({
line: i + 1,
text: stripped
});
}
return result;
}
function refresh(): void { function refresh(): void {
readProc.buffer = []; readProc.buffer = [];
readProc.running = true; readProc.running = true;
@@ -182,7 +98,7 @@ Singleton {
const token = previewToken; const token = previewToken;
if (!currentEntry) { if (!currentEntry) {
previewText = ""; previewText = [];
previewImageSource = ""; previewImageSource = "";
previewIsImage = false; previewIsImage = false;
return; return;
@@ -269,7 +185,7 @@ Singleton {
onStreamFinished: { onStreamFinished: {
if (previewTextProc.token !== root.previewToken) if (previewTextProc.token !== root.previewToken)
return; return;
root.previewText = this.text; root.previewText = root.processPreviewLines(this.text);
} }
} }
} }
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
Singleton {
id: root
property alias hasNotifications: adapter.hasNotifications
JsonObject {
id: adapter
property bool hasNotifications: false
}
}
+21 -1
View File
@@ -39,6 +39,21 @@ Singleton {
readonly property real uploadSpeed: _uploadSpeed readonly property real uploadSpeed: _uploadSpeed
readonly property real uploadTotal: _uploadTotal readonly property real uploadTotal: _uploadTotal
function resetSampling(): void {
netDevFile.reload();
const content = netDevFile.text();
if (!content)
return;
const data = root.parseNetDev(content);
const now = Date.now();
root._prevRxBytes = data.rx;
root._prevTxBytes = data.tx;
root._prevTimestamp = now;
}
function formatBytes(bytes: real): var { function formatBytes(bytes: real): var {
// Handle negative or invalid values // Handle negative or invalid values
if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) { if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
@@ -156,10 +171,15 @@ Singleton {
Timer { Timer {
interval: Config.dashboard.resourceUpdateInterval interval: Config.dashboard.resourceUpdateInterval
repeat: true repeat: true
running: root.refCount > 0 running: true
triggeredOnStart: true triggeredOnStart: true
onTriggered: { onTriggered: {
if (root.refCount <= 0) {
root.resetSampling();
return;
}
netDevFile.reload(); netDevFile.reload();
const content = netDevFile.text(); const content = netDevFile.text();
if (!content) if (!content)
+26
View File
@@ -0,0 +1,26 @@
pragma Singleton
import Quickshell
Singleton {
property var _regexCache: ({})
function testRegexList(filterList: list<string>, target: string): bool {
const regexChecker = /^\^.*\$$/;
for (const filter of filterList) {
if (regexChecker.test(filter)) {
let re = _regexCache[filter];
if (!re) {
re = new RegExp(filter);
_regexCache[filter] = re;
}
if (re.test(target))
return true;
} else {
if (filter === target)
return true;
}
}
return false;
}
}
+64 -6
View File
@@ -1,17 +1,30 @@
pragma Singleton pragma Singleton
import qs.Config import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import QtQuick import qs.Config
import qs.Paths
Singleton { Singleton {
id: root id: root
property string boardName
property string boardVendor
readonly property string device: {
if (!boardName)
return boardVendor;
if (!boardVendor || boardName.toLowerCase().startsWith(boardVendor.toLowerCase()))
return boardName;
return `${boardVendor} ${boardName}`;
}
property string firmware
property string hostname
property bool isDefaultLogo: true property bool isDefaultLogo: true
property string kernel
property string osId property string osId
property list<string> osIdLike property list<string> osIdLike
property string osLogo property string osLogo: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/logo.svg`)
property string osName property string osName
property string osPrettyName property string osPrettyName
readonly property string shell: Quickshell.env("SHELL").split("/").pop() readonly property string shell: Quickshell.env("SHELL").split("/").pop()
@@ -19,6 +32,12 @@ Singleton {
readonly property string user: Quickshell.env("USER") readonly property string user: Quickshell.env("USER")
readonly property string wm: Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP") readonly property string wm: Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP")
function sanitiseDmi(s: string): string {
const t = s.trim();
const junk = ["to be filled by o.e.m.", "system product name", "system manufacturer", "system version", "default string", "o.e.m.", "not specified", "not applicable", "unknown", "none", ""];
return junk.includes(t.toLowerCase()) ? "" : t;
}
FileView { FileView {
id: osRelease id: osRelease
@@ -35,7 +54,10 @@ Singleton {
root.osIdLike = fd("ID_LIKE").split(" "); root.osIdLike = fd("ID_LIKE").split(" ");
const logo = Quickshell.iconPath(fd("LOGO"), true); const logo = Quickshell.iconPath(fd("LOGO"), true);
if (Config.general.logo) { if (Config.general.logo === "zshell") {
root.osLogo = Qt.resolvedUrl(`${Quickshell.shellDir}/assets/logo.svg`);
root.isDefaultLogo = true;
} else if (Config.general.logo) {
root.osLogo = Quickshell.iconPath(Config.general.logo, true) || "file://" + Paths.absolutePath(Config.general.logo); root.osLogo = Quickshell.iconPath(Config.general.logo, true) || "file://" + Paths.absolutePath(Config.general.logo);
root.isDefaultLogo = false; root.isDefaultLogo = false;
} else if (logo) { } else if (logo) {
@@ -53,6 +75,39 @@ Singleton {
target: Config.general target: Config.general
} }
FileView {
path: "/proc/sys/kernel/osrelease"
onLoaded: root.kernel = text().trim()
}
FileView {
path: "/proc/sys/kernel/hostname"
onLoaded: root.hostname = text().trim()
}
FileView {
path: "/sys/class/dmi/id/sys_vendor"
printErrors: false
onLoaded: root.boardVendor = root.sanitiseDmi(text())
}
FileView {
path: "/sys/class/dmi/id/product_name"
printErrors: false
onLoaded: root.boardName = root.sanitiseDmi(text())
}
FileView {
path: "/sys/class/dmi/id/bios_version"
printErrors: false
onLoaded: root.firmware = root.sanitiseDmi(text())
}
Timer { Timer {
interval: 15000 interval: 15000
repeat: true repeat: true
@@ -69,12 +124,15 @@ Singleton {
onLoaded: { onLoaded: {
const up = parseInt(text().split(" ")[0] ?? 0); const up = parseInt(text().split(" ")[0] ?? 0);
const hours = Math.floor(up / 3600); const days = Math.floor(up / 86400);
const hours = Math.floor((up % 86400) / 3600);
const minutes = Math.floor((up % 3600) / 60); const minutes = Math.floor((up % 3600) / 60);
let str = ""; let str = "";
if (days > 0)
str += `${days} day${days === 1 ? "" : "s"}`;
if (hours > 0) if (hours > 0)
str += `${hours} hour${hours === 1 ? "" : "s"}`; str += `${str ? ", " : ""}${hours} hour${hours === 1 ? "" : "s"}`;
if (minutes > 0 || !str) if (minutes > 0 || !str)
str += `${str ? ", " : ""}${minutes} minute${minutes === 1 ? "" : "s"}`; str += `${str ? ", " : ""}${minutes} minute${minutes === 1 ? "" : "s"}`;
root.uptime = str; root.uptime = str;
-464
View File
@@ -1,464 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
import qs.Config
Singleton {
id: root
property string autoGpuType: "NONE"
property string cpuName: ""
property real cpuPerc
property real cpuTemp
// Individual disks: Array of { mount, used, total, free, perc }
property var disks: []
property real gpuMemTotal: 0
property real gpuMemUsed
property string gpuName
property real gpuPerc
property real gpuTemp
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
property real lastCpuIdle
property real lastCpuTotal
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
property real memTotal
property real memUsed
property int refCount
readonly property real storagePerc: {
let totalUsed = 0;
let totalSize = 0;
for (const disk of disks) {
totalUsed += disk.used;
totalSize += disk.total;
}
return totalSize > 0 ? totalUsed / totalSize : 0;
}
function cleanCpuName(name: string): string {
return name.replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/CPU/gi, "").replace(/\d+th Gen /gi, "").replace(/\d+nd Gen /gi, "").replace(/\d+rd Gen /gi, "").replace(/\d+st Gen /gi, "").replace(/Core /gi, "").replace(/Processor/gi, "").replace(/\s+/g, " ").trim();
}
function cleanGpuName(name: string): string {
return name.replace(/NVIDIA GeForce /gi, "").replace(/NVIDIA /gi, "").replace(/AMD Radeon /gi, "").replace(/AMD /gi, "").replace(/Intel /gi, "").replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/Graphics/gi, "").replace(/\s+/g, " ").trim();
}
function formatKib(kib: real): var {
const mib = 1024;
const gib = 1024 ** 2;
const tib = 1024 ** 3;
if (kib >= tib)
return {
value: kib / tib,
unit: "TiB"
};
if (kib >= gib)
return {
value: kib / gib,
unit: "GiB"
};
if (kib >= mib)
return {
value: kib / mib,
unit: "MiB"
};
return {
value: kib,
unit: "KiB"
};
}
Timer {
interval: Config.dashboard.resourceUpdateInterval
repeat: true
running: root.refCount > 0
triggeredOnStart: true
onTriggered: {
stat.reload();
meminfo.reload();
if (root.gpuType === "GENERIC")
gpuUsage.running = true;
if (root.gpuType === "GENERIC" && root.gpuMemTotal === 0)
oneshotMemAmd.running = true;
}
}
Timer {
interval: 60000 * 120
repeat: true
running: true
triggeredOnStart: true
onTriggered: {
storage.running = true;
}
}
Timer {
interval: Config.dashboard.resourceUpdateInterval * 5
repeat: true
running: root.refCount > 0
triggeredOnStart: true
onTriggered: {
sensors.running = true;
}
}
FileView {
id: cpuinfoInit
path: "/proc/cpuinfo"
onLoaded: {
const nameMatch = text().match(/model name\s*:\s*(.+)/);
if (nameMatch)
root.cpuName = root.cleanCpuName(nameMatch[1]);
}
}
FileView {
id: stat
path: "/proc/stat"
onLoaded: {
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
if (data) {
const stats = data.slice(1).map(n => parseInt(n, 10));
const total = stats.reduce((a, b) => a + b, 0);
const idle = stats[3] + (stats[4] ?? 0);
const totalDiff = total - root.lastCpuTotal;
const idleDiff = idle - root.lastCpuIdle;
const newCpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
root.lastCpuTotal = total;
root.lastCpuIdle = idle;
if (Math.abs(newCpuPerc - root.cpuPerc) >= 0.01)
root.cpuPerc = newCpuPerc;
}
}
}
FileView {
id: meminfo
path: "/proc/meminfo"
onLoaded: {
const data = text();
const total = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
const used = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
if (root.memTotal !== total)
root.memTotal = total;
if (Math.abs(used - root.memUsed) >= 16384)
root.memUsed = used;
}
}
Process {
id: storage
command: ["lsblk", "-b", "-o", "NAME,SIZE,TYPE,FSUSED,FSSIZE", "-P"]
stdout: StdioCollector {
onStreamFinished: {
const diskMap = {}; // Map disk name -> { name, totalSize, used, fsTotal }
const lines = text.trim().split("\n");
for (const line of lines) {
if (line.trim() === "")
continue;
const nameMatch = line.match(/NAME="([^"]+)"/);
const sizeMatch = line.match(/SIZE="([^"]+)"/);
const typeMatch = line.match(/TYPE="([^"]+)"/);
const fsusedMatch = line.match(/FSUSED="([^"]*)"/);
const fssizeMatch = line.match(/FSSIZE="([^"]*)"/);
if (!nameMatch || !typeMatch)
continue;
const name = nameMatch[1];
const type = typeMatch[1];
const size = parseInt(sizeMatch?.[1] || "0", 10);
const fsused = parseInt(fsusedMatch?.[1] || "0", 10);
const fssize = parseInt(fssizeMatch?.[1] || "0", 10);
if (type === "disk") {
// Skip zram (swap) devices
if (name.startsWith("zram"))
continue;
// Initialize disk entry
if (!diskMap[name]) {
diskMap[name] = {
name: name,
totalSize: size,
used: 0,
fsTotal: 0
};
}
} else if (type === "part") {
// Find parent disk (remove trailing numbers/p+numbers)
let parentDisk = name.replace(/p?\d+$/, "");
// For nvme devices like nvme0n1p1, parent is nvme0n1
if (name.match(/nvme\d+n\d+p\d+/))
parentDisk = name.replace(/p\d+$/, "");
// Aggregate partition usage to parent disk
if (diskMap[parentDisk]) {
diskMap[parentDisk].used += fsused;
diskMap[parentDisk].fsTotal += fssize;
}
}
}
const diskList = [];
let totalUsed = 0;
let totalSize = 0;
for (const diskName of Object.keys(diskMap).sort()) {
const disk = diskMap[diskName];
// Use filesystem total if available, otherwise use disk size
const total = disk.fsTotal > 0 ? disk.fsTotal : disk.totalSize;
const used = disk.used;
const perc = total > 0 ? used / total : 0;
// Convert bytes to KiB for consistency with formatKib
diskList.push({
mount: disk.name // Using 'mount' property for compatibility
,
used: used / 1024,
total: total / 1024,
free: (total - used) / 1024,
perc: perc
});
totalUsed += used;
totalSize += total;
}
root.disks = diskList;
}
}
}
Process {
id: gpuNameDetect
command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || lspci 2>/dev/null | grep -i 'vga\\|3d\\|display' | head -1"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const output = text.trim();
if (!output)
return;
// Check if it's from nvidia-smi (clean GPU name)
if (output.toLowerCase().includes("nvidia") || output.toLowerCase().includes("geforce") || output.toLowerCase().includes("rtx") || output.toLowerCase().includes("gtx")) {
root.gpuName = root.cleanGpuName(output);
} else {
// Parse lspci output: extract name from brackets or after colon
const bracketMatch = output.match(/\[([^\]]+)\]/);
if (bracketMatch) {
root.gpuName = root.cleanGpuName(bracketMatch[1]);
} else {
const colonMatch = output.match(/:\s*(.+)/);
if (colonMatch)
root.gpuName = root.cleanGpuName(colonMatch[1]);
}
}
}
}
}
Process {
id: gpuTypeCheck
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
running: !Config.services.gpuType
stdout: StdioCollector {
onStreamFinished: root.autoGpuType = text.trim()
}
}
Process {
id: oneshotMem
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
running: root.gpuType === "NVIDIA" && root.gpuMemTotal === 0
stdout: StdioCollector {
onStreamFinished: {
root.gpuMemTotal = Number(this.text.trim());
oneshotMem.running = false;
}
}
}
Process {
id: oneshotMemAmd
command: ["sh", "-c", "cat /sys/class/drm/card*/device/mem_info_vram_total"]
running: root.gpuType === "GENERIC" && root.gpuMemTotal === 0
stdout: StdioCollector {
onStreamFinished: {
const values = text.trim().split("\n").map(v => parseInt(v, 10)).filter(v => Number.isFinite(v));
if (values.length > 0) {
const totalBytes = values.reduce((a, b) => a + b, 0);
root.gpuMemTotal = totalBytes / (1024 * 1024);
}
oneshotMemAmd.running = false;
}
}
}
Process {
id: gpuUsageNvidia
command: ["/usr/bin/nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu,memory.used", "--format=csv,noheader,nounits", "-lms", "1000"]
running: root.refCount > 0 && root.gpuType === "NVIDIA"
stdout: SplitParser {
onRead: data => {
const parts = String(data).trim().split(/\s*,\s*/);
if (parts.length < 3)
return;
const usageRaw = parseInt(parts[0], 10);
const tempRaw = parseInt(parts[1], 10);
const memRaw = parseInt(parts[2], 10);
if (!Number.isFinite(usageRaw) || !Number.isFinite(tempRaw) || !Number.isFinite(memRaw))
return;
const newGpuPerc = Math.max(0, Math.min(1, usageRaw / 100));
const newGpuTemp = tempRaw;
const newGpuMemUsed = root.gpuMemTotal > 0 ? Math.max(0, Math.min(1, memRaw / root.gpuMemTotal)) : 0;
// Only publish meaningful changes to avoid needless binding churn / repaints
if (Math.abs(root.gpuPerc - newGpuPerc) >= 0.01)
root.gpuPerc = newGpuPerc;
if (Math.abs(root.gpuTemp - newGpuTemp) >= 1)
root.gpuTemp = newGpuTemp;
if (Math.abs(root.gpuMemUsed - newGpuMemUsed) >= 0.01)
root.gpuMemUsed = newGpuMemUsed;
}
}
}
Process {
id: gpuUsage
command: root.gpuType === "GENERIC" ? ["sh", "-c", "paste -d ' ' /sys/class/drm/card*/device/gpu_busy_percent /sys/class/drm/card*/device/mem_info_vram_used"] : ["echo"]
stdout: StdioCollector {
onStreamFinished: {
if (root.gpuType === "GENERIC") {
const lines = text.trim().split("\n");
let percSum = 0;
let memSum = 0;
let count = 0;
for (const line of lines) {
const parts = line.trim().split(/\s+/);
if (parts.length < 2)
continue;
const gpuBusy = parseInt(parts[0], 10);
const memUsed = parseInt(parts[1], 10);
if (!Number.isFinite(gpuBusy) || !Number.isFinite(memUsed))
continue;
percSum += gpuBusy;
memSum += memUsed;
count++;
}
if (count > 0) {
// GPU usage %
root.gpuPerc = (percSum / count) / 100;
// VRAM usage (bytes → MiB → normalized)
const memUsedMiB = memSum / (1024 * 1024);
const newGpuMemUsed = root.gpuMemTotal > 0 ? Math.max(0, Math.min(1, memUsedMiB / root.gpuMemTotal)) : 0;
if (Math.abs(root.gpuMemUsed - newGpuMemUsed) >= 0.01)
root.gpuMemUsed = newGpuMemUsed;
}
} else {
root.gpuPerc = 0;
root.gpuTemp = 0;
}
}
}
}
Process {
id: sensors
command: ["sensors"]
environment: ({
LANG: "C.UTF-8",
LC_ALL: "C.UTF-8"
})
stdout: StdioCollector {
onStreamFinished: {
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
if (!cpuTemp)
// If AMD Tdie pattern failed, try fallback on Tctl
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
if (cpuTemp && Math.abs(parseFloat(cpuTemp[1]) - root.cpuTemp) >= 0.5)
root.cpuTemp = parseFloat(cpuTemp[1]);
if (root.gpuType !== "GENERIC")
return;
let eligible = false;
let sum = 0;
let count = 0;
for (const line of text.trim().split("\n")) {
if (line === "Adapter: PCI adapter")
eligible = true;
else if (line === "")
eligible = false;
else if (eligible) {
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (!match)
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (match) {
sum += parseFloat(match[2]);
count++;
}
}
}
root.gpuTemp = count > 0 ? sum / count : 0;
}
}
}
}
+22 -12
View File
@@ -21,6 +21,13 @@ Searcher {
property bool recentlyChanged property bool recentlyChanged
property bool showPreview: false property bool showPreview: false
function getCategoryFor(w: FileSystemEntry): string {
let category = w.parentDir.slice(Paths.wallsdir.length + 1);
if (category.includes("/"))
category = category.slice(0, category.indexOf("/"));
return category;
}
function getCrop(screen: string): var { function getCrop(screen: string): var {
return root.crops[screen]; return root.crops[screen];
} }
@@ -32,33 +39,26 @@ Searcher {
showPreview = true; showPreview = true;
} }
function setCrop(screen: string, rect: rect, scaledRect: rect, zoom: real): void { function setCrop(screen: string, rect: rect, zoom: real): void {
let updated = Object.assign({}, root.crops);
if (zoom <= 0) if (zoom <= 0)
zoom = 1.0; zoom = 1.0;
else if (zoom > 5.0) else if (zoom > 5.0)
zoom = 5.0; zoom = 5.0;
updated[screen] = { root.crops[screen] = {
x: rect.x, x: rect.x,
y: rect.y, y: rect.y,
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
scaledX: scaledRect.x,
scaledY: scaledRect.y,
scaledWidth: scaledRect.width,
scaledHeight: scaledRect.height,
zoom: zoom zoom: zoom
}; };
// root.crops = updated;
root.crops = updated;
} }
function setWallpaper(path: string): void { function setWallpaper(path: string): void {
actualCurrent = path; actualCurrent = path;
WallpaperPath.currentWallpaperPath = path; WallpaperPath.currentWallpaperPath = path;
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), Qt.rect(0, 0, 0, 0), 1.0)); Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 1.0));
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]); Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
if (Config.general.color.schemeGeneration) if (Config.general.color.schemeGeneration)
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]);
@@ -91,7 +91,7 @@ Searcher {
path: `${Paths.state}/wallpaper-crops.json` path: `${Paths.state}/wallpaper-crops.json`
watchChanges: true watchChanges: true
onAdapterUpdated: writeAdapter() onAdapterUpdated: cropWriteDelay.restart()
onFileChanged: reload() onFileChanged: reload()
JsonAdapter { JsonAdapter {
@@ -101,6 +101,16 @@ Searcher {
} }
} }
Timer {
id: cropWriteDelay
interval: 100
repeat: false
running: false
onTriggered: monitorCrops.writeAdapter()
}
FileSystemModel { FileSystemModel {
id: wallpapers id: wallpapers
+15 -5
View File
@@ -18,6 +18,7 @@ RowLayout {
required property Wrapper popouts required property Wrapper popouts
required property ClipWrapper popoutsWrapper required property ClipWrapper popoutsWrapper
required property ShellScreen screen required property ShellScreen screen
required property bool fullscreen
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -56,15 +57,15 @@ RowLayout {
const item = ch.item; const item = ch.item;
const itemWidth = item.implicitWidth; const itemWidth = item.implicitWidth;
if (id === "audio" && Config.barConfig.popouts.audio) { if (id === "audio" && Config.bar.popouts.audio) {
popouts.currentName = "audio"; popouts.currentName = "audio";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x); popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true; popouts.hasCurrent = true;
} else if (id === "network" && Config.barConfig.popouts.network) { } else if (id === "network" && Config.bar.popouts.network) {
popouts.currentName = "network"; popouts.currentName = "network";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x); popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true; popouts.hasCurrent = true;
} else if (id === "upower" && Config.barConfig.popouts.upower) { } else if (id === "upower" && Config.bar.popouts.upower) {
popouts.currentName = "upower"; popouts.currentName = "upower";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x); popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true; popouts.hasCurrent = true;
@@ -80,8 +81,8 @@ RowLayout {
Repeater { Repeater {
id: repeater id: repeater
// model: Config.barConfig.entries.filted(n => n.index > 50).sort(n => n.index) // model: Config.bar.entries.filted(n => n.index > 50).sort(n => n.index)
model: Config.barConfig.entries model: Config.bar.entries
DelegateChooser { DelegateChooser {
role: "id" role: "id"
@@ -91,6 +92,7 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: HyprsunsetWidget { sourceComponent: HyprsunsetWidget {
visible: !root.fullscreen
} }
} }
} }
@@ -109,6 +111,7 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: Workspaces { sourceComponent: Workspaces {
screen: root.screen screen: root.screen
visible: !root.fullscreen
} }
} }
} }
@@ -120,6 +123,7 @@ RowLayout {
sourceComponent: TrayWidget { sourceComponent: TrayWidget {
loader: root loader: root
popouts: root.popouts popouts: root.popouts
visible: !root.fullscreen
} }
} }
} }
@@ -130,6 +134,7 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: Resources { sourceComponent: Resources {
visibilities: root.visibilities visibilities: root.visibilities
visible: !root.fullscreen
} }
} }
} }
@@ -139,6 +144,7 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: UpdatesWidget { sourceComponent: UpdatesWidget {
visible: !root.fullscreen
} }
} }
} }
@@ -150,6 +156,7 @@ RowLayout {
sourceComponent: NotifBell { sourceComponent: NotifBell {
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities visibilities: root.visibilities
visible: !root.fullscreen
} }
} }
} }
@@ -162,6 +169,7 @@ RowLayout {
loader: root loader: root
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities visibilities: root.visibilities
visible: !root.fullscreen
} }
} }
} }
@@ -172,6 +180,7 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: WindowTitle { sourceComponent: WindowTitle {
bar: root bar: root
visible: !root.fullscreen
} }
} }
} }
@@ -190,6 +199,7 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: MediaWidget { sourceComponent: MediaWidget {
visible: !root.fullscreen
} }
} }
} }
+8 -6
View File
@@ -15,14 +15,15 @@ import qs.Modules.Network
Item { Item {
id: root id: root
readonly property int contentHeight: Config.barConfig.height + padding * 2 readonly property int contentHeight: Config.bar.height + padding * 2
readonly property int exclusiveZone: Config.barConfig.autoHide ? Config.barConfig.border : contentHeight readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentHeight
required property bool fullscreen
property bool isHovered property bool isHovered
readonly property int padding: Math.max(Appearance.padding.smaller, Config.barConfig.border) readonly property int padding: Math.max(Appearance.padding.smaller, Config.bar.border)
required property Wrapper popouts required property Wrapper popouts
required property ClipWrapper popoutsWrapper required property ClipWrapper popoutsWrapper
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeVisible: (!Config.barConfig.autoHide || visibilities.bar || isHovered) readonly property bool shouldBeVisible: !fullscreen && (!Config.bar.autoHide || visibilities.bar || isHovered)
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -30,8 +31,8 @@ Item {
content.item?.checkPopout(x); content.item?.checkPopout(x);
} }
implicitHeight: Config.barConfig.border implicitHeight: fullscreen ? 0 : Config.bar.border
visible: height > Config.barConfig.border visible: height > Config.bar.border
states: State { states: State {
name: "visible" name: "visible"
@@ -75,6 +76,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
sourceComponent: Bar { sourceComponent: Bar {
fullscreen: root.fullscreen
height: root.contentHeight height: root.contentHeight
popouts: root.popouts popouts: root.popouts
popoutsWrapper: root.popoutsWrapper popoutsWrapper: root.popoutsWrapper
+4 -4
View File
@@ -38,11 +38,11 @@ Item {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.margins: Config.barConfig.border + 1 anchors.margins: Config.bar.border + 1
anchors.topMargin: root.bar.implicitHeight + 1 anchors.topMargin: root.bar.implicitHeight + 1
radius: Config.barConfig.border > 0 ? Config.barConfig.rounding : 0 radius: Config.bar.border > 0 ? Config.bar.rounding : 0
topLeftRadius: Config.barConfig.rounding topLeftRadius: Config.bar.rounding
topRightRadius: Config.barConfig.rounding topRightRadius: Config.bar.rounding
} }
} }
} }
+2 -1
View File
@@ -8,6 +8,7 @@ import qs.Config
Item { Item {
id: root id: root
required property real borderThickness
readonly property alias content: content readonly property alias content: content
property real offsetScale: y > 0 || content.hasCurrent ? 0 : 1 property real offsetScale: y > 0 || content.hasCurrent ? 0 : 1
required property ShellScreen screen required property ShellScreen screen
@@ -17,7 +18,7 @@ Item {
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
visible: width > 0 && height > 0 visible: width > 0 && height > 0
x: { x: {
const off = content.currentCenter - Config.barConfig.border - content.nonAnimWidth / 2; const off = content.currentCenter - borderThickness - content.nonAnimWidth / 2;
const diff = parent.width - Math.floor(off + content.nonAnimWidth); const diff = parent.width - Math.floor(off + content.nonAnimWidth);
if (diff < 0) if (diff < 0)
return off + diff; return off + diff;
+172 -92
View File
@@ -71,7 +71,7 @@ Item {
anchors.leftMargin: Appearance.spacing.normal anchors.leftMargin: Appearance.spacing.normal
anchors.top: parent.top anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: ClipHistory.previewIsImage ? Math.max(Math.min(imagePreview.sourceSize.width + imagePreview.anchors.margins * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) : Math.max(Math.min(textPreview.paintedWidth + textPreview.anchors.margins * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) implicitWidth: ClipHistory.previewIsImage ? Math.max(Math.min(imagePreview.sourceSize.width + Appearance.padding.large * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) : Math.max(Math.min(textPreviewColumn.width + textPreviewColumn.anchors.margins * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth)
radius: 25 radius: 25
Behavior on implicitWidth { Behavior on implicitWidth {
@@ -79,25 +79,111 @@ Item {
} }
} }
CustomText { Column {
id: textPreview id: textPreviewColumn
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Appearance.padding.large anchors.leftMargin: 0
anchors.margins: Appearance.padding.normal
anchors.top: parent.top anchors.top: parent.top
font.family: ClipHistory.previewIsCode ? Appearance.font.family.mono : Appearance.font.family.sans
text: ClipHistory.previewMarkup
textFormat: ClipHistory.previewIsCode ? Text.RichText : Text.PlainText
visible: !ClipHistory.previewIsImage visible: !ClipHistory.previewIsImage
width: Config.clipboard.sizes.previewWidth - anchors.margins * 2
wrapMode: Text.Wrap Repeater {
id: processedLines
model: ClipHistory.previewText
Row {
id: lineRow
required property int index
required property var modelData
spacing: Appearance.spacing.normal
CustomRect {
color: lineRow.index % 2 ? DynamicColors.tPalette.m3surfaceContainer : "transparent"
implicitHeight: lineText.paintedHeight + Appearance.padding.extraSmall * 2
implicitWidth: 50
CustomText {
id: number
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: lineRow.modelData.line
}
}
Repeater {
model: lineRow.modelData.text.split("\t").slice(1)
RowLayout {
height: lineText.height
width: lineText.height + Appearance.spacing.extraSmall
Item {
Layout.fillHeight: true
Layout.fillWidth: true
CustomRect {
anchors.centerIn: parent
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: parent.height / 8
implicitWidth: parent.height / 8
radius: Appearance.rounding.full
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
CustomRect {
anchors.centerIn: parent
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: parent.height / 8
implicitWidth: parent.height / 8
radius: Appearance.rounding.full
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
CustomRect {
anchors.centerIn: parent
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: parent.height / 8
implicitWidth: parent.height / 8
radius: Appearance.rounding.full
}
}
}
}
CustomText {
id: lineText
color: DynamicColors.palette.m3onSurface
font.family: ClipHistory.previewIsCode ? Appearance.font.family.mono : Appearance.font.family.sans
height: lineText.paintedHeight + Appearance.padding.extraSmall * 2
text: lineRow.modelData.text.trim()
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
}
}
} }
Image { Image {
id: imagePreview id: imagePreview
anchors.fill: parent anchors.centerIn: parent
anchors.margins: Appearance.padding.large
asynchronous: true asynchronous: true
cache: false cache: false
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
@@ -106,6 +192,7 @@ Item {
smooth: true smooth: true
source: ClipHistory.previewImageSource source: ClipHistory.previewImageSource
visible: ClipHistory.previewIsImage && ClipHistory.previewImageSource !== "" visible: ClipHistory.previewIsImage && ClipHistory.previewImageSource !== ""
width: Math.min(sourceSize.width, Config.clipboard.sizes.previewWidth)
} }
} }
@@ -133,111 +220,103 @@ Item {
flickable: view flickable: view
minimumSize: 0.1 minimumSize: 0.1
} }
delegate: RowLayout { delegate: CustomRect {
id: clipItem id: clipItem
readonly property bool isImage: ClipHistory.entryIsImage(modelData) readonly property bool isImage: ClipHistory.entryIsImage(modelData)
required property string modelData required property string modelData
height: root.itemHeight implicitHeight: root.itemHeight
spacing: Appearance.spacing.small implicitWidth: view.width
width: view.width radius: textLayer.pressed ? (Appearance.rounding.small / 2) : Appearance.rounding.small
CustomClippingRect { Behavior on radius {
id: textRect Anim {
type: Anim.FastEffects
Layout.fillHeight: true
Layout.fillWidth: true
radius: textLayer.pressed ? (Appearance.rounding.small / 2) : Appearance.rounding.small
Behavior on Layout.preferredWidth {
Anim {
type: Anim.FastEffects
}
}
Behavior on radius {
Anim {
type: Anim.FastEffects
}
} }
}
Item { RowLayout {
id: textWrapper anchors.fill: parent
spacing: Appearance.spacing.small
anchors.fill: parent CustomClippingRect {
layer.enabled: true id: textRect
layer.effect: OpacityMask { Layout.fillHeight: true
maskSource: fadeMask Layout.fillWidth: true
}
MaterialIcon { Item {
id: icon id: textWrapper
anchors.left: parent.left anchors.fill: parent
anchors.margins: Appearance.padding.normal layer.enabled: true
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large
text: clipItem.isImage ? "image" : "text_fields"
}
CustomText { layer.effect: OpacityMask {
id: text maskSource: fadeMask
anchors.left: icon.right
anchors.margins: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
text: clipItem.isImage ? qsTr("Image") : ClipHistory.displayText(clipItem.modelData)
}
}
CustomRect {
id: fadeMask
anchors.fill: parent
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: 0.85
} }
GradientStop { MaterialIcon {
color: Qt.rgba(1, 1, 1, 0) id: icon
position: 1.0
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large
text: clipItem.isImage ? "image" : "text_fields"
}
CustomText {
id: text
anchors.left: icon.right
anchors.margins: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
text: clipItem.isImage ? qsTr("Image") : ClipHistory.displayText(clipItem.modelData)
}
}
CustomRect {
id: fadeMask
anchors.fill: parent
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: 0.85
}
GradientStop {
color: Qt.rgba(1, 1, 1, 0)
position: 1.0
}
} }
} }
} }
StateLayer { IconButton {
id: textLayer Layout.fillHeight: true
Layout.margins: Appearance.padding.normal
Layout.preferredWidth: height
icon: "delete"
inactiveColor: Qt.alpha(DynamicColors.palette.m3error, 0.8)
inactiveOnColor: DynamicColors.palette.m3onError
isToggle: false
onClicked: ClipHistory.copy(clipItem.modelData) onClicked: ClipHistory.deleteEntry(clipItem.modelData)
} }
} }
IconButton { StateLayer {
Layout.bottomMargin: Appearance.padding.normal id: textLayer
Layout.fillHeight: true
Layout.preferredWidth: height
Layout.topMargin: Appearance.padding.normal
icon: "content_copy"
isToggle: false
}
IconButton { onClicked: ClipHistory.copy(clipItem.modelData)
Layout.fillHeight: true
Layout.margins: Appearance.padding.normal
Layout.preferredWidth: height
icon: "delete"
inactiveColor: Qt.alpha(DynamicColors.palette.m3error, 0.8)
inactiveOnColor: DynamicColors.palette.m3onError
isToggle: false
} }
} }
highlight: CustomRect { highlight: CustomRect {
@@ -266,6 +345,7 @@ Item {
ClipHistory.currentEntry = currentItem.modelData; ClipHistory.currentEntry = currentItem.modelData;
ClipHistory.refreshPreview(); ClipHistory.refreshPreview();
} }
onVisibleChanged: currentIndex = 0
CustomClippingWrapperRect { CustomClippingWrapperRect {
anchors.fill: parent anchors.fill: parent
+1 -2
View File
@@ -11,7 +11,7 @@ Item {
property int contentHeight property int contentHeight
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.clipboard readonly property bool shouldBeActive: visibilities.clipboard && Config.clipboard.enabled
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
@@ -32,7 +32,6 @@ Item {
active: root.shouldBeActive || root.visible active: root.shouldBeActive || root.visible
anchors.centerIn: parent anchors.centerIn: parent
asynchronous: true
sourceComponent: Content { sourceComponent: Content {
screen: root.screen screen: root.screen
+1 -1
View File
@@ -14,7 +14,7 @@ CustomRect {
required property PersistentProperties visibilities required property PersistentProperties visibilities
color: visibilities.dashboard ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer color: visibilities.dashboard ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: timeText.contentWidth + Appearance.padding.normal * 2 implicitWidth: timeText.contentWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.full radius: Appearance.rounding.full
+2 -2
View File
@@ -10,9 +10,9 @@ import qs.Components
Item { Item {
id: root id: root
required property PersistentProperties dashState
readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2 readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2 readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
required property PersistentProperties state
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: nonAnimHeight implicitHeight: nonAnimHeight
@@ -59,7 +59,7 @@ Item {
index: 0 index: 0
sourceComponent: Dash { sourceComponent: Dash {
state: root.state dashState: root.dashState
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
+3 -3
View File
@@ -10,9 +10,9 @@ import qs.Modules.Dashboard.Dash
GridLayout { GridLayout {
id: root id: root
required property PersistentProperties dashState
readonly property bool dashboardVisible: visibilities.dashboard readonly property bool dashboardVisible: visibilities.dashboard
property int radius: Appearance.rounding.smallest property int radius: Appearance.rounding.smallest
required property PersistentProperties state
required property PersistentProperties visibilities required property PersistentProperties visibilities
columnSpacing: Appearance.spacing.smaller columnSpacing: Appearance.spacing.smaller
@@ -60,7 +60,7 @@ GridLayout {
User { User {
id: user id: user
state: root.state dashState: root.dashState
} }
} }
@@ -86,7 +86,7 @@ GridLayout {
Calendar { Calendar {
id: calendar id: calendar
state: root.state dashState: root.dashState
} }
} }
+290 -146
View File
@@ -1,25 +1,50 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import qs.Components import qs.Components
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
CustomMouseArea { CustomMouseArea {
id: root id: root
readonly property int currMonth: state.currentDate.getMonth() property int activeGrid: 1
readonly property int currYear: state.currentDate.getFullYear() required property PersistentProperties dashState
required property var state property bool initialized: false
property int month1
property int month2
readonly property int realCurrMonth: dashState.currentDate.getMonth()
readonly property int realCurrYear: dashState.currentDate.getFullYear()
property int year1
property int year2
function handleDateChange() {
const currM = activeGrid === 1 ? month1 : month2;
const currY = activeGrid === 1 ? year1 : year2;
if (realCurrMonth !== currM || realCurrYear !== currY) {
monthChangeAnim.direction = (realCurrYear > currY || (realCurrYear === currY && realCurrMonth > currM)) ? -1 : 1;
if (activeGrid === 1) {
month2 = realCurrMonth;
year2 = realCurrYear;
activeGrid = 2;
} else {
month1 = realCurrMonth;
year1 = realCurrYear;
activeGrid = 1;
}
if (initialized)
monthChangeAnim.restart();
}
}
function onWheel(event: WheelEvent): void { function onWheel(event: WheelEvent): void {
if (event.angleDelta.y > 0) if (event.angleDelta.y > 0)
root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1); root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1);
else if (event.angleDelta.y < 0) else if (event.angleDelta.y < 0)
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1); root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1);
} }
acceptedButtons: Qt.MiddleButton acceptedButtons: Qt.MiddleButton
@@ -27,102 +52,160 @@ CustomMouseArea {
anchors.right: parent.right anchors.right: parent.right
implicitHeight: inner.implicitHeight + inner.anchors.margins * 2 implicitHeight: inner.implicitHeight + inner.anchors.margins * 2
onClicked: root.state.currentDate = new Date() Component.onCompleted: {
month1 = realCurrMonth;
year1 = realCurrYear;
month2 = realCurrMonth;
year2 = realCurrYear;
initialized = true;
}
onClicked: root.dashState.currentDate = new Date()
onRealCurrMonthChanged: handleDateChange()
onRealCurrYearChanged: handleDateChange()
SequentialAnimation {
id: monthChangeAnim
property int direction: 0
ScriptAction {
script: {
if (activeGrid === 1) {
titleTranslate1.x = -monthChangeAnim.direction * titleClip.width;
grid1Translate.x = -monthChangeAnim.direction * gridClip.width;
titleTranslate2.x = 0;
grid2Translate.x = 0;
} else {
titleTranslate2.x = -monthChangeAnim.direction * titleClip.width;
grid2Translate.x = -monthChangeAnim.direction * gridClip.width;
titleTranslate1.x = 0;
grid1Translate.x = 0;
}
}
}
ParallelAnimation {
Anim {
property: "x"
target: titleTranslate1
to: root.activeGrid === 1 ? 0 : monthChangeAnim.direction * titleClip.width
type: Anim.DefaultSpatial
}
Anim {
property: "x"
target: grid1Translate
to: root.activeGrid === 1 ? 0 : monthChangeAnim.direction * gridClip.width
type: Anim.DefaultSpatial
}
Anim {
property: "x"
target: titleTranslate2
to: root.activeGrid === 2 ? 0 : monthChangeAnim.direction * titleClip.width
type: Anim.DefaultSpatial
}
Anim {
property: "x"
target: grid2Translate
to: root.activeGrid === 2 ? 0 : monthChangeAnim.direction * gridClip.width
type: Anim.DefaultSpatial
}
}
}
ColumnLayout { ColumnLayout {
id: inner id: inner
anchors.fill: parent anchors.fill: parent
anchors.margins: Appearance.padding.large anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.small spacing: Appearance.spacing.extraSmall
RowLayout { RowLayout {
id: monthNavigationRow id: monthNavigationRow
Layout.fillWidth: true Layout.fillWidth: true
spacing: Appearance.spacing.small spacing: Appearance.spacing.extraSmall
Item { IconButton {
implicitHeight: prevMonthText.implicitHeight + Appearance.padding.small * 2 icon: "chevron_left"
implicitWidth: implicitHeight padding: Appearance.padding.small
type: IconButton.Text
StateLayer { onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1)
id: prevMonthStateLayer
radius: Appearance.rounding.full
onClicked: {
root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1);
}
}
MaterialIcon {
id: prevMonthText
anchors.centerIn: parent
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
text: "chevron_left"
}
} }
Item { Item {
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: monthYearDisplay.implicitHeight + Appearance.padding.small * 2 implicitHeight: monthYearDisplay1.implicitHeight + Appearance.padding.extraSmall * 2
implicitWidth: monthYearDisplay.implicitWidth + Appearance.padding.small * 2 implicitWidth: monthYearDisplay1.implicitWidth + Appearance.padding.large * 2
StateLayer { StateLayer {
anchors.fill: monthYearDisplay color: DynamicColors.palette.m3primary
anchors.leftMargin: -Appearance.padding.normal
anchors.margins: -Appearance.padding.small
anchors.rightMargin: -Appearance.padding.normal
enabled: { enabled: {
const now = new Date(); const now = new Date();
return root.currMonth !== now.getMonth() || root.currYear !== now.getFullYear(); return root.realCurrMonth !== now.getMonth() || root.realCurrYear !== now.getFullYear();
} }
radius: Appearance.rounding.full radius: pressed ? Appearance.rounding.small : Appearance.rounding.large
onClicked: { Behavior on radius {
root.state.currentDate = new Date(); Anim {
type: Anim.DefaultEffects
}
} }
onClicked: root.dashState.currentDate = new Date()
} }
CustomText { Item {
id: monthYearDisplay id: titleClip
anchors.centerIn: parent anchors.fill: parent
color: DynamicColors.palette.m3primary clip: true
font.capitalization: Font.Capitalize
font.pointSize: Appearance.font.size.normal CustomText {
font.weight: 500 id: monthYearDisplay1
text: grid.title
anchors.centerIn: parent
// qmllint enable missing-property
color: DynamicColors.palette.m3primary
// qmllint disable missing-property
text: grid1.item ? grid1.item.title : ""
visible: root.activeGrid === 1 || monthChangeAnim.running
transform: Translate {
id: titleTranslate1
}
}
CustomText {
id: monthYearDisplay2
anchors.centerIn: parent
// qmllint enable missing-property
color: DynamicColors.palette.m3primary
// qmllint disable missing-property
text: grid2.item ? grid2.item.title : ""
visible: root.activeGrid === 2 || monthChangeAnim.running
transform: Translate {
id: titleTranslate2
}
}
} }
} }
Item { IconButton {
implicitHeight: nextMonthText.implicitHeight + Appearance.padding.small * 2 icon: "chevron_right"
implicitWidth: implicitHeight padding: Appearance.padding.small
type: IconButton.Text
StateLayer { onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1)
id: nextMonthStateLayer
radius: Appearance.rounding.full
onClicked: {
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1);
}
}
MaterialIcon {
id: nextMonthText
anchors.centerIn: parent
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
text: "chevron_right"
}
} }
} }
@@ -130,109 +213,170 @@ CustomMouseArea {
id: daysRow id: daysRow
Layout.fillWidth: true Layout.fillWidth: true
locale: grid.locale locale: Qt.locale()
delegate: CustomText { delegate: CustomText {
required property var model required property var model
color: (model.day === 0) ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3onSurfaceVariant color: (model.day === 0) ? DynamicColors.palette.m3tertiary : DynamicColors.palette.m3onSurface
font.weight: 500
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: model.shortName text: Qt.locale("en_US").standaloneDayName(model.day, Locale.ShortFormat)
} }
} }
Item { Item {
id: gridClip
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: grid.implicitHeight clip: true
implicitHeight: grid1.implicitHeight
MonthGrid { Component {
id: grid id: gridComp
anchors.fill: parent Item {
locale: Qt.locale("en_SE") id: internalGridContainer
month: root.currMonth
spacing: 3
year: root.currYear
delegate: Item { property int month
id: dayItem property alias title: internalGrid.title
property int year
required property var model implicitHeight: internalGrid.implicitHeight
implicitHeight: text.implicitHeight + Appearance.padding.small * 2 MonthGrid {
implicitWidth: implicitHeight id: internalGrid
CustomText { anchors.fill: parent
id: text locale: daysRow.locale
month: internalGridContainer.month
spacing: 3
title: `${Qt.locale("en_US").standaloneMonthName(month, Locale.LongFormat)} ${year}`
year: internalGridContainer.year
anchors.centerIn: parent delegate: Item {
color: { id: dayItem
const dayOfWeek = dayItem.model.date.getUTCDay();
if (dayOfWeek === 6)
return DynamicColors.palette.m3secondary;
return DynamicColors.palette.m3onSurfaceVariant; required property var model
implicitHeight: text.implicitHeight + Appearance.padding.normal * 2
implicitWidth: implicitHeight
CustomText {
id: text
anchors.centerIn: parent
color: {
const dayOfWeek = dayItem.model.date.getDay();
if (dayOfWeek === 0)
return DynamicColors.palette.m3tertiary;
return DynamicColors.palette.m3onSurfaceVariant;
}
horizontalAlignment: Text.AlignHCenter
opacity: dayItem.model.today || dayItem.model.month === internalGrid.month ? 1 : 0.4
text: internalGrid.locale.toString(dayItem.model.day)
}
}
}
CustomRect {
id: todayIndicator
property Item today
readonly property Item todayItem: internalGrid.contentItem.children.find(c => c.model.today) ?? null
clip: true
color: DynamicColors.palette.m3primary
implicitHeight: width
implicitWidth: today ? (Math.max(today.implicitWidth, today.implicitHeight) - Appearance.padding.extraSmall) : 0
opacity: todayItem ? 1 : 0
radius: Appearance.rounding.full
scale: todayItem ? 1 : 0.7
x: today ? today.x + (today.width - implicitWidth) / 2 : 0
y: today ? today.y + Appearance.padding.extraSmall / 2 : 0
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
Behavior on x {
Anim {
}
}
Behavior on y {
Anim {
}
}
onTodayItemChanged: {
if (todayItem)
today = todayItem;
}
Coloriser {
colorizationColor: DynamicColors.palette.m3onPrimary
implicitHeight: internalGrid.height
implicitWidth: internalGrid.width
source: internalGrid
sourceColor: DynamicColors.palette.m3onSurface
x: -todayIndicator.x
y: -todayIndicator.y
} }
font.pointSize: Appearance.font.size.normal
font.weight: 500
horizontalAlignment: Text.AlignHCenter
opacity: dayItem.model.today || dayItem.model.month === grid.month ? 1 : 0.4
text: grid.locale.toString(dayItem.model.day)
} }
} }
} }
CustomRect { Loader {
id: todayIndicator id: grid1
property Item today anchors.fill: parent
readonly property Item todayItem: grid.contentItem.children.find(c => c.model.today) ?? null sourceComponent: gridComp
visible: root.activeGrid === 1 || monthChangeAnim.running
clip: true transform: Translate {
color: DynamicColors.palette.m3primary id: grid1Translate
implicitHeight: today?.implicitHeight ?? 0
implicitWidth: today?.implicitWidth ?? 0
opacity: todayItem ? 1 : 0
radius: Appearance.rounding.full
scale: todayItem ? 1 : 0.7
x: today ? today.x + (today.width - implicitWidth) / 2 : 0
y: today?.y ?? 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on x {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on y {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
} }
onTodayItemChanged: { Binding {
if (todayItem) property: "month"
today = todayItem; target: grid1.item
value: root.month1
} }
Coloriser { Binding {
colorizationColor: DynamicColors.palette.m3onPrimary property: "year"
implicitHeight: grid.height target: grid1.item
implicitWidth: grid.width value: root.year1
source: grid }
sourceColor: DynamicColors.palette.m3onSurface }
x: -todayIndicator.x
y: -todayIndicator.y Loader {
id: grid2
anchors.fill: parent
sourceComponent: gridComp
visible: root.activeGrid === 2 || monthChangeAnim.running
transform: Translate {
id: grid2Translate
}
Binding {
property: "month"
target: grid2.item
value: root.month2
}
Binding {
property: "year"
target: grid2.item
value: root.year2
} }
} }
} }
+65 -28
View File
@@ -4,6 +4,7 @@ import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Shapes import QtQuick.Shapes
import QtQuick.Effects
import ZShell.Services import ZShell.Services
import qs.Daemons import qs.Daemons
import qs.Components import qs.Components
@@ -45,49 +46,85 @@ Item {
Shape { Shape {
id: visualizer id: visualizer
readonly property real barW: Math.max(0, (width - gap * (bars - 1)) / bars)
readonly property int bars: Config.services.visualizerBars readonly property int bars: Config.services.visualizerBars
property color color: DynamicColors.palette.m3primary property color color: DynamicColors.palette.m3tertiary
readonly property real gap: Appearance.spacing.small property color fillColor: Qt.alpha(color, 0.25)
anchors.fill: layout anchors.fill: layout
anchors.leftMargin: -(shape.strokeWidth / 2)
layer.enabled: true
asynchronous: true asynchronous: true
data: visualizerBars.instances
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
}
Variants {
id: visualizerBars
model: Array.from({
length: Config.services.visualizerBars
}, (_, i) => i)
ShapePath { ShapePath {
id: visualizerBar id: shape
readonly property real magnitude: value * Config.dashboard.sizes.mediaVisualiserSize
required property int modelData
readonly property real value: Math.max(1e-3, Audio.cava.values[modelData])
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
startX: (visualizer.barW / 2) + modelData * (visualizer.barW + visualizer.gap)
startY: layout.y + layout.height
strokeColor: visualizer.color strokeColor: visualizer.color
strokeWidth: visualizer.barW fillColor: visualizer.fillColor
strokeWidth: 2
Behavior on strokeColor { PathSvg {
CAnim { path: shape.curvePath
}
property string curvePath: {
const values = Audio.cava.values;
const n = values.length;
if (n < 2)
return "";
const h = layout.height + shape.strokeWidth / 2;
const w = layout.width + shape.strokeWidth;
function x(i) {
return i * w / (n - 1);
} }
}
PathLine { function y(i) {
relativeX: 0 return h - values[i] * Config.dashboard.sizes.mediaVisualizerSize;
relativeY: -visualizerBar.magnitude }
let d = `M 0 ${h} `;
d += `L ${x(0)} ${y(0)} `;
for (let i = 0; i < n - 1; ++i) {
const x0 = x(i);
const y0 = y(i);
const x1 = x(i + 1);
const y1 = y(i + 1);
const prev = Math.max(0, i - 1);
const next = Math.min(n - 1, i + 2);
const c1x = x0 + (x1 - x(prev)) / 6;
const c1y = y0 + (y1 - y(prev)) / 6;
const c2x = x1 - (x(next) - x0) / 6;
const c2y = y1 - (y(next) - y0) / 6;
d += `C ${c1x} ${c1y}, ${c2x} ${c2y}, ${x1} ${y1} `;
}
d += `L ${w} ${h} `;
d += `L 0 ${h} Z`;
return d;
} }
} }
} }
CustomRect {
anchors.fill: visualizer
color: Qt.alpha(DynamicColors.palette.m3shadow, 0.2)
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: visualizer
}
}
Shape { Shape {
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
+56 -59
View File
@@ -1,92 +1,89 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import ZShell.Services
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
import qs.Config import qs.Config
Row { Item {
id: root id: root
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.top: parent.top anchors.top: parent.top
padding: Appearance.padding.large implicitWidth: layout.implicitWidth + layout.anchors.margins * 4
spacing: Appearance.spacing.large
Ref { ServiceRef {
service: SystemUsage service: Storage
} }
Resource { ServiceRef {
color: DynamicColors.palette.m3primary service: Memory
icon: "memory"
value: SystemUsage.cpuPerc
} }
Resource { ServiceRef {
color: DynamicColors.palette.m3secondary service: Cpu
icon: "memory_alt"
value: SystemUsage.memPerc
} }
Resource { ServiceRef {
color: DynamicColors.palette.m3tertiary service: Gpu
icon: "gamepad"
value: SystemUsage.gpuPerc
} }
Resource { ColumnLayout {
color: DynamicColors.palette.m3primary id: layout
icon: "host"
value: SystemUsage.gpuMemUsed
}
Resource {
color: DynamicColors.palette.m3secondary
icon: "hard_disk"
value: SystemUsage.storagePerc
}
component Resource: Item {
id: res
required property color color
required property string icon
required property real value
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Appearance.padding.large anchors.margins: Appearance.padding.large
anchors.top: parent.top anchors.top: parent.top
implicitWidth: icon.implicitWidth spacing: Appearance.spacing.normal
Behavior on value { Resource {
Anim { fgColor: DynamicColors.palette.m3primary
duration: Appearance.anim.durations.large icon: "memory"
} value: Cpu.percentage
} }
CustomRect { Resource {
anchors.bottom: icon.top fgColor: DynamicColors.palette.m3secondary
anchors.bottomMargin: Appearance.spacing.small icon: "memory_alt"
anchors.horizontalCenter: parent.horizontalCenter value: Memory.percentage
anchors.top: parent.top }
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitWidth: Config.dashboard.sizes.resourceProgessThickness
radius: Appearance.rounding.full
CustomRect { Resource {
anchors.bottom: parent.bottom fgColor: DynamicColors.palette.m3tertiary
anchors.left: parent.left icon: "gamepad"
anchors.right: parent.right value: Gpu.percentage
color: res.color }
implicitHeight: res.value * parent.height
radius: Appearance.rounding.full Resource {
fgColor: DynamicColors.palette.m3primary
icon: "host"
value: Gpu.memoryUsed / Gpu.memoryTotal
}
Resource {
fgColor: DynamicColors.palette.m3secondary
icon: "hard_disk"
value: Storage.percentage
}
}
component Resource: CircularProgress {
id: res
required property string icon
Layout.fillHeight: true
implicitSize: height
Behavior on clampedVal {
Anim {
} }
} }
MaterialIcon { MaterialIcon {
id: icon anchors.centerIn: parent
color: res.fgColor
anchors.bottom: parent.bottom
color: res.color
text: res.icon text: res.icon
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@ import QtQuick
Row { Row {
id: root id: root
required property PersistentProperties state required property PersistentProperties dashState
padding: 20 padding: 20
spacing: 12 spacing: 12
+1 -1
View File
@@ -32,7 +32,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: Content { sourceComponent: Content {
state: root.dashState dashState: root.dashState
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
+102 -46
View File
@@ -1,33 +1,35 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import ZShell.Components
import qs.Config import qs.Config
import qs.Components import qs.Components
Item { Item {
id: root id: root
readonly property var colors: ["#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#a855f7", "#ec4899", "#ffffff", "#000000"] readonly property var colors1: ["#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4"]
required property Canvas drawing readonly property var colors2: ["#3b82f6", "#a855f7", "#ec4899", "#ffffff", "#000000"]
required property var drawing
required property var visibilities required property var visibilities
required property Wrapper wrapper
function syncFromPenColor() { function syncFromPenColor() {
if (!drawing) if (!drawing)
return; return;
if (!saturationSlider.pressed) if (!saturationSlider.pressed)
saturationSlider.value = drawing.penColor.hsvSaturation; saturationSlider.value = drawing.drawingState.penColor.hsvSaturation;
if (!brightnessSlider.pressed) if (!brightnessSlider.pressed)
brightnessSlider.value = drawing.penColor.hsvValue; brightnessSlider.value = drawing.drawingState.penColor.hsvValue;
} }
function updatePenColorFromHsv() { function updatePenColorFromHsv() {
if (!drawing) if (!drawing)
return; return;
drawing.penColor = Qt.hsva(huePicker.currentHue, saturationSlider.value, brightnessSlider.value, drawing.penColor.a); drawing.drawingState.penColor = Qt.hsva(huePicker.currentHue, saturationSlider.value, brightnessSlider.value, drawing.drawingState.penColor.a);
} }
implicitHeight: column.height + Appearance.padding.larger * 2 implicitHeight: column.height + Appearance.padding.larger * 2
@@ -40,7 +42,7 @@ Item {
root.syncFromPenColor(); root.syncFromPenColor();
} }
target: root.drawing target: root.drawing.drawingState
} }
Column { Column {
@@ -58,13 +60,14 @@ Item {
GradientSlider { GradientSlider {
id: saturationSlider id: saturationSlider
anchors.left: parent.left
anchors.right: parent.right
brightness: brightnessSlider.value brightness: brightnessSlider.value
channel: "saturation" channel: "saturation"
from: 0 from: 0
hue: huePicker.currentHue hue: huePicker.currentHue
icon: "\ue40a" icon: "\ue40a"
implicitHeight: 30 implicitHeight: 30
implicitWidth: palette.width
orientation: Qt.Horizontal orientation: Qt.Horizontal
to: 1 to: 1
@@ -74,12 +77,13 @@ Item {
GradientSlider { GradientSlider {
id: brightnessSlider id: brightnessSlider
anchors.left: parent.left
anchors.right: parent.right
channel: "brightness" channel: "brightness"
from: 0 from: 0
hue: huePicker.currentHue hue: huePicker.currentHue
icon: "\ue1ac" icon: "\ue1ac"
implicitHeight: 30 implicitHeight: 30
implicitWidth: palette.width
orientation: Qt.Horizontal orientation: Qt.Horizontal
saturation: saturationSlider.value saturation: saturationSlider.value
to: 1 to: 1
@@ -87,65 +91,117 @@ Item {
onMoved: root.updatePenColorFromHsv() onMoved: root.updatePenColorFromHsv()
} }
GridLayout { ButtonRow {
id: palette id: row1
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
columns: 5 spacing: Appearance.spacing.normal
rowSpacing: 8
rows: 2
Repeater { Repeater {
model: root.colors model: root.colors1
delegate: Item { delegate: ColorButton {
id: colorCircle row: row1
}
}
}
required property color modelData ButtonRow {
readonly property bool selected: Qt.colorEqual(root.drawing.penColor, modelData) id: row2
Layout.fillWidth: true anchors.left: parent.left
height: 28 anchors.right: parent.right
spacing: Appearance.spacing.normal
CustomRect { Repeater {
anchors.centerIn: parent model: root.colors2
border.color: Qt.rgba(0, 0, 0, 0.25)
border.width: Qt.colorEqual(modelData, "#ffffff") ? 1 : 0
color: colorCircle.modelData
height: 20
radius: width / 2
width: 20
}
CustomRect { delegate: ColorButton {
anchors.centerIn: parent row: row2
border.color: selected ? "#ffffff" : Qt.rgba(1, 1, 1, 0.28)
border.width: selected ? 3 : 1
color: "transparent"
height: parent.height
radius: width / 2
width: parent.height
StateLayer {
onClicked: root.drawing.penColor = colorCircle.modelData
}
}
} }
} }
} }
FilledSlider { FilledSlider {
anchors.left: parent.left
anchors.right: parent.right
from: 1 from: 1
icon: "border_color" icon: "border_color"
implicitHeight: 30 implicitHeight: 30
implicitWidth: palette.width
multiplier: 1 multiplier: 1
orientation: Qt.Horizontal orientation: Qt.Horizontal
to: 45 to: 45
value: root.drawing.penWidth value: root.drawing.drawingState.penWidth
onMoved: root.drawing.penWidth = value onMoved: root.drawing.drawingState.penWidth = value
}
ButtonRow {
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
IconTextButton {
fillWidth: true
font.pointSize: Appearance.font.size.normal
icon: "close"
inactiveColor: DynamicColors.palette.m3error
inactiveOnColor: DynamicColors.palette.m3onError
isRound: true
shapeMorph: true
text: "Exit"
onClicked: root.visibilities.isDrawing = false
}
IconTextButton {
fillWidth: true
font.pointSize: Appearance.font.size.normal
icon: "ink_eraser"
inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: DynamicColors.palette.m3onSurfaceVariant
isRound: true
shapeMorph: true
text: "Clear"
onClicked: root.drawing.content.clear()
}
} }
} }
IconButton {
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
checked: root.wrapper.pinned
icon: "keep"
isToggle: true
shapeMorph: true
onClicked: {
root.wrapper.togglePinned();
}
}
component ColorButton: IconButton {
id: colorButton
readonly property real buttonSize: (row.width - row.spacing * 4) / 5
required property color modelData
required property ButtonRow row
fillWidth: false
font.pointSize: Appearance.font.size.normal
icon: ""
implicitHeight: buttonSize
implicitWidth: buttonSize
inactiveColor: modelData
inactiveOnColor: DynamicColors.on(modelData)
isRound: true
shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
onClicked: root.drawing.drawingState.penColor = modelData
}
} }
+27 -3
View File
@@ -10,13 +10,36 @@ import qs.Daemons
Item { Item {
id: root id: root
required property Canvas drawing required property var drawing
property bool expanded: true property bool expanded: true
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
property bool pinned: false
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.isDrawing readonly property bool shouldBeActive: visibilities.isDrawing
required property var visibilities required property var visibilities
function collapse(): void {
if (pinned)
return;
expanded = false;
}
function expand(): void {
expanded = true;
}
function toggleExpanded(): void {
if (pinned)
return;
expanded = !expanded;
}
function togglePinned(): void {
pinned = !pinned;
}
anchors.leftMargin: (-implicitWidth - 5) * offsetScale anchors.leftMargin: (-implicitWidth - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: root.expanded ? content.implicitWidth : icon.implicitWidth implicitWidth: root.expanded ? content.implicitWidth : icon.implicitWidth
@@ -44,7 +67,7 @@ Item {
Loader { Loader {
id: icon id: icon
active: root.shouldBeActive || root.visible active: (root.shouldBeActive || root.visible) && opacity > 0
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
asynchronous: true asynchronous: true
@@ -63,7 +86,7 @@ Item {
Loader { Loader {
id: content id: content
active: root.shouldBeActive || root.visible active: (root.shouldBeActive || root.visible) && opacity > 0
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
asynchronous: true asynchronous: true
@@ -76,6 +99,7 @@ Item {
sourceComponent: Content { sourceComponent: Content {
drawing: root.drawing drawing: root.drawing
visibilities: root.visibilities visibilities: root.visibilities
wrapper: root
} }
} }
} }
+7 -1
View File
@@ -9,7 +9,7 @@ CustomRect {
property bool tempEnabled: Hyprsunset.enabled property bool tempEnabled: Hyprsunset.enabled
color: root.tempEnabled ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer color: root.tempEnabled ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -24,6 +24,12 @@ CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
animate: true animate: true
color: root.tempEnabled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface color: root.tempEnabled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: root.tempEnabled ? 1 : 0
text: root.tempEnabled ? "lightbulb" : "light_off" text: root.tempEnabled ? "lightbulb" : "light_off"
Behavior on fill {
Anim {
}
}
} }
} }
+1 -1
View File
@@ -10,7 +10,7 @@ import qs.Config
CustomListView { CustomListView {
id: root id: root
required property CustomTextField search required property SearchBar search
required property PersistentProperties visibilities required property PersistentProperties visibilities
highlightFollowsCurrentItem: false highlightFollowsCurrentItem: false
+47 -109
View File
@@ -16,13 +16,13 @@ Item {
readonly property int rounding: Appearance.rounding.large readonly property int rounding: Appearance.rounding.large
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: searchWrapper.height + listWrapper.height + padding * 2 implicitHeight: search.height + listWrapper.height + padding * 2
implicitWidth: listWrapper.width + padding * 2 implicitWidth: listWrapper.width + padding * 2
Item { Item {
id: listWrapper id: listWrapper
anchors.bottom: searchWrapper.top anchors.bottom: search.top
anchors.bottomMargin: root.padding anchors.bottomMargin: root.padding
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: list.height + root.padding implicitHeight: list.height + root.padding
@@ -32,7 +32,7 @@ Item {
id: list id: list
content: root content: root
maxHeight: root.maxHeight - searchWrapper.implicitHeight - root.padding * 3 maxHeight: root.maxHeight - search.implicitHeight - root.padding * 3
padding: root.padding padding: root.padding
panels: root.panels panels: root.panels
rounding: root.rounding rounding: root.rounding
@@ -41,131 +41,69 @@ Item {
} }
} }
CustomRect { SearchBar {
id: searchWrapper id: search
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.margins: root.padding anchors.margins: root.padding
anchors.right: parent.right anchors.right: parent.right
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2) bottomPadding: Appearance.padding.larger
implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight) placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
radius: Appearance.rounding.smallest topPadding: Appearance.padding.larger
MaterialIcon { Component.onCompleted: {
id: searchIcon console.log(search.color);
console.log(search.placeholderTextColor);
anchors.left: parent.left forceActiveFocus();
anchors.leftMargin: root.padding + 10
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSurfaceVariant
text: "search"
} }
Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
CustomTextField { Keys.onEscapePressed: root.visibilities.launcher = false
id: search Keys.onPressed: event => {
if (event.modifiers & Qt.ControlModifier) {
anchors.left: searchIcon.right if (event.key === Qt.Key_J) {
anchors.leftMargin: Appearance.spacing.small
anchors.right: clearIcon.left
anchors.rightMargin: Appearance.spacing.small
bottomPadding: Appearance.padding.larger
placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
topPadding: Appearance.padding.larger
Component.onCompleted: forceActiveFocus()
Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
Keys.onEscapePressed: root.visibilities.launcher = false
Keys.onPressed: event => {
if (!Config.launcher.vimKeybinds)
return;
if (event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_J) {
list.currentList?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_K) {
list.currentList?.decrementCurrentIndex();
event.accepted = true;
}
} else if (event.key === Qt.Key_Tab) {
list.currentList?.incrementCurrentIndex(); list.currentList?.incrementCurrentIndex();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) { } else if (event.key === Qt.Key_K) {
list.currentList?.decrementCurrentIndex(); list.currentList?.decrementCurrentIndex();
event.accepted = true; event.accepted = true;
} }
} else if (event.key === Qt.Key_Tab) {
list.currentList?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
list.currentList?.decrementCurrentIndex();
event.accepted = true;
} }
Keys.onUpPressed: list.currentList?.incrementCurrentIndex() }
onAccepted: { Keys.onUpPressed: list.currentList?.incrementCurrentIndex()
const currentItem = list.currentList?.currentItem; onAccepted: {
if (currentItem) { const currentItem = list.currentList?.currentItem;
if (list.showWallpapers) { if (currentItem) {
if (DynamicColors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent) if (list.showWallpapers) {
Wallpapers.previewColourLock = true; if (DynamicColors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent)
Wallpapers.setWallpaper(currentItem.modelData.path); Wallpapers.previewColourLock = true;
root.visibilities.launcher = false; Wallpapers.setWallpaper(currentItem.modelData.path);
} else if (text.startsWith(Config.launcher.actionPrefix)) { root.visibilities.launcher = false;
if (text.startsWith(`${Config.launcher.actionPrefix}calc `)) } else if (text.startsWith(Config.launcher.actionPrefix)) {
currentItem.onClicked(); if (text.startsWith(`${Config.launcher.actionPrefix}calc `))
else currentItem.onClicked();
currentItem.modelData.onClicked(list.currentList); else
} else { currentItem.modelData.onClicked(list.currentList);
Apps.launch(currentItem.modelData); } else {
root.visibilities.launcher = false; Apps.launch(currentItem.modelData);
} root.visibilities.launcher = false;
} }
} }
Connections {
function onLauncherChanged(): void {
if (!root.visibilities.launcher)
search.text = "";
}
target: root.visibilities
}
} }
MaterialIcon { Connections {
id: clearIcon function onLauncherChanged(): void {
if (!root.visibilities.launcher)
anchors.right: parent.right search.text = "";
anchors.rightMargin: root.padding + 10
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSurfaceVariant
opacity: {
if (!search.text)
return 0;
if (mouse.pressed)
return 0.7;
if (mouse.containsMouse)
return 0.8;
return 1;
}
text: "close"
width: search.text ? implicitWidth : implicitWidth / 2
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
}
Behavior on width {
Anim {
duration: Appearance.anim.durations.small
}
} }
MouseArea { target: root.visibilities
id: mouse
anchors.fill: parent
cursorShape: search.text ? Qt.PointingHandCursor : undefined
hoverEnabled: true
onClicked: search.text = ""
}
} }
} }
} }
+1 -1
View File
@@ -17,7 +17,7 @@ Item {
required property int padding required property int padding
required property var panels required property var panels
required property int rounding required property int rounding
required property CustomTextField search required property SearchBar search
readonly property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `) readonly property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `)
required property PersistentProperties visibilities required property PersistentProperties visibilities
+15
View File
@@ -65,5 +65,20 @@ Item {
width: root.width - icon.width - Appearance.rounding.normal * 2 width: root.width - icon.width - Appearance.rounding.normal * 2
} }
} }
Loader {
id: favoriteIcon
active: root.modelData && Strings.testRegexList(Config.launcher.favoriteApps, root.modelData.id)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
text: "favorite"
}
}
} }
} }
+1 -1
View File
@@ -19,7 +19,7 @@ Searcher {
Variants { Variants {
id: variants id: variants
model: Config.launcher.actions.filter(a => (a.enabled ?? true)) model: Config.launcher.actions.filter(a => (a.enabled ?? true) && (Config.launcher.enableDangerousActions || !(a.dangerous ?? false)))
Action { Action {
} }
+2 -1
View File
@@ -74,7 +74,8 @@ Searcher {
AppDb { AppDb {
id: appDb id: appDb
entries: DesktopEntries.applications.values entries: DesktopEntries.applications.values.filter(a => !Strings.testRegexList(Config.launcher.hiddenApps, a.id))
favoriteApps: Config.launcher.favoriteApps
path: `${Paths.state}/apps.sqlite` path: `${Paths.state}/apps.sqlite`
} }
} }
+2 -2
View File
@@ -22,7 +22,7 @@ PathView {
let outerMargins = 0; let outerMargins = 0;
if ((visibilities.utilities || visibilities.sidebar) && panels.utilities.implicitWidth > outerMargins) if ((visibilities.utilities || visibilities.sidebar) && panels.utilities.implicitWidth > outerMargins)
outerMargins = panels.utilities.implicitWidth; outerMargins = panels.utilities.implicitWidth;
const maxWidth = screen.width - Config.barConfig.rounding * 4 - (barMargins + outerMargins) * 2; const maxWidth = screen.width - Config.bar.rounding * 4 - (barMargins + outerMargins) * 2;
if (maxWidth <= 0) if (maxWidth <= 0)
return 0; return 0;
@@ -37,7 +37,7 @@ PathView {
return visible; return visible;
} }
required property var panels required property var panels
required property CustomTextField search required property SearchBar search
required property var visibilities required property var visibilities
cacheItemCount: 4 cacheItemCount: 4
+15 -11
View File
@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Modules import ZShell.Services
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
import qs.Config import qs.Config
@@ -16,30 +16,34 @@ GridLayout {
rowSpacing: Appearance.spacing.large rowSpacing: Appearance.spacing.large
rows: 1 rows: 1
Ref { ServiceRef {
service: SystemUsage service: Memory
}
ServiceRef {
service: Cpu
} }
Resource { Resource {
Layout.bottomMargin: Appearance.padding.large Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large Layout.topMargin: Appearance.padding.large
colour: DynamicColors.palette.m3primary fgColor: DynamicColors.palette.m3primary
icon: "memory" icon: "memory"
value: SystemUsage.cpuPerc value: Cpu.percentage
} }
Resource { Resource {
Layout.bottomMargin: Appearance.padding.large Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large Layout.topMargin: Appearance.padding.large
colour: DynamicColors.palette.m3secondary fgColor: DynamicColors.palette.m3secondary
icon: "memory_alt" icon: "memory_alt"
value: SystemUsage.memPerc value: Memory.percentage
} }
component Resource: CustomRect { component Resource: CustomRect {
id: res id: res
required property color colour required property color fgColor
required property string icon required property string icon
required property real value required property real value
@@ -58,8 +62,8 @@ GridLayout {
id: circ id: circ
anchors.fill: parent anchors.fill: parent
bgColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3) bgColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
fgColour: res.colour fgColor: res.fgColor
padding: Appearance.padding.large * 3 padding: Appearance.padding.large * 3
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
value: res.value value: res.value
@@ -69,7 +73,7 @@ GridLayout {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
color: res.colour color: res.fgColor
font.pointSize: (circ.arcRadius * 0.7) || 1 font.pointSize: (circ.arcRadius * 0.7) || 1
font.weight: 600 font.weight: 600
text: res.icon text: res.icon
+1 -1
View File
@@ -12,7 +12,7 @@ CustomRect {
readonly property int textWidth: Math.min(metrics.width, 200) readonly property int textWidth: Math.min(metrics.width, 200)
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: layout.implicitWidth + Appearance.padding.normal * 2 implicitWidth: layout.implicitWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.full radius: Appearance.rounding.full
+8 -4
View File
@@ -1,8 +1,7 @@
import Quickshell import Quickshell
import Quickshell.Hyprland
import QtQuick import QtQuick
import qs.Config import qs.Config
import qs.Helpers import qs.Daemons
import qs.Components import qs.Components
CustomRect { CustomRect {
@@ -12,7 +11,7 @@ CustomRect {
required property PersistentProperties visibilities required property PersistentProperties visibilities
color: visibilities.sidebar ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer color: visibilities.sidebar ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -21,14 +20,19 @@ CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
color: root.visibilities.sidebar ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface color: root.visibilities.sidebar ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: root.visibilities.sidebar ? 1 : 0
font.family: "Material Symbols Rounded" font.family: "Material Symbols Rounded"
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4" text: NotifServer.list.length ? "\uf4fe" : "\ue7f4"
Behavior on color { Behavior on color {
CAnim { CAnim {
} }
} }
Behavior on fill {
Anim {
}
}
} }
StateLayer { StateLayer {
+107 -95
View File
@@ -1,9 +1,12 @@
import qs.Components pragma ComponentBehavior: Bound
import qs.Config
import qs.Daemons
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick import QtQuick
import qs.Components
import qs.Config
import qs.Daemons
import qs.Helpers
Item { Item {
id: root id: root
@@ -20,28 +23,26 @@ Item {
if (count === 0) if (count === 0)
return 0; return 0;
let height = (count - 1) * 8; let height = (count - 1) * Appearance.spacing.small;
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++)
height += list.itemAtIndex(i)?.nonAnimHeight ?? 0; height += (list.itemAtIndex(i) as NotifWrapper)?.nonAnimHeight ?? 0;
if (visibilities && panels) { if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || visibilities.dashboard)
if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || visibilities.dashboard) return 0;
return 0;
if (visibilities.osd) { if (visibilities.osd) {
const h = panels.osd.y - 8 * 2 - padding * 2; const h = panels.osd.y - Appearance.spacing.small * 2 - padding * 2;
if (height > h) if (height > h)
height = h; height = h;
}
if (visibilities.session) {
const h = panels.session.y - 8 * 2 - padding * 2;
if (height > h)
height = h;
}
} }
return Math.min((QsWindow.window?.screen?.height ?? 0) - 1 * 2, height + padding * 2); if (visibilities.session) {
const h = panels.session.y - Appearance.spacing.small * 2 - padding * 2;
if (height > h)
height = h;
}
return Math.min(((QsWindow.window as QsWindow)?.screen?.height ?? 0) - 1 * 2, height + padding * 2);
} }
implicitWidth: Config.notifs.sizes.width + padding * 2 implicitWidth: Config.notifs.sizes.width + padding * 2
@@ -60,83 +61,11 @@ Item {
id: list id: list
anchors.fill: parent anchors.fill: parent
cacheBuffer: QsWindow.window?.screen.height ?? 0 cacheBuffer: (QsWindow.window as QsWindow)?.screen.height ?? 0
orientation: Qt.Vertical orientation: Qt.Vertical
spacing: 0 spacing: 0
delegate: Item { delegate: NotifWrapper {
id: wrapper
property int idx
required property int index
required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notif.nonAnimHeight
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : Appearance.spacing.small)
implicitWidth: notif.implicitWidth
ListView.onRemove: removeAnim.start()
onIndexChanged: {
if (index !== -1)
idx = index;
}
SequentialAnimation {
id: removeAnim
PropertyAction {
property: "ListView.delayRemove"
target: wrapper
value: true
}
PropertyAction {
property: "enabled"
target: wrapper
value: false
}
PropertyAction {
property: "implicitHeight"
target: wrapper
value: 0
}
PropertyAction {
property: "z"
target: wrapper
value: 1
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "x"
target: notif
to: (notif.x >= 0 ? Config.notifs.sizes.width : -Config.notifs.sizes.width) * 2
}
PropertyAction {
property: "ListView.delayRemove"
target: wrapper
value: false
}
}
ClippingRectangle {
anchors.top: parent.top
anchors.topMargin: wrapper.idx === 0 ? 0 : 8
color: "transparent"
implicitHeight: notif.implicitHeight
implicitWidth: notif.implicitWidth
radius: Appearance.rounding.smallest / 2
Notification {
id: notif
modelData: wrapper.modelData
}
}
} }
displaced: Transition { displaced: Transition {
Anim { Anim {
@@ -144,7 +73,15 @@ Item {
} }
} }
model: ScriptModel { model: ScriptModel {
values: NotifServer.popups.filter(n => !n.closed) values: {
const popups = NotifServer.popups.filter(n => !n.closed);
if (!Config.notifs.showInFullscreen) {
if (Hypr.monitorFor(root.panels.screen).activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false)
return [];
}
return popups;
}
} }
move: Transition { move: Transition {
Anim { Anim {
@@ -158,4 +95,79 @@ Item {
duration: Appearance.anim.durations.expressiveDefaultSpatial duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
} }
component NotifWrapper: Item {
id: wrapper
property int idx
required property int index
required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notif.nonAnimHeight
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : Appearance.spacing.small)
implicitWidth: notif.implicitWidth
ListView.onRemove: removeAnim.start()
onIndexChanged: {
if (index !== -1)
idx = index;
}
SequentialAnimation {
id: removeAnim
PropertyAction {
property: "ListView.delayRemove"
target: wrapper
value: true
}
PropertyAction {
property: "enabled"
target: wrapper
value: false
}
PropertyAction {
property: "implicitHeight"
target: wrapper
value: 0
}
PropertyAction {
property: "z"
target: wrapper
value: 1
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "x"
target: notif
to: (notif.x >= 0 ? Config.notifs.sizes.width : -Config.notifs.sizes.width) * 2
}
PropertyAction {
property: "ListView.delayRemove"
target: wrapper
value: false
}
}
ClippingRectangle {
anchors.top: parent.top
anchors.topMargin: wrapper.idx === 0 ? 0 : 8
color: "transparent"
implicitHeight: notif.implicitHeight
implicitWidth: notif.implicitWidth
radius: Appearance.rounding.small
Notification {
id: notif
implicitWidth: root.implicitWidth - root.padding * 2
modelData: wrapper.modelData
}
}
}
} }
+163 -116
View File
@@ -1,15 +1,14 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import qs.Components import QtQuick
import qs.Config import QtQuick.Shapes
import qs.Modules import Quickshell
import Quickshell.Services.Notifications
import ZShell.Components
import qs.Daemons import qs.Daemons
import qs.Helpers import qs.Helpers
import Quickshell import qs.Config
import Quickshell.Widgets import qs.Components
import Quickshell.Services.Notifications
import QtQuick
import QtQuick.Layouts
CustomRect { CustomRect {
id: root id: root
@@ -18,17 +17,16 @@ CustomRect {
readonly property bool hasAppIcon: modelData.appIcon.length > 0 readonly property bool hasAppIcon: modelData.appIcon.length > 0
readonly property bool hasImage: modelData.image.length > 0 readonly property bool hasImage: modelData.image.length > 0
required property NotifServer.Notif modelData required property NotifServer.Notif modelData
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2 readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? Appearance.spacing.extraSmall * 2 + appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: inner.implicitHeight implicitHeight: inner.implicitHeight
implicitWidth: Config.notifs.sizes.width radius: Appearance.rounding.small
radius: 6 x: implicitWidth
x: Config.notifs.sizes.width
Behavior on x { Behavior on x {
Anim { Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
} }
} }
@@ -54,7 +52,7 @@ CustomRect {
return; return;
const actions = root.modelData.actions; const actions = root.modelData.actions;
if (actions?.length === 1) if (actions.length === 1)
actions[0].invoke(); actions[0].invoke();
} }
onEntered: root.modelData.timer.stop() onEntered: root.modelData.timer.stop()
@@ -79,7 +77,7 @@ CustomRect {
if (!containsMouse) if (!containsMouse)
root.modelData.timer.start(); root.modelData.timer.start();
if (Math.abs(root.x) < Config.notifs.sizes.width * Config.notifs.clearThreshold) if (Math.abs(root.x) < root.implicitWidth * Config.notifs.clearThreshold)
root.x = 0; root.x = 0;
else else
root.modelData.popup = false; root.modelData.popup = false;
@@ -89,15 +87,13 @@ CustomRect {
id: inner id: inner
anchors.left: parent.left anchors.left: parent.left
anchors.margins: 8 anchors.margins: Appearance.padding.normal
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
implicitHeight: root.nonAnimHeight implicitHeight: root.nonAnimHeight
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
} }
} }
@@ -112,7 +108,8 @@ CustomRect {
visible: root.hasImage || root.hasAppIcon visible: root.hasImage || root.hasAppIcon
width: Config.notifs.sizes.image width: Config.notifs.sizes.image
sourceComponent: ClippingRectangle { sourceComponent: CustomClippingRect {
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) : DynamicColors.palette.m3secondaryContainer
implicitHeight: Config.notifs.sizes.image implicitHeight: Config.notifs.sizes.image
implicitWidth: Config.notifs.sizes.image implicitWidth: Config.notifs.sizes.image
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -122,8 +119,11 @@ CustomRect {
asynchronous: true asynchronous: true
cache: false cache: false
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
mipmap: true
source: Qt.resolvedUrl(root.modelData.image) source: Qt.resolvedUrl(root.modelData.image)
sourceSize: {
const size = Config.notifs.sizes.image * ((QsWindow.window as QsWindow)?.devicePixelRatio ?? 1);
return Qt.size(size, size);
}
} }
} }
} }
@@ -153,8 +153,9 @@ CustomRect {
height: Math.round(parent.width * 0.6) height: Math.round(parent.width * 0.6)
width: Math.round(parent.width * 0.6) width: Math.round(parent.width * 0.6)
sourceComponent: CustomIcon { sourceComponent: ColoredIcon {
anchors.fill: parent anchors.fill: parent
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
layer.enabled: root.modelData.appIcon.endsWith("symbolic") layer.enabled: root.modelData.appIcon.endsWith("symbolic")
source: Quickshell.iconPath(root.modelData.appIcon) source: Quickshell.iconPath(root.modelData.appIcon)
} }
@@ -163,34 +164,67 @@ CustomRect {
Loader { Loader {
active: !root.hasAppIcon active: !root.hasAppIcon
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: -18 * 0.02 anchors.verticalCenterOffset: 1
anchors.verticalCenterOffset: 18 * 0.02
asynchronous: true asynchronous: true
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: 18
text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency) text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
} }
} }
} }
} }
Shape {
id: progressIndicator
anchors.centerIn: appIcon
height: appIcon.implicitHeight + progressShape.strokeWidth * 2
preferredRendererType: Shape.CurveRenderer
width: appIcon.implicitWidth + progressShape.strokeWidth * 2
ShapePath {
id: progressShape
capStyle: ShapePath.RoundCap
fillColor: "transparent"
strokeColor: DynamicColors.palette.m3primary
strokeWidth: 2
PathAngleArc {
id: progressArc
centerX: progressIndicator.width / 2
centerY: progressIndicator.height / 2
radiusX: progressIndicator.width / 2 - Appearance.padding.extraSmall / 2
radiusY: progressIndicator.height / 2 - Appearance.padding.extraSmall / 2
startAngle: -90
sweepAngle: ((root.modelData.hints.value ?? 0) / 100) * 360
Behavior on sweepAngle {
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
}
}
}
}
}
CustomText { CustomText {
id: appName id: appName
anchors.left: image.right anchors.left: image.right
anchors.leftMargin: 10 anchors.leftMargin: Appearance.spacing.small
anchors.top: parent.top anchors.top: parent.top
animate: true animate: true
color: DynamicColors.palette.m3onSurfaceVariant color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
maximumLineCount: 1 maximumLineCount: 1
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
text: appNameMetrics.elidedText text: appNameMetrics.elidedText
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
} }
@@ -199,9 +233,8 @@ CustomRect {
id: appNameMetrics id: appNameMetrics
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3 elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - Appearance.spacing.small * 3
font.family: appName.font.family font: appName.font
font.pointSize: appName.font.pointSize
text: root.modelData.appName text: root.modelData.appName
} }
@@ -209,9 +242,10 @@ CustomRect {
id: summary id: summary
anchors.left: image.right anchors.left: image.right
anchors.leftMargin: 10 anchors.leftMargin: Appearance.spacing.small
anchors.top: parent.top anchors.top: parent.top
animate: true animate: true
font.pointSize: Appearance.font.size.small
height: implicitHeight height: implicitHeight
maximumLineCount: 1 maximumLineCount: 1
text: summaryMetrics.elidedText text: summaryMetrics.elidedText
@@ -225,6 +259,9 @@ CustomRect {
when: root.expanded when: root.expanded
PropertyChanges { PropertyChanges {
body.anchors.topMargin: Appearance.spacing.extraSmall
bodyPreview.anchors.topMargin: Appearance.spacing.extraSmall
summary.anchors.topMargin: Appearance.spacing.extraSmall
summary.maximumLineCount: undefined summary.maximumLineCount: undefined
} }
@@ -239,10 +276,11 @@ CustomRect {
target: summary target: summary
} }
AnchorAnimation { Anim {
duration: MaterialEasing.expressiveEffectsTime property: "topMargin"
easing.bezierCurve: MaterialEasing.expressiveEffects }
easing.type: Easing.BezierSpline
AnchorAnim {
} }
} }
} }
@@ -251,9 +289,8 @@ CustomRect {
id: summaryMetrics id: summaryMetrics
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3 elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - Appearance.spacing.small * 3
font.family: summary.font.family font: summary.font
font.pointSize: summary.font.pointSize
text: root.modelData.summary text: root.modelData.summary
} }
@@ -261,10 +298,9 @@ CustomRect {
id: timeSep id: timeSep
anchors.left: summary.right anchors.left: summary.right
anchors.leftMargin: 7 anchors.leftMargin: Appearance.spacing.small
anchors.top: parent.top anchors.top: parent.top
color: DynamicColors.palette.m3onSurfaceVariant color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
text: "•" text: "•"
states: State { states: State {
@@ -277,10 +313,7 @@ CustomRect {
} }
} }
transitions: Transition { transitions: Transition {
AnchorAnimation { AnchorAnim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline
} }
} }
} }
@@ -289,11 +322,11 @@ CustomRect {
id: time id: time
anchors.left: timeSep.right anchors.left: timeSep.right
anchors.leftMargin: 7 anchors.leftMargin: Appearance.spacing.small
anchors.top: parent.top anchors.top: parent.top
animate: true animate: true
color: DynamicColors.palette.m3onSurfaceVariant color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10 font.pointSize: Appearance.font.size.small
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
text: root.modelData.timeStr text: root.modelData.timeStr
} }
@@ -303,25 +336,33 @@ CustomRect {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
implicitHeight: expandIcon.height anchors.topMargin: -Appearance.padding.extraSmall
implicitWidth: expandIcon.height implicitHeight: expandIcon.implicitHeight
implicitWidth: expandIcon.implicitHeight
StateLayer { StateLayer {
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
radius: Appearance.rounding.full radius: Appearance.rounding.full
onClicked: { onClicked: root.expanded = !root.expanded
root.expanded = !root.expanded;
}
} }
MaterialIcon { MaterialIcon {
id: expandIcon id: expandIcon
anchors.centerIn: parent anchors.centerIn: parent
animate: true anchors.verticalCenterOffset: root.expanded ? -1 : 1
font.pointSize: 13 rotation: root.expanded ? 180 : 0
text: root.expanded ? "expand_less" : "expand_more" text: "expand_more"
Behavior on anchors.verticalCenterOffset {
Anim {
}
}
Behavior on rotation {
Anim {
}
}
} }
} }
@@ -330,17 +371,18 @@ CustomRect {
anchors.left: summary.left anchors.left: summary.left
anchors.right: expandBtn.left anchors.right: expandBtn.left
anchors.rightMargin: 7 anchors.rightMargin: Appearance.spacing.small
anchors.top: summary.bottom anchors.top: summary.bottom
animate: true animate: true
color: DynamicColors.palette.m3onSurfaceVariant color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10 font.pointSize: Appearance.font.size.small
opacity: root.expanded ? 0 : 1 opacity: root.expanded ? 0 : 1
text: bodyPreviewMetrics.elidedText text: bodyPreviewMetrics.elidedText
textFormat: Text.MarkdownText textFormat: Text.MarkdownText
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
} }
@@ -350,8 +392,7 @@ CustomRect {
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: bodyPreview.width elideWidth: bodyPreview.width
font.family: bodyPreview.font.family font: bodyPreview.font
font.pointSize: bodyPreview.font.pointSize
text: root.modelData.body text: root.modelData.body
} }
@@ -360,11 +401,10 @@ CustomRect {
anchors.left: summary.left anchors.left: summary.left
anchors.right: expandBtn.left anchors.right: expandBtn.left
anchors.rightMargin: 7 anchors.rightMargin: Appearance.spacing.small
anchors.top: summary.bottom anchors.top: summary.bottom
animate: true animate: true
color: DynamicColors.palette.m3onSurfaceVariant color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
height: text ? implicitHeight : 0 height: text ? implicitHeight : 0
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
text: root.modelData.body text: root.modelData.body
@@ -373,6 +413,7 @@ CustomRect {
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
@@ -385,83 +426,89 @@ CustomRect {
} }
} }
RowLayout { ButtonRow {
id: actions id: actions
anchors.horizontalCenter: parent.horizontalCenter anchors.left: parent.left
anchors.right: parent.right
anchors.top: body.bottom anchors.top: body.bottom
anchors.topMargin: 7 anchors.topMargin: Appearance.spacing.small
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
spacing: 10 spacing: Appearance.spacing.extraSmall
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
Action { IconButton {
modelData: QtObject { enabled: root.expanded
readonly property string text: qsTr("Close") fillWidth: root.modelData.actions.length === 0
icon: "close"
inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
isRound: true
padding: Appearance.padding.extraSmall
shapeMorph: true
function invoke(): void { onClicked: root.modelData.close()
root.modelData.close();
}
}
} }
Repeater { Repeater {
model: root.modelData.actions model: root.modelData.actions
delegate: Component { TextButton {
Action { required property var modelData
enabled: root.expanded
fillWidth: true
font: {
const f = Qt.font(font);
f.pointSize = Appearance.font.size.small;
return f;
} }
implicitWidth: label.implicitWidth
inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
isRound: true
label.anchors.centerIn: undefined
label.anchors.left: left
label.anchors.margins: Appearance.padding.normal
label.anchors.right: right
label.anchors.verticalCenter: verticalCenter
label.elide: Text.ElideRight
label.horizontalAlignment: Text.AlignHCenter
shapeMorph: true
text: modelData.text
onClicked: modelData.invoke()
}
}
IconButton {
enabled: root.expanded
fillWidth: root.modelData.actions.length === 0
icon: copyTimer.running ? "inventory" : "content_copy"
inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
isRound: true
label.animate: true
padding: Appearance.padding.extraSmall
shapeMorph: true
onClicked: {
Quickshell.clipboardText = root.modelData.body;
copyTimer.restart();
}
Timer {
id: copyTimer
interval: 3000
} }
} }
} }
} }
} }
component Action: CustomRect {
id: action
required property var modelData
Layout.preferredHeight: actionText.height + 4 * 2
Layout.preferredWidth: actionText.width + 8 * 2
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: actionText.height + 4 * 2
implicitWidth: actionText.width + 8 * 2
radius: Appearance.rounding.full
StateLayer {
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurface
radius: Appearance.rounding.full
onClicked: {
action.modelData.invoke();
}
}
CustomText {
id: actionText
anchors.centerIn: parent
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
text: actionTextMetrics.elidedText
}
TextMetrics {
id: actionTextMetrics
elide: Text.ElideRight
elideWidth: {
const numActions = root.modelData.actions.length + 1;
return (inner.width - actions.spacing * (numActions - 1)) / numActions - 8 * 2;
}
font.family: actionText.font.family
font.pointSize: actionText.font.pointSize
text: action.modelData.text
}
}
} }
+2 -2
View File
@@ -13,7 +13,7 @@ Item {
id: layout id: layout
anchors.fill: parent anchors.fill: parent
spacing: 8 spacing: Appearance.spacing.small
CustomRect { CustomRect {
Layout.fillHeight: true Layout.fillHeight: true
@@ -29,7 +29,7 @@ Item {
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 8 - layout.spacing Layout.topMargin: Appearance.padding.normal - layout.spacing
color: DynamicColors.tPalette.m3outlineVariant color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: 1 implicitHeight: 1
} }
+2 -2
View File
@@ -17,7 +17,7 @@ Item {
required property var visibilities required property var visibilities
anchors.fill: parent anchors.fill: parent
anchors.margins: 8 anchors.margins: Appearance.padding.normal
Component.onCompleted: NotifServer.list.forEach(n => n.popup = false) Component.onCompleted: NotifServer.list.forEach(n => n.popup = false)
@@ -156,7 +156,7 @@ Item {
Loader { Loader {
active: opacity > 0 active: opacity > 0
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.margins: 8 anchors.margins: Appearance.padding.normal
anchors.right: parent.right anchors.right: parent.right
opacity: root.notifCount > 0 ? 1 : 0 opacity: root.notifCount > 0 ? 1 : 0
scale: root.notifCount > 0 ? 1 : 0.5 scale: root.notifCount > 0 ? 1 : 0.5
@@ -7,7 +7,7 @@ ShapePath {
id: root id: root
readonly property bool flatten: wrapper.height < rounding * 2 readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Config.barConfig.rounding readonly property real rounding: Config.bar.rounding
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property var sidebar required property var sidebar
required property Wrapper wrapper required property Wrapper wrapper
@@ -73,6 +73,7 @@ CustomRect {
CustomSplitButton { CustomSplitButton {
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0] active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
enabled: !Recorder.running enabled: !Recorder.running
menuOnTop: true
menuItems: [ menuItems: [
MenuItem { MenuItem {
@@ -2,9 +2,9 @@ import Quickshell.Bluetooth
import Quickshell.Networking as QSNetwork import Quickshell.Networking as QSNetwork
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import ZShell.Components
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Modules
import qs.Helpers import qs.Helpers
import qs.Daemons import qs.Daemons
@@ -19,74 +19,69 @@ CustomRect {
implicitHeight: layout.implicitHeight + 18 * 2 implicitHeight: layout.implicitHeight + 18 * 2
radius: Appearance.rounding.smallest radius: Appearance.rounding.smallest
ColumnLayout { ButtonRow {
id: layout id: layout
anchors.fill: parent anchors.fill: parent
anchors.margins: 18 anchors.margins: 18
spacing: 10 spacing: Appearance.spacing.extraSmall
RowLayout { Toggle {
Layout.alignment: Qt.AlignHCenter checked: Network.wifiEnabled
spacing: 7 icon: Network.wifiEnabled ? "wifi" : "wifi_off"
visible: QSNetwork.Networking.devices.values.some(n => n.type === QSNetwork.DeviceType.Wifi)
Toggle { onClicked: Network.toggleWifi()
checked: Network.wifiEnabled }
icon: Network.wifiEnabled ? "wifi" : "wifi_off"
visible: QSNetwork.Networking.devices.values.some(n => n.type === QSNetwork.DeviceType.Wifi)
onClicked: Network.toggleWifi() Toggle {
id: toggle
checked: !NotifServer.dnd
icon: NotifServer.dnd ? "notifications_off" : "notifications"
onClicked: NotifServer.dnd = !NotifServer.dnd
}
Toggle {
checked: !Audio.sourceMuted
icon: Audio.sourceMuted ? "mic_off" : "mic"
onClicked: {
const audio = Audio.source?.audio;
if (audio)
audio.muted = !audio.muted;
} }
}
Toggle { Toggle {
id: toggle checked: !Audio.muted
icon: Audio.muted ? "volume_off" : "volume_up"
checked: !NotifServer.dnd onClicked: {
icon: NotifServer.dnd ? "notifications_off" : "notifications" const audio = Audio.sink?.audio;
if (audio)
onClicked: NotifServer.dnd = !NotifServer.dnd audio.muted = !audio.muted;
} }
}
Toggle { Toggle {
checked: !Audio.sourceMuted checked: Bluetooth.defaultAdapter?.enabled ?? false
icon: Audio.sourceMuted ? "mic_off" : "mic" icon: Bluetooth.defaultAdapter?.enabled ? "bluetooth" : "bluetooth_disabled"
visible: Bluetooth.defaultAdapter ?? false
onClicked: { onClicked: {
const audio = Audio.source?.audio; const adapter = Bluetooth.defaultAdapter;
if (audio) if (adapter)
audio.muted = !audio.muted; adapter.enabled = !adapter.enabled;
}
} }
}
Toggle { Toggle {
checked: !Audio.muted checked: GameMode.enabled
icon: Audio.muted ? "volume_off" : "volume_up" icon: GameMode.enabled ? "videogame_asset" : "videogame_asset_off"
onClicked: { onClicked: GameMode.enabled = !GameMode.enabled
const audio = Audio.sink?.audio;
if (audio)
audio.muted = !audio.muted;
}
}
Toggle {
checked: Bluetooth.defaultAdapter?.enabled ?? false
icon: Bluetooth.defaultAdapter?.enabled ? "bluetooth" : "bluetooth_disabled"
visible: Bluetooth.defaultAdapter ?? false
onClicked: {
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.enabled = !adapter.enabled;
}
}
Toggle {
checked: GameMode.enabled
icon: GameMode.enabled ? "videogame_asset" : "videogame_asset_off"
onClicked: GameMode.enabled = !GameMode.enabled
}
} }
} }
@@ -99,19 +94,9 @@ CustomRect {
} }
component Toggle: IconButton { component Toggle: IconButton {
Layout.fillWidth: true fillWidth: true
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0) isRound: true
inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
isToggle: true isToggle: true
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8 shapeMorph: true
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
Behavior on Layout.preferredWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
} }
} }
+1 -1
View File
@@ -10,7 +10,7 @@ Item {
required property var visibilities required property var visibilities
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: Math.max(sidebarPanel.width * (1 - sidebarPanel.offsetScale), content.implicitWidth) implicitWidth: content.implicitWidth
visible: height > 0 visible: height > 0
Content { Content {
+2
View File
@@ -97,7 +97,9 @@ Item {
FilledSlider { FilledSlider {
anchors.fill: parent anchors.fill: parent
from: Config.services.minBrightness
icon: `brightness_${(Math.round(value * 6) + 1)}` icon: `brightness_${(Math.round(value * 6) + 1)}`
to: 1.0
value: root.brightness value: root.brightness
onPressedChanged: { onPressedChanged: {
+22 -12
View File
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import QtQuick.Layouts import QtQuick.Layouts
import ZShell.Services
import qs.Helpers import qs.Helpers
import qs.Modules import qs.Modules
import qs.Config import qs.Config
@@ -15,8 +16,8 @@ CustomRect {
clip: true clip: true
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2 implicitWidth: rowLayout.implicitWidth + Appearance.padding.larger * 2
radius: Appearance.rounding.full radius: Appearance.rounding.full
StateLayer { StateLayer {
@@ -29,11 +30,20 @@ CustomRect {
id: rowLayout id: rowLayout
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: -2
implicitHeight: root.implicitHeight implicitHeight: root.implicitHeight
spacing: Appearance.spacing.smaller spacing: Appearance.spacing.smaller
Ref { ServiceRef {
service: SystemUsage service: Gpu
}
ServiceRef {
service: Cpu
}
ServiceRef {
service: Memory
} }
Resource { Resource {
@@ -41,8 +51,8 @@ CustomRect {
Layout.fillHeight: true Layout.fillHeight: true
icon: "memory" icon: "memory"
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
mainColor: root.visibilities.resources ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3primary mainColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
percentage: SystemUsage.cpuPerc percentage: Cpu.percentage
warningThreshold: 95 warningThreshold: 95
} }
@@ -50,8 +60,8 @@ CustomRect {
Layout.fillHeight: true Layout.fillHeight: true
icon: "memory_alt" icon: "memory_alt"
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
mainColor: root.visibilities.resources ? DynamicColors.palette.m3secondaryContainer : DynamicColors.palette.m3secondary mainColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3secondary
percentage: SystemUsage.memPerc percentage: Memory.percentage
warningThreshold: 80 warningThreshold: 80
} }
@@ -59,16 +69,16 @@ CustomRect {
Layout.fillHeight: true Layout.fillHeight: true
icon: "gamepad" icon: "gamepad"
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
mainColor: root.visibilities.resources ? DynamicColors.palette.m3tertiaryContainer : DynamicColors.palette.m3tertiary mainColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3tertiary
percentage: SystemUsage.gpuPerc percentage: Gpu.percentage
} }
Resource { Resource {
Layout.fillHeight: true Layout.fillHeight: true
icon: "developer_board" icon: "developer_board"
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
mainColor: root.visibilities.resources ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3primary mainColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
percentage: SystemUsage.gpuMemUsed percentage: Gpu.memoryUsed / Gpu.memoryTotal
} }
} }
} }
+134
View File
@@ -0,0 +1,134 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.UPower
import qs.Components
import qs.Config
CustomClippingRect {
id: root
property real animPerc: UPower.displayDevice.percentage
color: DynamicColors.palette.m3secondaryContainer
implicitWidth: 120
radius: Appearance.rounding.large
Behavior on animPerc {
Anim {
}
}
Contents {
id: layout
accentColor: DynamicColors.palette.m3primary
anchors.fill: parent
anchors.margins: Appearance.padding.larger
subTextColor: DynamicColors.palette.m3onSurfaceVariant
textColor: DynamicColors.palette.m3onSurface
}
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
clip: true
color: DynamicColors.palette.m3secondary
implicitHeight: parent.height * root.animPerc
radius: Appearance.rounding.extraSmall
Contents {
accentColor: DynamicColors.palette.m3primaryContainer
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: layout.anchors.margins
anchors.right: parent.right
height: layout.height
subTextColor: DynamicColors.palette.m3secondaryContainer
textColor: DynamicColors.palette.m3onSecondary
}
}
component Contents: ColumnLayout {
id: contents
required property color accentColor
readonly property bool charging: [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state)
required property color subTextColor
required property color textColor
spacing: 0
MaterialIcon {
Layout.leftMargin: -Appearance.padding.extraSmall
color: contents.accentColor
text: "battery_full"
}
CustomText {
Layout.fillWidth: true
color: contents.textColor
text: qsTr("Battery")
}
Item {
Layout.fillHeight: true
}
CustomText {
Layout.alignment: Qt.AlignRight
animate: true
color: contents.subTextColor
text: {
if (UPower.displayDevice.state === UPowerDeviceState.FullyCharged)
return qsTr("Full");
if (contents.charging)
return qsTr("Charging");
const s = UPower.displayDevice.timeToEmpty;
if (s === 0)
return qsTr("...");
const hr = Math.floor(s / 3600);
const min = Math.floor((s % 3600) / 60);
if (hr > 0)
return `${hr}h ${min}m`;
return `${min}m`;
}
}
RowLayout {
Layout.alignment: Qt.AlignRight
Layout.bottomMargin: -Appearance.padding.small
Layout.rightMargin: -Appearance.padding.extraSmall
Layout.topMargin: -Appearance.padding.extraSmall
spacing: Appearance.spacing.extraSmall
MaterialIcon {
color: contents.accentColor
fill: 1
opacity: contents.charging ? 1 : 0
scale: contents.charging ? 1 : 0
text: "bolt"
Behavior on opacity {
Anim {
type: Anim.FastEffects
}
}
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
}
CustomText {
color: contents.accentColor
text: `${Math.round(UPower.displayDevice.percentage * 100)}%`
}
}
}
}
+138
View File
@@ -0,0 +1,138 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomClippingRect {
id: root
required property color accent
required property string icon
required property string label
required property string subLabel
required property real temperature
required property real usage
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Math.max(tempProg.implicitHeight + detailsRow.implicitHeight + Appearance.spacing.large, usageColumn.implicitHeight + usageLabel.implicitHeight) + Appearance.padding.large * 2
implicitWidth: 450
radius: Appearance.rounding.large - Appearance.padding.normal
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
color: Qt.alpha(root.accent, 0.05)
implicitWidth: parent.width * root.usage
Behavior on implicitWidth {
Anim {
}
}
}
CircularProgress {
id: tempProg
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.top: parent.top
fgColor: root.accent
implicitSize: Math.max(icon.implicitWidth, icon.implicitHeight) + Appearance.padding.larger * 2
spacing: Appearance.spacing.extraSmall
strokeWidth: Appearance.padding.extraSmall
value: root.usage
Behavior on clampedVal {
Anim {
}
}
MaterialIcon {
id: icon
anchors.centerIn: parent
color: root.accent
text: root.icon
}
}
ColumnLayout {
anchors.left: tempProg.right
anchors.margins: Appearance.spacing.large
anchors.right: usageColumn.left
anchors.verticalCenter: tempProg.verticalCenter
spacing: Appearance.spacing.extraSmall
CustomText {
color: root.accent
text: root.label
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
text: root.subLabel
}
}
ColumnLayout {
id: detailsRow
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.largeIncreased
spacing: Appearance.spacing.extraSmall
RowLayout {
Layout.leftMargin: -Appearance.padding.extraSmall
spacing: Appearance.spacing.extraSmall
MaterialIcon {
Layout.topMargin: Math.round(fontInfo.pointSize * 0.08)
color: root.temperature > 90 ? DynamicColors.palette.m3error : root.accent
fill: 1
text: root.temperature > 90 ? "thermometer_alert" : "thermometer"
}
CustomText {
text: `${Math.ceil(root.temperature)}°${"C"}`
}
}
CustomProgressBar {
fgColor: root.accent
implicitHeight: Appearance.padding.small
indeterminate: isNaN(root.usage) || isNaN(root.temperature)
value: root.temperature / 100
}
}
Column {
id: usageColumn
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.rightMargin: 32
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: usageLabel
anchors.right: parent.right
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: qsTr("Usage")
}
CustomText {
anchors.right: parent.right
color: root.accent
font.pointSize: Appearance.font.size.extraLarge
font.weight: Font.Medium
text: isNaN(root.usage) ? "...%" : Math.round(root.usage * 100) + "%"
}
}
}
+86
View File
@@ -0,0 +1,86 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Services
import qs.Components
import qs.Config
CustomRect {
id: root
readonly property color accent: DynamicColors.palette.m3tertiary
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + Appearance.padding.large * 2
implicitWidth: layout.implicitWidth + Appearance.padding.extraLargeIncreased * 2
radius: Appearance.rounding.medium
ServiceRef {
service: Memory
}
ColumnLayout {
id: layout
anchors.centerIn: parent
spacing: Appearance.spacing.extraSmall
RowLayout {
Layout.leftMargin: -Appearance.padding.extraSmall
spacing: Appearance.spacing.small
MaterialIcon {
color: root.accent
fill: 1
text: "memory_alt"
}
CustomText {
text: qsTr("Memory")
}
}
CircularProgress {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Appearance.spacing.large
fgColor: root.accent
implicitSize: usageColumn.implicitHeight + thickness + Appearance.padding.largeIncreased * 2
startAngle: -225
sweepAngle: 270
value: Memory.percentage
Behavior on clampedVal {
Anim {
}
}
ColumnLayout {
id: usageColumn
anchors.centerIn: parent
anchors.verticalCenterOffset: Appearance.padding.extraSmall
spacing: 0
CustomText {
Layout.alignment: Qt.AlignHCenter
color: root.accent
font.pointSize: Appearance.font.size.large
text: Math.round(Memory.percentage * 100) + "%"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
text: qsTr("Used")
}
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
text: {
const fmt = UsageFmt.formatKib(Memory.used, Memory.total);
return `${fmt.value.toFixed(1)} / ${Math.floor(fmt.total)} ${fmt.unit}`;
}
}
}
}
+198
View File
@@ -0,0 +1,198 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Internal
import qs.Modules.Resources
import qs.Helpers
import qs.Components
import qs.Config
CustomRect {
id: root
required property Wrapper wrapper
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 220
implicitWidth: 300
radius: Appearance.rounding.large - Appearance.padding.normal
Ref {
service: NetworkUsage
}
ColumnLayout {
id: layout
anchors.bottomMargin: Appearance.padding.larger
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: 0
RowLayout {
spacing: Appearance.spacing.small
MaterialIcon {
color: DynamicColors.palette.m3primary
text: "swap_vert"
}
CustomText {
text: qsTr("Network")
}
}
Item {
Layout.bottomMargin: Appearance.spacing.small
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.larger
SparklineItem {
id: sparkline
property bool initialized: false
property real smoothMax: targetMax
property real targetMax: 1024
anchors.fill: parent
historyLength: NetworkUsage.historyLength
line1: NetworkUsage.uploadBuffer // qmllint disable missing-type
line1Color: DynamicColors.palette.m3secondary
line1FillAlpha: 0.15
line2: NetworkUsage.downloadBuffer // qmllint disable missing-type
line2Color: DynamicColors.palette.m3tertiary
line2FillAlpha: 0.2
maxValue: smoothMax
slideProgress: 1
Behavior on smoothMax {
enabled: sparkline.initialized
Anim {
}
}
Component.onCompleted: {
sparkline.targetMax = Math.max(NetworkUsage.downloadBuffer.maximum, NetworkUsage.uploadBuffer.maximum, 1024);
sparkline.smoothMax = Qt.binding(() => sparkline.targetMax);
sparkline.initialized = true;
}
Connections {
function onValuesChanged(): void {
sparkline.targetMax = Math.max(NetworkUsage.downloadBuffer.maximum, NetworkUsage.uploadBuffer.maximum, 1024);
slideAnim.restart();
}
target: NetworkUsage.downloadBuffer
}
NumberAnimation {
id: slideAnim
duration: Config.dashboard.resourceUpdateInterval
easing.type: Easing.Linear
from: 0
property: "slideProgress"
target: sparkline
to: 1
}
}
// "Collecting data" placeholder
CustomText {
anchors.centerIn: parent
color: DynamicColors.palette.m3outline
text: qsTr("Collecting data...")
visible: NetworkUsage.downloadBuffer.count < 2
}
}
// Download row
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
MaterialIcon {
color: DynamicColors.palette.m3tertiary
text: "download"
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
text: qsTr("Download")
}
Item {
Layout.fillWidth: true
}
CustomText {
color: DynamicColors.palette.m3tertiary
text: {
const fmt = NetworkUsage.formatBytes(NetworkUsage.downloadSpeed ?? 0);
return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
}
}
}
// Upload row
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
MaterialIcon {
color: DynamicColors.palette.m3secondary
text: "upload"
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
text: qsTr("Upload")
}
Item {
Layout.fillWidth: true
}
CustomText {
color: DynamicColors.palette.m3secondary
text: {
const fmt = NetworkUsage.formatBytes(NetworkUsage.uploadSpeed ?? 0);
return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
}
}
}
// Session totals
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
text: "history"
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
text: qsTr("Total")
}
Item {
Layout.fillWidth: true
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
text: {
const down = NetworkUsage.formatBytesTotal(NetworkUsage.downloadTotal ?? 0);
const up = NetworkUsage.formatBytesTotal(NetworkUsage.uploadTotal ?? 0);
return (down && up) ? `${down.value.toFixed(1)}${down.unit} ${up.value.toFixed(1)}${up.unit}` : "↓0.0B ↑0.0B";
}
}
}
}
}
+128
View File
@@ -0,0 +1,128 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Services
import qs.Components
import qs.Config
CustomRect {
id: root
readonly property color accent: DynamicColors.palette.m3secondary
readonly property real percentage: Storage.primaryDisk?.perc ?? 0
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + Appearance.padding.large * 2
implicitWidth: layout.implicitWidth + layout.anchors.margins * 2
radius: Appearance.rounding.large - Appearance.padding.normal
ServiceRef {
service: Storage
}
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
RowLayout {
id: row
Layout.alignment: Qt.AlignHCenter
spacing: Appearance.spacing.large
CircularProgress {
fgColor: root.accent
implicitSize: usageColumn.implicitHeight + thickness + Appearance.padding.large * 2
startAngle: -225
sweepAngle: 270
value: root.percentage
Behavior on clampedVal {
Anim {
}
}
ColumnLayout {
id: usageColumn
anchors.centerIn: parent
spacing: 0
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: root.accent
font.pointSize: Appearance.font.size.medium
text: "hard_drive"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: root.accent
font.pointSize: Appearance.font.size.large
text: Math.round(root.percentage * 100) + "%"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
text: qsTr("Used")
}
}
}
ColumnLayout {
spacing: Appearance.spacing.extraSmall
CustomText {
text: qsTr("Storage")
}
CustomText {
color: root.accent
text: {
if (!Storage.primaryDisk)
return qsTr("No disks detected");
const fmt = UsageFmt.formatKib(Storage.primaryDisk.used, Storage.primaryDisk.total);
return `${fmt.value.toFixed(1)} / ${Math.floor(fmt.total)} ${fmt.unit}`;
}
}
}
}
CustomSplitButton {
Layout.alignment: Qt.AlignHCenter
active: menuItems.find(m => m.modelData === Storage.primaryDisk) ?? menuItems[0] ?? null
enabled: Storage.disks.length
fallbackIcon: "storage"
fallbackText: qsTr("No disks")
menuItems: disks.instances
minLeftWidth: row.implicitWidth * 0.6
type: CustomSplitButton.Tonal
menu.onItemSelected: item => Storage.manualPrimaryDisk = (item as DiskItem).modelData
Variants {
id: disks
model: Storage.disks
DiskItem {
}
}
}
}
component DiskItem: MenuItem {
required property var modelData
activeIcon: "storage"
icon: modelData === Storage.primaryDisk ? "check" : ""
text: modelData.mount
}
}
+74 -743
View File
@@ -1,42 +1,27 @@
import Quickshell
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Services.UPower import ZShell.Services
import ZShell.Internal import qs.Modules.Resources.Cards
import qs.Components
import qs.Helpers import qs.Helpers
import qs.Components
import qs.Config import qs.Config
Item { Item {
id: root id: root
readonly property int minWidth: 400 + 400 + Appearance.spacing.normal + 120 + Appearance.padding.large * 2 required property Wrapper wrapper
readonly property real nonAnimHeight: content.implicitHeight + Appearance.padding.normal * 2
readonly property real nonAnimWidth: Math.max(minWidth, content.implicitWidth) + Appearance.padding.normal * 2
required property real padding
required property PersistentProperties visibilities
function displayTemp(temp: real): string { implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
return `${Math.ceil(temp)}°C`; implicitWidth: content.implicitWidth
}
implicitHeight: nonAnimHeight
implicitWidth: nonAnimWidth
RowLayout { RowLayout {
id: content id: content
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: root.padding anchors.margins: Appearance.padding.normal
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: root.padding
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal spacing: Appearance.spacing.larger
Ref {
service: SystemUsage
}
ColumnLayout { ColumnLayout {
id: mainColumn id: mainColumn
@@ -45,748 +30,94 @@ Item {
spacing: Appearance.spacing.normal spacing: Appearance.spacing.normal
RowLayout { RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal spacing: Appearance.spacing.normal
visible: Config.dashboard.performance.showCpu || (Config.dashboard.performance.showGpu && SystemUsage.gpuType !== "NONE") visible: cpuCard.active || gpuCard.active
HeroCard { WrappedLoader {
Layout.fillWidth: true id: cpuCard
Layout.minimumWidth: 400
Layout.preferredHeight: 150 active: Config.dashboard.performance.showCpu
accentColor: DynamicColors.palette.m3primary
icon: "memory" sourceComponent: HeroCard {
mainLabel: qsTr("Usage") accent: DynamicColors.palette.m3primary
mainValue: `${Math.round(SystemUsage.cpuPerc * 100)}%` icon: "memory"
secondaryLabel: qsTr("Temp") label: qsTr("CPU")
secondaryValue: root.displayTemp(SystemUsage.cpuTemp) subLabel: Cpu.name
temperature: SystemUsage.cpuTemp temperature: Cpu.temperature
title: SystemUsage.cpuName ? `CPU - ${SystemUsage.cpuName}` : qsTr("CPU") usage: Cpu.percentage
usage: SystemUsage.cpuPerc
visible: Config.dashboard.performance.showCpu ServiceRef {
service: Cpu
}
}
} }
HeroCard { WrappedLoader {
Layout.fillWidth: true id: gpuCard
Layout.minimumWidth: 400
Layout.preferredHeight: 150 active: Config.dashboard.performance.showGpu && Gpu.type !== Gpu.None
accentColor: DynamicColors.palette.m3secondary
icon: "desktop_windows" sourceComponent: HeroCard {
mainLabel: qsTr("Usage") accent: DynamicColors.palette.m3secondary
mainValue: `${Math.round(SystemUsage.gpuPerc * 100)}%` icon: "desktop_windows"
secondaryLabel: qsTr("Temp") label: qsTr("GPU")
secondaryValue: root.displayTemp(SystemUsage.gpuTemp) subLabel: Gpu.name
temperature: SystemUsage.gpuTemp temperature: Gpu.temperature
title: SystemUsage.gpuName ? `GPU - ${SystemUsage.gpuName}` : qsTr("GPU") usage: Gpu.percentage
usage: SystemUsage.gpuPerc
visible: Config.dashboard.performance.showGpu && SystemUsage.gpuType !== "NONE" ServiceRef {
service: Gpu
}
}
} }
} }
RowLayout { RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal spacing: Appearance.spacing.normal
visible: Config.dashboard.performance.showMemory || Config.dashboard.performance.showStorage || Config.dashboard.performance.showNetwork visible: storageCard.active || networkCard.active || memoryCard.active
GaugeCard { WrappedLoader {
Layout.fillWidth: !Config.dashboard.performance.showStorage && !Config.dashboard.performance.showNetwork id: storageCard
Layout.minimumWidth: 250
Layout.preferredHeight: 220 active: Config.dashboard.performance.showStorage
accentColor: DynamicColors.palette.m3tertiary
icon: "memory_alt" sourceComponent: StorageCard {
percentage: SystemUsage.memPerc
subtitle: {
const usedFmt = SystemUsage.formatKib(SystemUsage.memUsed);
const totalFmt = SystemUsage.formatKib(SystemUsage.memTotal);
return `${usedFmt.value.toFixed(1)} / ${Math.floor(totalFmt.value)} ${totalFmt.unit}`;
} }
title: qsTr("Memory")
visible: Config.dashboard.performance.showMemory
} }
StorageGaugeCard { WrappedLoader {
Layout.fillWidth: !Config.dashboard.performance.showNetwork id: memoryCard
Layout.minimumWidth: 250
Layout.preferredHeight: 220 active: Config.dashboard.performance.showMemory
visible: Config.dashboard.performance.showStorage
sourceComponent: MemoryCard {
}
} }
NetworkCard { WrappedLoader {
Layout.fillWidth: true id: networkCard
Layout.minimumWidth: 200
Layout.preferredHeight: 220 active: Config.dashboard.performance.showNetwork
visible: Config.dashboard.performance.showNetwork
sourceComponent: NetworkCard {
wrapper: root.wrapper
}
} }
} }
} }
BatteryTank { WrappedLoader {
Layout.preferredHeight: mainColumn.implicitHeight Layout.fillWidth: false
Layout.preferredWidth: 120 active: Battery.isLaptop && Config.dashboard.performance.showBattery
visible: UPower.displayDevice.isLaptopBattery && Config.dashboard.performance.showBattery
sourceComponent: BatteryTank {
}
} }
} }
component BatteryTank: CustomClippingRect { component WrappedLoader: Loader {
id: batteryTank Layout.fillHeight: true
property color accentColor: DynamicColors.palette.m3primary
property real animatedPercentage: 0
property bool isCharging: UPower.displayDevice.state === UPowerDeviceState.Charging
property real percentage: UPower.displayDevice.percentage
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.large - Appearance.padding.normal
Behavior on animatedPercentage {
Anim {
duration: Appearance.anim.durations.large
}
}
Component.onCompleted: animatedPercentage = percentage
onPercentageChanged: animatedPercentage = percentage
// Background Fill
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: Qt.alpha(batteryTank.accentColor, 0.15)
height: parent.height * batteryTank.animatedPercentage
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.small
// Header Section
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
MaterialIcon {
color: batteryTank.accentColor
font.pointSize: Appearance.font.size.large
text: {
if (!UPower.displayDevice.isLaptopBattery) {
if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "energy_savings_leaf";
if (PowerProfiles.profile === PowerProfile.Performance)
return "rocket_launch";
return "balance";
}
if (UPower.displayDevice.state === UPowerDeviceState.FullyCharged)
return "battery_full";
const perc = UPower.displayDevice.percentage;
const charging = [UPowerDeviceState.Charging, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state);
if (perc >= 0.99)
return "battery_full";
let level = Math.floor(perc * 7);
if (charging && (level === 4 || level === 1))
level--;
return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
}
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.normal
text: qsTr("Battery")
}
}
Item {
Layout.fillHeight: true
}
// Bottom Info Section
ColumnLayout {
Layout.fillWidth: true
spacing: -4
CustomText {
Layout.alignment: Qt.AlignRight
color: batteryTank.accentColor
font.pointSize: Appearance.font.size.extraLarge
font.weight: Font.Medium
text: `${Math.round(batteryTank.percentage * 100)}%`
}
CustomText {
Layout.alignment: Qt.AlignRight
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.smaller
text: {
if (UPower.displayDevice.state === UPowerDeviceState.FullyCharged)
return qsTr("Full");
if (batteryTank.isCharging)
return qsTr("Charging");
const s = UPower.displayDevice.timeToEmpty;
if (s === 0)
return qsTr("...");
const hr = Math.floor(s / 3600);
const min = Math.floor((s % 3600) / 60);
if (hr > 0)
return `${hr}h ${min}m`;
return `${min}m`;
}
}
}
}
}
component CardHeader: RowLayout {
property color accentColor: DynamicColors.palette.m3primary
property string icon
property string title
Layout.fillWidth: true Layout.fillWidth: true
spacing: Appearance.spacing.small visible: active
MaterialIcon {
color: parent.accentColor
fill: 1
font.pointSize: Appearance.spacing.large
text: parent.icon
}
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.normal
text: parent.title
}
}
component GaugeCard: CustomRect {
id: gaugeCard
property color accentColor: DynamicColors.palette.m3primary
property real animatedPercentage: 0
readonly property real arcStartAngle: 0.75 * Math.PI
readonly property real arcSweep: 1.5 * Math.PI
property string icon
property real percentage: 0
property string subtitle
property string title
clip: true
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.large - Appearance.padding.normal
Behavior on animatedPercentage {
Anim {
duration: Appearance.anim.durations.large
}
}
Component.onCompleted: animatedPercentage = percentage
onPercentageChanged: animatedPercentage = percentage
ColumnLayout {
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.smaller
CardHeader {
accentColor: gaugeCard.accentColor
icon: gaugeCard.icon
title: gaugeCard.title
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
ArcGauge {
accentColor: gaugeCard.accentColor
anchors.centerIn: parent
height: width
percentage: gaugeCard.animatedPercentage
startAngle: gaugeCard.arcStartAngle
sweepAngle: gaugeCard.arcSweep
trackColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
width: Math.min(parent.width, parent.height)
}
CustomText {
anchors.centerIn: parent
color: gaugeCard.accentColor
font.pointSize: Appearance.font.size.extraLarge
font.weight: Font.Medium
text: `${Math.round(gaugeCard.percentage * 100)}%`
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.smaller
text: gaugeCard.subtitle
}
}
}
component HeroCard: CustomClippingRect {
id: heroCard
property color accentColor: DynamicColors.palette.m3primary
property real animatedTemp: 0
property real animatedUsage: 0
property string icon
property string mainLabel
property string mainValue
readonly property real maxTemp: 100
property string secondaryLabel
property string secondaryValue
readonly property real tempProgress: Math.min(1, Math.max(0, temperature / maxTemp))
property real temperature: 0
property string title
property real usage: 0
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.large - Appearance.padding.normal
Behavior on animatedTemp {
Anim {
duration: Appearance.anim.durations.large
}
}
Behavior on animatedUsage {
Anim {
duration: Appearance.anim.durations.large
}
}
Component.onCompleted: {
animatedUsage = usage;
animatedTemp = tempProgress;
}
onTempProgressChanged: animatedTemp = tempProgress
onUsageChanged: animatedUsage = usage
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
color: Qt.alpha(heroCard.accentColor, 0.15)
implicitWidth: parent.width * heroCard.animatedUsage
}
CardHeader {
accentColor: heroCard.accentColor
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.large
anchors.top: parent.top
anchors.topMargin: Math.round(Appearance.padding.large * 1.2)
icon: heroCard.icon
title: heroCard.title
width: parent.width - anchors.leftMargin - usageColumn.anchors.rightMargin - usageLabel.width - Appearance.spacing.normal
}
Column {
anchors.bottom: parent.bottom
anchors.bottomMargin: Math.round(Appearance.padding.large * 1.3)
anchors.left: parent.left
anchors.margins: Math.round(Appearance.padding.large * 1.2)
anchors.right: parent.right
spacing: Appearance.spacing.small
Row {
spacing: Appearance.spacing.small
CustomText {
font.pointSize: Appearance.font.size.normal
font.weight: Font.Medium
text: heroCard.secondaryValue
}
CustomText {
anchors.baseline: parent.children[0].baseline
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: heroCard.secondaryLabel
}
}
ProgressBar {
bgColor: Qt.alpha(heroCard.accentColor, 0.2)
fgColor: heroCard.accentColor
implicitHeight: 6
implicitWidth: parent.width * 0.5
value: heroCard.tempProgress
}
}
Column {
id: usageColumn
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.rightMargin: 32
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: usageLabel
anchors.right: parent.right
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: heroCard.mainLabel
}
CustomText {
anchors.right: parent.right
color: heroCard.accentColor
font.pointSize: Appearance.font.size.extraLarge
font.weight: Font.Medium
text: heroCard.mainValue
}
}
}
component NetworkCard: CustomRect {
id: networkCard
property color accentColor: DynamicColors.palette.m3primary
clip: true
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.large - Appearance.padding.normal
Ref {
service: NetworkUsage
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.small
CardHeader {
accentColor: networkCard.accentColor
icon: "swap_vert"
title: qsTr("Network")
}
// Sparkline graph
Item {
Layout.fillHeight: true
Layout.fillWidth: true
SparklineItem {
id: sparkline
property real smoothMax: targetMax
property real targetMax: 1024
anchors.fill: parent
historyLength: NetworkUsage.historyLength
line1: NetworkUsage.uploadBuffer // qmllint disable missing-type
line1Color: DynamicColors.palette.m3secondary
line1FillAlpha: 0.15
line2: NetworkUsage.downloadBuffer // qmllint disable missing-type
line2Color: DynamicColors.palette.m3tertiary
line2FillAlpha: 0.2
maxValue: smoothMax
Behavior on smoothMax {
Anim {
duration: Appearance.anim.durations.large
}
}
Connections {
function onValuesChanged(): void {
sparkline.targetMax = Math.max(NetworkUsage.downloadBuffer.maximum, NetworkUsage.uploadBuffer.maximum, 1024);
slideAnim.restart();
}
target: NetworkUsage.downloadBuffer
}
NumberAnimation {
id: slideAnim
duration: Config.dashboard.resourceUpdateInterval
from: 0
property: "slideProgress"
target: sparkline
to: 1
}
}
// "No data" placeholder
CustomText {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
opacity: 0.6
text: qsTr("Collecting data...")
visible: NetworkUsage.downloadBuffer.count < 2
}
}
// Download row
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
MaterialIcon {
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
text: "download"
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: qsTr("Download")
}
Item {
Layout.fillWidth: true
}
CustomText {
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: Font.Medium
text: {
const fmt = NetworkUsage.formatBytes(NetworkUsage.downloadSpeed ?? 0);
return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
}
}
}
// Upload row
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
MaterialIcon {
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.normal
text: "upload"
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: qsTr("Upload")
}
Item {
Layout.fillWidth: true
}
CustomText {
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.normal
font.weight: Font.Medium
text: {
const fmt = NetworkUsage.formatBytes(NetworkUsage.uploadSpeed ?? 0);
return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
}
}
}
// Session totals
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: "history"
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: qsTr("Total")
}
Item {
Layout.fillWidth: true
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: {
const down = NetworkUsage.formatBytesTotal(NetworkUsage.downloadTotal ?? 0);
const up = NetworkUsage.formatBytesTotal(NetworkUsage.uploadTotal ?? 0);
return (down && up) ? `${down.value.toFixed(1)}${down.unit} ${up.value.toFixed(1)}${up.unit}` : "↓0.0B ↑0.0B";
}
}
}
}
}
component ProgressBar: CustomRect {
id: progressBar
property real animatedValue: 0
property color bgColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
property color fgColor: DynamicColors.palette.m3primary
property real value: 0
color: bgColor
radius: Appearance.rounding.full
Behavior on animatedValue {
Anim {
duration: Appearance.anim.durations.large
}
}
Component.onCompleted: animatedValue = value
onValueChanged: animatedValue = value
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
color: progressBar.fgColor
radius: Appearance.rounding.full
width: parent.width * progressBar.animatedValue
}
}
component StorageGaugeCard: CustomRect {
id: storageGaugeCard
property color accentColor: DynamicColors.palette.m3secondary
property real animatedPercentage: 0
readonly property real arcStartAngle: 0.75 * Math.PI
readonly property real arcSweep: 1.5 * Math.PI
readonly property var currentDisk: SystemUsage.disks.length > 0 ? SystemUsage.disks[currentDiskIndex] : null
property int currentDiskIndex: 0
property int diskCount: 0
clip: true
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.large - Appearance.padding.normal
Behavior on animatedPercentage {
Anim {
duration: Appearance.anim.durations.large
}
}
Component.onCompleted: {
diskCount = SystemUsage.disks.length;
if (currentDisk)
animatedPercentage = currentDisk.perc;
}
onCurrentDiskChanged: {
if (currentDisk)
animatedPercentage = currentDisk.perc;
}
// Update diskCount and animatedPercentage when disks data changes
Connections {
function onDisksChanged() {
if (SystemUsage.disks.length !== storageGaugeCard.diskCount)
storageGaugeCard.diskCount = SystemUsage.disks.length;
// Update animated percentage when disk data refreshes
if (storageGaugeCard.currentDisk)
storageGaugeCard.animatedPercentage = storageGaugeCard.currentDisk.perc;
}
target: SystemUsage
}
MouseArea {
anchors.fill: parent
onWheel: wheel => {
if (wheel.angleDelta.y > 0)
storageGaugeCard.currentDiskIndex = (storageGaugeCard.currentDiskIndex - 1 + storageGaugeCard.diskCount) % storageGaugeCard.diskCount;
else if (wheel.angleDelta.y < 0)
storageGaugeCard.currentDiskIndex = (storageGaugeCard.currentDiskIndex + 1) % storageGaugeCard.diskCount;
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.smaller
CardHeader {
accentColor: storageGaugeCard.accentColor
icon: "hard_disk"
title: {
const base = qsTr("Storage");
if (!storageGaugeCard.currentDisk)
return base;
return `${base} - ${storageGaugeCard.currentDisk.mount}`;
}
// Scroll hint icon
MaterialIcon {
ToolTip.delay: 500
ToolTip.text: qsTr("Scroll to switch disks")
ToolTip.visible: hintHover.hovered
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
opacity: 0.7
text: "unfold_more"
visible: storageGaugeCard.diskCount > 1
HoverHandler {
id: hintHover
}
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
ArcGauge {
accentColor: storageGaugeCard.accentColor
anchors.centerIn: parent
height: width
percentage: storageGaugeCard.animatedPercentage
startAngle: storageGaugeCard.arcStartAngle
sweepAngle: storageGaugeCard.arcSweep
trackColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
width: Math.min(parent.width, parent.height)
}
CustomText {
anchors.centerIn: parent
color: storageGaugeCard.accentColor
font.pointSize: Appearance.font.size.extraLarge
font.weight: Font.Medium
text: storageGaugeCard.currentDisk ? `${Math.round(storageGaugeCard.currentDisk.perc * 100)}%` : "—"
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.smaller
text: {
if (!storageGaugeCard.currentDisk)
return "—";
const usedFmt = SystemUsage.formatKib(storageGaugeCard.currentDisk.used);
const totalFmt = SystemUsage.formatKib(storageGaugeCard.currentDisk.total);
return `${usedFmt.value.toFixed(1)} / ${Math.floor(totalFmt.value)} ${totalFmt.unit}`;
}
}
}
} }
} }
+1 -2
View File
@@ -34,8 +34,7 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
sourceComponent: Content { sourceComponent: Content {
padding: Appearance.padding.normal wrapper: root
visibilities: root.visibilities
} }
} }
} }
-370
View File
@@ -1,370 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules as Modules
import qs.Config
import qs.Helpers
Item {
id: root
required property Item content
signal settingSelected(string category, string section, string settingName)
// Function to select category by key
function selectCategory(categoryKey: string) {
for (let i = 0; i < appearanceCats.count; i++) {
const item = appearanceCats.get(i);
if (item.key === categoryKey) {
appearanceView.currentIndex = i;
root.content.setCategory(item.key, item._index);
return;
}
}
for (let i = 0; i < systemCats.count; i++) {
const item = systemCats.get(i);
if (item.key === categoryKey) {
sysView.currentIndex = i;
root.content.setCategory(item.key, item._index);
return;
}
}
for (let i = 0; i < panelCats.count; i++) {
const item = panelCats.get(i);
if (item.key === categoryKey) {
panelsView.currentIndex = i;
root.content.setCategory(item.key, item._index);
return;
}
}
}
implicitHeight: searchBar.implicitHeight + Appearance.spacing.smaller + clayout.contentHeight + clayout.anchors.margins * 2
implicitWidth: clayout.contentWidth + clayout.anchors.margins * 2
ListModel {
id: systemCats
ListElement {
_index: 0
icon: "settings"
key: "general"
name: "General"
}
ListElement {
_index: 1
icon: "build_circle"
key: "services"
name: "Services"
}
ListElement {
_index: 2
icon: "notifications"
key: "notifications"
name: "Notifications"
}
ListElement {
_index: 3
icon: "handyman"
key: "utilities"
name: "Utilities"
}
ListElement {
_index: 4
icon: "cached"
key: "updates"
name: "Updates"
}
}
ListModel {
id: panelCats
ListElement {
_index: 5
icon: "settop_component"
key: "bar"
name: "Bar"
}
ListElement {
_index: 6
icon: "lock"
key: "lockscreen"
name: "Lockscreen"
}
ListElement {
_index: 7
icon: "view_sidebar"
key: "sidebar"
name: "Sidebar"
}
ListElement {
_index: 8
icon: "dashboard"
key: "dashboard"
name: "Dashboard"
}
ListElement {
_index: 9
icon: "display_settings"
key: "osd"
name: "On screen display"
}
ListElement {
_index: 10
icon: "rocket_launch"
key: "launcher"
name: "Launcher"
}
}
ListModel {
id: appearanceCats
ListElement {
_index: 11
icon: "wallpaper"
key: "wallpaper"
name: "Wallpaper"
}
ListElement {
_index: 12
icon: "screenshot_region"
key: "screenshot"
name: "Screenshot"
}
ListElement {
_index: 13
icon: "colors"
key: "appearance"
name: "Appearance"
}
}
CustomClippingRect {
anchors.fill: parent
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
Flickable {
id: clayout
anchors.fill: parent
anchors.margins: Appearance.padding.extraSmall
contentHeight: contentItem.childrenRect.height
contentWidth: contentItem.childrenRect.width
flickableDirection: Flickable.VerticalFlick
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.normal
CustomListView {
id: sysView
Layout.fillWidth: true
Layout.preferredHeight: contentItem.childrenRect.height
Layout.preferredWidth: contentItem.childrenRect.width
boundsBehavior: Flickable.StopAtBounds
highlightFollowsCurrentItem: false
interactive: false
model: systemCats
spacing: 2
delegate: Category {
view: sysView
}
// highlight: CustomRect {
// color: DynamicColors.palette.m3primary
// implicitHeight: sysView.currentItem?.implicitHeight ?? 0
// implicitWidth: sysView.width
// radius: Appearance.rounding.normal - Appearance.padding.smaller
// y: sysView.currentItem?.y ?? 0
//
// Behavior on y {
// Anim {
// duration: Appearance.anim.durations.small
// easing.bezierCurve: Appearance.anim.curves.expressiveEffects
// }
// }
// }
}
CustomListView {
id: panelsView
Layout.fillWidth: true
Layout.preferredHeight: contentItem.childrenRect.height
Layout.preferredWidth: contentItem.childrenRect.width
boundsBehavior: Flickable.StopAtBounds
contentWidth: contentItem.childrenRect.width
highlightFollowsCurrentItem: false
interactive: false
model: panelCats
spacing: 2
delegate: Category {
view: panelsView
}
// highlight: CustomRect {
// color: DynamicColors.palette.m3primary
// implicitHeight: panelsView.currentItem?.implicitHeight ?? 0
// implicitWidth: panelsView.width
// radius: Appearance.rounding.normal - Appearance.padding.smaller
// y: panelsView.currentItem?.y ?? 0
//
// Behavior on y {
// Anim {
// duration: Appearance.anim.durations.small
// easing.bezierCurve: Appearance.anim.curves.expressiveEffects
// }
// }
// }
}
CustomListView {
id: appearanceView
Layout.fillWidth: true
Layout.preferredHeight: contentItem.childrenRect.height
Layout.preferredWidth: contentItem.childrenRect.width
boundsBehavior: Flickable.StopAtBounds
contentWidth: contentItem.childrenRect.width
highlightFollowsCurrentItem: false
interactive: false
model: appearanceCats
spacing: 2
delegate: Category {
view: appearanceView
}
// highlight: CustomRect {
// color: DynamicColors.palette.m3primary
// implicitHeight: appearanceView.currentItem?.implicitHeight ?? 0
// implicitWidth: appearanceView.width
// radius: Appearance.rounding.normal - Appearance.padding.smaller
// y: appearanceView.currentItem?.y ?? 0
//
// Behavior on y {
// Anim {
// duration: Appearance.anim.durations.small
// easing.bezierCurve: Appearance.anim.curves.expressiveEffects
// }
// }
// }
}
}
}
}
component Category: CustomRect {
id: categoryItem
required property int _index
readonly property bool first: index === 0
required property string icon
required property int index
required property string key
readonly property bool last: index === view.model.count - 1
required property string name
readonly property bool selected: key === root.content.currentCategory
required property ListView view
bottomLeftRadius: layer.pressed ? Appearance.rounding.smallest : selected || last ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall
bottomRightRadius: layer.pressed ? Appearance.rounding.smallest : selected || last ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall
color: selected ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 50
implicitWidth: 256
topLeftRadius: layer.pressed ? Appearance.rounding.smallest : selected || first ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall
topRightRadius: layer.pressed ? Appearance.rounding.smallest : selected || first ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall
Behavior on bottomLeftRadius {
Anim {
type: Anim.FastEffects
}
}
Behavior on bottomRightRadius {
Anim {
type: Anim.FastEffects
}
}
Behavior on topLeftRadius {
Anim {
type: Anim.FastEffects
}
}
Behavior on topRightRadius {
Anim {
type: Anim.FastEffects
}
}
RowLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.smaller
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
id: icon
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillHeight: true
Layout.leftMargin: Appearance.padding.small
Layout.preferredWidth: icon.contentWidth
color: categoryItem.key === root.content.currentCategory ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: categoryItem.key === root.content.currentCategory ? 1 : 0
font.pointSize: Appearance.font.size.small * 2
text: categoryItem.icon
verticalAlignment: Text.AlignVCenter
Behavior on fill {
Anim {
}
}
}
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: Appearance.spacing.normal
color: categoryItem.key === root.content.currentCategory ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
text: categoryItem.name
verticalAlignment: Text.AlignVCenter
}
}
StateLayer {
id: layer
color: categoryItem.key === root.content.currentCategory ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
onClicked: {
root.content.setCategory(categoryItem.key, categoryItem._index);
categoryItem.view.currentIndex = categoryItem.index;
}
}
}
}

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