Author SHA1 Message Date
zach d3345906b9 Merge pull request 'Feat: LLM Chat' (#155) from feat/llm-chat-tab into main
Reviewed-on: #155
2026-10-18 17:05:05 +02:00
zach 08d42e1f8a chore: formatter & clangd lsp option
C++ / fmt (pull_request) Successful in 5s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 27s
Rust / build (pull_request) Successful in 50s
Python / verify (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 28s
Rust / clippy (pull_request) Successful in 45s
2026-10-18 15:23:40 +02:00
zach 45aa6c9005 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 20s
C++ / clang-tidy (pull_request) Failing after 12s
JS/TS / fmt (pull_request) Successful in 6s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 48s
Rust / fmt (pull_request) Successful in 28s
Python / verify (pull_request) Successful in 1m45s
Rust / clippy (pull_request) Successful in 46s
2026-10-18 14:43:00 +02:00
zach 1763b03287 Merge pull request 'Chore/remove nix support' (#159) from chore/remove-nix-support into main
Reviewed-on: #159
2026-10-18 14:42:50 +02:00
zach 72ff6ad42a Merge branch 'main' into chore/remove-nix-support
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 50s
Python / verify (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 29s
Rust / clippy (pull_request) Successful in 55s
2026-10-18 14:42:43 +02:00
zach 2a45740020 Merge pull request 'fix(pr-template): adds an "Other" issue type' (#160) from fix/ci-pr-template into main
Reviewed-on: #160
2026-10-18 14:42:35 +02:00
zach 0a503bc27c fix: remove unimplemented method
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 27s
Rust / build (pull_request) Successful in 49s
Rust / fmt (pull_request) Successful in 28s
Python / verify (pull_request) Successful in 1m41s
Rust / clippy (pull_request) Successful in 45s
2026-10-18 13:03:25 +02:00
zach 1fe8a3a959 chore: restructure llm plugin to be more coherent & fix various bugs in llm ui
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 30s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 36s
Rust / clippy (pull_request) Successful in 54s
2026-10-18 12:58:38 +02:00
AramJonghu ae25f33fc8 fix(pr-template): adds an "Other" issue type
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 10s
Rust / fmt (pull_request) Successful in 48s
Rust / build (pull_request) Successful in 1m9s
Rust / clippy (pull_request) Successful in 52s
Python / static (pull_request) Successful in 55s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 9s
Python / verify (pull_request) Successful in 1m26s
2026-10-14 00:22:33 +02:00
AramJonghu 02267ec9a6 docs(readme): adjust Nix support documentation
JS/TS / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 27s
Rust / build (pull_request) Successful in 50s
Rust / fmt (pull_request) Successful in 32s
Python / verify (pull_request) Successful in 2m16s
C++ / fmt (pull_request) Failing after 7s
C++ / build (pull_request) Failing after 12s
Rust / clippy (pull_request) Successful in 50s
C++ / clang-tidy (pull_request) Failing after 26s
2026-10-14 00:08:17 +02:00
AramJonghu 4b8d74a7e5 ci(cleanup): removed NixOS from issue template
Avoids confusion
2026-10-14 00:08:17 +02:00
AramJonghu 1fa3c84a56 chore(nix): remove all .nix files and flake.lock 2026-10-14 00:08:17 +02:00
zach fc81be922f Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 48s
Python / verify (pull_request) Successful in 1m38s
Rust / fmt (pull_request) Successful in 28s
Rust / clippy (pull_request) Successful in 46s
2026-10-12 16:06:23 +02:00
zach 8d54e1ce6f fix: undo accidental debug code 2026-10-12 16:05:52 +02:00
zach bee8cd061b Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 7s
C++ / build (pull_request) Failing after 11s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 48s
Python / verify (pull_request) Successful in 1m39s
Rust / fmt (pull_request) Successful in 28s
Rust / clippy (pull_request) Successful in 46s
2026-10-12 15:58:33 +02:00
zach b9e818faa9 fix: add proper atRestOverride for notification background 2026-10-12 15:58:25 +02:00
zach 2cabd72fc0 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 12s
C++ / clang-tidy (pull_request) Failing after 12s
JS/TS / fmt (pull_request) Successful in 8s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 31s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m32s
Rust / fmt (pull_request) Successful in 51s
Rust / clippy (pull_request) Successful in 1m1s
2026-10-12 15:54:52 +02:00
zach 255db9e533 fix: ignore stale coordinate data for panels + convert wavyline to use new qt 6.12 canvas painter api 2026-10-12 15:54:32 +02:00
zach a4f6aa1d4a Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 16s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 14s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 29s
Rust / build (pull_request) Successful in 50s
Python / verify (pull_request) Successful in 1m37s
Rust / fmt (pull_request) Successful in 31s
Rust / clippy (pull_request) Successful in 49s
2026-10-11 08:42:22 +02:00
zach 3883917228 fix: ui no longer blocks when volumes are unreachable + add support for network and fuse volumes 2026-10-11 08:42:05 +02:00
zach bf6a7027e4 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 7s
C++ / build (pull_request) Failing after 13s
C++ / clang-tidy (pull_request) Failing after 12s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 27s
Rust / build (pull_request) Successful in 49s
Rust / fmt (pull_request) Successful in 28s
Python / verify (pull_request) Successful in 1m40s
Rust / clippy (pull_request) Successful in 47s
2026-10-08 12:09:59 +02:00
zach 873b2eb01b fix: dashboard tabs scroll now only loads previous item + next item 2026-10-08 12:09:50 +02:00
zach d70fb61e4a gemini glow fixes + positioning for window view
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 50s
Python / verify (pull_request) Successful in 1m38s
Rust / fmt (pull_request) Successful in 27s
Rust / clippy (pull_request) Successful in 53s
2026-10-08 12:08:48 +02:00
zach 4c410d638c Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 7s
C++ / build (pull_request) Failing after 21s
C++ / clang-tidy (pull_request) Failing after 12s
JS/TS / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 29s
Rust / build (pull_request) Successful in 47s
Python / verify (pull_request) Successful in 1m33s
Rust / fmt (pull_request) Successful in 28s
Rust / clippy (pull_request) Successful in 49s
2026-10-07 22:39:19 +02:00
zach aef7cd5918 fix: qt 6.12 build 2026-10-07 22:38:49 +02:00
zach a13fa8442b fix: qt 6.12 build
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 22s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 54s
Python / verify (pull_request) Successful in 1m41s
Rust / fmt (pull_request) Successful in 28s
Rust / clippy (pull_request) Successful in 53s
2026-10-07 22:37:46 +02:00
zach 4d75b317cd revert rounding var
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 18s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 8s
JS/TS / lint (pull_request) Successful in 12s
Python / static (pull_request) Successful in 30s
Rust / build (pull_request) Successful in 52s
Python / verify (pull_request) Successful in 1m38s
Rust / fmt (pull_request) Successful in 31s
Rust / clippy (pull_request) Successful in 1m0s
2026-10-07 13:33:54 +02:00
zach 76823eff79 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 29s
C++ / build (pull_request) Failing after 33s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 51s
Python / verify (pull_request) Successful in 1m36s
Rust / fmt (pull_request) Successful in 43s
Rust / clippy (pull_request) Successful in 1m4s
2026-10-07 13:32:49 +02:00
zach 909b5113fb fix: closed panels don't add resistance to bounce + use qtrendering for text 2026-10-07 13:32:37 +02:00
zach c06cbea854 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 8s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 30s
Rust / build (pull_request) Successful in 48s
Python / verify (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 25s
Rust / clippy (pull_request) Successful in 43s
2026-10-06 13:33:41 +02:00
zach 376595cd0b fix: don't use zoom value when rendering wallpaper 2026-10-06 13:30:27 +02:00
zach a00bbe50f2 add rounding var
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 8s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 48s
Python / verify (pull_request) Successful in 1m43s
Rust / fmt (pull_request) Successful in 25s
Rust / clippy (pull_request) Successful in 46s
2026-10-06 00:55:50 +02:00
zach 11e94fbb4b Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 47s
Python / verify (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 29s
Rust / clippy (pull_request) Successful in 46s
2026-10-05 19:33:42 +02:00
zach 9f5e62f9c7 feat: use custom wayland layer-shell to render wallpaper to reduce vram 2026-10-05 19:31:15 +02:00
zach b91befa9f7 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 11s
Python / static (pull_request) Successful in 30s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m30s
Rust / fmt (pull_request) Successful in 30s
Rust / clippy (pull_request) Successful in 48s
2026-10-05 09:58:00 +02:00
zach f90e9bb1a0 fix: all apps icon size and suppress warnings 2026-10-05 09:57:47 +02:00
zach 99fdfbc465 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 4s
C++ / clang-tidy (pull_request) Failing after 10s
C++ / build (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 35s
Rust / build (pull_request) Successful in 50s
Python / verify (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 28s
Rust / clippy (pull_request) Successful in 51s
2026-10-04 18:20:23 +02:00
zach 2c88b7abcb move picker to main panelwindow to reduce vram usage 2026-10-04 18:20:10 +02:00
zach e148a0f843 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 12s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 8s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 26s
Rust / build (pull_request) Successful in 48s
Rust / fmt (pull_request) Successful in 29s
Python / verify (pull_request) Successful in 1m40s
Rust / clippy (pull_request) Successful in 44s
2026-10-04 13:55:22 +02:00
zach f1918172c3 fix: merge two strokecanvas into one + better cursorShape and pointer handling
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 8s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 47s
Python / verify (pull_request) Successful in 1m34s
Rust / fmt (pull_request) Successful in 35s
Rust / clippy (pull_request) Successful in 1m0s
2026-10-04 13:53:21 +02:00
zach 5507b99fdc fix: merge two strokecanvas into one + better cursorShape and pointer handling 2026-10-04 13:48:50 +02:00
zach 506b69165d Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Successful in 35s
Rust / build (pull_request) Successful in 50s
Python / verify (pull_request) Successful in 1m39s
Rust / fmt (pull_request) Successful in 29s
Rust / clippy (pull_request) Successful in 51s
2026-10-03 20:26:19 +02:00
zach dc99d6677c remove layer usage to reduce vram consumption 2026-10-03 20:26:00 +02:00
zach add3beed59 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 30s
Rust / build (pull_request) Successful in 48s
Python / verify (pull_request) Successful in 1m37s
Rust / fmt (pull_request) Successful in 26s
Rust / clippy (pull_request) Successful in 47s
2026-10-02 19:54:45 +02:00
zach fc672924d1 fix: settings gpu enum 2026-10-02 19:54:33 +02:00
zach 4b67cc1d88 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 8s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 32s
Rust / build (pull_request) Successful in 51s
Rust / fmt (pull_request) Successful in 29s
Python / verify (pull_request) Successful in 1m55s
Rust / clippy (pull_request) Successful in 45s
2026-10-01 17:11:29 +02:00
zach 765b84a19c fix: settings drag gesture and dashboard popout regions 2026-10-01 17:11:20 +02:00
zach c64088441b Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 24s
C++ / clang-tidy (pull_request) Failing after 18s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 27s
Rust / build (pull_request) Successful in 52s
Rust / fmt (pull_request) Successful in 29s
Python / verify (pull_request) Successful in 1m44s
Rust / clippy (pull_request) Successful in 45s
2026-09-30 23:33:42 +02:00
zach ad36b4ed8f fix: sizing and positioning of portrait aspect ratios for wallpaper cropper 2026-09-30 23:33:22 +02:00
zach e8bf8b9727 fix: restore lost changes
C++ / fmt (pull_request) Failing after 3s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 25s
Rust / build (pull_request) Successful in 50s
Rust / fmt (pull_request) Successful in 30s
Python / verify (pull_request) Successful in 1m44s
Rust / clippy (pull_request) Successful in 45s
2026-09-30 19:30:06 +02:00
zach 54f966b4ea Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 6s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 29s
Rust / build (pull_request) Successful in 47s
Rust / fmt (pull_request) Successful in 30s
Python / verify (pull_request) Successful in 1m43s
Rust / clippy (pull_request) Successful in 46s
2026-09-30 18:41:12 +02:00
zach c1f26e7dcb fix: add wheelinverter to cmake 2026-09-30 18:40:46 +02:00
zach 8dd65a90a8 fix: re-add lost changes missed files 2026-09-30 18:39:24 +02:00
zach 3c359ce25f fix: re-add lost changes 2026-09-30 18:38:07 +02:00
zach 63b96471b0 Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 3s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 27s
Rust / clippy (pull_request) Successful in 46s
2026-09-30 17:59:14 +02:00
zach 42026ba5e5 Merge pull request 'Feat: Dashboard tabs and repositioning. New processes view. Settings backend changes' (#158) from feat/dashboard-tabs-processes-and-settings-backend into main
Reviewed-on: #158
2026-09-30 16:51:15 +02:00
zach e86d1e8220 remove unrelated files
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 13s
C++ / clang-tidy (pull_request) Failing after 18s
JS/TS / fmt (pull_request) Successful in 6s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 24s
Rust / build (pull_request) Successful in 54s
Rust / fmt (pull_request) Successful in 32s
Python / verify (pull_request) Successful in 1m45s
Rust / clippy (pull_request) Successful in 46s
2026-09-30 15:18:16 +02:00
zach 4f797a9741 separate dashboard and settings changes to new branch
C++ / fmt (pull_request) Successful in 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 26s
Rust / clippy (pull_request) Successful in 46s
2026-09-30 15:11:51 +02:00
zach 83bc153ee3 fix: format and apply lint fixes in python script 2026-09-30 15:01:28 +02:00
zach ca51f660ad chore: format & fix prettierignore 2026-09-30 15:01:28 +02:00
zach 5153cc4f17 Merge branch 'main' into feat/llm-chat-tab 2026-09-30 15:01:28 +02:00
Inorishioandzach bdfb9fc241 fixed: mousewheel swallow 2026-09-30 15:01:28 +02:00
zach 13f1a3fede chore: format 2026-09-30 15:01:28 +02:00
zach d8a3d5732e new chatlistview component, file attachments, performance & primitive test 2026-09-30 15:01:28 +02:00
zach 51ed270774 Merge branch 'main' into feat/llm-chat-tab 2026-09-30 15:01:28 +02:00
zach 83f6f95f6e gitignore 2026-09-30 15:01:28 +02:00
zach cafdce2352 Merge branch 'main' into feat/llm-chat-tab 2026-09-30 15:01:28 +02:00
zach 5fc6fda804 fix: truncate context logic 2026-09-30 15:01:28 +02:00
zach a59e4286ad fix: tool call prompt 2026-09-30 15:01:28 +02:00
zach e2786a3f1e fix: truncate webfetch content to fit context 2026-09-30 15:01:28 +02:00
zach e700a4f96b use blobgroup for window chat interface + slim sidebar 2026-09-30 15:01:28 +02:00
zach a8fdc31b8d feat: implement findChild* methods 2026-09-30 15:01:28 +02:00
zach bc927b16af feat: implement findChild* methods 2026-09-30 15:01:28 +02:00
zach 57a6e48dcb fix: property link modules 2026-09-30 15:01:28 +02:00
zach 15769ba78e fix: add gnuinstalldirs include 2026-09-30 15:01:28 +02:00
zach be541d8f8d fix: cmake install dir 2026-09-30 15:01:28 +02:00
zach 0d4c08cfbb fix: cmake install dir 2026-09-30 15:01:28 +02:00
zach d3ac675492 fix: uncomment settings index cache path 2026-09-30 15:01:28 +02:00
zach e58440fea4 chore: split cmake into multiple files and fix settings searching regex + add enabled option to llm 2026-09-30 15:01:28 +02:00
zach d5d2311c24 fix: font family 2026-09-30 15:01:19 +02:00
AramJonghuandzach 5ec682d780 fix(Docker): add required deps to dockerfile 2026-09-30 15:01:19 +02:00
zach 79bedd5119 chore: format python codeicons 2026-09-30 15:01:19 +02:00
zach 14b54206b1 chore: format llm c files 2026-09-30 15:01:19 +02:00
zach 2df1cfe9aa chore: format llm c files 2026-09-30 15:01:19 +02:00
zach 88bb413f9a fab menu and model selector 2026-09-30 15:01:19 +02:00
zach 1ab49d4b38 better support for opening chat in floating window 2026-09-30 15:01:19 +02:00
zach 7cc673430c separate dashboard and settings changes to new branch
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 5s
JS/TS / lint (pull_request) Successful in 7s
Python / static (pull_request) Successful in 26s
Rust / build (pull_request) Successful in 46s
Python / verify (pull_request) Successful in 1m38s
Rust / fmt (pull_request) Successful in 28s
Rust / clippy (pull_request) Successful in 49s
2026-09-30 14:48:38 +02:00
zach 08bb221699 feat: config object migration path and add new options to settings 2026-09-30 14:40:22 +02:00
zach 60293abe61 fix: define declared method + insert new bar entries to config file list + rename Hyprsunset -> Nightlight 2026-09-30 14:33:58 +02:00
zach a22022e969 fix: changesMade bool now binding for robustness 2026-09-30 14:26:59 +02:00
zach 62e19b5261 fix: show bar on hover 2026-09-30 14:26:59 +02:00
zach d76aaf093b fix: normalize zoom slider to linear scale 2026-09-30 14:26:59 +02:00
zach 4ebb4ce859 feat: wallpaper cropper sdf shader rectangle, better confirmation controls + hide passive tray icons 2026-09-30 14:26:59 +02:00
zach be3c938c39 feat: progress on changing interactions to mousearea again 2026-09-30 14:26:59 +02:00
zach f6fa2f0230 fix: remove clippingrectangle in favor of passing radius to sub-items 2026-09-30 14:26:59 +02:00
zach 4a021d81ac chore: formatter 2026-09-30 14:26:59 +02:00
zach 10d763cfe5 fix: define declared method + insert new bar entries to config file list + rename Hyprsunset -> Nightlight 2026-09-30 14:26:56 +02:00
zach 133d453f6f fix: fix nvidia update interval on config change + more robust read path for generic 2026-09-30 14:26:50 +02:00
zach 3373f11193 fix: column index desync after reorder 2026-09-30 14:26:50 +02:00
zach 359f832609 more unfinished changes, process tab for dashboard 2026-09-30 14:26:50 +02:00
zach 0bfda04e91 intermediary changes, plan to revert to mousearea for gestures + config load bugfixes 2026-09-30 14:23:46 +02:00
zach f825e4d53b feat: fontbuilder to streamline font styling 2026-09-30 14:23:31 +02:00
zach 2c90339c77 feat: dashboard show on hover, media and weather tabs added 2026-09-30 14:22:22 +02:00
zach d259c6ffbe fix: shader fade animation linear curve 2026-09-30 14:17:16 +02:00
zach 5330ae71d2 add nightlight hyprland plugin + rename hyprsunsetmanager -> nightlightmanager to control both 2026-09-30 14:17:16 +02:00
zach 23d2b31c0a expandable dialogs for color scheme, fonts and scheduling modes 2026-09-30 14:16:16 +02:00
zach 731b323670 remove test code 2026-09-19 09:09:04 +02:00
zach 520020b7f8 fix: elide text in power popout 2026-09-18 21:55:54 +02:00
zach 82ca5b5335 fix: properly convert vram mib -> kib & generic gpu vram reading 2026-09-13 22:25:58 +02:00
zach bd699e72a7 fix: polkit width 2026-09-04 19:43:13 +02:00
zach ed7ec326b1 fix: idlemonitors list value 2026-09-03 16:15:55 +02:00
zach 04869656aa Merge pull request 'changed old appearance vars for the lockscreen + warning hunting for the polkit' (#154) from fix/lockscreen-font into main
Reviewed-on: #154
Reviewed-by: AramJonghu <aramjonghu@tutamail.com>
Reviewed-by: zach <zach@brohn.se>
2026-08-31 00:14:06 +02:00
zach 293bf67e09 better scrollbar in codeblocks + enter anim for delegates 2026-08-27 01:47:17 +02:00
zach c33dbc147f rotated view + wheel inverter 2026-08-26 17:10:34 +02:00
zach 78988700be async parsing + tex caching 2026-08-26 02:06:40 +02:00
zach 3946541f87 async parsing + tex caching 2026-08-26 02:06:10 +02:00
zach e18875a752 add markdown parsing + latex + tree-sitter highlighting for codeblocks in llm responses 2026-08-24 23:55:51 +02:00
zach aae3757daf better chat list view + anchor to bottom. sqlite db for chats + LIFO-ordered 2026-08-23 16:37:11 +02:00
zach 37c6a9986e start initial work on detaching chat to floating window 2026-08-20 20:24:54 +02:00
zach 9657e5092a add removal of chat sessions, auto title + icon, proper stick-to-bottom as response is streamed 2026-08-20 17:40:33 +02:00
zach 47bab21e22 initial commit for llm chat tab in sidebar 2026-08-20 00:27:48 +02:00
469 changed files with 40646 additions and 7925 deletions
+5
View File
@@ -0,0 +1,5 @@
CompileFlags:
Remove:
# GCC-only flags from the system's default spec that clang/clangd cannot
# parse. They appear in compile_commands.json but are not set by the project.
- -mno-direct-extern-access
-1
View File
@@ -38,7 +38,6 @@ body:
attributes: attributes:
label: OS label: OS
options: options:
- NixOS
- Arch - Arch
- Debian / Ubuntu - Debian / Ubuntu
- Fedora - Fedora
+1
View File
@@ -30,6 +30,7 @@ body:
- Refactor / style - Refactor / style
- CI / build / tooling - CI / build / tooling
- Documentation - Documentation
- Other (Describe in description box with header "Issue type")
validations: validations:
required: true required: true
+3
View File
@@ -18,3 +18,6 @@ dist/
network-dev/ network-dev/
**/zshell.build/ **/zshell.build/
**/zshell.dist/ **/zshell.dist/
.opencode/
run-agent.sh
AGENTS.md
+1
View File
@@ -1,3 +1,4 @@
.venv/ .venv/
scripts/fzf.js scripts/fzf.js
scripts/fuzzysort.js scripts/fuzzysort.js
scripts/settings-indexer.js
+2 -1
View File
@@ -3,8 +3,9 @@ FunctionsSpacing=true
IndentWidth=4 IndentWidth=4
MaxColumnWidth=-1 MaxColumnWidth=-1
NewlineType=native NewlineType=native
NormalizeOrder=true GroupAttributesTogether=false
ObjectsSpacing=true ObjectsSpacing=true
SemicolonRule=always SemicolonRule=always
SingleLineEmptyObjects=true
SortImports=false SortImports=false
UseTabs=true UseTabs=true
+31 -22
View File
@@ -26,49 +26,51 @@ if(NOT DEFINED VERSION)
endif() endif()
endif() endif()
if(NOT DEFINED GIT_REVISION)
execute_process(COMMAND git rev-parse HEAD
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_REVISION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if("${GIT_REVISION}" STREQUAL "")
message(FATAL_ERROR "GIT_REVISION is not set and failed to get from git")
endif()
endif()
set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
project(ZShell VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) project(ZShell VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
message(STATUS "ZShell version: ${VERSION}") message(STATUS "ZShell version: ${VERSION}")
include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(ENABLE_MODULES "plugin;shell;m3shapes" CACHE STRING "Modules to build/install") set(ENABLE_MODULES "plugin;shell;cli;m3shapes" CACHE STRING "Modules to build/install")
set(INSTALL_LIBDIR "usr/lib/ZShell" CACHE STRING "Library install dir") set(INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}/ZShell" CACHE STRING "Library install dir")
set(INSTALL_QMLDIR "usr/lib/qt6/qml" CACHE STRING "QML install dir") set(INSTALL_QMLDIR "${CMAKE_INSTALL_LIBDIR}/qt6/qml" CACHE STRING "QML install dir")
set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell config install dir") set(INSTALL_QSCONFDIR "${CMAKE_INSTALL_SYSCONFDIR}/xdg/quickshell/zshell" CACHE STRING "Quickshell config install dir")
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir") set(INSTALL_GREETERCONFDIR "${CMAKE_INSTALL_SYSCONFDIR}/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
set(CMAKE_INSTALL_MESSAGE NEVER) set(CMAKE_INSTALL_MESSAGE NEVER)
add_compile_options( add_compile_options(
-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wold-style-cast -Wnull-dereference -Wdouble-promotion -Wnull-dereference -Wdouble-promotion
-Wformat=2 -Wfloat-equal -Woverloaded-virtual -Wformat=2 -Wfloat-equal
-Wsign-conversion -Wredundant-decls -Wswitch -Wsign-conversion -Wredundant-decls -Wswitch
-Wunreachable-code -Wunreachable-code
$<$<COMPILE_LANGUAGE:CXX>:-Wold-style-cast>
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
) )
if("shell" IN_LIST ENABLE_MODULES) if("cli" IN_LIST ENABLE_MODULES)
# Build settings index
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(SETTINGS_INDEX_JSON "${CMAKE_BINARY_DIR}/settings-index.json")
execute_process(
COMMAND ${Python3_EXECUTABLE}
"${CMAKE_SOURCE_DIR}/scripts/build-settings-index.py"
"${CMAKE_SOURCE_DIR}/Modules/Settings"
"${SETTINGS_INDEX_JSON}"
RESULT_VARIABLE SETTINGS_INDEX_RESULT
)
if(NOT SETTINGS_INDEX_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to build settings search index")
endif()
# Nuitka compilation # Nuitka compilation
set(ZSHELL_CLI_BUILD_DIR "${CMAKE_BINARY_DIR}/zshell-cli") set(ZSHELL_CLI_BUILD_DIR "${CMAKE_BINARY_DIR}/zshell-cli")
set(ZSHELL_CLI_DIST "${ZSHELL_CLI_BUILD_DIR}/zshell.dist") set(ZSHELL_CLI_DIST "${ZSHELL_CLI_BUILD_DIR}/zshell.dist")
@@ -188,4 +190,11 @@ if("m3shapes" IN_LIST ENABLE_MODULES)
if(TARGET m3shapesplugin) if(TARGET m3shapesplugin)
set_target_properties(m3shapesplugin PROPERTIES INSTALL_RPATH "$ORIGIN") set_target_properties(m3shapesplugin PROPERTIES INSTALL_RPATH "$ORIGIN")
endif() endif()
# Silence warnings that come from m3shapes files we do not manage.
foreach(m3shapes_target m3shapes_core m3shapes_morph m3shapes_shapes m3shapes m3shapesplugin)
if(TARGET ${m3shapes_target})
target_compile_options(${m3shapes_target} PRIVATE -Wno-shadow -Wno-float-equal)
endif()
endforeach()
endif() endif()
+14 -35
View File
@@ -80,49 +80,29 @@ Slider {
property bool moving property bool moving
function update(): void {
animate = !moving;
binding.when = moving;
font.pointSize = moving ? Tokens.font.size.small : Tokens.font.size.larger;
font.family = moving ? Config.appearance.font.family.sans : Config.appearance.font.family.material;
}
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: 1
text: moving ? Math.round(root.value * 100) : root.icon
color: Colors.palette.m3inverseOnSurface color: Colors.palette.m3inverseOnSurface
text: root.icon font: moving ? Tokens.font.body.small : Tokens.font.icon.medium
onMovingChanged: anim.restart()
Binding {
id: binding
property: "text"
target: icon
value: Math.round(root.value * root.multiplier)
when: false
}
Behavior on moving {
SequentialAnimation { SequentialAnimation {
id: anim
Anim { Anim {
duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardAccel
property: "scale"
target: icon target: icon
to: 0 property: "scale"
to: 0.3
duration: Tokens.anim.durations.small / 2
easing: Tokens.anim.standardAccel
} }
PropertyAction {}
ScriptAction {
script: icon.update()
}
Anim { Anim {
target: icon
property: "scale"
to: 1
duration: Tokens.anim.durations.normal / 2 duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardDecel easing: Tokens.anim.standardDecel
property: "scale" }
target: icon
to: 1
} }
} }
} }
@@ -130,7 +110,7 @@ Slider {
} }
Behavior on value { Behavior on value {
Anim { Anim {
duration: Tokens.anim.durations.large type: Anim.StandardLarge
} }
} }
@@ -138,7 +118,6 @@ Slider {
onValueChanged: { onValueChanged: {
if (!initialized) { if (!initialized) {
initialized = true; initialized = true;
oldValue = value;
return; return;
} }
+2 -6
View File
@@ -20,11 +20,7 @@ CustomRect {
property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1) property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1)
property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38) property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38)
property bool fillWidth property bool fillWidth
property font font: ({ property font font: Tokens.font.body.small
family: Config.appearance.font.family.sans,
pointSize: Tokens.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
@@ -55,7 +51,7 @@ CustomRect {
if (internalChecked) if (internalChecked)
return checkedRadius; return checkedRadius;
if (isRound) if (isRound)
return (height || implicitHeight) / 2 * Math.min(1, Tokens.rounding.scale); return implicitHeight / 2 * Math.min(1, Tokens.rounding.scale);
return defaultRadius; return defaultRadius;
} }
+1 -1
View File
@@ -20,7 +20,7 @@ BusyIndicator {
property int animState property int animState
property color bgColor: Colors.palette.m3secondaryContainer property color bgColor: Colors.palette.m3secondaryContainer
property color fgColor: Colors.palette.m3primary property color fgColor: Colors.palette.m3primary
property real implicitSize: Tokens.font.size.normal * 3 property real implicitSize: Tokens.font.body.medium.pointSize * 3
property real internalStrokeWidth: strokeWidth property real internalStrokeWidth: strokeWidth
readonly property alias progress: manager.progress readonly property alias progress: manager.progress
property real strokeWidth: Tokens.padding.extraSmall * 0.8 property real strokeWidth: Tokens.padding.extraSmall * 0.8
+5 -5
View File
@@ -46,10 +46,10 @@ Item {
} }
function syncFromPenColor() { function syncFromPenColor() {
if (!drawing.drawingState) if (!drawing.screenState)
return; return;
const c = drawing.drawingState.penColor; const c = drawing.screenState.penColor;
if (c.hsvSaturation > 0) { if (c.hsvSaturation > 0) {
currentHue = c.hsvHue; currentHue = c.hsvHue;
@@ -86,7 +86,7 @@ Item {
currentHue = relative / arcSweep; currentHue = relative / arcSweep;
lastChromaticHue = currentHue; lastChromaticHue = currentHue;
drawing.drawingState.penColor = Qt.hsva(currentHue, drawing.drawingState.penColor.hsvSaturation, drawing.drawingState.penColor.hsvValue, drawing.drawingState.penColor.a); drawing.screenState.penColor = Qt.hsva(currentHue, drawing.screenState.penColor.hsvSaturation, drawing.screenState.penColor.hsvValue, drawing.screenState.penColor.a);
} }
implicitHeight: 180 implicitHeight: 180
@@ -112,7 +112,7 @@ Item {
root.syncFromPenColor(); root.syncFromPenColor();
} }
target: root.drawing.drawingState target: root.drawing.screenState
} }
Canvas { Canvas {
@@ -152,7 +152,7 @@ Item {
CustomRect { CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
color: root.drawing?.drawingState.penColor color: root.drawing?.screenState.penColor
implicitHeight: implicitWidth implicitHeight: implicitWidth
implicitWidth: canvas.height - root.handleSize - Tokens.padding.extraLarge * 2 implicitWidth: canvas.height - root.handleSize - Tokens.padding.extraLarge * 2
radius: Tokens.rounding.full radius: Tokens.rounding.full
+6 -3
View File
@@ -1,12 +1,12 @@
import QtQuick import QtQuick
import qs.Helpers import qs.Services
Flickable { Flickable {
id: root id: root
property bool doneFakeFlick property bool doneFakeFlick
interactive: !Visibilities.getForActive().isDrawing interactive: !ShellState.forActive().isDrawing
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
@@ -28,6 +28,9 @@ Flickable {
interval: 10 interval: 10
running: root.doneFakeFlick running: root.doneFakeFlick
onTriggered: root.doneFakeFlick = false onTriggered: {
root.doneFakeFlick = false;
root.returnToBounds();
}
} }
} }
+19 -16
View File
@@ -1,33 +1,36 @@
import QtQuick import QtQuick
import qs.Helpers import qs.Services
ListView { ListView {
id: root id: root
property bool doneFakeFlick property bool doneFakeFlick
interactive: !Visibilities.getForActive()?.isDrawing interactive: !ShellState.forActive()?.isDrawing
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
onRunningChanged: { // onRunningChanged: {
if (!running && !root.doneFakeFlick) { // if (!running && !root.doneFakeFlick) {
root.doneFakeFlick = true; // root.doneFakeFlick = true;
root.flick(1, 1); // root.flick(1, 1);
root.flick(-1, -1); // root.flick(-1, -1);
Qt.callLater(() => root.cancelFlick()); // Qt.callLater(() => root.cancelFlick());
} // }
} // }
Anim { Anim {
properties: "x,y" properties: "x,y"
type: Anim.DefaultEffects
} }
} }
Timer { // Timer {
interval: 10 // interval: 10
running: root.doneFakeFlick // running: root.doneFakeFlick
//
onTriggered: root.doneFakeFlick = false // onTriggered: {
} // root.doneFakeFlick = false;
// }
// }
} }
+1
View File
@@ -4,6 +4,7 @@ MouseArea {
property int scrollAccumulatedY: 0 property int scrollAccumulatedY: 0
function onWheel(event: WheelEvent): void { function onWheel(event: WheelEvent): void {
event.accepted = false;
} }
onWheel: event => { onWheel: event => {
+3 -4
View File
@@ -6,7 +6,7 @@ import qs.Services
RadioButton { RadioButton {
id: root id: root
font.pointSize: Tokens.font.size.normal font: Tokens.font.body.medium
implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight) implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight)
implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin
@@ -14,7 +14,7 @@ RadioButton {
anchors.left: outerCircle.right anchors.left: outerCircle.right
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.pointSize: root.font.pointSize font: root.font
text: root.text text: root.text
} }
indicator: Rectangle { indicator: Rectangle {
@@ -29,8 +29,7 @@ RadioButton {
radius: Tokens.rounding.full radius: Tokens.rounding.full
Behavior on border.color { Behavior on border.color {
CAnim { CAnim {}
}
} }
StateLayer { StateLayer {
+1 -2
View File
@@ -6,7 +6,6 @@ Rectangle {
color: "transparent" color: "transparent"
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
} }
} }
+71 -83
View File
@@ -1,32 +1,41 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Helpers
import ZShell.Config
import qs.Services import qs.Services
import ZShell.Config
ScrollBar { ScrollBar {
id: root id: root
required property Flickable flickable
readonly property bool isHorizontal: flickable && flickable.ScrollBar.horizontal === root
readonly property real axisSize: isHorizontal ? flickable.width : flickable.height
readonly property real axisContentSize: isHorizontal ? flickable.contentWidth : flickable.contentHeight
readonly property real axisContentPos: isHorizontal ? (reversed ? flickable.contentX : (flickable.contentX - flickable.originX)) : (reversed ? flickable.contentY : (flickable.contentY - flickable.originY))
readonly property real axisLength: isHorizontal ? root.width : root.height
readonly property real effectiveSize: Math.max(nonAnimHeight, root.minimumSize) readonly property real effectiveSize: Math.max(nonAnimHeight, root.minimumSize)
readonly property real effectiveTravel: Math.max(0, 1 - root.effectiveSize) readonly property real effectiveTravel: Math.max(0, 1 - root.effectiveSize)
required property Flickable flickable readonly property real nonAnimHeight: root.axisSize / root.axisContentSize
readonly property real nonAnimHeight: flickable.height / flickable.contentHeight readonly property real nonAnimY: root.axisContentPos / root.axisContentSize
readonly property real nonAnimY: flickable.contentY / flickable.contentHeight
readonly property real rawTravel: Math.max(0, 1 - root.nonAnimHeight) readonly property real rawTravel: Math.max(0, 1 - root.nonAnimHeight)
readonly property bool reversed: flickable instanceof ListView && flickable.verticalLayoutDirection === ListView.BottomToTop readonly property bool reversed: isHorizontal ? (flickable instanceof ListView && flickable.layoutDirection === Qt.RightToLeft) : (flickable instanceof ListView && flickable.verticalLayoutDirection === ListView.BottomToTop)
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 property bool shouldBeActive
implicitWidth: Tokens.padding.extraSmall * 2 property bool inverted
contentItem: Item { enabled: !ShellState.forActive().isDrawing
}
implicitWidth: size === 1 ? 0 : isHorizontal ? 0 : Tokens.padding.extraSmall * 2
implicitHeight: size === 1 ? 0 : isHorizontal ? Tokens.padding.extraSmall * 2 : 0
contentItem: Item {}
Behavior on position { Behavior on position {
enabled: !fullMouse.pressed enabled: !fullMouse.pressed
Anim { Anim {}
}
} }
onHoveredChanged: { onHoveredChanged: {
@@ -47,54 +56,27 @@ ScrollBar {
target: root.flickable target: root.flickable
} }
CustomClippingRect { Loader {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.top: parent.top
implicitWidth: handle.implicitWidth
radius: Tokens.rounding.full
CustomRect {
id: handle
anchors.right: parent.right
color: Colors.palette.m3secondary
implicitHeight: root.height * root.effectiveSize
implicitWidth: fullMouse.pressed || fullMouse.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
opacity: {
if (!root.enabled)
return 0;
if (root.size === 1)
return 0;
if (fullMouse.pressed)
return 1;
if (fullMouse.containsMouse)
return 0.8;
if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive)
return 0.6;
return 0;
}
radius: Tokens.rounding.full
y: root.reversed ? root.height * (1 + root.nonAnimY) * root.travelScale : root.height * root.nonAnimY * root.travelScale
Behavior on implicitWidth {
Anim {
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
MouseArea {
id: mouse
acceptedButtons: Qt.NoButton
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor active: root.size < 1
hoverEnabled: true sourceComponent: root.isHorizontal ? horizontalTrack : verticalTrack
} }
Component {
id: verticalTrack
VerticalScrollBarTrack {
scrollBar: root
mouseArea: fullMouse
}
}
Component {
id: horizontalTrack
HorizontalScrollBarTrack {
scrollBar: root
mouseArea: fullMouse
} }
} }
@@ -111,25 +93,40 @@ ScrollBar {
property real pressOffset: 0 property real pressOffset: 0
function contentYFromThumbTop(thumbTop) { function contentPosFromThumbStart(thumbStart) {
var visualPos = root.effectiveTravel > 0 ? thumbTop / root.travelScale : 0; var visualPos = root.effectiveTravel > 0 ? thumbStart / root.travelScale : 0;
return root.reversed ? (visualPos - 1) * root.axisContentSize : visualPos * root.axisContentSize;
return root.reversed ? (visualPos - 1) * root.flickable.contentHeight : visualPos * root.flickable.contentHeight;
} }
function updateFromEvent(event) { function updateFromEvent(event) {
var posInTrack = event.y / root.height; var eventPos = root.isHorizontal ? event.x : event.y;
var thumbTop = posInTrack - pressOffset; var posInTrack = eventPos / root.axisLength;
thumbTop = Math.max(0, Math.min(root.effectiveTravel, thumbTop)); var thumbStart = posInTrack - pressOffset;
thumbStart = Math.max(0, Math.min(root.effectiveTravel, thumbStart));
root.flickable.contentY = contentYFromThumbTop(thumbTop); var newPos = contentPosFromThumbStart(thumbStart);
if (root.isHorizontal)
root.flickable.contentX = newPos + (root.flickable.originX + (root.reversed ? root.flickable.contentWidth : 0));
else
root.flickable.contentY = newPos + (root.flickable.originY + (root.reversed ? root.flickable.contentHeight : 0));
} }
function visualThumbTop() { function visualThumbStart() {
const visualPos = root.reversed ? (1 + root.nonAnimY) : root.nonAnimY; const visualPos = root.reversed ? (1 + root.nonAnimY) : root.nonAnimY;
return visualPos * root.travelScale; return visualPos * root.travelScale;
} }
function onWheel(event: WheelEvent): void {
if (root.horizontal) {
event.accepted = false;
return;
}
var delta = event.angleDelta.y > 0 ? -0.1 : 0.1;
var newPos = Math.max(0, Math.min(1 - root.size, root.position + (root.inverted ? -delta : delta)));
root.position = newPos;
}
anchors.fill: parent anchors.fill: parent
cursorShape: undefined cursorShape: undefined
hoverEnabled: true hoverEnabled: true
@@ -140,24 +137,15 @@ ScrollBar {
updateFromEvent(event); updateFromEvent(event);
} }
onPressed: event => { onPressed: event => {
var currentTop = visualThumbTop(); var currentStart = visualThumbStart();
var currentBottom = currentTop + root.effectiveSize; var currentEnd = currentStart + root.effectiveSize;
var clickPos = event.y / root.height; var eventPos = root.isHorizontal ? event.x : event.y;
var clickPos = eventPos / root.axisLength;
var clickedInsideThumb = clickPos >= currentTop && clickPos <= currentBottom; var clickedInsideThumb = clickPos >= currentStart && clickPos <= currentEnd;
pressOffset = clickedInsideThumb ? (clickPos - currentStart) : root.effectiveSize / 2;
if (clickedInsideThumb) {
pressOffset = clickPos - currentTop;
} else {
pressOffset = root.effectiveSize / 2;
}
updateFromEvent(event); updateFromEvent(event);
} }
onWheel: event => {
var delta = event.angleDelta.y > 0 ? -0.1 : 0.1;
var newPos = Math.max(0, Math.min(1 - root.size, root.position + delta));
root.position = newPos;
}
} }
} }
+9 -7
View File
@@ -100,15 +100,19 @@ Slider {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.top: parent.top anchors.top: parent.top
color: root.insetColor color: root.insetColor
font.pixelSize: Tokens.font.size.smaller * 2 fontStyle: {
if (root.height < 96)
return Tokens.font.icon.pixelSize(24).build();
else
return Tokens.font.icon.pixelSize(32).build();
}
text: root.insetIcon text: root.insetIcon
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
visible: !root.wavy && root.insetIcon !== "" visible: !root.wavy && root.insetIcon !== ""
x: Tokens.spacing.extraSmall + dockT * (handle.x + Tokens.spacing.small) x: Tokens.spacing.extraSmall + dockT * (handle.x + Tokens.spacing.small)
Behavior on dockT { Behavior on dockT {
Anim { Anim {}
}
} }
} }
@@ -138,8 +142,7 @@ Slider {
startX: x startX: x
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
} }
Anim on waveProgress { Anim on waveProgress {
duration: root.waveDuration duration: root.waveDuration
@@ -156,8 +159,7 @@ Slider {
Behavior on filledWidth { Behavior on filledWidth {
id: widthBehavior id: widthBehavior
Anim { Anim {}
}
} }
Component.onCompleted: { Component.onCompleted: {
+2 -3
View File
@@ -14,10 +14,9 @@ Text {
property real animateTo: 1 property real animateTo: 1
color: Colors.palette.m3onSurface color: Colors.palette.m3onSurface
font.family: Config.appearance.font.family.sans font: Tokens.font.body.small
font.pointSize: Tokens.font.size.normal
linkColor: Colors.palette.m3onPrimaryFixedVariant linkColor: Colors.palette.m3onPrimaryFixedVariant
renderType: Text.NativeRendering renderType: Text.QtRendering
textFormat: Text.PlainText textFormat: Text.PlainText
Behavior on color { Behavior on color {
+26 -22
View File
@@ -15,28 +15,33 @@ TextFieldBase {
readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius) readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius)
readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText
readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
readonly property real smallFontScale: smallFontSize / font.pointSize
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.spacing.extraSmall : 0
readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
property int horizontalPadding: Tokens.padding.large
property int verticalPadding: Tokens.padding.large
property int radius: Tokens.rounding.small
property string supportingText
property string trailingIcon
property int smallFontSize: Tokens.font.label.small.pointSize
property int type: CustomTextField.Outlined
property var validate // Regex or function
property bool emptyIsValid: true property bool emptyIsValid: true
property string errorText property string errorText
readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
readonly property int horizontalPadding: Tokens.padding.large
property bool isError property bool isError
property string leadingIcon property string leadingIcon
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
property int radius: Tokens.rounding.small
readonly property real smallFontScale: smallFontSize / font.pointSize
property int smallFontSize: Tokens.font.size.small
property string supportingText
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.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
bottomPadding: Tokens.padding.large + supportingTextOffset - filledOffset bottomPadding: verticalPadding + supportingTextOffset - filledOffset
leftPadding: horizontalPadding + leadingOffset leftPadding: horizontalPadding + leadingOffset
rightPadding: horizontalPadding + trailingOffset rightPadding: horizontalPadding + trailingOffset
topPadding: Tokens.padding.large + filledOffset topPadding: verticalPadding + filledOffset
background: Loader { background: Loader {
anchors.bottomMargin: root.supportingTextOffset anchors.bottomMargin: root.supportingTextOffset
@@ -146,6 +151,7 @@ TextFieldBase {
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
text: root.leadingIcon text: root.leadingIcon
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
} }
} }
@@ -160,6 +166,7 @@ TextFieldBase {
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
text: root.trailingIcon text: root.trailingIcon
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
} }
} }
} }
@@ -174,7 +181,7 @@ TextFieldBase {
sourceComponent: CustomText { sourceComponent: CustomText {
color: root.isError ? Colors.palette.m3error : Colors.palette.m3onSurfaceVariant color: root.isError ? Colors.palette.m3error : Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small font: Tokens.font.label.small
text: root.effectiveSupportingText text: root.effectiveSupportingText
} }
} }
@@ -212,12 +219,10 @@ TextFieldBase {
} }
} }
Behavior on strokeColor { Behavior on strokeColor {
CAnim { CAnim {}
}
} }
Behavior on strokeWidth { Behavior on strokeWidth {
Anim { Anim {}
}
} }
PathLine { PathLine {
@@ -292,8 +297,7 @@ TextFieldBase {
implicitHeight: root.activeFocus ? 2 : 1 implicitHeight: root.activeFocus ? 2 : 1
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {}
}
} }
} }
} }
+42
View File
@@ -0,0 +1,42 @@
import QtQuick
import Quickshell
ShaderEffect {
id: root
property Item target: parent
property real glowSize: 40
property real innerGlow: 0
property real lineWidth: 2
property real intensity: 1.0
property real speed: 1.0
property int period: 6000
property bool running: true
property color color1: "#4285F4"
property color color2: "#9B72CB"
property color color3: "#D96570"
property color color4: "#F2A54A"
readonly property size itemSize: Qt.size(width, height)
readonly property size rectSize: target ? Qt.size(target.width, target.height) : Qt.size(0, 0)
readonly property real radius: (target as Rectangle).radius ?? 0
property real phase: 0
anchors.fill: target
anchors.margins: -glowSize
z: -1
enabled: false
fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/geminiglow.frag.qsb`)
FrameAnimation {
running: root.running && root.visible
onTriggered: {
const dt = frameTime * 1000;
root.phase = (root.phase + dt * root.speed / root.period) % 1.0;
}
}
}
+56
View File
@@ -0,0 +1,56 @@
import QtQuick
import ZShell.Config
import qs.Services
CustomClippingRect {
id: track
required property var scrollBar
required property var mouseArea
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
implicitHeight: handle.implicitHeight
radius: Tokens.rounding.full
CustomRect {
id: handle
anchors.bottom: parent.bottom
color: Colors.palette.m3secondary
implicitWidth: track.scrollBar.width * track.scrollBar.effectiveSize
implicitHeight: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
opacity: {
if (!track.scrollBar.enabled)
return 0;
if (track.scrollBar.size === 1)
return 0;
if (track.mouseArea.pressed)
return 1;
if (track.mouseArea.containsMouse)
return 0.8;
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
return 0.6;
return 0;
}
radius: Tokens.rounding.full
x: track.scrollBar.reversed ? track.scrollBar.width * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.width * track.scrollBar.nonAnimY * track.scrollBar.travelScale
Behavior on implicitHeight {
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
MouseArea {
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ ButtonBase {
property alias icon: label.text property alias icon: label.text
readonly property alias label: label readonly property alias label: label
font: Tokens.font.icon.medium
activeColor: type === IconButton.Filled ? Colors.palette.m3primary : Colors.palette.m3secondary activeColor: type === IconButton.Filled ? Colors.palette.m3primary : Colors.palette.m3secondary
activeOnColor: type === IconButton.Filled ? Colors.palette.m3onPrimary : type === IconButton.Tonal ? Colors.palette.m3onSecondary : Colors.palette.m3primary activeOnColor: type === IconButton.Filled ? Colors.palette.m3onPrimary : type === IconButton.Tonal ? Colors.palette.m3onSecondary : Colors.palette.m3primary
implicitHeight: { implicitHeight: {
@@ -36,7 +37,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 fontStyle: root.font
Behavior on fill { Behavior on fill {
Anim { Anim {
+1 -2
View File
@@ -46,10 +46,9 @@ ButtonBase {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: root.onColor color: root.onColor
fill: root.internalChecked ? 1 : 0 fill: root.internalChecked ? 1 : 0
font: { fontStyle: {
const f = Qt.font(root.font); const f = Qt.font(root.font);
f.pointSize = Math.round(root.font.pointSize * 1.2); f.pointSize = Math.round(root.font.pointSize * 1.2);
f.family = "Material Symbols Rounded";
return f; return f;
} }
+467
View File
@@ -0,0 +1,467 @@
pragma ComponentBehavior: Bound
import QtQuick
Item {
id: root
required property list<var> values
property int xSteps: 24
property real yMin: NaN
property real yMax: NaN
property real yPaddingTop: 0
property real yPaddingBottom: 0
property int verticalLineStep: 0
property int horizontalLineStep: 0
property bool firstXLine: true
property bool lastXLine: false
property int minHorizontalLines: 0
property var xLabel: null
property var yLabel: null
property real smoothing: 0
property bool curveSmoothing: true
property real markerX: -1
property real markerGap: 2 // px gap between line and marker dot edge, each side
property color lineColor: "white"
property color gridColor: "white"
property color markerColor: "white"
property color labelColor: "white"
property real gridAlpha: 0.12
property real labelAlpha: 0.6
property real fillAlpha: 0.15
property real lineWidth: 2
property real markerDotRadius: 4
property real labelFontSize: 10
Canvas {
id: canvas
anchors.fill: parent
renderStrategy: Canvas.Immediate
onWidthChanged: requestPaint()
onHeightChanged: requestPaint()
function strokeCurve(ctx, pts) {
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
if (root.curveSmoothing && pts.length > 2) {
const k = 1 / 6;
const n = pts.length;
for (let i = 0; i < n - 1; i++) {
const p0 = pts[Math.max(i - 1, 0)];
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = pts[Math.min(i + 2, n - 1)];
let c1x = p1.x + (p2.x - p0.x) * k;
let c1y = p1.y + (p2.y - p0.y) * k;
let c2x = p2.x - (p3.x - p1.x) * k;
let c2y = p2.y - (p3.y - p1.y) * k;
const minY = Math.min(p1.y, p2.y);
const maxY = Math.max(p1.y, p2.y);
c1y = Math.max(minY, Math.min(maxY, c1y));
c2y = Math.max(minY, Math.min(maxY, c2y));
ctx.bezierCurveTo(c1x, c1y, c2x, c2y, p2.x, p2.y);
}
} else {
for (let i = 0; i < pts.length - 1; i++) {
const p0 = pts[i];
const p1 = pts[i + 1];
const cx = (p0.x + p1.x) * 0.5;
ctx.bezierCurveTo(cx, p0.y, cx, p1.y, p1.x, p1.y);
}
}
}
// Control points for segment i (pts[i] -> pts[i+1]), matching strokeCurve
function segmentControls(pts, i) {
const p0 = pts[Math.max(i - 1, 0)];
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = pts[Math.min(i + 2, pts.length - 1)];
if (root.curveSmoothing && pts.length > 2) {
const k = 1 / 6;
let c1x = p1.x + (p2.x - p0.x) * k;
let c1y = p1.y + (p2.y - p0.y) * k;
let c2x = p2.x - (p3.x - p1.x) * k;
let c2y = p2.y - (p3.y - p1.y) * k;
const minY = Math.min(p1.y, p2.y);
const maxY = Math.max(p1.y, p2.y);
c1y = Math.max(minY, Math.min(maxY, c1y));
c2y = Math.max(minY, Math.min(maxY, c2y));
return [c1x, c1y, c2x, c2y];
}
const cx = (p1.x + p2.x) * 0.5;
return [cx, p1.y, cx, p2.y];
}
// Point on the curve at a given x (x is monotonic per segment)
function pointAtX(pts, targetX) {
let j = pts.length - 2;
for (let i = 0; i < pts.length - 1; i++) {
if (targetX <= pts[i + 1].x) {
j = i;
break;
}
}
const p1 = pts[j];
const p2 = pts[j + 1];
const [c1x, c1y, c2x, c2y] = segmentControls(pts, j);
let tlo = 0;
let thi = 1;
for (let it = 0; it < 24; it++) {
const tm = (tlo + thi) / 2;
const u = 1 - tm;
const x = u * u * u * p1.x + 3 * u * u * tm * c1x + 3 * u * tm * tm * c2x + tm * tm * tm * p2.x;
if (x < targetX)
tlo = tm;
else
thi = tm;
}
const t = (tlo + thi) / 2;
const u = 1 - t;
const y = u * u * u * p1.y + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * p2.y;
return { x: targetX, y };
}
// Part of the curve left/right of a cut x, including the cut point
function cutLeft(pts, targetX) {
if (targetX <= pts[0].x)
return [];
if (targetX >= pts[pts.length - 1].x)
return pts.slice();
let j = pts.length - 2;
for (let i = 0; i < pts.length - 1; i++) {
if (targetX <= pts[i + 1].x) {
j = i;
break;
}
}
const left = pts.slice(0, j + 1);
if (targetX > pts[j].x)
left.push(pointAtX(pts, targetX));
return left;
}
function cutRight(pts, targetX) {
if (targetX >= pts[pts.length - 1].x)
return [];
if (targetX <= pts[0].x)
return pts.slice();
let j = pts.length - 2;
for (let i = 0; i < pts.length - 1; i++) {
if (targetX <= pts[i + 1].x) {
j = i;
break;
}
}
const right = pts.slice(j + 1);
if (targetX < pts[j + 1].x)
right.unshift(pointAtX(pts, targetX));
return right;
}
onPaint: {
const ctx = getContext("2d");
ctx.reset();
const W = width;
const H = height;
if (W < 2 || H < 2)
return;
const padLeft = root.labelFontSize * 3.4;
const padRight = 2;
const padTop = 2;
const padBottom = root.labelFontSize + 8;
const plot = {
x: padLeft,
y: padTop,
w: W - padLeft - padRight,
h: H - padTop - padBottom
};
if (plot.w < 2 || plot.h < 2)
return;
// Auto-fit: NaN yMin/yMax derive from the data, snapped to the
// horizontal step grid, honoring minHorizontalLines
let lo = root.yMin;
let hi = root.yMax;
const loAuto = Number.isNaN(lo);
const hiAuto = Number.isNaN(hi);
if (loAuto || hiAuto) {
let dmin = Infinity;
let dmax = -Infinity;
for (const v of root.values) {
if (v == null)
continue;
if (v < dmin)
dmin = v;
if (v > dmax)
dmax = v;
}
if (dmin === Infinity) {
dmin = 0;
dmax = 1;
}
const step = root.horizontalLineStep;
const minSteps = root.minHorizontalLines > 1 ? root.minHorizontalLines - 1 : 0;
if (step > 0) {
if (hiAuto) {
const baseLo = loAuto ? Math.floor(dmin / step) * step : lo;
const span = Math.max(Math.ceil((dmax - baseLo) / step), minSteps, 1);
lo = baseLo;
hi = baseLo + span * step;
} else {
const span = Math.max(Math.ceil((hi - dmin) / step), minSteps, 1);
lo = hi - span * step;
}
} else {
if (loAuto)
lo = dmin;
if (hiAuto)
hi = dmax;
if (hi <= lo)
hi = lo + 1;
}
}
const scaleBottom = lo - root.yPaddingBottom;
const scaleTop = hi + root.yPaddingTop;
const range = scaleTop - scaleBottom;
if (range <= 0 || root.xSteps < 1)
return;
let series = root.values;
if (root.smoothing > 0 && series.length > 0) {
const radius = Math.round(root.smoothing);
const raw = series;
series = raw.map((v, i) => {
if (v == null)
return null;
let sum = 0;
let n = 0;
for (let j = -radius; j <= radius; j++) {
const u = raw[i + j];
if (u != null) {
sum += u;
n++;
}
}
return n > 0 ? sum / n : null;
});
}
const xAt = i => plot.x + (i / root.xSteps) * plot.w;
const yAt = v => plot.y + (1 - (v - scaleBottom) / range) * plot.h;
const crisp = v => Math.floor(v) + 0.5;
// 1. Grid lines
ctx.strokeStyle = Qt.alpha(root.gridColor, root.gridAlpha);
ctx.lineWidth = 1;
if (root.verticalLineStep > 0) {
for (let i = 0; i <= root.xSteps; i += root.verticalLineStep) {
if (i === 0 && !root.firstXLine)
continue;
if (i === root.xSteps && !root.lastXLine)
continue;
const x = crisp(xAt(i));
ctx.beginPath();
ctx.moveTo(x, plot.y);
ctx.lineTo(x, H);
ctx.stroke();
}
}
// Lines extend down into yPaddingBottom, snapped to the grid
const vStart = lo - (root.yPaddingBottom > 0 ? Math.floor(root.yPaddingBottom / root.horizontalLineStep) * root.horizontalLineStep : 0);
if (root.horizontalLineStep > 0) {
for (let v = vStart; v <= hi + 0.001; v += root.horizontalLineStep) {
const y = crisp(yAt(v));
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(plot.x + plot.w, y);
ctx.stroke();
}
}
// 2. Labels
ctx.fillStyle = Qt.alpha(root.labelColor, root.labelAlpha);
ctx.font = root.labelFontSize + "px sans-serif";
ctx.textAlign = "left";
ctx.textBaseline = "top";
if (root.verticalLineStep > 0 && root.xLabel) {
for (let i = 0; i <= root.xSteps; i += root.verticalLineStep) {
if (i === 0 && !root.firstXLine)
continue;
if (i === root.xSteps && !root.lastXLine)
continue;
const text = root.xLabel(i);
if (text)
ctx.fillText(text, xAt(i) + 4, plot.y + plot.h + 4);
}
}
ctx.textBaseline = "bottom";
if (root.horizontalLineStep > 0 && root.yLabel) {
for (let v = vStart; v <= hi + 0.001; v += root.horizontalLineStep) {
const text = root.yLabel(v);
if (text)
ctx.fillText(text, 2, yAt(v) - 2);
}
}
// 3. Curve, split into segments at null gaps
const segments = [];
let current = null;
for (let i = 0; i < series.length; i++) {
if (series[i] == null) {
if (current)
segments.push(current);
current = null;
continue;
}
const pt = {
x: xAt(i),
y: yAt(series[i])
};
if (!current)
current = [];
current.push(pt);
}
if (current)
segments.push(current);
ctx.lineCap = "round";
ctx.lineJoin = "round";
const hasMarker = root.markerX >= 0 && root.markerX <= root.xSteps;
const markerPx = hasMarker ? xAt(root.markerX) : 0;
let gapPx = 0;
if (hasMarker) {
// markerGap is measured from the dot edge, perpendicular to the
// curve; project onto x via the local curve slope
let slope = 0;
const seg = segments.find(s => s[0].x <= markerPx && markerPx <= s[s.length - 1].x);
if (seg && seg.length > 2 && markerPx - 1 >= seg[0].x && markerPx + 1 <= seg[seg.length - 1].x)
slope = (pointAtX(seg, markerPx + 1).y - pointAtX(seg, markerPx - 1).y) / 2;
gapPx = (root.markerDotRadius + root.markerGap) / Math.sqrt(1 + slope * slope);
}
for (const seg of segments) {
if (seg.length < 2)
continue;
// Fill stays continuous under the marker
ctx.fillStyle = Qt.alpha(root.lineColor, root.fillAlpha);
strokeCurve(ctx, seg);
ctx.lineTo(seg[seg.length - 1].x, plot.y + plot.h);
ctx.lineTo(seg[0].x, plot.y + plot.h);
ctx.closePath();
ctx.fill();
// Stroke has a gap around the marker
const pieces = hasMarker ? [cutLeft(seg, markerPx - gapPx), cutRight(seg, markerPx + gapPx)] : [seg];
for (const pts of pieces) {
if (pts.length < 2)
continue;
ctx.strokeStyle = root.lineColor;
ctx.lineWidth = root.lineWidth;
strokeCurve(ctx, pts);
ctx.stroke();
}
}
// 4. Marker: dashed vertical + dot
if (root.markerX >= 0 && root.markerX <= root.xSteps) {
const x = markerPx;
ctx.setLineDash([4, 4]);
ctx.strokeStyle = Qt.alpha(root.markerColor, 0.6);
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(crisp(x), plot.y);
ctx.lineTo(crisp(x), H);
ctx.stroke();
ctx.setLineDash([]);
const i0 = Math.floor(root.markerX);
const v0 = series[i0];
const v1 = series[i0 + 1];
let y = null;
if (v0 != null && v1 != null)
y = yAt(v0 + (v1 - v0) * (root.markerX - i0));
else if (v0 != null)
y = yAt(v0);
if (y != null) {
ctx.fillStyle = root.markerColor;
ctx.beginPath();
ctx.arc(x, y, root.markerDotRadius, 0, Math.PI * 2);
ctx.fill();
}
}
}
}
onValuesChanged: canvas.requestPaint()
onXStepsChanged: canvas.requestPaint()
onYMinChanged: canvas.requestPaint()
onYMaxChanged: canvas.requestPaint()
onYPaddingTopChanged: canvas.requestPaint()
onYPaddingBottomChanged: canvas.requestPaint()
onVerticalLineStepChanged: canvas.requestPaint()
onHorizontalLineStepChanged: canvas.requestPaint()
onFirstXLineChanged: canvas.requestPaint()
onLastXLineChanged: canvas.requestPaint()
onMinHorizontalLinesChanged: canvas.requestPaint()
onXLabelChanged: canvas.requestPaint()
onYLabelChanged: canvas.requestPaint()
onSmoothingChanged: canvas.requestPaint()
onCurveSmoothingChanged: canvas.requestPaint()
onMarkerXChanged: canvas.requestPaint()
onMarkerGapChanged: canvas.requestPaint()
onLineColorChanged: canvas.requestPaint()
onGridColorChanged: canvas.requestPaint()
onMarkerColorChanged: canvas.requestPaint()
onLabelColorChanged: canvas.requestPaint()
onGridAlphaChanged: canvas.requestPaint()
onLabelAlphaChanged: canvas.requestPaint()
onFillAlphaChanged: canvas.requestPaint()
onLineWidthChanged: canvas.requestPaint()
onMarkerDotRadiusChanged: canvas.requestPaint()
onLabelFontSizeChanged: canvas.requestPaint()
}
+4 -17
View File
@@ -1,27 +1,14 @@
import QtQuick
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
CustomText { CustomText {
id: root id: root
readonly property list<int> allowed: [20, 24, 40, 48]
property real fill: 0 property real fill: 0
property int grade: Colors.light ? 0 : -25 property int grade: Colors.light ? 0 : -25
readonly property int opsz: clampOpsz(optical) property font fontStyle: Tokens.font.icon.small
property int optical: fontInfo.pixelSize
function clampOpsz(value): int { renderType: Text.NativeRendering
return allowed.reduce((closest, current) => { font: Tokens.font.icon.size(fontStyle.pointSize).weight(fontStyle.weight).vaxes(fontStyle.variableAxes).fill(fill.toFixed(1)).grade(grade).build()
return Math.abs(current - value) < Math.abs(closest - value) ? current : closest;
});
}
font.family: "Material Symbols Rounded"
font.pointSize: Tokens.font.size.larger
font.variableAxes: ({
FILL: fill,
GRAD: grade,
opsz: opsz,
wght: fontInfo.weight
})
} }
+13 -12
View File
@@ -124,7 +124,7 @@ MouseArea {
model: root.items model: root.items
CustomRect { CustomRect {
id: item id: menuItem
readonly property bool active: modelData === root?.active readonly property bool active: modelData === root?.active
required property int index required property int index
@@ -142,13 +142,13 @@ MouseArea {
} }
StateLayer { StateLayer {
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
enabled: root.expanded enabled: root.expanded
onClicked: { onClicked: {
root.itemSelected(item.modelData); root.itemSelected(menuItem.modelData);
root.active = item.modelData; root.active = menuItem.modelData;
item.modelData.clicked(); menuItem.modelData.clicked();
root.expanded = false; root.expanded = false;
} }
} }
@@ -162,26 +162,27 @@ MouseArea {
MaterialIcon { MaterialIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
text: item.modelData?.icon ?? "" text: menuItem.modelData?.icon ?? ""
visible: text !== ""
} }
CustomText { CustomText {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
text: item.modelData?.text ?? "" text: menuItem.modelData?.text ?? ""
} }
Loader { Loader {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
active: item.modelData?.trailingIcon.length > 0 active: menuItem.modelData?.trailingIcon.length > 0
asynchronous: true asynchronous: true
visible: active visible: active
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
text: item.modelData.trailingIcon text: menuItem.modelData.trailingIcon
} }
} }
} }
+40
View File
@@ -0,0 +1,40 @@
import QtQuick
import Quickshell
PersistentProperties {
required property ShellScreen modelData
reloadableId: "screenState"
// Drawer visibilities
property bool bar
property bool clipboard
property bool dashboard
property bool dock
property bool isDrawing
property bool launcher
property bool osd
property bool resources
property bool settings
property bool sidebar
// Dashboard state
property int dashboardTab: 0
property date dashboardDate: new Date()
// Sidebar state
property int sidebarTab: 0
property list<string> expandedNotifs: []
// Utilities state
property string recordingConfirmDelete
property bool recordingListExpanded
property string recordingMode
// Drawing state
property color penColor: "white"
property int penWidth: 4
// Screenshot state
property bool screenshot
}
+1 -1
View File
@@ -62,7 +62,7 @@ TextFieldBase {
anchors.leftMargin: Tokens.padding.large anchors.leftMargin: Tokens.padding.large
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
text: "search" text: "search"
} }
+3 -4
View File
@@ -2,7 +2,6 @@ import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import ZShell import ZShell
import ZShell.Config import ZShell.Config
import qs.Helpers
import qs.Services import qs.Services
MouseArea { MouseArea {
@@ -52,7 +51,7 @@ MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: !enabled ? undefined : Qt.PointingHandCursor cursorShape: !enabled ? undefined : Qt.PointingHandCursor
enabled: parent.enabled && !Visibilities.getForActive()?.isDrawing enabled: parent.enabled && !ShellState.forActive()?.isDrawing
hoverEnabled: true hoverEnabled: true
Behavior on stateOpacity { Behavior on stateOpacity {
@@ -99,13 +98,13 @@ MouseArea {
id: base id: base
anchors.fill: parent anchors.fill: parent
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
color: Colors.palette.m3onSurface color: Colors.palette.m3onSurface
opacity: root.stateOpacity opacity: root.stateOpacity
// Pick up radius from parent if it has one (parent can be anything with radius props) // Pick up radius from parent if it has one (parent can be anything with radius props)
// qmllint disable missing-property // qmllint disable missing-property
radius: root.parent?.radius ?? 0 radius: root.parent?.radius ?? 0
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0 topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0 topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
// qmllint enable missing-property // qmllint enable missing-property
+86
View File
@@ -0,0 +1,86 @@
import QtQuick
import QtQuick.Templates
import ZShell.Config
import qs.Services
TextArea {
id: root
color: Colors.palette.m3onSurface
cursorVisible: !readOnly
font: Tokens.font.body.small
implicitHeight: contentHeight + topPadding + bottomPadding
implicitWidth: contentWidth + leftPadding + rightPadding
placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
renderType: TextArea.NativeRendering
selectedTextColor: color
selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4)
verticalAlignment: TextInput.AlignVCenter
Behavior on color {
CAnim {}
}
Behavior on selectionColor {
CAnim {}
}
cursorDelegate: Item {}
CustomRect {
id: cursor
property bool disableBlink
color: Colors.palette.m3primary
implicitHeight: root.cursorRectangle.height
implicitWidth: 1.5
radius: Tokens.rounding.largeIncreased
x: root.cursorRectangle.x
y: root.cursorRectangle.y
Behavior on x {
Anim {
duration: Tokens.anim.durations.expressiveFastEffects
easing.bezierCurve: [0.2, 1, 0.21, 1, 1, 1]
}
}
Behavior on opacity {
Anim {
type: Anim.StandardSmall
}
}
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
}
}
}
+96
View File
@@ -0,0 +1,96 @@
import QtQuick
import QtQuick.Templates
import ZShell.Config
import qs.Services
TextEdit {
id: root
property bool animateCursor: true
property alias cursor: cursor
color: Colors.palette.m3onSurface
cursorVisible: !readOnly
font: Tokens.font.body.small
renderType: TextField.NativeRendering
selectedTextColor: color
selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4)
verticalAlignment: TextInput.AlignVCenter
Behavior on color {
CAnim {}
}
cursorDelegate: Item {}
Behavior on selectionColor {
CAnim {}
}
CustomRect {
id: cursor
property bool disableBlink
color: Colors.palette.m3primary
implicitHeight: root.cursorRectangle.height
implicitWidth: 1.5
radius: Tokens.rounding.largeIncreased
x: root.cursorRectangle.x
y: root.cursorRectangle.y
Behavior on opacity {
Anim {
type: Anim.StandardSmall
}
}
Behavior on x {
enabled: root.animateCursor
Anim {
duration: Tokens.anim.durations.expressiveFastEffects
easing.bezierCurve: [0.2, 1, 0.21, 1, 1, 1]
}
}
Behavior on y {
enabled: root.animateCursor
Anim {
duration: Tokens.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
}
}
}
+4 -7
View File
@@ -8,7 +8,7 @@ TextField {
color: Colors.palette.m3onSurface color: Colors.palette.m3onSurface
cursorVisible: !readOnly cursorVisible: !readOnly
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
implicitHeight: contentHeight + topPadding + bottomPadding implicitHeight: contentHeight + topPadding + bottomPadding
implicitWidth: contentWidth + leftPadding + rightPadding implicitWidth: contentWidth + leftPadding + rightPadding
placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
@@ -18,14 +18,11 @@ TextField {
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
}
cursorDelegate: Item {
} }
cursorDelegate: Item {}
Behavior on selectionColor { Behavior on selectionColor {
CAnim { CAnim {}
}
} }
CustomRect { CustomRect {
+3 -4
View File
@@ -36,8 +36,7 @@ CustomRect {
radius: Tokens.rounding.medium radius: Tokens.rounding.medium
Behavior on border.color { Behavior on border.color {
CAnim { CAnim {}
}
} }
Elevation { Elevation {
@@ -84,7 +83,7 @@ CustomRect {
return Colors.palette.m3onError; return Colors.palette.m3onError;
return Colors.palette.m3onSurfaceVariant; return Colors.palette.m3onSurfaceVariant;
} }
font.pointSize: Math.round(Tokens.font.size.large * 1.2) fontStyle: Tokens.font.icon.builders.large.scale(1.2).build()
text: root.modelData.icon text: root.modelData.icon
} }
} }
@@ -107,7 +106,7 @@ CustomRect {
return Colors.palette.m3onSurface; return Colors.palette.m3onSurface;
} }
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: Tokens.font.size.normal font: Tokens.font.title.small
text: root.modelData.title text: root.modelData.title
} }
+7 -3
View File
@@ -7,7 +7,9 @@ CustomListView {
id: root id: root
property real endFadeOpacity: fadeShouldBeActive(false) ? 0 : 1 property real endFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property bool fadeEnabled: true
property real fadeAmount: 0.1 property real fadeAmount: 0.1
property real fadeThreshold: 0.0
readonly property bool horizontal: orientation === ListView.Horizontal readonly property bool horizontal: orientation === ListView.Horizontal
property real startFadeOpacity: fadeShouldBeActive(true) ? 0 : 1 property real startFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
@@ -16,7 +18,9 @@ CustomListView {
} }
function fadeShouldBeActive(isStart: bool): bool { function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts. if (!fadeEnabled)
return false;
if (contentSize() + marginStart() + marginEnd() < viewportSize() && rebound.running && ((isStart ? overshootStart() > 0 : overshootStart() < 0))) { if (contentSize() + marginStart() + marginEnd() < viewportSize() && rebound.running && ((isStart ? overshootStart() > 0 : overshootStart() < 0))) {
return false; return false;
} }
@@ -28,11 +32,11 @@ CustomListView {
} }
function marginEnd(): real { function marginEnd(): real {
return horizontal ? rightMargin : bottomMargin; return horizontal ? rightMargin - fadeThreshold : bottomMargin - fadeThreshold;
} }
function marginStart(): real { function marginStart(): real {
return horizontal ? leftMargin : topMargin; return horizontal ? leftMargin - fadeThreshold : topMargin - fadeThreshold;
} }
function overshootStart(): real { function overshootStart(): real {
+56
View File
@@ -0,0 +1,56 @@
import QtQuick
import ZShell.Config
import qs.Services
CustomClippingRect {
id: track
required property var scrollBar
required property var mouseArea
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.top: parent.top
implicitWidth: handle.implicitWidth
radius: Tokens.rounding.full
CustomRect {
id: handle
anchors.right: parent.right
color: Colors.palette.m3secondary
implicitHeight: track.scrollBar.height * track.scrollBar.effectiveSize
implicitWidth: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
opacity: {
if (!track.scrollBar.enabled)
return 0;
if (track.scrollBar.size === 1)
return 0;
if (track.mouseArea.pressed)
return 1;
if (track.mouseArea.containsMouse)
return 0.8;
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
return 0.6;
return 0;
}
radius: Tokens.rounding.full
y: track.scrollBar.reversed ? track.scrollBar.height * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.height * track.scrollBar.nonAnimY * track.scrollBar.travelScale
Behavior on implicitWidth {
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
MouseArea {
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
}
}
+2 -1
View File
@@ -10,6 +10,7 @@ import ZShell
import qs.Components import qs.Components
import qs.Modules import qs.Modules
import qs.Helpers import qs.Helpers
import qs.Services
import qs.Paths import qs.Paths
import ZShell.Config import ZShell.Config
@@ -25,7 +26,7 @@ Singleton {
property alias server: server property alias server: server
function shouldThrottle(appName: string): bool { function shouldThrottle(appName: string): bool {
if (props.dnd || [...Visibilities.screens.values()].some(v => v.sidebar)) if (props.dnd || ShellState.anySidebarOpen())
return false; return false;
const key = (appName || "unknown").trim().toLowerCase(); const key = (appName || "unknown").trim().toLowerCase();
+5 -10
View File
@@ -3,28 +3,23 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import ZShell.Internal import ZShell.Internal
import qs.Components
Item { Item {
id: root id: root
readonly property alias content: contentLoader.item readonly property alias content: contentLoader.item
readonly property PersistentProperties drawingState: PersistentProperties { required property ScreenState screenState
property color penColor: "white"
property int penWidth: 4
reloadableId: "drawingState"
}
required property PersistentProperties visibilities
Loader { Loader {
id: contentLoader id: contentLoader
active: root.visibilities.isDrawing active: root.screenState.isDrawing
anchors.fill: parent anchors.fill: parent
sourceComponent: StrokeCanvas { sourceComponent: StrokeCanvas {
penColor: root.drawingState.penColor penColor: root.screenState.penColor
penWidth: root.drawingState.penWidth penWidth: root.screenState.penWidth
} }
} }
} }
+75 -51
View File
@@ -1,7 +1,7 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Helpers import qs.Helpers
import qs.Modules.Bar.Popouts import qs.Modules.Bar.Popouts
@@ -19,7 +19,7 @@ Item {
required property ShellScreen screen required property ShellScreen screen
property bool singleGestureTriggered: false property bool singleGestureTriggered: false
property bool utilitiesShortcutActive property bool utilitiesShortcutActive
required property PersistentProperties visibilities required property ScreenState screenState
function inBottomPanel(panel: Item, x: real, y: real): bool { function inBottomPanel(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0)); const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
@@ -63,7 +63,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 enabled: !root.screenState.isDrawing
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything
maximumPointCount: 1 maximumPointCount: 1
minimumPointCount: 1 minimumPointCount: 1
@@ -76,45 +76,59 @@ Item {
onCentroidChanged: { onCentroidChanged: {
const x = centroid.position.x; const x = centroid.position.x;
const y = centroid.position.y; const y = centroid.position.y;
const dragX = x - centroid.pressPosition.x; const pressX = centroid.pressPosition.x;
const dragY = y - centroid.pressPosition.y; const pressY = centroid.pressPosition.y;
const dragX = x - pressX;
const dragY = y - pressY;
if (centroid.pressPosition.y >= root.screen.height - Config.bar.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold) if (pressY >= root.screen.height - Config.bar.border && pressX > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold)
root.visibilities.launcher = true; root.screenState.launcher = true;
if (root.singleGestureTriggered) if (root.singleGestureTriggered)
return; return;
if (root.geometry.insetTop(root.borderThickness) > centroid.pressPosition.y) { const inDashboard = root.inTopPanel(root.panels.dashboardWrapper, pressX, pressY);
if (inDashboard && root.withinPanelWidth(root.panels.dashboardWrapper, x, y)) {
if (dragY > Config.dashboard.dragThreshold) {
root.screenState.dashboard = true;
root.singleGestureTriggered = true;
} else if (dragY < -Config.dashboard.dragThreshold) {
root.screenState.dashboard = false;
root.singleGestureTriggered = true;
}
}
if (root.geometry.insetTop(root.borderThickness) > pressY && (Config.dashboard.showOnHover || !inDashboard)) {
if (dragY > 20) { if (dragY > 20) {
root.visibilities.settings = true; root.screenState.settings = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} else if (dragY < -20) { } else if (dragY < -20) {
root.visibilities.settings = false; root.screenState.settings = false;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
} }
if (centroid.pressPosition.y > root.screen.height - root.geometry.insetBottom(root.borderThickness) && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50) if (pressY > root.screen.height - root.geometry.insetBottom(root.borderThickness) && pressX < root.screen.width / 5 && dragY < -50)
root.visibilities.clipboard = true; root.screenState.clipboard = true;
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.geometry.insetTop(root.borderThickness) && centroid.pressPosition.x > root.screen.width / 5 && !root.visibilities.launcher) if (!Config.dock.hoverToReveal && pressY > root.screen.height - root.geometry.insetTop(root.borderThickness) && pressX > root.screen.width / 5 && !root.screenState.launcher)
if (dragY < -10) { if (dragY < -10) {
root.visibilities.dock = true; root.screenState.dock = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
if (centroid.pressPosition.x > root.screen.width - Config.bar.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) { if (pressX > root.screen.width - Config.bar.border && pressY < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) {
root.visibilities.sidebar = true; root.screenState.sidebar = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
if (centroid.pressPosition.x >= root.screen.width - Config.bar.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) { if (pressX >= root.screen.width - Config.bar.border && pressY > (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.bar.border && dragX > 20) { if (pressX <= 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;
} }
@@ -127,7 +141,7 @@ Item {
property bool setInitialPoint: false property bool setInitialPoint: false
acceptedButtons: Qt.LeftButton | Qt.RightButton 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) enabled: root.screenState.isDrawing && (!root.inLeftPanel(root.panels.drawing, hoverHandler.point.position.x, hoverHandler.point.position.y) || !root.panels.drawing.expanded)
onActiveChanged: { onActiveChanged: {
if (!active) { if (!active) {
@@ -178,12 +192,12 @@ Item {
HoverHandler { HoverHandler {
id: hoverHandler id: hoverHandler
cursorShape: root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, point.position.x, point.position.y) ? Qt.BlankCursor : undefined cursorShape: root.screenState.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) {
root.visibilities.osd = false; root.screenState.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
} }
@@ -193,13 +207,16 @@ Item {
if (Config.bar.autoHide || root.bar.fullscreen) if (Config.bar.autoHide || root.bar.fullscreen)
root.bar.isHovered = false; root.bar.isHovered = false;
if (root.screenState.dashboard && Config.dashboard.showOnHover)
root.screenState.dashboard = false;
} }
} }
onPointChanged: { onPointChanged: {
const x = point.position.x; const x = point.position.x;
const y = point.position.y; const y = point.position.y;
if (root.visibilities.isDrawing) { if (root.screenState.isDrawing) {
if (root.inLeftPanel(root.panels.drawing, x, y) && !(drawingHandler.point.pressedButtons & Qt.LeftButton)) { if (root.inLeftPanel(root.panels.drawing, x, y) && !(drawingHandler.point.pressedButtons & Qt.LeftButton)) {
root.panels.drawing.expand(); root.panels.drawing.expand();
root.drawing.content.hideHover(); root.drawing.content.hideHover();
@@ -210,7 +227,7 @@ Item {
return; return;
} }
if (!root.visibilities.bar && Config.bar.autoHide && root.geometry.barContains(x, y, true)) if (!root.screenState.bar && Config.bar.autoHide && root.geometry.barContains(x, y, true))
root.bar.isHovered = true; root.bar.isHovered = true;
if (root.bar.fullscreen && !root.bar.isHovered) if (root.bar.fullscreen && !root.bar.isHovered)
@@ -228,7 +245,7 @@ Item {
const showOsd = outOfSidebar && root.inRightPanel(root.panels.osdWrapper, x, y); const showOsd = outOfSidebar && root.inRightPanel(root.panels.osdWrapper, x, y);
if (!root.osdShortcutActive) { if (!root.osdShortcutActive) {
root.visibilities.osd = showOsd; root.screenState.osd = showOsd;
root.panels.osd.hovered = showOsd; root.panels.osd.hovered = showOsd;
} else if (showOsd) { } else if (showOsd) {
root.osdShortcutActive = false; root.osdShortcutActive = false;
@@ -236,8 +253,13 @@ Item {
} }
} }
if (Config.dock.enable && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y)) if (Config.dock.enable && Config.dock.hoverToReveal && !root.screenState.dock && !root.screenState.launcher && root.inBottomPanel(root.panels.dock, x, y))
root.visibilities.dock = true; root.screenState.dock = true;
const inDashboard = root.inTopPanel(root.panels.dashboardWrapper, x, y);
if (Config.dashboard.showOnHover)
root.screenState.dashboard = inDashboard;
if (root.geometry.barContains(x, y)) if (root.geometry.barContains(x, y))
root.bar.checkPopout(root.geometry.axisPos(x, y)); root.bar.checkPopout(root.geometry.axisPos(x, y));
@@ -246,16 +268,17 @@ Item {
Connections { Connections {
function onDashboardChanged() { function onDashboardChanged() {
if (root.visibilities.dashboard) { if (root.screenState.dashboard) {
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
if (!inDashboardArea) { if (!inDashboardArea) {
root.dashboardShortcutActive = true; root.dashboardShortcutActive = true;
} }
if (root.panels.launcher.x + root.panels.launcher.width > root.panels.dashboardWrapper.x) if (root.panels.launcher.x + root.panels.launcher.width > root.panels.dashboardWrapper.x)
root.visibilities.launcher = false; root.screenState.launcher = false;
root.visibilities.settings = false; root.screenState.settings = false;
root.visibilities.sidebar = false; root.screenState.sidebar = false;
root.screenState.resources = false;
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} else { } else {
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
@@ -263,12 +286,12 @@ Item {
} }
function onIsDrawingChanged() { function onIsDrawingChanged() {
if (!root.visibilities.isDrawing) if (!root.screenState.isDrawing)
root.drawing.clear(); root.drawing.clear();
} }
function onLauncherChanged() { function onLauncherChanged() {
if (!root.visibilities.launcher) { if (!root.screenState.launcher) {
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
root.osdShortcutActive = false; root.osdShortcutActive = false;
root.utilitiesShortcutActive = false; root.utilitiesShortcutActive = false;
@@ -276,23 +299,23 @@ Item {
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) { if (!inOsdArea) {
root.visibilities.osd = false; root.screenState.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
} }
} }
if (root.visibilities.launcher) { if (root.screenState.launcher) {
if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) { if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) {
root.visibilities.dashboard = false; root.screenState.dashboard = false;
} }
root.visibilities.dock = false; root.screenState.dock = false;
root.visibilities.settings = false; root.screenState.settings = false;
} }
} }
function onOsdChanged() { function onOsdChanged() {
if (root.visibilities.osd) { if (root.screenState.osd) {
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) { if (!inOsdArea) {
root.osdShortcutActive = true; root.osdShortcutActive = true;
@@ -303,32 +326,33 @@ Item {
} }
function onResourcesChanged() { function onResourcesChanged() {
if (root.visibilities.resources && (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))) { if (root.screenState.resources) {
if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
}
if (root.visibilities.resources) root.screenState.settings = false;
root.visibilities.settings = false; root.screenState.dashboard = false;
}
} }
function onSettingsChanged() { function onSettingsChanged() {
if (root.visibilities.settings) { if (root.screenState.settings) {
root.visibilities.resources = false; root.screenState.resources = false;
root.visibilities.dashboard = false; root.screenState.dashboard = false;
root.visibilities.sidebar = false; root.screenState.sidebar = false;
root.panels.popouts.hasCurrent = false; root.panels.popouts.hasCurrent = false;
root.visibilities.launcher = false; root.screenState.launcher = false;
} }
} }
function onSidebarChanged() { function onSidebarChanged() {
if (root.visibilities.sidebar) { if (root.screenState.sidebar) {
root.visibilities.dashboard = false; root.screenState.dashboard = false;
root.visibilities.settings = false; root.screenState.settings = false;
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} }
} }
target: root.visibilities target: root.screenState
} }
} }
+315
View File
@@ -0,0 +1,315 @@
import Quickshell
import QtQuick
import ZShell.Config
import ZShell.Internal
import qs.Components
import qs.Helpers
import qs.Modules.Bar.Popouts
CustomMouseArea {
id: root
required property Item bar
required property real borderThickness
property bool dashboardShortcutActive
property point dragStart
required property Drawing drawing
required property EdgeGeometry geometry
property bool osdShortcutActive
required property Panels panels
required property Wrapper popouts
required property ShellScreen screen
required property ScreenState screenState
property bool setInitialPoint: false
property bool singleGestureTriggered: false
property bool utilitiesShortcutActive
function inBottomPanel(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
return y > root.height - (panelHeight + geometry.insetBottom(borderThickness)) && withinPanelWidth(panel, x, y);
}
function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
}
function inPopoutArea(x: real, y: real): bool {
const panel = panels.popoutsWrapper;
if (!geometry.horizontal)
return inLeftPanel(panel, x, y);
return withinPanelWidth(panel, x, y) && withinPanelHeight(panel, x, y);
}
function inRightPanel(panel: Item, x: real, y: real): bool {
return x > panel.x + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
}
function inTopPanel(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
return y < geometry.insetTop(borderThickness) + panelHeight && withinPanelWidth(panel, x, y);
}
function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelY = panel.y + geometry.insetTop(borderThickness);
return y >= panelY && y <= panelY + panel.height;
}
function withinPanelWidth(panel: Item, x: real, y: real): bool {
const panelX = panel.x + geometry.insetLeft(borderThickness);
return x >= panelX && x <= panelX + panel.width;
}
acceptedButtons: bar.fullscreen ? Qt.NoButton : Qt.AllButtons
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: {
if (!containsMouse) {
if (!osdShortcutActive) {
screenState.osd = false;
panels.osd.hovered = false;
}
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
popouts.hasCurrent = false;
}
if (Config.bar.autoHide || bar.fullscreen)
bar.isHovered = false;
if (screenState.dashboard && Config.dashboard.showOnHover)
screenState.dashboard = false;
}
}
onPositionChanged: event => {
const x = event.x;
const y = event.y;
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
if (screenState.isDrawing) {
if (pressed) {
drawing.content.appendPoint(x, y);
} else {
if (inLeftPanel(panels.drawing, x, y)) {
if (!(pressedButtons & Qt.LeftButton)) {
panels.drawing.expand();
drawing.content.hideHover();
Cursor.clearOverride();
}
} else {
Cursor.setOverride(Qt.BlankCursor);
drawing.content.showHover(x, y);
}
}
if (drawing.content.hoverVisible)
drawing.content.showHover(x, y);
return;
}
if (root.bar.fullscreen && !root.bar.isHovered)
unhideTimer.start();
if (pressed && dragStart.y >= screen.height - Config.bar.border && dragStart.x > screen.width / 5 && dragY < -Config.launcher.dragThreshold)
screenState.launcher = true;
const entryAt = bar.entryAt(geometry.axisPos(x, y));
const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboardWrapper, x, y) && (!entryAt || entryAt === "activeWindow");
if (!dashboardShortcutActive) {
screenState.dashboard = showDashboard;
} else if (showDashboard) {
dashboardShortcutActive = false;
}
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && withinPanelWidth(panels.dashboardWrapper, x, y)) {
if (dragY > Config.dashboard.dragThreshold)
screenState.dashboard = true;
else if (dragY < -Config.dashboard.dragThreshold)
screenState.dashboard = false;
}
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && !inTopPanel(panels.dashboardWrapper, dragStart.x, dragStart.y)) {
if (dragY > 20) {
screenState.settings = true;
} else if (dragY < -20) {
screenState.settings = false;
}
}
if (pressed && dragStart.y > screen.height - geometry.insetBottom(borderThickness) && dragStart.x < screen.width / 5 && dragY < -50)
screenState.clipboard = true;
if (pressed && !Config.dock.hoverToReveal && dragStart.y > screen.height - geometry.insetTop(borderThickness) && dragStart.x > screen.width / 5 && !screenState.launcher)
if (dragY < -10)
screenState.dock = true;
if (pressed && dragStart.x > screen.width - Config.bar.border && dragStart.y < (screen.height / 2) && dragX < -Config.sidebar.dragThreshold)
screenState.sidebar = true;
if (pressed && dragStart.x >= screen.width - Config.bar.border && dragStart.y > (screen.height / 2) && dragX < -20)
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
if (pressed && dragStart.x <= Config.bar.border && dragX > 20)
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
if (!screenState.bar && Config.bar.autoHide && geometry.barContains(x, y, true))
bar.isHovered = true;
if (panels.sidebar.offsetScale === 1) {
const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) {
osdShortcutActive = false;
panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width * (1 - panels.sidebar.offsetScale);
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (!osdShortcutActive) {
screenState.osd = showOsd;
panels.osd.hovered = showOsd;
} else if (showOsd) {
osdShortcutActive = false;
panels.osd.hovered = true;
}
}
if (Config.dock.enable && Config.dock.hoverToReveal && !screenState.dock && !screenState.launcher && inBottomPanel(panels.dock, x, y))
screenState.dock = true;
if (geometry.barContains(x, y))
bar.checkPopout(geometry.axisPos(x, y));
}
onPressed: event => {
dragStart = Qt.point(event.x, event.y);
if (screenState.isDrawing) {
if (event.button & Qt.RightButton) {
drawing.content.clear();
return;
}
if (!inLeftPanel(panels.drawing, mouseX, mouseY))
drawing.content.beginStroke(event.x, event.y);
}
}
onPressedChanged: {
if (!pressed) {
if (screenState.isDrawing) {
setInitialPoint = false;
drawing.content.endStroke();
if (!inLeftPanel(panels.drawing, mouseX, mouseY))
drawing.content.showHover(mouseX, mouseY);
}
} else {
if (screenState.isDrawing) {
panels.drawing.collapse();
}
}
}
Timer {
id: unhideTimer
interval: Config.bar.revealDelay
repeat: false
running: false
onTriggered: {
if (root.containsMouse)
root.bar.isHovered = true;
}
}
Connections {
function onDashboardChanged(): void {
if (root.screenState.dashboard) {
const inDashboardArea = root.inTopPanel(root.panels.dashboardWrapper, root.mouseX, root.mouseY);
if (!inDashboardArea) {
root.dashboardShortcutActive = true;
}
root.screenState.launcher = false;
root.screenState.settings = false;
root.screenState.sidebar = false;
root.screenState.resources = false;
root.popouts.hasCurrent = false;
} else {
root.dashboardShortcutActive = false;
}
}
function onIsDrawingChanged(): void {
if (root.screenState.isDrawing) {
Cursor.setOverride(Qt.BlankCursor);
} else {
Cursor.clearOverride();
}
}
function onLauncherChanged(): void {
if (!root.screenState.launcher) {
root.dashboardShortcutActive = false;
root.osdShortcutActive = false;
root.utilitiesShortcutActive = false;
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) {
root.screenState.osd = false;
root.panels.osd.hovered = false;
}
}
if (root.screenState.launcher) {
root.screenState.dashboard = false;
root.screenState.dock = false;
root.screenState.settings = false;
}
}
function onOsdChanged(): void {
if (root.screenState.osd) {
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) {
root.osdShortcutActive = true;
}
} else {
root.osdShortcutActive = false;
}
}
function onResourcesChanged(): void {
if (root.screenState.resources) {
if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
root.popouts.hasCurrent = false;
root.screenState.settings = false;
root.screenState.dashboard = false;
}
}
function onSettingsChanged(): void {
if (root.screenState.settings) {
root.screenState.resources = false;
root.screenState.dashboard = false;
root.screenState.sidebar = false;
root.panels.popouts.hasCurrent = false;
root.screenState.launcher = false;
}
}
function onSidebarChanged(): void {
if (root.screenState.sidebar) {
if (root.panels.dashboardWrapper.x + root.panels.dashboardWrapper.width > root.panels.sidebar.x)
root.screenState.dashboard = false;
root.screenState.settings = false;
root.popouts.hasCurrent = false;
}
}
target: root.screenState
}
}
+17 -21
View File
@@ -37,12 +37,12 @@ Item {
readonly property alias resources: resources readonly property alias resources: resources
readonly property alias resourcesWrapper: resourcesWrapper readonly property alias resourcesWrapper: resourcesWrapper
required property ShellScreen screen required property ShellScreen screen
required property ScreenState screenState
readonly property alias settings: settings readonly property alias settings: settings
readonly property alias settingsWrapper: settingsWrapper readonly property alias settingsWrapper: settingsWrapper
readonly property alias sidebar: sidebar readonly property alias sidebar: sidebar
readonly property alias toasts: toasts readonly property alias toasts: toasts
readonly property alias utilities: utilities readonly property alias utilities: utilities
required property PersistentProperties visibilities
anchors.bottomMargin: geometry.insetBottom(borderThickness) anchors.bottomMargin: geometry.insetBottom(borderThickness)
anchors.fill: parent anchors.fill: parent
@@ -64,7 +64,7 @@ Item {
id: resources id: resources
position: root.geometry.position position: root.geometry.position
visibilities: root.visibilities screenState: root.screenState
} }
} }
@@ -84,8 +84,8 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
screen: root.screen screen: root.screen
screenState: root.screenState
sidebarOrSessionVisible: sidebar.visible sidebarOrSessionVisible: sidebar.visible
visibilities: root.visibilities
} }
} }
@@ -96,7 +96,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
drawing: root.drawingItem drawing: root.drawingItem
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
Popouts.ClipWrapper { Popouts.ClipWrapper {
@@ -121,8 +121,8 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
panels: root panels: root
screenState: root.screenState
sidebarPanel: sidebar sidebarPanel: sidebar
visibilities: root.visibilities
} }
Launcher.Wrapper { Launcher.Wrapper {
@@ -132,7 +132,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
panels: root panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
Utils.Wrapper { Utils.Wrapper {
@@ -140,9 +140,8 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
popouts: popouts screenState: root.screenState
sidebar: sidebar sidebar: sidebar
visibilities: root.visibilities
} }
Item { Item {
@@ -150,13 +149,11 @@ Item {
property real offsetScale: dashboard.shouldBeActive ? 0 : 1 property real offsetScale: dashboard.shouldBeActive ? 0 : 1
anchors.bottom: root.geometry.barOnLeft || root.geometry.barOnBottom ? parent.bottom : undefined anchors.horizontalCenter: parent.horizontalCenter
anchors.left: root.geometry.barOnLeft ? parent.left : undefined anchors.top: parent.top
anchors.right: root.geometry.barOnLeft ? undefined : parent.right clip: offsetScale > 0
anchors.top: root.geometry.barOnBottom || root.geometry.barOnLeft ? undefined : parent.top implicitHeight: dashboard.implicitHeight * (1 - offsetScale)
clip: true implicitWidth: dashboard.implicitWidth
implicitHeight: root.geometry.horizontal ? dashboard.implicitHeight * (1 - offsetScale) : dashboard.implicitHeight
implicitWidth: root.geometry.horizontal ? dashboard.implicitWidth : dashboard.implicitWidth * (1 - offsetScale)
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
@@ -169,8 +166,7 @@ Item {
id: dashboard id: dashboard
offsetScale: dashboardWrapper.offsetScale offsetScale: dashboardWrapper.offsetScale
position: root.geometry.position screenState: root.screenState
visibilities: root.visibilities
} }
} }
@@ -181,7 +177,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: notifications.bottom anchors.top: notifications.bottom
panels: root panels: root
visibilities: root.visibilities screenState: root.screenState
} }
Item { Item {
@@ -197,7 +193,7 @@ Item {
anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale
panels: root panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
} }
@@ -208,7 +204,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
panels: root panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
Clipboard.Wrapper { Clipboard.Wrapper {
@@ -217,6 +213,6 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
} }
+95 -102
View File
@@ -50,49 +50,47 @@ CustomWindow {
readonly property alias menuRegion: regions.menuPopoutRegion readonly property alias menuRegion: regions.menuPopoutRegion
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen) readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
property var root: Quickshell.shellDir property var root: Quickshell.shellDir
readonly property ScreenState screenState: ShellState.forScreen(screen)
readonly property real sdfBorderOffset: 2 * fsTransitionProg readonly property real sdfBorderOffset: 2 * fsTransitionProg
readonly property real shadowOpacity: 0.7 * (1 - (bar.isHovered ? 0 : fsTransitionProg)) readonly property real shadowOpacity: 0.5 * (1 - (bar.isHovered ? 0 : fsTransitionProg))
property color surfaceColor: Colors.tPalette.m3surface property color surfaceColor: Colors.tPalette.m3surface
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.settings ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: screenState.screenshot ? WlrKeyboardFocus.Exclusive : screenState.launcher || screenState.settings || screenState.sidebar ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
color: "transparent" color: "transparent"
contentItem.focus: true mask: screenState.isDrawing || screenState.screenshot ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions)
mask: visibilities.isDrawing ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions)
name: "Bar" name: "Bar"
Behavior on fsTransitionProg { Behavior on fsTransitionProg {
Anim { Anim {}
}
} }
Behavior on surfaceColor { Behavior on surfaceColor {
CAnim { CAnim {}
}
} }
contentItem.Keys.onEscapePressed: { contentItem.Keys.onEscapePressed: {
if (Config.bar.autoHide) if (Config.bar.autoHide)
visibilities.bar = false; screenState.bar = false;
visibilities.launcher = false; screenState.launcher = false;
visibilities.sidebar = false; screenState.sidebar = false;
visibilities.dashboard = false; screenState.dashboard = false;
visibilities.osd = false; screenState.osd = false;
visibilities.settings = false; screenState.settings = false;
visibilities.resources = false; screenState.resources = false;
visibilities.dock = false; screenState.dock = false;
visibilities.clipboard = false; screenState.clipboard = false;
panels.popouts.hasCurrent = false; panels.popouts.hasCurrent = false;
} }
onHasFullscreenChanged: { onHasFullscreenChanged: {
visibilities.launcher = false; screenState.launcher = false;
visibilities.sidebar = false; screenState.sidebar = false;
visibilities.dashboard = false; screenState.dashboard = false;
visibilities.osd = false; screenState.osd = false;
visibilities.settings = false; screenState.settings = false;
visibilities.resources = false; screenState.resources = false;
visibilities.clipboard = false; screenState.clipboard = false;
visibilities.dock = false; screenState.dock = false;
panels.popouts.hasCurrent = false; panels.popouts.hasCurrent = false;
} }
@@ -144,44 +142,41 @@ CustomWindow {
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!Config.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1)) active: {
const s = root.screenState;
const c = Config;
if ((s.launcher && c.launcher.enabled) || (s.sidebar && c.sidebar.enabled) || (s.resources && c.dashboard.performance.enabled) || (s.clipboard && c.clipboard.enabled))
return true;
if (s.dashboard && !c.dashboard.showOnHover && c.dashboard.enabled)
return true;
if (!c.dock.hoverToReveal && s.dock && c.dock.enable)
return true;
if (panels.popouts.currentName.startsWith("traymenu") && (panels.popouts.current as StackView)?.depth > 1)
return true;
if (s.settings)
return true;
return false;
}
windows: [root] windows: [root]
onCleared: { onCleared: {
visibilities.launcher = false; root.screenState.launcher = false;
visibilities.sidebar = false; root.screenState.sidebar = false;
visibilities.dashboard = false; root.screenState.dashboard = false;
visibilities.osd = false; root.screenState.osd = false;
visibilities.settings = false; root.screenState.settings = false;
visibilities.resources = false; root.screenState.resources = false;
visibilities.clipboard = false; root.screenState.clipboard = false;
visibilities.dock = false; root.screenState.dock = false;
panels.popouts.hasCurrent = false; panels.popouts.hasCurrent = false;
} }
} }
PersistentProperties {
id: visibilities
property bool bar
property bool clipboard
property bool dashboard
property bool dock
property bool isDrawing
property bool launcher
property bool notif: NotifServer.popups.length > 0
property bool osd
property bool resources
property bool settings
property bool sidebar
Component.onCompleted: Visibilities.load(root.screen, this)
}
Binding { Binding {
property: "bar" property: "bar"
target: visibilities target: root.screenState
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.bar.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered value: root.screenState.sidebar || root.screenState.dashboard || root.screenState.osd || (!Config.bar.hideWhenNotif && NotifServer.popups.length > 0) || root.screenState.resources || root.screenState.settings || bar.isHovered
when: Config.bar.autoHide when: Config.bar.autoHide
} }
@@ -189,23 +184,19 @@ CustomWindow {
id: surface id: surface
anchors.fill: parent anchors.fill: parent
layer.enabled: true
opacity: root.surfaceColor.a opacity: root.surfaceColor.a
layer.effect: MultiEffect { BlobGroup2 {
blurMax: 32
shadowColor: Qt.alpha(Colors.palette.m3shadow, Math.max(0, root.shadowOpacity))
shadowEnabled: true
}
BlobGroup {
id: blobGroup id: blobGroup
anchors.fill: parent
color: root.surfaceColor color: root.surfaceColor
shadowColor: Qt.alpha(Colors.palette.m3shadow, Math.max(0, root.shadowOpacity))
shadowRadius: 18
smoothing: Config.bar.smoothing smoothing: Config.bar.smoothing
} }
BlobInvertedRect { BlobInvertedRect2 {
anchors.fill: parent anchors.fill: parent
anchors.margins: -50 anchors.margins: -50
borderBottom: geometry.insetBottom(root.borderThickness) - anchors.margins - root.sdfBorderOffset borderBottom: geometry.insetBottom(root.borderThickness) - anchors.margins - root.sdfBorderOffset
@@ -255,6 +246,7 @@ CustomWindow {
PanelBg { PanelBg {
id: osdBg id: osdBg
atRestOverride: panels.osd.offsetScale < 0.99
deformAmount: 0.1 deformAmount: 0.1
implicitHeight: panels.osd.height implicitHeight: panels.osd.height
implicitWidth: panels.osd.width implicitWidth: panels.osd.width
@@ -266,6 +258,7 @@ CustomWindow {
PanelBg { PanelBg {
id: notifsBg id: notifsBg
atRestOverride: panel.implicitHeight > 0
panel: panels.notifications panel: panels.notifications
radius: Tokens.rounding.large radius: Tokens.rounding.large
} }
@@ -293,21 +286,23 @@ CustomWindow {
y: panels.popoutsWrapper.y + panels.popouts.y + geometry.insetTop(root.borderThickness) - (geometry.barOnTop ? panels.popouts.height * extraExtent : 0) y: panels.popoutsWrapper.y + panels.popouts.y + geometry.insetTop(root.borderThickness) - (geometry.barOnTop ? panels.popouts.height * extraExtent : 0)
Behavior on extraExtent { Behavior on extraExtent {
Anim { Anim {}
}
} }
} }
PanelBg { PanelBg {
id: resourcesBg id: resourcesBg
property real extraExtent: 0.2
atRestOverride: panels.resources.offsetScale < 0.99
deformAmount: 0.05 deformAmount: 0.05
implicitHeight: panels.resources.height implicitHeight: panels.resources.height * (1 + extraExtent)
implicitWidth: panels.resources.width implicitWidth: panels.resources.width * (1 + extraExtent)
panel: panels.resourcesWrapper panel: panels.resourcesWrapper
radius: Tokens.rounding.largeIncreased + Tokens.padding.small radius: Tokens.rounding.largeIncreased + Tokens.padding.small
x: panels.resourcesWrapper.x + panels.resources.x + geometry.insetLeft(root.borderThickness) x: panels.resourcesWrapper.x + panels.resources.x + geometry.insetLeft(root.borderThickness) - panels.resources.width * extraExtent
y: panels.resourcesWrapper.y + panels.resources.y + geometry.insetTop(root.borderThickness) y: panels.resourcesWrapper.y + panels.resources.y + geometry.insetTop(root.borderThickness) - panels.resources.height * extraExtent
} }
PanelBg { PanelBg {
@@ -337,9 +332,15 @@ CustomWindow {
PanelBg { PanelBg {
id: clipboardBg id: clipboardBg
property real extraExtent: 0.2
deformAmount: 0.03 deformAmount: 0.03
implicitHeight: panels.clipboard.height * (1 + extraExtent)
implicitWidth: panels.clipboard.width * (1 + extraExtent)
panel: panels.clipboard panel: panels.clipboard
radius: 29 radius: 29
x: panels.clipboard.x + geometry.insetLeft(root.borderThickness) - panels.clipboard.width * extraExtent
y: panels.clipboard.y + geometry.insetTop(root.borderThickness)
} }
} }
@@ -347,37 +348,11 @@ CustomWindow {
id: drawing id: drawing
anchors.fill: parent anchors.fill: parent
layer.enabled: true screenState: root.screenState
visibilities: visibilities
z: 2 z: 2
layer.effect: MultiEffect {
maskEnabled: true
maskInverted: true
maskSource: maskSource
}
} }
Item { Interactions2 {
id: maskSource
anchors.fill: parent
layer.enabled: true
visible: false
CustomRect {
readonly property int extraWidth: radius
color: "white"
implicitHeight: panels.drawing.height
implicitWidth: panels.drawing.width + extraWidth
radius: drawingBg.radius
x: -extraWidth + root.borderThickness + panels.drawing.x
y: panels.drawing.y + bar.implicitHeight
}
}
Interactions {
id: interactions id: interactions
anchors.fill: parent anchors.fill: parent
@@ -389,7 +364,7 @@ CustomWindow {
panels: panels panels: panels
popouts: panels.popouts popouts: panels.popouts
screen: root.screen screen: root.screen
visibilities: visibilities screenState: root.screenState
Panels { Panels {
id: panels id: panels
@@ -399,7 +374,7 @@ CustomWindow {
drawingItem: drawing drawingItem: drawing
geometry: geometry geometry: geometry
screen: root.screen screen: root.screen
visibilities: visibilities screenState: root.screenState
clipboard.transform: Matrix4x4 { clipboard.transform: Matrix4x4 {
matrix: clipboardBg.deformMatrix matrix: clipboardBg.deformMatrix
@@ -442,22 +417,40 @@ CustomWindow {
anchors.bottom: geometry.barOnBottom ? parent.bottom : undefined anchors.bottom: geometry.barOnBottom ? parent.bottom : undefined
anchors.left: parent.left anchors.left: parent.left
anchors.top: geometry.barOnBottom ? undefined : parent.top anchors.top: geometry.barOnBottom ? undefined : parent.top
enabled: !visibilities.isDrawing enabled: !screenState.isDrawing
fullscreen: root.hasFullscreen fullscreen: root.hasFullscreen
height: geometry.horizontal ? implicitHeight : parent.height height: geometry.horizontal ? implicitHeight : parent.height
popouts: panels.popouts popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper popoutsWrapper: panels.popoutsWrapper
position: geometry.position position: geometry.position
screen: root.screen screen: root.screen
visibilities: visibilities screenState: root.screenState
width: geometry.horizontal ? parent.width : implicitWidth width: geometry.horizontal ? parent.width : implicitWidth
} }
} }
component PanelBg: BlobRect { LazyLoader {
id: loader
property bool closing: !root.screenState.screenshot
onClosingChanged: if (!closing)
activeAsync = true
Picker {
loader: loader
parent: root.contentItem
screen: root.screen
screenState: root.screenState
}
}
component PanelBg: BlobRect2 {
property real deformAmount: 0.15 property real deformAmount: 0.15
required property Item panel required property Item panel
atRestOverride: panel?.offsetScale < 0.99 ?? false
damping: 28.3
deformScale: (deformAmount * Config.appearance.deform.scale) / 10000 deformScale: (deformAmount * Config.appearance.deform.scale) / 10000
group: blobGroup group: blobGroup
implicitHeight: panel.height implicitHeight: panel.height
+5 -10
View File
@@ -13,21 +13,17 @@ import qs.Paths
Singleton { Singleton {
id: root id: root
readonly property M3Palette current: M3Palette { readonly property M3Palette current: M3Palette {}
}
property bool currentLight property bool currentLight
property string flavour property string flavour
readonly property bool light: showPreview ? previewLight : currentLight readonly property bool light: showPreview ? previewLight : currentLight
readonly property M3Palette palette: showPreview ? preview : current readonly property M3Palette palette: showPreview ? preview : current
readonly property M3Palette preview: M3Palette { readonly property M3Palette preview: M3Palette {}
}
property bool previewLight property bool previewLight
property string scheme property string scheme
property bool showPreview property bool showPreview
readonly property M3TPalette tPalette: M3TPalette { readonly property M3TPalette tPalette: M3TPalette {}
} readonly property Transparency transparency: Transparency {}
readonly property Transparency transparency: Transparency {
}
readonly property alias wallLuminance: analyser.luminance readonly property alias wallLuminance: analyser.luminance
function alterColor(c: color, a: real, layer: int): color { function alterColor(c: color, a: real, layer: int): color {
@@ -112,8 +108,7 @@ Singleton {
function setMode(mode: string): void { function setMode(mode: string): void {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]);
Config.general.color.mode = mode; Config.colors.mode = mode;
Config.save();
} }
function swapRG(c: color): color { function swapRG(c: color): color {
+2 -1
View File
@@ -119,7 +119,8 @@ ColumnLayout {
length: count length: count
}, () => null); }, () => null);
return forecast.slice(0, count); const start = Math.max(0, Weather.currentHourIndex);
return forecast.slice(start, start + count);
} }
ColumnLayout { ColumnLayout {
-88
View File
@@ -1,88 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import Quickshell.Hyprland
import ZShell
import qs.Components
Scope {
LazyLoader {
id: root
property bool closing
property bool freeze
Variants {
model: Quickshell.screens
PanelWindow {
id: win
required property ShellScreen modelData
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: root.closing ? WlrKeyboardFocus.None : WlrKeyboardFocus.Exclusive
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "areapicker"
color: "transparent"
mask: root.closing ? empty : null
screen: modelData
anchors {
bottom: true
left: true
right: true
top: true
}
Region {
id: empty
}
Picker {
loader: root
screen: win.modelData
}
}
}
}
IpcHandler {
function open(): void {
root.freeze = false;
root.closing = false;
root.activeAsync = true;
}
function openFreeze(): void {
root.freeze = true;
root.closing = false;
root.activeAsync = true;
}
target: "picker"
}
CustomShortcut {
name: "screenshot"
onPressed: {
root.freeze = false;
root.closing = false;
root.activeAsync = true;
}
}
CustomShortcut {
name: "screenshotFreeze"
onPressed: {
root.freeze = true;
root.closing = false;
root.activeAsync = true;
}
}
}
+6 -6
View File
@@ -11,14 +11,14 @@ import qs.Services
Singleton { Singleton {
id: root id: root
readonly property int darkEnd: Config.general.color.scheduleDarkEnd readonly property int darkEnd: Config.colors.scheduleDarkEnd
readonly property int darkStart: Config.general.color.scheduleDarkStart readonly property int darkStart: Config.colors.scheduleDarkStart
readonly property bool enabled: Config.general.color.scheduleDark && Config.general.color.schemeGeneration readonly property bool enabled: Config.colors.scheduleDark && Config.colors.schemeGen
function applyDarkMode() { function applyDarkMode() {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]);
Config.general.color.mode = "dark"; Config.colors.mode = "dark";
Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-dark'"]); Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-dark'"]);
@@ -30,13 +30,13 @@ Singleton {
function applyLightMode() { function applyLightMode() {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "light"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "light"]);
Config.general.color.mode = "light"; Config.colors.mode = "light";
Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-light'"]); Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-light'"]);
Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Dark.colors/color_scheme_path=\\1Light.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`]); Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Dark.colors/color_scheme_path=\\1Light.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`]);
if (Config.general.color.neovimColors) if (Config.colors.neovimColors)
Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"onelight\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"]); Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"onelight\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"]);
} }
-242
View File
@@ -1,242 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import ZShell.Internal
import ZShell.Config
Singleton {
id: root
property real _downloadSpeed: 0
property real _downloadTotal: 0
property real _initialRxBytes: 0
property real _initialTxBytes: 0
property bool _initialized: false
property real _prevRxBytes: 0
property real _prevTimestamp: 0
property real _prevTxBytes: 0
property real _uploadSpeed: 0
property real _uploadTotal: 0
readonly property CircularBuffer downloadBuffer: _downloadBuffer
readonly property real downloadSpeed: _downloadSpeed
readonly property real downloadTotal: _downloadTotal
readonly property int historyLength: Config.dashboard.performance.showVram ? 70 : 30
property int refCount: 0
readonly property CircularBuffer uploadBuffer: _uploadBuffer
readonly property real uploadSpeed: _uploadSpeed
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 {
// Handle negative or invalid values
if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
return {
value: 0,
unit: "B/s"
};
}
if (bytes < 1024) {
return {
value: bytes,
unit: "B/s"
};
} else if (bytes < 1024 * 1024) {
return {
value: bytes / 1024,
unit: "KB/s"
};
} else if (bytes < 1024 * 1024 * 1024) {
return {
value: bytes / (1024 * 1024),
unit: "MB/s"
};
} else {
return {
value: bytes / (1024 * 1024 * 1024),
unit: "GB/s"
};
}
}
function formatBytesTotal(bytes: real): var {
// Handle negative or invalid values
if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
return {
value: 0,
unit: "B"
};
}
if (bytes < 1024) {
return {
value: bytes,
unit: "B"
};
} else if (bytes < 1024 * 1024) {
return {
value: bytes / 1024,
unit: "KB"
};
} else if (bytes < 1024 * 1024 * 1024) {
return {
value: bytes / (1024 * 1024),
unit: "MB"
};
} else {
return {
value: bytes / (1024 * 1024 * 1024),
unit: "GB"
};
}
}
function parseNetDev(content: string): var {
const lines = content.split("\n");
let totalRx = 0;
let totalTx = 0;
for (let i = 2; i < lines.length; i++) {
const line = lines[i].trim();
if (!line)
continue;
const parts = line.split(/\s+/);
if (parts.length < 10)
continue;
const iface = parts[0].replace(":", "");
// Skip loopback interface
if (iface === "lo")
continue;
const rxBytes = parseFloat(parts[1]) || 0;
const txBytes = parseFloat(parts[9]) || 0;
totalRx += rxBytes;
totalTx += txBytes;
}
return {
rx: totalRx,
tx: totalTx
};
}
CircularBuffer {
id: _downloadBuffer
capacity: root.historyLength + 1
}
CircularBuffer {
id: _uploadBuffer
capacity: root.historyLength + 1
}
FileView {
id: netDevFile
path: "/proc/net/dev"
}
Timer {
interval: Config.dashboard.resourceUpdateInterval
repeat: true
running: true
triggeredOnStart: true
onTriggered: {
if (root.refCount <= 0) {
root.resetSampling();
return;
}
netDevFile.reload();
const content = netDevFile.text();
if (!content)
return;
const data = root.parseNetDev(content);
const now = Date.now();
if (!root._initialized) {
root._initialRxBytes = data.rx;
root._initialTxBytes = data.tx;
root._prevRxBytes = data.rx;
root._prevTxBytes = data.tx;
root._prevTimestamp = now;
root._initialized = true;
return;
}
const timeDelta = (now - root._prevTimestamp) / 1000; // seconds
if (timeDelta > 0) {
// Calculate byte deltas
let rxDelta = data.rx - root._prevRxBytes;
let txDelta = data.tx - root._prevTxBytes;
// Handle counter overflow (when counters wrap around from max to 0)
// This happens when counters exceed 32-bit or 64-bit limits
if (rxDelta < 0) {
// Counter wrapped around - assume 64-bit counter
rxDelta += Math.pow(2, 64);
}
if (txDelta < 0) {
txDelta += Math.pow(2, 64);
}
// Calculate speeds
root._downloadSpeed = rxDelta / timeDelta;
root._uploadSpeed = txDelta / timeDelta;
if (root._downloadSpeed >= 0 && isFinite(root._downloadSpeed))
_downloadBuffer.push(root._downloadSpeed);
if (root._uploadSpeed >= 0 && isFinite(root._uploadSpeed))
_uploadBuffer.push(root._uploadSpeed);
}
// Calculate totals with overflow handling
let downTotal = data.rx - root._initialRxBytes;
let upTotal = data.tx - root._initialTxBytes;
// Handle counter overflow for totals
if (downTotal < 0) {
downTotal += Math.pow(2, 64);
}
if (upTotal < 0) {
upTotal += Math.pow(2, 64);
}
root._downloadTotal = downTotal;
root._uploadTotal = upTotal;
root._prevRxBytes = data.rx;
root._prevTxBytes = data.tx;
root._prevTimestamp = now;
}
}
}
@@ -9,13 +9,13 @@ Singleton {
id: root id: root
readonly property bool enabled: service.enabled readonly property bool enabled: service.enabled
readonly property int end: Config.general.color.scheduleHyprsunsetEnd readonly property int end: Config.display.nightlight.scheduleEnd
property bool manualToggle: false property bool manualToggle: false
readonly property int start: Config.general.color.scheduleHyprsunsetStart readonly property int start: Config.display.nightlight.scheduleStart
readonly property int temp: Config.general.color.hyprsunsetTemp readonly property int temp: Config.display.nightlight.temp
function checkStartup(): void { function checkStartup(): void {
if (!Config.general.color.scheduleHyprsunset) if (!Config.display.nightlight.schedule)
return; return;
service.apply(); service.apply();
@@ -26,12 +26,14 @@ Singleton {
service.toggle(); service.toggle();
} }
HyprsunsetManager { NightlightManager {
id: service id: service
activeAuto: Config.general.color.scheduleHyprsunset activeAuto: Config.display.nightlight.schedule
endTime: root.end endTime: root.end
startTime: root.start startTime: root.start
temp: root.temp temp: root.temp
fadeDuration: Config.display.nightlight.fadeDuration / 1000
useNativeNightlight: Config.display.nightlight.useNative
} }
} }
+98 -58
View File
@@ -41,8 +41,9 @@ Item {
property real rsy: Math.min(sy, ey) property real rsy: Math.min(sy, ey)
readonly property real scaleRatio: Hypr.monitorFor(screen).scale readonly property real scaleRatio: Hypr.monitorFor(screen).scale
required property ShellScreen screen required property ShellScreen screen
required property ScreenState screenState
property real sh: Math.abs(sy - ey) property real sh: Math.abs(sy - ey)
readonly property color shadowColor: Hypr.options.decoration.shadow.color readonly property color shadowColor: Hypr.options.decoration.shadow?.color ?? "black"
readonly property bool shadowEnabled: Hypr.options.decoration.shadow.enabled readonly property bool shadowEnabled: Hypr.options.decoration.shadow.enabled
readonly property var shadowOffset: Hypr.options.decoration.shadow.offset readonly property var shadowOffset: Hypr.options.decoration.shadow.offset
readonly property int shadowRange: Hypr.options.decoration.shadow.range readonly property int shadowRange: Hypr.options.decoration.shadow.range
@@ -97,10 +98,6 @@ Item {
const args = Config.screenshot.mode === "auto" ? ["--rounding", `${rounding}`, "--radius", root.cornerRadius, "--shadow", root.shadowEnabled, "--shadow-blur", `${shadow_blur}`, "--shadow-color", `${r},${g},${b},${a}`, "--shadow-offset-x", root.shadowOffset[0], "--shadow-offset-y", root.shadowOffset[1]] : []; const args = Config.screenshot.mode === "auto" ? ["--rounding", `${rounding}`, "--radius", root.cornerRadius, "--shadow", root.shadowEnabled, "--shadow-blur", `${shadow_blur}`, "--shadow-color", `${r},${g},${b},${a}`, "--shadow-offset-x", root.shadowOffset[0], "--shadow-offset-y", root.shadowOffset[1]] : [];
const postCmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--scale", root.scaleRatio, ...args, "--image"] : null; const postCmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--scale", root.scaleRatio, ...args, "--image"] : null;
overlay.visible = border.visible = false;
handles.visible = false;
toolbar.visible = false;
ZShellIo.saveItem(captureLayer, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => { ZShellIo.saveItem(captureLayer, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => {
const finalize = finalPath => { const finalize = finalPath => {
if (copyToClipboard) { if (copyToClipboard) {
@@ -126,39 +123,35 @@ Item {
focus: true focus: true
opacity: 0 opacity: 0
Behavior on rsx {
enabled: !selectArea.pressed && root.mode === "select"
Anim {}
}
Behavior on rsy {
enabled: !selectArea.pressed && root.mode === "select"
Anim {}
}
Behavior on sh {
enabled: !selectArea.pressed && root.mode === "select"
Anim {}
}
Behavior on sw {
enabled: !selectArea.pressed && root.mode === "select"
Anim {}
}
Behavior on opacity { Behavior on opacity {
Anim { Anim {
duration: 300 duration: 300
} }
} }
Behavior on rsx {
enabled: !selectArea.pressed && root.mode === "select"
ExAnim {
}
}
Behavior on rsy {
enabled: !selectArea.pressed && root.mode === "select"
ExAnim {
}
}
Behavior on sh {
enabled: !selectArea.pressed && root.mode === "select"
ExAnim {
}
}
Behavior on sw {
enabled: !selectArea.pressed && root.mode === "select"
ExAnim {
}
}
Component.onCompleted: { Component.onCompleted: {
Hypr.extras.refreshOptions(); Hypr.extras.refreshOptions();
screencopy.active = true; root.forceActiveFocus();
opacity = 1; opacity = 1;
@@ -223,13 +216,63 @@ Item {
onReleased: root.enterEditMode() onReleased: root.enterEditMode()
} }
Connections {
function onClosingChanged(): void {
if (root.loader.closing)
closeAnim.start();
else {
closeAnim.stop();
returnAnim.start();
}
}
target: root.loader
}
ParallelAnimation {
id: returnAnim
Anim {
duration: 300
property: "opacity"
target: root
to: 1
}
Anim {
property: "rsx"
target: root
to: Math.min(root.sx, root.ex)
}
Anim {
property: "rsy"
target: root
to: Math.min(root.sy, root.ey)
}
Anim {
property: "sw"
target: root
to: Math.abs(root.sx - root.ex)
}
Anim {
property: "sh"
target: root
to: Math.abs(root.sy - root.ey)
}
}
SequentialAnimation { SequentialAnimation {
id: closeAnim id: closeAnim
PropertyAction { ScriptAction {
property: "closing" script: {
target: root.loader const states = ShellState.allStates();
value: true for (const s of states)
s.screenshot = false;
}
} }
ParallelAnimation { ParallelAnimation {
@@ -240,19 +283,19 @@ Item {
to: 0 to: 0
} }
ExAnim { Anim {
properties: "rsx,rsy" properties: "rsx,rsy"
target: root target: root
to: 0 to: 0
} }
ExAnim { Anim {
property: "sw" property: "sw"
target: root target: root
to: root.screen.width to: root.screen.width
} }
ExAnim { Anim {
property: "sh" property: "sh"
target: root target: root
to: root.screen.height to: root.screen.height
@@ -271,18 +314,13 @@ Item {
anchors.fill: parent anchors.fill: parent
Loader { ScreencopyView {
id: screencopy id: screencopy
active: false
anchors.fill: parent anchors.fill: parent
asynchronous: true
sourceComponent: ScreencopyView {
captureSource: root.screen captureSource: root.screen
paintCursor: false paintCursor: false
} }
}
AnnotationCanvas { AnnotationCanvas {
id: annotations id: annotations
@@ -290,6 +328,7 @@ Item {
anchors.fill: parent anchors.fill: parent
inkColor: root.inkColor inkColor: root.inkColor
layer.enabled: true layer.enabled: true
selection: selectionRect
tool: root.mode === "edit" && root.tool !== "move" ? root.tool : "none" tool: root.mode === "edit" && root.tool !== "move" ? root.tool : "none"
layer.effect: MultiEffect { layer.effect: MultiEffect {
@@ -301,15 +340,14 @@ Item {
} }
} }
Rectangle { CustomRect {
id: overlay id: overlay
anchors.fill: parent anchors.fill: parent
color: "white" color: "white"
layer.enabled: true layer.enabled: true
opacity: 0.3 opacity: 0.3
radius: root.realRounding visible: screencopy.hasContent
visible: screencopy.item?.hasContent ?? false
layer.effect: MultiEffect { layer.effect: MultiEffect {
maskEnabled: true maskEnabled: true
@@ -338,39 +376,44 @@ Item {
} }
} }
Rectangle { CustomRect {
id: border id: border
border.color: Colors.palette.m3primary border.color: Colors.palette.m3primary
border.width: root.realBorderWidth border.width: root.realBorderWidth
color: "transparent"
implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2 implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2
implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2 implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2
radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0 radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0
visible: screencopy.item?.hasContent ?? false visible: screencopy.hasContent
x: selectionRect.x - root.realBorderWidth x: selectionRect.x - root.realBorderWidth
y: selectionRect.y - root.realBorderWidth y: selectionRect.y - root.realBorderWidth
Behavior on border.color { Behavior on border.color {
Anim { Anim {}
}
} }
} }
SelectionHandles { SelectionHandles {
id: handles id: handles
border: root.realBorderWidth
selH: root.sh selH: root.sh
selW: root.sw selW: root.sw
selX: root.rsx selX: root.rsx
selY: root.rsy selY: root.rsy
selection: selectionRect
tool: root.tool
visible: root.mode === "edit" && root.tool === "move" visible: root.mode === "edit" && root.tool === "move"
onBoundsChanged: (x, y, w, h) => { onBoundsChanged: (x, y, w, h) => {
root.sx = x; let nx = Math.max(0, x);
root.sy = y; let ny = Math.max(0, y);
root.ex = x + w; let nw = Math.min(w, root.screen.width - nx);
root.ey = y + h; let nh = Math.min(h, root.screen.height - ny);
root.sx = nx;
root.sy = ny;
root.ex = nx + nw;
root.ey = ny + nh;
} }
onMoveBy: (dx, dy) => { onMoveBy: (dx, dy) => {
const w = root.sw, h = root.sh; const w = root.sw, h = root.sh;
@@ -401,7 +444,4 @@ Item {
onToolSelected: t => root.tool = t onToolSelected: t => root.tool = t
onUndoRequested: annotations.undo() onUndoRequested: annotations.undo()
} }
component ExAnim: Anim {
}
} }
+51 -56
View File
@@ -8,13 +8,13 @@ Item {
property color inkColor: "#ff3b30" property color inkColor: "#ff3b30"
property real inkWidth: 4 property real inkWidth: 4
required property Rectangle selection
property string tool: "none" property string tool: "none"
signal changed signal changed
function clearAll() { function clearAll() {
committedCanvas.clear(); canvas.clear();
previewCanvas.clear();
changed(); changed();
} }
@@ -54,90 +54,85 @@ Item {
} }
function undo() { function undo() {
committedCanvas.undoLastGroup(); canvas.undoLastGroup();
changed(); changed();
} }
anchors.fill: parent anchors.fill: parent
enabled: tool !== "none"
StrokeCanvas { StrokeCanvas {
id: committedCanvas id: canvas
anchors.fill: parent anchors.fill: parent
penColor: root.inkColor penColor: root.inkColor
penWidth: root.inkWidth penWidth: root.inkWidth
} }
StrokeCanvas { MouseArea {
id: previewCanvas
anchors.fill: parent
penColor: root.inkColor
penWidth: root.inkWidth
}
PointHandler {
id: drawHandler id: drawHandler
property real startX property real startX
property real startY property real startY
property bool started: false
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: root.tool !== "none" anchors.fill: parent
cursorShape: undefined
enabled: root.enabled
onActiveChanged: { onPositionChanged: e => {
if (active) const x = e.x;
const y = e.y;
if (!pressed)
return; return;
if (root.tool === "pen") { if (root.tool === "pen") {
committedCanvas.endStroke(); canvas.appendPoint(x, y);
} else if (started) {
const pts = root.sampleShape(root.tool, startX, startY, point.position.x, point.position.y);
if (pts.length > 0) {
committedCanvas.beginStroke(pts[0][0], pts[0][1], true);
for (let i = 1; i < pts.length; i++)
committedCanvas.appendPoint(pts[i][0], pts[i][1]);
committedCanvas.endStroke();
}
previewCanvas.clear();
}
started = false;
root.changed();
}
onPointChanged: {
if (!active)
return; return;
}
if (point.pressedButtons & Qt.RightButton) { const pts = root.sampleShape(root.tool, startX, startY, x, y);
root.clearAll(); if (pts.length > 0) {
return; canvas.beginStroke(pts[0][0], pts[0][1], true);
for (let i = 1; i < pts.length; i++)
canvas.appendPoint(pts[i][0], pts[i][1]);
} }
}
onPressed: e => {
if (e.button & Qt.LeftButton) {
startX = e.x;
startY = e.y;
canvas.beginStroke(e.x, e.y);
} else if (e.button & Qt.RightButton) {
root.clearAll();
}
}
onPressedChanged: if (!pressed)
canvas.endStroke()
}
HoverHandler {
cursorShape: {
if (root.tool === "none")
return undefined;
const x = point.position.x; const x = point.position.x;
const y = point.position.y; const y = point.position.y;
const withinXWidth = x > root.selection.x && x < root.selection.x + root.selection.implicitWidth;
const withinYHeight = y > root.selection.y && y < root.selection.y + root.selection.implicitHeight;
if (root.tool === "pen") { if (withinXWidth && withinYHeight) {
if (!started) { if (!drawHandler.pressed)
started = true; canvas.showHover(x, y);
committedCanvas.beginStroke(x, y); else
return; canvas.showHover(drawHandler.mouseX, drawHandler.mouseY);
} return Qt.BlankCursor;
committedCanvas.appendPoint(x, y); } else {
return; canvas.hideHover();
} return undefined;
if (!started) {
started = true;
startX = point.pressPosition.x;
startY = point.pressPosition.y;
}
const pts = root.sampleShape(root.tool, startX, startY, x, y);
if (pts.length > 0) {
previewCanvas.beginStroke(pts[0][0], pts[0][1], true);
for (let i = 1; i < pts.length; i++)
previewCanvas.appendPoint(pts[i][0], pts[i][1]);
} }
} }
enabled: root.enabled
} }
} }
+7 -7
View File
@@ -73,7 +73,7 @@ CustomRect {
required property var modelData required property var modelData
color: root.tool === modelData.id ? "#3584e4" : "transparent" color: root.tool === modelData.id ? "#3584e4" : "transparent"
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: tool.modelData.glyph icon: tool.modelData.glyph
inactiveColor: root.tool === modelData.id ? Colors.palette.m3primary : "transparent" inactiveColor: root.tool === modelData.id ? Colors.palette.m3primary : "transparent"
inactiveOnColor: root.tool === modelData.id ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface inactiveOnColor: root.tool === modelData.id ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
@@ -103,7 +103,7 @@ CustomRect {
required property color modelData required property color modelData
fillWidth: false fillWidth: false
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "" icon: ""
implicitHeight: buttonSize implicitHeight: buttonSize
implicitWidth: buttonSize implicitWidth: buttonSize
@@ -128,7 +128,7 @@ CustomRect {
spacing: Tokens.spacing.extraSmall spacing: Tokens.spacing.extraSmall
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "undo" icon: "undo"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
@@ -140,7 +140,7 @@ CustomRect {
} }
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "delete_history" icon: "delete_history"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
@@ -152,7 +152,7 @@ CustomRect {
} }
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "content_copy" icon: "content_copy"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
@@ -164,7 +164,7 @@ CustomRect {
} }
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "delete_forever" icon: "delete_forever"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
@@ -176,7 +176,7 @@ CustomRect {
} }
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "check" icon: "check"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
+40 -14
View File
@@ -1,18 +1,23 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import ZShell.Internal
import qs.Services
Item { Item {
id: root id: root
property color handleBorder: "#3584e4" required property int border
property color handleFill: "white" property color handleBorder: Colors.palette.m3primary
property color handleFill: Colors.palette.m3onPrimary
property real hs: 12 property real hs: 12
property real minSize: 24 property real minSize: 24
required property real selH required property real selH
required property real selW required property real selW
required property real selX required property real selX
required property real selY required property real selY
required property Rectangle selection
required property string tool
signal boundsChanged(x: real, y: real, w: real, h: real) signal boundsChanged(x: real, y: real, w: real, h: real)
signal dragFinished signal dragFinished
@@ -23,12 +28,16 @@ Item {
x: selX x: selX
y: selY y: selY
Component.onDestruction: Cursor.clearOverride()
DragHandler { DragHandler {
id: moveHandler id: moveHandler
property real accumX: 0 property real accumX: 0
property real accumY: 0 property real accumY: 0
cursorShape: undefined
dragThreshold: 0
target: null target: null
onActiveChanged: { onActiveChanged: {
@@ -45,51 +54,63 @@ Item {
} }
} }
HoverHandler {
cursorShape: {
if (root.tool !== "move")
return undefined;
if ((moveHandler.centroid.pressedButtons & Qt.LeftButton) && moveHandler.active)
return Qt.ClosedHandCursor;
else
return Qt.OpenHandCursor;
}
}
Handle { Handle {
edge: "tl" edge: "tl"
x: -root.hs / 2 x: -(root.hs + root.border) / 2
y: -root.hs / 2 y: -(root.hs + root.border) / 2
} }
Handle { Handle {
edge: "t" edge: "t"
x: root.width / 2 - root.hs / 2 x: root.width / 2 - root.hs / 2
y: -root.hs / 2 y: -(root.hs + root.border) / 2
} }
Handle { Handle {
edge: "tr" edge: "tr"
x: root.width - root.hs / 2 x: root.width - root.hs / 2 + root.border / 2
y: -root.hs / 2 y: -(root.hs + root.border) / 2
} }
Handle { Handle {
edge: "r" edge: "r"
x: root.width - root.hs / 2 x: root.width - root.hs / 2 + root.border / 2
y: root.height / 2 - root.hs / 2 y: root.height / 2 - root.hs / 2
} }
Handle { Handle {
edge: "br" edge: "br"
x: root.width - root.hs / 2 x: root.width - root.hs / 2 + root.border / 2
y: root.height - root.hs / 2 y: root.height - root.hs / 2 + root.border / 2
} }
Handle { Handle {
edge: "b" edge: "b"
x: root.width / 2 - root.hs / 2 x: root.width / 2 - root.hs / 2
y: root.height - root.hs / 2 y: root.height - root.hs / 2 + root.border / 2
} }
Handle { Handle {
edge: "bl" edge: "bl"
x: -root.hs / 2 x: -(root.hs + root.border) / 2
y: root.height - root.hs / 2 y: root.height - root.hs / 2 + root.border / 2
} }
Handle { Handle {
edge: "l" edge: "l"
x: -root.hs / 2 x: -(root.hs + root.border) / 2
y: root.height / 2 - root.hs / 2 y: root.height / 2 - root.hs / 2
} }
@@ -107,6 +128,8 @@ Item {
z: 10 z: 10
HoverHandler { HoverHandler {
id: hover
cursorShape: { cursorShape: {
switch (handle.edge) { switch (handle.edge) {
case "tl": case "tl":
@@ -132,6 +155,7 @@ Item {
property real startX property real startX
property real startY property real startY
dragThreshold: 0
grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.TakeOverForbidden grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.TakeOverForbidden
target: null target: null
@@ -141,8 +165,10 @@ Item {
startY = root.y; startY = root.y;
startW = root.width; startW = root.width;
startH = root.height; startH = root.height;
Cursor.setOverride(hover.cursorShape);
} else { } else {
root.dragFinished(); root.dragFinished();
Cursor.clearOverride();
} }
} }
onTranslationChanged: { onTranslationChanged: {
+9
View File
@@ -5,6 +5,15 @@ import Quickshell
Singleton { Singleton {
property var _regexCache: ({}) property var _regexCache: ({})
function percent(value: int): string {
// TRANSLATORS: %1 = a number
return qsTr("%1%").arg(value);
}
function percentOne(value: real): string {
return percent(Math.round(value * 100));
}
function testRegexList(filterList: list<string>, target: string): bool { function testRegexList(filterList: list<string>, target: string): bool {
const regexChecker = /^\^.*\$$/; const regexChecker = /^\^.*\$$/;
for (const filter of filterList) { for (const filter of filterList) {
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
property var bars: new Map()
property var screens: new Map()
function getForActive(): PersistentProperties {
return screens.get(Hypr.focusedMonitor);
}
function load(screen: ShellScreen, visibilities: var): void {
screens.set(Hypr.monitorFor(screen), visibilities);
}
}
+41 -3
View File
@@ -5,7 +5,9 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import QtQuick import QtQuick
import ZShell.Models import ZShell.Models
import ZShell.Internal
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Services import qs.Services
import qs.Helpers import qs.Helpers
import qs.Paths import qs.Paths
@@ -38,7 +40,7 @@ Searcher {
previewPath = path; previewPath = path;
showPreview = true; showPreview = true;
if (Config.general.color.schemeGeneration) if (Config.colors.schemeGen)
previewColorsProc.running = true; previewColorsProc.running = true;
} }
@@ -62,8 +64,8 @@ Searcher {
WallpaperPath.currentWallpaperPath = path; WallpaperPath.currentWallpaperPath = path;
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 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.colors.schemeGen)
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.colors.mode}`]);
} }
function stopPreview(): void { function stopPreview(): void {
@@ -140,4 +142,40 @@ Searcher {
} }
} }
} }
WallpaperService {
id: wallpaper
path: root.current
}
Variants {
model: Quickshell.screens
WallpaperObject {
readonly property var crops: root.getCrop(modelData.name)
required property var modelData
cropHeight: crops?.height ?? 1.0
cropWidth: crops?.width ?? 1.0
cropX: crops?.x ?? 0.0
cropY: crops?.y ?? 0.0
screen: modelData.name
service: wallpaper
zoom: crops?.zoom ?? 1.0
Behavior on cropHeight {
Anim {}
}
Behavior on cropWidth {
Anim {}
}
Behavior on cropX {
Anim {}
}
Behavior on cropY {
Anim {}
}
}
}
} }
+28 -7
View File
@@ -3,25 +3,36 @@ pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
import ZShell import ZShell
import ZShell.Internal
import ZShell.Config import ZShell.Config
Singleton { Singleton {
id: root id: root
readonly property var cachedCities: new Map() readonly property var cachedCities: new Map()
property var cc
property string city
readonly property string description: cc?.weatherDesc ?? qsTr("No weather") readonly property string description: cc?.weatherDesc ?? qsTr("No weather")
readonly property string feelsLike: formatTemp(cc?.feelsLikeC) readonly property string feelsLike: formatTemp(cc?.feelsLikeC)
property list<var> forecast
property list<var> hourlyForecast
readonly property int humidity: cc?.humidity ?? 0 readonly property int humidity: cc?.humidity ?? 0
readonly property string icon: cc ? Icons.getWeatherIcon(cc.weatherCode) : "cloud_alert" readonly property string icon: cc ? Icons.getWeatherIcon(cc.weatherCode) : "cloud_alert"
property string loc
readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--" readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--" readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
readonly property string temp: formatTemp(cc?.tempC) readonly property string temp: formatTemp(cc?.tempC)
readonly property real windSpeed: cc?.windSpeed ?? 0 readonly property real windSpeed: cc?.windSpeed ?? 0
readonly property CircularBuffer precipitationForecast: precipitationBuffer
property var cc
property string city
property list<var> forecast
property list<var> hourlyForecast
readonly property int currentHourIndex: {
const now = new Date();
for (let i = 0; i < hourlyForecast.length; i++) {
if (new Date(hourlyForecast[i].timestamp.replace("T", " ")) >= now)
return i;
}
return -1;
}
property string loc
function fetchCityFromCoords(coords: string): void { function fetchCityFromCoords(coords: string): void {
if (cachedCities.has(coords)) { if (cachedCities.has(coords)) {
@@ -112,11 +123,12 @@ Singleton {
forecast = forecastList; forecast = forecastList;
const hourlyList = []; const hourlyList = [];
const now = new Date(); const startOfToday = new Date();
startOfToday.setHours(0, 0, 0, 0);
for (let i = 0; i < json.hourly.time.length; i++) { for (let i = 0; i < json.hourly.time.length; i++) {
const time = new Date(json.hourly.time[i].replace("T", " ")); const time = new Date(json.hourly.time[i].replace("T", " "));
if (time < now) if (time < startOfToday)
continue; continue;
hourlyList.push({ hourlyList.push({
@@ -129,6 +141,10 @@ Singleton {
}); });
} }
hourlyForecast = hourlyList; hourlyForecast = hourlyList;
precipitationBuffer.clear();
for (const hour of hourlyList)
precipitationBuffer.push(hour.precipChance);
}); });
} }
@@ -258,6 +274,11 @@ Singleton {
onLocChanged: fetchWeatherData() onLocChanged: fetchWeatherData()
CircularBuffer {
id: precipitationBuffer
capacity: 24
}
Connections { Connections {
function onWeatherLocationChanged(): void { function onWeatherLocationChanged(): void {
root.reload(); root.reload();
+9 -8
View File
@@ -4,6 +4,7 @@ import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Modules.Bar.Components import qs.Modules.Bar.Components
import qs.Modules.Bar.Popouts import qs.Modules.Bar.Popouts
@@ -16,12 +17,12 @@ GridLayout {
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 ScreenState screenState
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities
function axisCenterOf(item: Item): real { function axisCenterOf(item: Item): real {
const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2); const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2);
return horizontal ? c.x : c.y; return horizontal ? Math.floor(c.x) : Math.floor(c.y);
} }
function checkPopout(pos: real): void { function checkPopout(pos: real): void {
@@ -34,7 +35,7 @@ GridLayout {
return; return;
} }
if (visibilities.sidebar || visibilities.dashboard || visibilities.resources || visibilities.settings) if (screenState.sidebar || screenState.dashboard || screenState.resources || screenState.settings)
return; return;
if (id === "statusIcons" && Config.bar.popouts.statusIcons) { if (id === "statusIcons" && Config.bar.popouts.statusIcons) {
@@ -88,10 +89,10 @@ GridLayout {
role: "id" role: "id"
DelegateChoice { DelegateChoice {
roleValue: "hyprsunset" roleValue: "nightlight"
delegate: EntryWrapper { delegate: EntryWrapper {
HyprsunsetWidget { NightlightWidget {
horizontal: root.horizontal horizontal: root.horizontal
visible: !root.fullscreen || root.isHovered visible: !root.fullscreen || root.isHovered
} }
@@ -149,7 +150,7 @@ GridLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
Resources { Resources {
horizontal: root.horizontal horizontal: root.horizontal
visibilities: root.visibilities screenState: root.screenState
visible: !root.fullscreen || root.isHovered visible: !root.fullscreen || root.isHovered
} }
} }
@@ -173,7 +174,7 @@ GridLayout {
NotifBell { NotifBell {
horizontal: root.horizontal horizontal: root.horizontal
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities screenState: root.screenState
visible: !root.fullscreen || root.isHovered visible: !root.fullscreen || root.isHovered
} }
} }
@@ -187,7 +188,7 @@ GridLayout {
horizontal: root.horizontal horizontal: root.horizontal
loader: root loader: root
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities screenState: root.screenState
visible: !root.fullscreen || root.isHovered visible: !root.fullscreen || root.isHovered
} }
} }
+4 -4
View File
@@ -10,7 +10,7 @@ Item {
id: root id: root
readonly property int clampedExtent: Math.max(Config.bar.border, extent) readonly property int clampedExtent: Math.max(Config.bar.border, extent)
readonly property int contentThickness: Math.floor(Math.max(Config.bar.height, Tokens.bar.innerSize * (horizontal ? 1 : 1.5))) + padding * 2 readonly property int contentThickness: Math.floor(Math.max(Config.bar.height, Tokens.sizes.bar.innerSize * (horizontal ? 1 : 1.5))) + padding * 2
readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentThickness readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentThickness
property real extent: fullscreen ? 0 : Config.bar.border property real extent: fullscreen ? 0 : Config.bar.border
required property bool fullscreen required property bool fullscreen
@@ -21,9 +21,9 @@ Item {
required property ClipWrapper popoutsWrapper required property ClipWrapper popoutsWrapper
required property string position required property string position
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeVisible: (!fullscreen && (!Config.bar.autoHide || visibilities.bar)) || isHovered readonly property bool shouldBeVisible: (!fullscreen && (!Config.bar.autoHide || screenState.bar)) || isHovered
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property ScreenState screenState
function checkPopout(pos: real): void { function checkPopout(pos: real): void {
(content.item as Bar)?.checkPopout(pos); (content.item as Bar)?.checkPopout(pos);
@@ -85,7 +85,7 @@ Item {
popouts: root.popouts popouts: root.popouts
popoutsWrapper: root.popoutsWrapper popoutsWrapper: root.popoutsWrapper
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@ Item {
id: root id: root
required property Item bar required property Item bar
required property PersistentProperties visibilities required property ScreenState screenState
anchors.fill: parent anchors.fill: parent
+12 -25
View File
@@ -13,7 +13,8 @@ import qs.Services
WidgetBase { WidgetBase {
id: root id: root
readonly property color contentColor: visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface readonly property font font: Tokens.font.clock.family(Config.appearance.font.family.clock).build()
readonly property color contentColor: Colors.palette.m3onSurface
readonly property string effectiveFormat: { readonly property string effectiveFormat: {
let fmt = Config.general.dateFormat; let fmt = Config.general.dateFormat;
if (!root.horizontal) if (!root.horizontal)
@@ -44,10 +45,9 @@ WidgetBase {
} }
required property GridLayout loader required property GridLayout loader
required property Wrapper popouts required property Wrapper popouts
readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.small * 2 : verticalColumn.implicitHeight + Tokens.padding.small * 2 readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.medium * 2 : verticalColumn.implicitHeight + Tokens.padding.medium * 2
required property PersistentProperties visibilities required property ScreenState screenState
color: visibilities.dashboard ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? baseSize : size implicitHeight: horizontal ? baseSize : size
implicitWidth: horizontal ? size : baseSize implicitWidth: horizontal ? size : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
@@ -57,14 +57,13 @@ WidgetBase {
anchors.centerIn: parent anchors.centerIn: parent
color: root.contentColor color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type font: root.font
height: implicitHeight height: implicitHeight
text: Time.dateStr text: Time.dateStr
visible: root.horizontal visible: root.horizontal
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
} }
} }
@@ -84,12 +83,11 @@ WidgetBase {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: root.contentColor color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type font: root.font
text: Qt.formatDateTime(Time.date, modelData) text: Qt.formatDateTime(Time.date, modelData)
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
} }
} }
} }
@@ -114,7 +112,7 @@ WidgetBase {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: root.contentColor color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type font: root.font
text: { text: {
if (modelData.includes("h")) if (modelData.includes("h"))
return Time.hourStr; return Time.hourStr;
@@ -126,8 +124,7 @@ WidgetBase {
} }
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
} }
} }
} }
@@ -135,23 +132,13 @@ WidgetBase {
CustomText { CustomText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: root.contentColor color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type font: root.font
text: Qt.formatDateTime(Time.date, "AP") text: Qt.formatDateTime(Time.date, "AP")
visible: Config.services.useTwelveHourClock && root.formatParts.timeTokens.length > 0 visible: Config.services.useTwelveHourClock && root.formatParts.timeTokens.length > 0
Behavior on color { Behavior on color {
CAnim { CAnim {}
} }
} }
} }
} }
StateLayer {
acceptedButtons: Qt.LeftButton
color: root.contentColor
onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard;
}
}
}
@@ -28,8 +28,7 @@ Row {
CustomText { CustomText {
color: Battery.getColors(root.percentage, root.devState).fg color: Battery.getColors(root.percentage, root.devState).fg
font.pointSize: Tokens.font.size.larger / 1.5 font: Tokens.font.body.builders.small.size(11).weight(Font.Bold).build()
font.weight: 800
height: track.height height: track.height
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: Math.round(root.percentage * 100) text: Math.round(root.percentage * 100)
@@ -60,8 +59,7 @@ Row {
clip: true clip: true
color: Battery.getColors(root.percentage, root.devState).bg color: Battery.getColors(root.percentage, root.devState).bg
font.pointSize: Tokens.font.size.larger / 1.5 font: Tokens.font.body.builders.small.size(11).weight(Font.Bold).build()
font.weight: 800
height: track.height height: track.height
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: Math.round(root.percentage * 100) text: Math.round(root.percentage * 100)
+1 -1
View File
@@ -6,7 +6,7 @@ import qs.Services
CustomRect { CustomRect {
id: root id: root
readonly property int baseSize: horizontal ? Math.max(Config.bar.height, Tokens.bar.innerSize) : Math.max(Config.bar.height, Math.floor(Tokens.bar.innerSize * 1.5)) readonly property int baseSize: horizontal ? Math.max(Config.bar.height, Tokens.sizes.bar.innerSize) : Math.max(Config.bar.height, Math.floor(Tokens.sizes.bar.innerSize * 1.5))
required property bool horizontal required property bool horizontal
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
+4 -8
View File
@@ -19,14 +19,12 @@ WidgetBase {
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: !root.horizontal enabled: !root.horizontal
Anim { Anim {}
}
} }
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: root.horizontal enabled: root.horizontal
Anim { Anim {}
}
} }
TextMetrics { TextMetrics {
@@ -47,15 +45,14 @@ WidgetBase {
columns: root.horizontal ? -1 : 1 columns: root.horizontal ? -1 : 1
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {}
}
} }
MaterialIcon { MaterialIcon {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
animate: true animate: true
color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: Players.active?.isPlaying ? "music_note" : "music_off" text: Players.active?.isPlaying ? "music_note" : "music_off"
} }
@@ -65,7 +62,6 @@ WidgetBase {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
animate: true animate: true
color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
implicitHeight: root.horizontal ? root.implicitHeight : root.textWidth implicitHeight: root.horizontal ? root.implicitHeight : root.textWidth
implicitWidth: root.horizontal ? root.textWidth : root.implicitWidth implicitWidth: root.horizontal ? root.textWidth : root.implicitWidth
@@ -8,15 +8,15 @@ import qs.Services
WidgetBase { WidgetBase {
id: root id: root
property bool tempEnabled: Hyprsunset.enabled property bool tempEnabled: Nightlight.enabled
color: root.tempEnabled ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: root.tempEnabled ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
radius: Tokens.rounding.full radius: Tokens.rounding.full
StateLayer { StateLayer {
onClicked: { onClicked: {
Hyprsunset.toggleNightLight(); Nightlight.toggleNightLight();
Hyprsunset.manualToggle = true; Nightlight.manualToggle = true;
} }
} }
@@ -25,12 +25,11 @@ WidgetBase {
animate: true animate: true
color: root.tempEnabled ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.tempEnabled ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: root.tempEnabled ? 1 : 0 fill: root.tempEnabled ? 1 : 0
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: root.tempEnabled ? "lightbulb" : "light_off" text: root.tempEnabled ? "lightbulb" : "light_off"
Behavior on fill { Behavior on fill {
Anim { Anim {}
}
} }
} }
} }
+9 -11
View File
@@ -11,35 +11,33 @@ WidgetBase {
id: root id: root
required property Wrapper popouts required property Wrapper popouts
required property PersistentProperties visibilities required property ScreenState screenState
color: visibilities.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: screenState.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
radius: Tokens.rounding.full radius: Tokens.rounding.full
MaterialIcon { MaterialIcon {
id: notificationCenterIcon id: notificationCenterIcon
anchors.centerIn: parent anchors.centerIn: parent
color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.screenState.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: root.visibilities.sidebar ? 1 : 0 fill: root.screenState.sidebar ? 1 : 0
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.size(16).build()
text: NotifServer.list.length ? "\uf4fe" : "\ue7f4" text: NotifServer.list.length ? "\uf4fe" : "\ue7f4"
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
} }
Behavior on fill { Behavior on fill {
Anim { Anim {}
}
} }
} }
StateLayer { StateLayer {
color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.screenState.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: { onClicked: {
root.visibilities.sidebar = !root.visibilities.sidebar; root.screenState.sidebar = !root.screenState.sidebar;
} }
} }
} }
+30 -17
View File
@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Shapes import Quickshell
import qs.Components import qs.Components
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
@@ -12,6 +12,7 @@ GridLayout {
property real animatedPercentage: 0 property real animatedPercentage: 0
readonly property real arcStartAngle: 0.75 * Math.PI readonly property real arcStartAngle: 0.75 * Math.PI
readonly property real arcSweep: 1.5 * Math.PI readonly property real arcSweep: 1.5 * Math.PI
readonly property real dpr: (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1
required property bool horizontal required property bool horizontal
property string icon property string icon
required property color iconColor required property color iconColor
@@ -22,6 +23,10 @@ GridLayout {
property bool warning: percentage * 100 >= warningThreshold property bool warning: percentage * 100 >= warningThreshold
property int warningThreshold: 80 property int warningThreshold: 80
function snap(v) {
return Math.round(v * dpr) / dpr;
}
columnSpacing: Tokens.spacing.medium columnSpacing: Tokens.spacing.medium
columns: horizontal ? -1 : 1 columns: horizontal ? -1 : 1
percentage: 0 percentage: 0
@@ -44,31 +49,39 @@ GridLayout {
Layout.preferredWidth: 16 Layout.preferredWidth: 16
color: root.iconColor color: root.iconColor
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: root.icon text: root.icon
} }
CustomClippingRect { CustomRect {
id: track
Layout.preferredHeight: root.horizontal ? icon.implicitHeight - Tokens.padding.extraSmall : 4 Layout.preferredHeight: root.horizontal ? icon.implicitHeight - Tokens.padding.extraSmall : 4
Layout.preferredWidth: root.horizontal ? 4 : icon.implicitWidth Layout.preferredWidth: root.horizontal ? 4 : icon.implicitWidth
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2) color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
radius: Tokens.rounding.full radius: Math.min(width, height) / 2
Item {
id: clipper
anchors.bottom: root.horizontal ? parent.bottom : undefined
anchors.horizontalCenter: root.horizontal ? parent.horizontalCenter : undefined
anchors.left: root.horizontal ? undefined : parent.left
anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter
clip: true
height: root.horizontal ? root.snap(root.percentage * track.height) : track.height * 2
width: root.horizontal ? track.width * 2 : root.snap(root.percentage * track.width)
CustomRect { CustomRect {
id: fill anchors.bottom: root.horizontal ? parent.bottom : undefined
anchors.horizontalCenter: root.horizontal ? parent.horizontalCenter : undefined
anchors.bottom: parent.bottom anchors.left: root.horizontal ? undefined : parent.left
anchors.left: parent.left anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter
anchors.right: root.horizontal ? parent.right : undefined
anchors.top: root.horizontal ? undefined : parent.top
color: root.mainColor color: root.mainColor
implicitHeight: root.horizontal ? Math.ceil(root.percentage * parent.height) : 0 height: track.height
implicitWidth: root.horizontal ? 0 : Math.ceil(root.percentage * parent.width) radius: track.radius
width: track.width
// Behavior on implicitHeight { }
// Anim {
// }
// }
} }
} }
} }
+12 -12
View File
@@ -12,18 +12,18 @@ import qs.Components
WidgetBase { WidgetBase {
id: root id: root
required property PersistentProperties visibilities required property ScreenState screenState
clip: true clip: true
color: visibilities.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: screenState.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? baseSize : gridLayout.implicitHeight + Tokens.padding.medium * 2 implicitHeight: horizontal ? baseSize : gridLayout.implicitHeight + Tokens.padding.medium * 2
implicitWidth: horizontal ? gridLayout.implicitWidth + Tokens.padding.medium * 2 : baseSize implicitWidth: horizontal ? gridLayout.implicitWidth + Tokens.padding.medium * 2 : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
StateLayer { StateLayer {
color: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: root.visibilities.resources = !root.visibilities.resources onClicked: root.screenState.resources = !root.screenState.resources
} }
GridLayout { GridLayout {
@@ -53,8 +53,8 @@ WidgetBase {
Layout.fillWidth: !root.horizontal Layout.fillWidth: !root.horizontal
horizontal: root.horizontal horizontal: root.horizontal
icon: "memory" icon: "memory"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
percentage: Cpu.percentage percentage: Cpu.percentage
warningThreshold: 95 warningThreshold: 95
} }
@@ -64,8 +64,8 @@ WidgetBase {
Layout.fillWidth: !root.horizontal Layout.fillWidth: !root.horizontal
horizontal: root.horizontal horizontal: root.horizontal
icon: "memory_alt" icon: "memory_alt"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3secondary mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3secondary
percentage: Memory.percentage percentage: Memory.percentage
warningThreshold: 80 warningThreshold: 80
} }
@@ -75,8 +75,8 @@ WidgetBase {
Layout.fillWidth: !root.horizontal Layout.fillWidth: !root.horizontal
horizontal: root.horizontal horizontal: root.horizontal
icon: "gamepad" icon: "gamepad"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3tertiary mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3tertiary
percentage: Gpu.percentage percentage: Gpu.percentage
} }
@@ -85,8 +85,8 @@ WidgetBase {
Layout.fillWidth: !root.horizontal Layout.fillWidth: !root.horizontal
horizontal: root.horizontal horizontal: root.horizontal
icon: "developer_board" icon: "developer_board"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
percentage: Gpu.memoryUsed / Gpu.memoryTotal percentage: Gpu.memoryUsed / Gpu.memoryTotal
} }
} }
@@ -13,19 +13,16 @@ MaterialIcon {
animate: true animate: true
color: Audio.muted ? Colors.palette.m3error : Colors.palette.m3onSurface color: Audio.muted ? Colors.palette.m3error : Colors.palette.m3onSurface
fill: 1 fill: 1
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: Audio.muted ? "volume_off" : "volume_up" text: Audio.muted ? "volume_off" : "volume_up"
Behavior on Layout.maximumWidth { Behavior on Layout.maximumWidth {
Anim { Anim {}
}
} }
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
Behavior on scale { Behavior on scale {
Anim { Anim {}
}
} }
} }
@@ -13,19 +13,16 @@ MaterialIcon {
animate: true animate: true
color: (Audio.sourceMuted ?? false) ? Colors.palette.m3error : Colors.palette.m3onSurface color: (Audio.sourceMuted ?? false) ? Colors.palette.m3error : Colors.palette.m3onSurface
fill: 1 fill: 1
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: Audio.sourceMuted ? "mic_off" : "mic" text: Audio.sourceMuted ? "mic_off" : "mic"
Behavior on Layout.maximumWidth { Behavior on Layout.maximumWidth {
Anim { Anim {}
}
} }
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
Behavior on scale { Behavior on scale {
Anim { Anim {}
}
} }
} }
@@ -15,20 +15,16 @@ Item {
implicitWidth: currentItem?.implicitWidth ?? 0 implicitWidth: currentItem?.implicitWidth ?? 0
Behavior on Layout.preferredHeight { Behavior on Layout.preferredHeight {
Anim { Anim {}
}
} }
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {
Anim { Anim {}
}
} }
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
Behavior on scale { Behavior on scale {
Anim { Anim {}
}
} }
Component.onCompleted: Battery.startDaemon() Component.onCompleted: Battery.startDaemon()
@@ -67,7 +63,7 @@ Item {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
fill: 1 fill: 1
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: { text: {
if (PowerProfiles.profile === PowerProfile.PowerSaver) if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "energy_savings_leaf"; return "energy_savings_leaf";
+4 -4
View File
@@ -62,10 +62,10 @@ Item {
root.popouts.currentName = `traymenu${root.ind}`; root.popouts.currentName = `traymenu${root.ind}`;
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x); root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
root.popouts.hasCurrent = true; root.popouts.hasCurrent = true;
if (visibilities.sidebar || visibilities.dashboard || visibilities.settings) { if (screenState.sidebar || screenState.dashboard || screenState.settings) {
visibilities.sidebar = false; screenState.sidebar = false;
visibilities.dashboard = false; screenState.dashboard = false;
visibilities.settings = false; screenState.settings = false;
} }
} }
} }
+1 -1
View File
@@ -42,7 +42,7 @@ WidgetBase {
Repeater { Repeater {
id: repeater id: repeater
model: SystemTray.items model: SystemTray.items.values.filter(i => i.status !== Status.Passive && i.hasMenu)
TrayItem { TrayItem {
id: trayItem id: trayItem
+1 -2
View File
@@ -27,14 +27,13 @@ WidgetBase {
MaterialIcon { MaterialIcon {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
font.pointSize: root.horizontal ? Tokens.font.size.larger : Tokens.font.size.large fontStyle: Tokens.font.icon.small
text: "package_2" text: "package_2"
} }
CustomText { CustomText {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
color: root.textColor color: root.textColor
font.pointSize: Tokens.font.size.normal
text: root.countUpdates text: root.countUpdates
} }
} }
+2 -4
View File
@@ -52,8 +52,7 @@ Item {
elide: Qt.ElideRight elide: Qt.ElideRight
elideWidth: root.maxSize - (root.horizontal ? root.current.anchors.leftMargin : 0) elideWidth: root.maxSize - (root.horizontal ? root.current.anchors.leftMargin : 0)
font.family: "Rubik" font: Tokens.font.body.small
font.pointSize: Tokens.font.size.normal
text: Hypr.activeToplevel?.title ?? qsTr("Desktop") text: Hypr.activeToplevel?.title ?? qsTr("Desktop")
onElideWidthChanged: root.current.text = elidedText onElideWidthChanged: root.current.text = elidedText
@@ -81,8 +80,7 @@ Item {
width: root.horizontal ? implicitWidth : implicitHeight width: root.horizontal ? implicitWidth : implicitHeight
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
transform: [ transform: [
Translate { Translate {
+3 -5
View File
@@ -17,7 +17,7 @@ Item {
required property bool horizontal required property bool horizontal
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
required property ShellScreen screen required property ShellScreen screen
readonly property real shortSize: Math.max(Config.bar.height, Tokens.bar.innerSize) readonly property real shortSize: Math.max(Config.bar.height, Tokens.sizes.bar.innerSize)
readonly property real size: (workspaceButtonWidth * workspacesShown) + activeWorkspaceMargin * 2 readonly property real size: (workspaceButtonWidth * workspacesShown) + activeWorkspaceMargin * 2
property int workspaceButtonWidth: (horizontal ? bgRect.implicitHeight : bgRect.implicitWidth) - root.activeWorkspaceMargin * 2 property int workspaceButtonWidth: (horizontal ? bgRect.implicitHeight : bgRect.implicitWidth) - root.activeWorkspaceMargin * 2
property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown
@@ -30,14 +30,12 @@ Item {
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: !root.horizontal enabled: !root.horizontal
Anim { Anim {}
}
} }
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: root.horizontal enabled: root.horizontal
Anim { Anim {}
}
} }
CustomRect { CustomRect {
+3 -3
View File
@@ -47,6 +47,7 @@ Column {
CustomText { CustomText {
Layout.fillWidth: true Layout.fillWidth: true
elide: CustomText.ElideRight
text: layout.modelData.model text: layout.modelData.model
} }
@@ -180,8 +181,7 @@ Column {
} }
] ]
transitions: Transition { transitions: Transition {
AnchorAnim { AnchorAnim {}
}
} }
} }
@@ -245,7 +245,7 @@ Column {
anchors.centerIn: parent anchors.centerIn: parent
color: profiles.current === text ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: profiles.current === text ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: profiles.current === text ? 1 : 0 fill: profiles.current === text ? 1 : 0
font.pointSize: Tokens.font.size.large * 2 fontStyle: Tokens.font.icon.extraLarge
grade: Colors.light ? 0 : (text === "balance" ? -25 : 0) grade: Colors.light ? 0 : (text === "balance" ? -25 : 0)
text: parent.icon text: parent.icon
+10 -8
View File
@@ -36,15 +36,17 @@ CustomClippingRect {
MaterialIcon { MaterialIcon {
id: noUpdatesIcon id: noUpdatesIcon
anchors.horizontalCenter: parent.horizontalCenter anchors.centerIn: parent
anchors.top: parent.top anchors.verticalCenterOffset: -noUpdatesText.implicitHeight
color: Colors.tPalette.m3onSurfaceVariant color: Colors.tPalette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.extraLarge * 3 fontStyle: Tokens.font.icon.builders.large.size(48).weight(Font.DemiBold).build()
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: "check" text: "check"
} }
CustomText { CustomText {
id: noUpdatesText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: noUpdatesIcon.bottom anchors.top: noUpdatesIcon.bottom
color: Colors.tPalette.m3onSurfaceVariant color: Colors.tPalette.m3onSurfaceVariant
@@ -89,7 +91,7 @@ CustomClippingRect {
anchors.rightMargin: Tokens.padding.small anchors.rightMargin: Tokens.padding.small
MaterialIcon { MaterialIcon {
font.pointSize: Tokens.font.size.large * 2 fontStyle: Tokens.font.icon.extraLarge
text: "package_2" text: "package_2"
} }
@@ -100,7 +102,7 @@ CustomClippingRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 25 Layout.preferredHeight: 25
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: Tokens.font.size.large font: Tokens.font.title.medium
text: update.sections[0] text: update.sections[0]
} }
@@ -122,7 +124,7 @@ CustomClippingRect {
Layout.preferredWidth: 125 Layout.preferredWidth: 125
animate: true animate: true
color: Colors.palette.m3tertiary color: Colors.palette.m3tertiary
font.pointSize: Tokens.font.size.large font: Tokens.font.title.medium
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true marqueeEnabled: true
pauseMs: 4000 pauseMs: 4000
@@ -133,7 +135,7 @@ CustomClippingRect {
MaterialIcon { MaterialIcon {
Layout.fillHeight: true Layout.fillHeight: true
color: Colors.palette.m3secondary color: Colors.palette.m3secondary
font.pointSize: Tokens.font.size.extraLarge fontStyle: Tokens.font.icon.large
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: "arrow_right_alt" text: "arrow_right_alt"
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -146,7 +148,7 @@ CustomClippingRect {
Layout.preferredWidth: 120 Layout.preferredWidth: 120
animate: true animate: true
color: Colors.palette.m3primary color: Colors.palette.m3primary
font.pointSize: Tokens.font.size.large font: Tokens.font.title.medium
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true marqueeEnabled: true
pauseMs: 4000 pauseMs: 4000
+4 -6
View File
@@ -14,7 +14,7 @@ Item {
readonly property int itemHeight: Config.clipboard.sizes.itemHeight readonly property int itemHeight: Config.clipboard.sizes.itemHeight
required property ShellScreen screen required property ShellScreen screen
required property PersistentProperties visibilities required property ScreenState screenState
implicitHeight: search.implicitHeight + entries.anchors.topMargin + ((view.spacing + itemHeight) * Config.clipboard.maxEntriesShown) - view.spacing implicitHeight: search.implicitHeight + entries.anchors.topMargin + ((view.spacing + itemHeight) * Config.clipboard.maxEntriesShown) - view.spacing
implicitWidth: Config.clipboard.sizes.width + preview.width + preview.anchors.leftMargin implicitWidth: Config.clipboard.sizes.width + preview.width + preview.anchors.leftMargin
@@ -49,7 +49,7 @@ Item {
Keys.onUpPressed: view.decrementCurrentIndex() Keys.onUpPressed: view.decrementCurrentIndex()
onAccepted: { onAccepted: {
ClipHistory.copy(view.currentItem.modelData); ClipHistory.copy(view.currentItem.modelData);
root.visibilities.clipboard = false; root.screenState.clipboard = false;
} }
} }
} }
@@ -66,8 +66,7 @@ Item {
radius: 25 radius: 25
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {}
}
} }
Column { Column {
@@ -104,7 +103,6 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small
text: lineRow.modelData.line text: lineRow.modelData.line
} }
} }
@@ -252,7 +250,7 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.small
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.large
text: clipItem.isImage ? "image" : "text_fields" text: clipItem.isImage ? "image" : "text_fields"
} }
+3 -3
View File
@@ -11,8 +11,8 @@ 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 && Config.clipboard.enabled readonly property bool shouldBeActive: screenState.clipboard && Config.clipboard.enabled
required property PersistentProperties visibilities required property ScreenState screenState
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight + Tokens.padding.small * 2 implicitHeight: content.implicitHeight + Tokens.padding.small * 2
@@ -35,7 +35,7 @@ Item {
sourceComponent: Content { sourceComponent: Content {
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
} }
} }
+124 -37
View File
@@ -1,82 +1,169 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Widgets
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import ZShell.Config import ZShell.Config
import ZShell
import qs.Components import qs.Components
Item { Item {
id: root id: root
required property PersistentProperties dashState readonly property var dashboardTabs: {
readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2 const allTabs = [
{
component: dashComponent,
iconName: "dashboard",
text: qsTr("Dashboard"),
enabled: Config.dashboard.showDashboard
},
{
component: weatherComponent,
iconName: "cloud",
text: qsTr("Weather"),
enabled: Config.dashboard.showWeather
},
{
component: mediaComponent,
iconName: "queue_music",
text: qsTr("Media"),
enabled: Config.dashboard.showMedia
},
{
component: procsComponent,
iconName: "speed",
text: qsTr("Processes"),
enabled: Config.dashboard.showProcesses
},
];
return allTabs.filter(tab => tab.enabled);
}
readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins + viewWrapper.anchors.topMargin
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2 readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
required property PersistentProperties visibilities required property ScreenState screenState
implicitHeight: nonAnimHeight implicitHeight: nonAnimHeight
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {
type: Anim.SlowEffects
} }
} }
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {
type: Anim.SlowEffects
} }
} }
ClippingRectangle { Tabs {
id: tabs
anchors.left: parent.left
anchors.margins: Tokens.padding.small
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: ZUtils.clamp(anchors.margins - Config.bar.border, 0, anchors.margins)
nonAnimWidth: view.implicitWidth
screenState: root.screenState
tabs: root.dashboardTabs
}
Item {
id: viewWrapper id: viewWrapper
property int radius: Tokens.rounding.extraLarge - anchors.margins
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.small
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: tabs.bottom
color: "transparent" anchors.topMargin: Tokens.spacing.small
radius: Tokens.rounding.extraLarge - anchors.margins clip: view.currentIndex !== 3 || (!view.currentItem?.settled ?? true)
Item { Item {
id: view id: view
readonly property int currentIndex: root.dashState?.currentTab readonly property int currentIndex: root.screenState.dashboardTab
readonly property Item currentItem: row.children[currentIndex] readonly property Item currentItem: {
repeater.count;
return repeater.itemAt(currentIndex);
}
anchors.fill: parent anchors.fill: parent
implicitHeight: currentItem.implicitHeight implicitHeight: currentItem?.implicitHeight ?? 0
implicitWidth: currentItem.implicitWidth implicitWidth: currentItem?.implicitWidth ?? 0
RowLayout { Repeater {
id: row id: repeater
Pane { delegate: Loader {
index: 0 id: paneLoader
sourceComponent: Dash {
dashState: root.dashState
visibilities: root.visibilities
}
}
}
}
}
component Pane: Loader {
id: pane
required property int index required property int index
required property var modelData
Layout.alignment: Qt.AlignTop readonly property real targetX: index < view.currentIndex ? -view.implicitWidth : index > view.currentIndex ? view.implicitWidth : 0
readonly property bool settled: x === targetX
Component.onCompleted: active = Qt.binding(() => { active: index === view.currentIndex || opacity > 0
// Always keep current tab loaded opacity: index === view.currentIndex ? 1 : 0
if (pane.index === view.currentIndex) sourceComponent: modelData.component
return true; visible: opacity > 0
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth); x: targetX
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth); y: 0
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
}) Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on x {
Anim {
type: Anim.DefaultEffects
}
}
}
model: ScriptModel {
values: root.dashboardTabs
}
}
Component {
id: dashComponent
Dash {
radius: viewWrapper.radius
screenState: root.screenState
}
}
Component {
id: weatherComponent
WeatherTab {
radius: viewWrapper.radius
}
}
Component {
id: mediaComponent
Media {
radius: viewWrapper.radius
screenState: root.screenState
}
}
Component {
id: procsComponent
ProcessesTab {
radius: viewWrapper.radius
}
}
}
} }
} }
+7 -23
View File
@@ -11,10 +11,9 @@ import qs.Services
GridLayout { GridLayout {
id: root id: root
required property PersistentProperties dashState readonly property bool dashboardVisible: screenState.dashboard
readonly property bool dashboardVisible: visibilities.dashboard required property int radius
property int radius: Tokens.rounding.medium required property ScreenState screenState
required property PersistentProperties visibilities
columnSpacing: Tokens.spacing.small columnSpacing: Tokens.spacing.small
rowSpacing: Tokens.spacing.small rowSpacing: Tokens.spacing.small
@@ -61,19 +60,18 @@ GridLayout {
User { User {
id: user id: user
dashState: root.dashState screenState: root.screenState
} }
} }
Rect { Rect {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: Config.dashboard.sizes.weatherWidth Layout.preferredWidth: Tokens.sizes.dashboard.weatherWidth
Layout.row: 0 Layout.row: 0
radius: root.radius radius: root.radius
Weather { Weather {}
}
} }
Rect { Rect {
@@ -87,7 +85,7 @@ GridLayout {
Calendar { Calendar {
id: calendar id: calendar
dashState: root.dashState screenState: root.screenState
} }
} }
@@ -104,20 +102,6 @@ GridLayout {
} }
} }
CustomClippingRect {
Layout.column: 0
Layout.columnSpan: 5
Layout.fillWidth: true
Layout.preferredHeight: media.implicitHeight
Layout.row: 2
color: Colors.tPalette.m3surfaceContainer
radius: root.radius
Media {
id: media
}
}
component Rect: CustomRect { component Rect: CustomRect {
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
} }
+9 -9
View File
@@ -12,12 +12,12 @@ CustomMouseArea {
id: root id: root
property int activeGrid: 1 property int activeGrid: 1
required property PersistentProperties dashState required property ScreenState screenState
property bool initialized: false property bool initialized: false
property int month1 property int month1
property int month2 property int month2
readonly property int realCurrMonth: dashState.currentDate.getMonth() readonly property int realCurrMonth: screenState.dashboardDate.getMonth()
readonly property int realCurrYear: dashState.currentDate.getFullYear() readonly property int realCurrYear: screenState.dashboardDate.getFullYear()
property int year1 property int year1
property int year2 property int year2
@@ -43,9 +43,9 @@ CustomMouseArea {
function onWheel(event: WheelEvent): void { function onWheel(event: WheelEvent): void {
if (event.angleDelta.y > 0) if (event.angleDelta.y > 0)
root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1); root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1);
else if (event.angleDelta.y < 0) else if (event.angleDelta.y < 0)
root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1); root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1);
} }
acceptedButtons: Qt.MiddleButton acceptedButtons: Qt.MiddleButton
@@ -61,7 +61,7 @@ CustomMouseArea {
initialized = true; initialized = true;
} }
onClicked: root.dashState.currentDate = new Date() onClicked: root.screenState.dashboardDate = new Date()
onRealCurrMonthChanged: handleDateChange() onRealCurrMonthChanged: handleDateChange()
onRealCurrYearChanged: handleDateChange() onRealCurrYearChanged: handleDateChange()
@@ -135,7 +135,7 @@ CustomMouseArea {
padding: Tokens.padding.extraSmall padding: Tokens.padding.extraSmall
type: IconButton.Text type: IconButton.Text
onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1) onClicked: root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1)
} }
Item { Item {
@@ -158,7 +158,7 @@ CustomMouseArea {
} }
} }
onClicked: root.dashState.currentDate = new Date() onClicked: root.screenState.dashboardDate = new Date()
} }
Item { Item {
@@ -206,7 +206,7 @@ CustomMouseArea {
padding: Tokens.padding.extraSmall padding: Tokens.padding.extraSmall
type: IconButton.Text type: IconButton.Text
onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1) onClicked: root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1)
} }
} }
-51
View File
@@ -1,51 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import ZShell.Config
import qs.Helpers
import qs.Services
Item {
id: root
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: 110
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: -(font.pointSize * 0.4)
color: Colors.palette.m3secondary
font.family: "Rubik"
font.pointSize: 18
font.weight: 600
text: Time.hourStr
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3primary
font.family: "Rubik"
font.pointSize: 18 * 0.9
text: "•••"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: -(font.pointSize * 0.4)
color: Colors.palette.m3secondary
font.family: "Rubik"
font.pointSize: 18
font.weight: 600
text: Time.minuteStr
}
}
}
-329
View File
@@ -1,329 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Services.Mpris
import QtQuick
import QtQuick.Layouts
import QtQuick.Shapes
import QtQuick.Effects
import ZShell.Components
import ZShell.Services
import ZShell.Config
import qs.Modules.Dashboard.Dash.Media
import qs.Daemons
import qs.Components
import qs.Helpers
import qs.Services
Item {
id: root
readonly property bool hasUnknownLength: (Players.active?.length ?? 0) > 2147483647
property real playerProgress: {
const active = Players.active;
return active?.length ? (active.position % active.length) / active.length : 0;
}
property int rowHeight: Tokens.padding.large + Config.dashboard.sizes.mediaProgressThickness + Tokens.spacing.small
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
function lengthStr(length: int): string {
if (length < 0)
return "-1:-1";
const hours = Math.floor(length / 3600);
const mins = Math.floor((length % 3600) / 60);
const secs = Math.floor(length % 60).toString().padStart(2, "0");
if (hours > 0)
return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
return `${mins}:${secs}`;
}
Behavior on playerProgress {
Anim {
duration: Tokens.anim.durations.large
}
}
Timer {
running: Players.active?.isPlaying ?? false
interval: Config.dashboard.mediaUpdateInterval
triggeredOnStart: true
repeat: true
onTriggered: Players.active?.positionChanged()
}
ServiceRef {
service: Audio.cava
}
BackgroundShapes {
anchors.fill: parent
}
Shape {
id: visualizer
readonly property int bars: Config.services.visualizerBars
property color color: Qt.alpha(Colors.palette.m3primary, 0.5)
property color fillColor: Qt.alpha(color, 0.1)
anchors.fill: parent
anchors.leftMargin: -(shape.strokeWidth / 2)
layer.enabled: true
asynchronous: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: shape
strokeColor: visualizer.color
fillColor: visualizer.fillColor
strokeWidth: 2
PathSvg {
path: shape.curvePath
}
property string curvePath: {
const values = Audio.cava.values;
const n = values.length;
if (n < 2)
return "";
const h = root.height + shape.strokeWidth / 2;
const w = root.width + shape.strokeWidth;
function x(i) {
return i * w / (n - 1);
}
function y(i) {
return h - (values[i] * Config.dashboard.sizes.mediaVisualizerSize) / 2;
}
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(Colors.palette.m3shadow, 0.2)
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: visualizer
}
}
RowLayout {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.large
anchors.leftMargin: Tokens.padding.extraLarge
CoverVisualizer {
Layout.fillHeight: true
implicitWidth: Config.dashboard.sizes.mediaCoverArtSize
}
ColumnLayout {
Layout.fillWidth: true
Layout.leftMargin: Tokens.spacing.largeIncreased
Layout.rightMargin: Tokens.spacing.largeIncreased
spacing: Tokens.spacing.small
MarqueeText {
id: title
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
color: Colors.palette.m3primary
font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter
pauseMs: 4000
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
implicitWidth: parent.width - Tokens.padding.large * 4
}
MarqueeText {
id: album
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
color: Colors.palette.m3outline
font.pointSize: Tokens.font.size.small
horizontalAlignment: Text.AlignHCenter
pauseMs: 4000
text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album")
implicitWidth: parent.width - Tokens.padding.large * 4
}
MarqueeText {
id: artist
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
color: Colors.palette.m3secondary
horizontalAlignment: Text.AlignHCenter
pauseMs: 4000
text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
implicitWidth: parent.width - Tokens.padding.large * 4
}
RowLayout {
id: positionSliderLayout
Layout.topMargin: Tokens.spacing.large
Layout.fillWidth: true
spacing: Tokens.spacing.small
TextMetrics {
id: timeMetrics
text: Players.active ? root.lengthStr(Math.max(Players.active.position, root.hasUnknownLength ? 0 : Players.active.length)).replace(/[1-9]/g, "0") : "00:00"
}
CustomText {
id: positionLabel
Layout.preferredWidth: timeMetrics.width
text: root.lengthStr(Players.active?.position ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
CustomSlider {
id: positionSlider
Layout.fillWidth: true
value: Players.active ? Players.active.position / (Players.active.length || 1) : 0
enabled: (Players.active?.canSeek ?? false) && !root.hasUnknownLength
wavy: true
animateWave: Players.active?.isPlaying ?? false
waveFrequency: 5
waveDuration: 2000
interactionOnMove: false
onInteraction: value => {
const active = Players.active;
if (active?.canSeek && active?.positionSupported)
active.position = value * active.length;
}
Binding {
target: positionLabel
property: "text"
value: root.lengthStr(positionSlider.pos * (Players.active?.length ?? 0))
when: positionSlider.dragging
}
}
CustomText {
Layout.preferredWidth: timeMetrics.width
text: root.hasUnknownLength ? "--:--" : root.lengthStr(Players.active?.length ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
}
ButtonRow {
Layout.topMargin: Tokens.spacing.largeIncreased
Layout.fillWidth: true
spacing: Tokens.spacing.extraSmall
Layout.leftMargin: positionSliderLayout.implicitWidth / 4
Layout.rightMargin: positionSliderLayout.implicitWidth / 4
IconButton {
type: IconButton.Tonal
icon: "shuffle"
isRound: true
shapeMorph: true
checked: Players.active?.shuffle ?? false
enabled: Players.active?.shuffleSupported
onClicked: Players.active.shuffle = !Players.active?.shuffle
}
IconButton {
id: previousBtn
type: IconButton.Tonal
icon: "skip_previous"
isRound: true
shapeMorph: true
enabled: Players.active?.canGoPrevious
onClicked: Players.active?.previous()
font.pointSize: Tokens.font.size.large
}
IconButton {
id: playPauseBtn
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
isRound: true
shapeMorph: true
fillWidth: true
checked: Players.active?.isPlaying ?? false
enabled: Players.active?.canTogglePlaying
onClicked: Players.active?.togglePlaying()
font.pointSize: Tokens.font.size.large
}
IconButton {
id: nextBtn
type: IconButton.Tonal
icon: "skip_next"
isRound: true
shapeMorph: true
enabled: Players.active?.canGoNext
onClicked: Players.active?.next()
font.pointSize: Tokens.font.size.large
}
IconButton {
type: IconButton.Tonal
icon: Players.active?.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
isRound: true
shapeMorph: true
checked: Players.active?.loopState === MprisLoopState.Track || Players.active?.loopState === MprisLoopState.Playlist
enabled: Players.active?.loopSupported
onClicked: {
const state = Players.active.loopState;
if (state === MprisLoopState.None)
Players.active.loopState = MprisLoopState.Track;
else if (state === MprisLoopState.Track)
Players.active.loopState = MprisLoopState.Playlist;
else
Players.active.loopState = MprisLoopState.None;
}
}
}
}
}
}
+7 -10
View File
@@ -9,7 +9,7 @@ import qs.Helpers
Row { Row {
id: root id: root
required property PersistentProperties dashState required property ScreenState screenState
padding: 20 padding: 20
spacing: 12 spacing: 12
@@ -23,8 +23,7 @@ Row {
MaterialIcon { MaterialIcon {
anchors.centerIn: parent anchors.centerIn: parent
fill: 1 fill: 1
font.pointSize: Math.floor(info.implicitHeight / 2) || 1 fontStyle: Tokens.font.icon.large
grade: 200
text: "person" text: "person"
} }
@@ -52,7 +51,7 @@ Row {
id: icon id: icon
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2 anchors.leftMargin: (Tokens.sizes.dashboard.infoIconSize - implicitWidth) / 2
color: Colors.palette.m3primary color: Colors.palette.m3primary
implicitSize: Math.floor(13 * 1.34) implicitSize: Math.floor(13 * 1.34)
source: SystemInfo.osLogo source: SystemInfo.osLogo
@@ -65,9 +64,8 @@ Row {
anchors.leftMargin: icon.anchors.leftMargin anchors.leftMargin: icon.anchors.leftMargin
anchors.verticalCenter: icon.verticalCenter anchors.verticalCenter: icon.verticalCenter
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: 13
text: `: ${SystemInfo.osPrettyName || SystemInfo.osName}` text: `: ${SystemInfo.osPrettyName || SystemInfo.osName}`
width: Config.dashboard.sizes.infoWidth width: Tokens.sizes.dashboard.infoWidth
} }
} }
@@ -100,10 +98,10 @@ Row {
id: icon id: icon
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2 anchors.leftMargin: (Tokens.sizes.dashboard.infoIconSize - implicitWidth) / 2
color: line.colour color: line.colour
fill: 1 fill: 1
font.pointSize: 13 fontStyle: Tokens.font.icon.small
text: line.icon text: line.icon
} }
@@ -114,9 +112,8 @@ Row {
anchors.leftMargin: icon.anchors.leftMargin anchors.leftMargin: icon.anchors.leftMargin
anchors.verticalCenter: icon.verticalCenter anchors.verticalCenter: icon.verticalCenter
elide: Text.ElideNone elide: Text.ElideNone
font.pointSize: 13
text: `: ${line.text}` text: `: ${line.text}`
width: Config.dashboard.sizes.infoWidth width: Tokens.sizes.dashboard.infoWidth
} }
} }
} }
+2 -3
View File
@@ -19,7 +19,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
animate: true animate: true
color: Colors.palette.m3secondary color: Colors.palette.m3secondary
font.pointSize: 54 fontStyle: Tokens.font.icon.extraLarge
text: Weather.icon text: Weather.icon
} }
@@ -35,8 +35,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
animate: true animate: true
color: Colors.palette.m3primary color: Colors.palette.m3primary
font.pointSize: Tokens.font.size.extraLarge font: Tokens.font.body.large
font.weight: 500
text: Weather.temp text: Weather.temp
} }
+158
View File
@@ -0,0 +1,158 @@
import QtQuick
import QtQuick.Layouts
import M3Shapes
import ZShell.Config
import qs.Modules.Dashboard.Media
import qs.Components
import qs.Services
import qs.Helpers
Item {
id: root
property ScreenState screenState
required property int radius
implicitWidth: 1000
implicitHeight: 320
CustomClippingRect {
anchors.fill: parent
radius: root.radius
BackgroundShapes {
anchors.fill: parent
}
}
RowLayout {
anchors.fill: parent
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.extraLarge
CoverVisualizer {
Layout.fillHeight: true
implicitWidth: 250
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
state: Players.active ? "" : "noMedia"
states: State {
name: "noMedia"
PropertyChanges {
noMedia.opacity: 1
content.opacity: 0
}
}
transitions: [
Transition {
from: ""
SequentialAnimation {
Anim {
target: content
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
target: noMedia
property: "opacity"
type: Anim.SlowEffects
}
}
},
Transition {
to: ""
SequentialAnimation {
Anim {
target: noMedia
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
target: content
property: "opacity"
type: Anim.SlowEffects
}
}
}
]
Loader {
id: noMedia
anchors.centerIn: parent
anchors.horizontalCenterOffset: -Tokens.padding.extraLarge * 2
asynchronous: true
active: opacity > 0
opacity: 0
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.small
MaterialShape {
Layout.topMargin: (pathBounds().height - implicitSize) / 2
Layout.bottomMargin: (pathBounds().height - implicitSize) / 2 + Tokens.spacing.small
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3primaryContainer
implicitSize: icon.implicitHeight + Tokens.padding.extraLarge * 2
shape: MaterialShape.ClamShell
Behavior on color {
CAnim {}
}
MaterialIcon {
id: icon
anchors.centerIn: parent
text: "queue_music"
fontStyle: Tokens.font.icon.large
color: Colors.palette.m3onPrimaryContainer
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Nothing playing")
font: Tokens.font.body.medium
}
CustomText {
text: qsTr("Play something for it to show up here!")
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.body.large
}
}
}
Loader {
id: content
anchors.fill: parent
asynchronous: true
active: opacity > 0
sourceComponent: RowLayout {
spacing: Tokens.spacing.extraLarge
Details {
Layout.fillWidth: true
}
LyricsAndSelector {
Layout.fillHeight: true
implicitWidth: 300
}
}
}
}
}
}
@@ -11,8 +11,8 @@ Item {
id: cover id: cover
anchors.centerIn: parent anchors.centerIn: parent
implicitHeight: Config.dashboard.sizes.mediaCoverArtSize implicitHeight: 200
implicitWidth: Config.dashboard.sizes.mediaCoverArtSize implicitWidth: 200
shape.shape: MaterialShape.Cookie9Sided shape.shape: MaterialShape.Cookie9Sided
} }
} }
+194
View File
@@ -0,0 +1,194 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.Mpris
import ZShell.Components
import ZShell.Config
import qs.Components
import qs.Services
import qs.Helpers
ColumnLayout {
id: root
readonly property bool hasUnknownLength: (Players.active?.length ?? 0) > 2147483647
function lengthStr(length: int): string {
if (length < 0)
return "-1:-1";
const hours = Math.floor(length / 3600);
const mins = Math.floor((length % 3600) / 60);
const secs = Math.floor(length % 60).toString().padStart(2, "0");
if (hours > 0)
return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
return `${mins}:${secs}`;
}
spacing: Tokens.spacing.extraSmall
Timer {
running: Players.active?.isPlaying ?? false
interval: Config.dashboard.mediaUpdateInterval
triggeredOnStart: true
repeat: true
onTriggered: Players.active?.positionChanged()
}
CustomText {
Layout.fillWidth: true
text: Players.active?.trackTitle ?? ""
font: Tokens.font.title.large
elide: Text.ElideRight
animate: true
}
CustomText {
Layout.fillWidth: true
text: Players.active?.trackArtist || qsTr("Unknown artist")
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.title.medium
elide: Text.ElideRight
animate: true
}
CustomText {
Layout.fillWidth: true
text: Players.active?.trackAlbum || qsTr("Unknown album")
color: Colors.palette.m3secondary
font: Tokens.font.title.medium
elide: Text.ElideRight
animate: true
}
RowLayout {
Layout.topMargin: Tokens.spacing.extraLargeIncreased
Layout.fillWidth: true
spacing: Tokens.spacing.small
TextMetrics {
id: timeMetrics
text: Players.active ? root.lengthStr(Math.max(Players.active.position, root.hasUnknownLength ? 0 : Players.active.length)).replace(/[1-9]/g, "0") : "00:00"
font: Tokens.font.label.medium
}
CustomText {
id: positionLabel
Layout.preferredWidth: timeMetrics.width
text: root.lengthStr(Players.active?.position ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
CustomSlider {
id: positionSlider
Layout.fillWidth: true
value: Players.active ? Players.active.position / (Players.active.length || 1) : 0
enabled: (Players.active?.canSeek ?? false) && !root.hasUnknownLength
wavy: true
animateWave: Players.active?.isPlaying ?? false
waveFrequency: 5
waveDuration: 2000
interactionOnMove: false
onInteraction: value => {
const active = Players.active;
if (active?.canSeek && active?.positionSupported)
active.position = value * active.length;
}
Binding {
target: positionLabel
property: "text"
value: root.lengthStr(positionSlider.pos * (Players.active?.length ?? 0))
when: positionSlider.dragging
}
}
CustomText {
Layout.preferredWidth: timeMetrics.width
text: root.hasUnknownLength ? "--:--" : root.lengthStr(Players.active?.length ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
}
ButtonRow {
Layout.topMargin: Tokens.spacing.largeIncreased
Layout.fillWidth: true
spacing: Tokens.spacing.extraSmall
IconButton {
type: IconButton.Tonal
icon: "shuffle"
isRound: true
shapeMorph: true
checked: Players.active?.shuffle ?? false
font: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
enabled: Players.active?.shuffleSupported
onClicked: Players.active.shuffle = !Players.active?.shuffle
implicitWidth: Math.round(implicitHeight * 0.9)
}
IconButton {
id: previousBtn
type: IconButton.Tonal
icon: "skip_previous"
isRound: true
shapeMorph: true
font: Tokens.font.icon.large
enabled: Players.active?.canGoPrevious
onClicked: Players.active?.previous()
}
IconButton {
id: playPauseBtn
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
isRound: true
shapeMorph: true
fillWidth: true
checked: Players.active?.isPlaying ?? false
font: Tokens.font.icon.large
enabled: Players.active?.canTogglePlaying
onClicked: Players.active?.togglePlaying()
}
IconButton {
id: nextBtn
type: IconButton.Tonal
icon: "skip_next"
isRound: true
shapeMorph: true
font: Tokens.font.icon.large
enabled: Players.active?.canGoNext
onClicked: Players.active?.next()
}
IconButton {
type: IconButton.Tonal
icon: Players.active?.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
isRound: true
shapeMorph: true
checked: Players.active?.loopState === MprisLoopState.Track || Players.active?.loopState === MprisLoopState.Playlist
font: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
enabled: Players.active?.loopSupported
onClicked: {
const state = Players.active.loopState;
if (state === MprisLoopState.None)
Players.active.loopState = MprisLoopState.Track;
else if (state === MprisLoopState.Track)
Players.active.loopState = MprisLoopState.Playlist;
else
Players.active.loopState = MprisLoopState.None;
}
implicitWidth: Math.round(implicitHeight * 0.9)
}
}
}
+317
View File
@@ -0,0 +1,317 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Effects
import qs.Services
import qs.Helpers
Item {
id: root
// Funny binding hack to make lyrics update
readonly property var _: {
const p = Players.active;
if (p)
Lyrics.setTrack(p.trackArtist, p.trackTitle, p.trackAlbum, p.length);
else
Lyrics.clearTrack();
}
readonly property real fadeAmount: 0.1
property list<string> lyricList: Lyrics.lyrics
layer.enabled: true
layer.effect: Mask {
maskSource: mask
Rectangle {
id: mask
layer.enabled: true
visible: false
implicitWidth: root.width
implicitHeight: root.height
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop {
color: Qt.alpha("black", 0)
position: 0
}
GradientStop {
color: Qt.alpha("black", 1)
position: root.fadeAmount
}
GradientStop {
color: Qt.alpha("black", 1)
position: 1 - root.fadeAmount
}
GradientStop {
color: Qt.alpha("black", 0)
position: 1
}
}
}
}
state: {
if (Lyrics.hasLyrics)
return "hasLyrics";
if (Lyrics.loading)
return "loading";
return "noLyrics";
}
states: [
State {
name: "loading"
PropertyChanges {
loadingIndicator.opacity: 1
lyrics.opacity: 0
noLyrics.opacity: 0
}
},
State {
name: "hasLyrics"
PropertyChanges {
loadingIndicator.opacity: 0
lyrics.opacity: 1
noLyrics.opacity: 0
}
},
State {
name: "noLyrics"
PropertyChanges {
loadingIndicator.opacity: 0
lyrics.opacity: 0
noLyrics.opacity: 1
}
}
]
transitions: [
Transition {
from: "loading"
SequentialAnimation {
Anim {
target: loadingIndicator
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
targets: [lyrics, noLyrics]
property: "opacity"
type: Anim.SlowEffects
}
}
},
Transition {
from: "hasLyrics"
SequentialAnimation {
Anim {
target: lyrics
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
targets: [loadingIndicator, noLyrics]
property: "opacity"
type: Anim.SlowEffects
}
}
},
Transition {
from: "noLyrics"
SequentialAnimation {
Anim {
target: noLyrics
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
targets: [loadingIndicator, lyrics]
property: "opacity"
type: Anim.SlowEffects
}
}
}
]
Loader {
id: loadingIndicator
anchors.centerIn: parent
asynchronous: true
active: opacity > 0
opacity: 0
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.large
CustomRect {
Layout.alignment: Qt.AlignHCenter
implicitWidth: shape.implicitSize + Tokens.padding.medium * 2
implicitHeight: shape.implicitSize + Tokens.padding.medium * 2
color: Colors.palette.m3primaryContainer
radius: Tokens.rounding.full
LoadingIndicator {
id: shape
anchors.centerIn: parent
implicitSize: Math.round(300 / 5)
containsIcon: true // This removes the pentagon, which is not centered
}
}
CustomText {
text: qsTr("Loading lyrics...")
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.body.medium
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Loader {
id: noLyrics
anchors.centerIn: parent
asynchronous: true
active: opacity > 0
opacity: 0
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: "sentiment_sad"
fontStyle: Tokens.font.icon.extraLarge
color: Colors.palette.m3outline
}
CustomText {
text: qsTr("No lyrics found")
color: Colors.palette.m3outline
font: Tokens.font.body.medium
}
}
}
CustomListView {
id: lyrics
anchors.fill: parent
anchors.topMargin: parent.height * root.fadeAmount / 2
anchors.bottomMargin: parent.height * root.fadeAmount / 2
displayMarginBeginning: anchors.topMargin
displayMarginEnd: anchors.bottomMargin
model: root.lyricList
Component.onCompleted: {
currentIndex = Qt.binding(() => {
model; // Force update when lyrics change
return Lyrics.indexForTime(Players.active?.position ?? 0);
});
positionViewAtIndex(currentIndex, ListView.Center);
}
onModelChanged: Qt.callLater(() => positionViewAtIndex(currentIndex, ListView.Center))
highlightRangeMode: ListView.ApplyRange
highlightMoveDuration: Tokens.anim.durations.large
highlightMoveVelocity: -1
preferredHighlightBegin: (height - (currentItem?.implicitHeight ?? 0)) / 2
preferredHighlightEnd: (height + (currentItem?.implicitHeight ?? 0)) / 2
spacing: Tokens.spacing.small
opacity: 0
enabled: opacity > 0
delegate: CustomText {
id: lyric
required property string modelData
required property int index
property real effectScale: ListView.isCurrentItem ? 1 : 0
anchors.left: lyrics.contentItem.left
anchors.right: lyrics.contentItem.right
text: modelData || ". . ."
color: ListView.isCurrentItem ? Colors.palette.m3primary : mouse.containsMouse ? Colors.palette.m3onSurface : Colors.palette.m3outline
font: Tokens.font.body.medium
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
layer.enabled: effectScale > 0
layer.effect: MultiEffect {
shadowEnabled: true
shadowColor: Colors.palette.m3primary
shadowOpacity: 0.5 * lyric.effectScale
shadowBlur: 0.6 * lyric.effectScale
blur: 0.4 * lyric.effectScale
}
Behavior on effectScale {
Anim {
type: Anim.SlowEffects
}
}
MouseArea {
id: mouse
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
const p = Players.active;
if (p)
p.position = Lyrics.timeForIndex(lyric.index);
}
}
}
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
Behavior on lyricList {
SequentialAnimation {
Anim {
target: lyrics
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
PropertyAction {}
Anim {
target: lyrics
property: "opacity"
to: 1
type: Anim.SlowEffects
}
}
}
}
@@ -0,0 +1,79 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Mpris
import ZShell.Config
import qs.Components
import qs.Helpers
import qs.Services
Item {
ColumnLayout {
id: layout
anchors.fill: parent
anchors.leftMargin: Tokens.padding.medium
spacing: Tokens.spacing.medium
RowLayout {
Layout.bottomMargin: -Tokens.spacing.medium
spacing: Tokens.spacing.medium
z: 1
MaterialIcon {
Layout.topMargin: Math.round(fontInfo.pointSize * 0.12)
text: "lyrics"
fontStyle: Tokens.font.icon.large
}
CustomText {
Layout.fillWidth: true
text: qsTr("Lyrics")
font: Tokens.font.body.large
}
LyricsInfo {}
}
LyricList {
Layout.fillWidth: true
Layout.fillHeight: true
}
SplitButton {
Layout.alignment: Qt.AlignHCenter
type: SplitButton.Tonal
enabled: Players.list.length
active: menuItems.find(m => m.modelData === Players.active) ?? menuItems[0] ?? null
menu.onItemSelected: item => Players.manualActive = (item as PlayerItem).modelData
menuItems: playerList.instances
fallbackIcon: "music_off"
fallbackText: qsTr("No players", "no media players active")
minLeftWidth: layout.width - expandBtn.implicitWidth - spacing
label.Layout.maximumWidth: minLeftWidth - iconLabel.implicitWidth - textRow.spacing - textRow.anchors.horizontalCenterOffset / 2 - horizontalPadding * 2
label.elide: Text.ElideRight
stateLayer.enabled: true
menuOnTop: true
Variants {
id: playerList
model: Players.list
PlayerItem {}
}
}
}
component PlayerItem: MenuItem {
required property MprisPlayer modelData
icon: modelData === Players.active ? "check" : ""
text: Players.getIdentity(modelData)
activeIcon: "animated_images"
}
}
+212
View File
@@ -0,0 +1,212 @@
import QtQuick
import QtQuick.Layouts
import ZShell
import ZShell.Blobs
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Services
Item {
id: root
property bool open
readonly property real padding: Tokens.padding.large
implicitWidth: btn.implicitWidth * 0.9
implicitHeight: btn.implicitHeight * 0.9
BlobGroup {
id: blobGroup
color: Colors.palette.m3surfaceContainerHighest
smoothing: Tokens.rounding.medium
cornerFill: false
Behavior on color {
CAnim {}
}
}
BlobRect {
id: btnRect
anchors.fill: parent
anchors.margins: !btn.pressed && btn.containsMouse ? -Tokens.padding.extraSmall : 0
group: blobGroup
radius: Tokens.rounding.medium
Behavior on anchors.margins {
Anim {}
}
}
BlobRect {
id: rect
anchors.right: parent.right
anchors.top: parent.top
implicitWidth: parent.width
implicitHeight: parent.height
group: blobGroup
radius: Tokens.rounding.medium
deformScale: 0.00001
states: State {
name: "open"
when: root.open
PropertyChanges {
rect.anchors.rightMargin: root.width - Tokens.spacing.small
rect.anchors.topMargin: -Tokens.padding.medium
rect.implicitWidth: Math.max(layout.implicitWidth, placeholder.implicitWidth) + root.padding * 2
rect.implicitHeight: Math.max(layout.implicitHeight, placeholder.implicitHeight) + root.padding * 2
content.opacity: 1
}
}
transitions: Transition {
Anim {
properties: "rightMargin,implicitWidth"
}
Anim {
properties: "topMargin,implicitHeight"
easing: Tokens.anim.expressiveFastSpatial
}
Anim {
property: "opacity"
type: Anim.DefaultEffects
}
}
Behavior on implicitWidth {
Anim {}
}
Behavior on implicitHeight {
Anim {}
}
Item {
id: content
anchors.fill: parent
clip: true
opacity: 0
state: Lyrics.loading || !Lyrics.hasLyrics ? "" : "hasLyrics"
states: State {
name: "hasLyrics"
PropertyChanges {
layout.opacity: 1
placeholder.opacity: 0
}
}
transitions: [
Transition {
from: "hasLyrics"
SequentialAnimation {
Anim {
target: layout
property: "opacity"
type: Anim.FastEffects
}
Anim {
target: placeholder
property: "opacity"
type: Anim.DefaultEffects
}
}
},
Transition {
to: "hasLyrics"
SequentialAnimation {
Anim {
target: placeholder
property: "opacity"
type: Anim.FastEffects
}
Anim {
target: layout
property: "opacity"
type: Anim.DefaultEffects
}
}
}
]
ColumnLayout {
id: layout
anchors.centerIn: parent
spacing: Tokens.spacing.extraSmall
opacity: 0
CustomText {
// TRANSLATORS: %1 = lyrics backend name, e.g. LRCLIB
text: qsTr("Backend: %1").arg(ZUtils.enumToString(Lyrics, "backend"))
color: Colors.palette.m3onSurfaceVariant
animate: true
}
CustomText {
Layout.maximumWidth: 1000 / 2
// TRANSLATORS: %1/%2/%3 = matched track title, artist and album
text: qsTr("Selected candidate: %1 | %2 | %3").arg(Lyrics.selectedCandidate.title).arg(Lyrics.selectedCandidate.artist).arg(Lyrics.selectedCandidate.album)
color: Colors.palette.m3onSurfaceVariant
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
animate: true
}
CustomText {
// TRANSLATORS: %1 = lyric timing offset; ms is the millisecond unit, leave untranslated
text: qsTr("Offset: %1 ms").arg(Lyrics.offset)
color: Colors.palette.m3onSurfaceVariant
animate: true
}
}
Item {
id: placeholder
anchors.centerIn: parent
implicitWidth: placeholderText.implicitWidth
implicitHeight: placeholderText.implicitHeight
CustomText {
id: placeholderText
text: Lyrics.loading ? qsTr("Loading...") : qsTr("No lyrics found")
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.body.medium
animate: true
}
}
}
}
MouseArea {
id: btn
anchors.centerIn: parent
implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + Tokens.padding.extraSmall * 2
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: root.open = !root.open
MaterialIcon {
id: icon
anchors.centerIn: parent
text: "more_vert"
fontStyle: Tokens.font.icon.medium
}
}
}
@@ -0,0 +1,220 @@
pragma Singleton
import QtQuick
import Quickshell
import ZShell.Config
import ZShell.Services
Singleton {
id: root
readonly property var columnDefs: [
{
id: "command",
title: qsTr("Command"),
width: 200,
field: 3
},
{
id: "pid",
title: qsTr("PID"),
width: 60,
field: 1
},
{
id: "cpuUsage",
title: qsTr("CPU %"),
width: 70,
field: 7
},
{
id: "memoryUsage",
title: qsTr("Memory"),
width: 90,
field: 8
},
{
id: "elapsed",
title: qsTr("Elapsed"),
width: 80,
field: 9
},
{
id: "ppid",
title: qsTr("PPID"),
width: 60,
field: 2
},
{
id: "user",
title: qsTr("User"),
width: 100,
field: 11
},
{
id: "uid",
title: qsTr("UID"),
width: 60,
field: 10
},
{
id: "state",
title: qsTr("State"),
width: 60,
field: 5
},
{
id: "sleeping",
title: qsTr("Sleeping"),
width: 80,
field: 6
},
{
id: "commandLine",
title: qsTr("Command line"),
width: 240,
field: 4
},
{
id: "ioReadBytes",
title: qsTr("IO read"),
width: 90,
field: 12
},
{
id: "ioWriteBytes",
title: qsTr("IO write"),
width: 90,
field: 13
}
]
readonly property var desktopEntries: DesktopEntries.applications.values.map(e => ({
icon: e.icon,
command: e.command
}))
Component.onCompleted: {
Processes.setDesktopEntries(root.desktopEntries);
Qt.callLater(ensurePinnedFirst);
}
onDesktopEntriesChanged: Processes.setDesktopEntries(root.desktopEntries)
readonly property string pinnedColumnId: "command"
readonly property var hiddenColumns: {
const visible = Config.dashboard.processColumns.values.map(e => e.id);
return root.columnDefs.filter(d => !visible.includes(d.id));
}
function pinnedIndex(): int {
const values = Config.dashboard.processColumns.values;
for (let i = 0; i < values.length; i++)
if (values[i].id === root.pinnedColumnId)
return i;
return -1;
}
function ensurePinnedFirst() {
const i = root.pinnedIndex();
if (i > 0)
Qt.callLater(() => {
const idx = root.pinnedIndex();
if (idx > 0)
Config.dashboard.processColumns.move(idx, 0);
});
}
function defFor(id: string): var {
for (const def of root.columnDefs)
if (def.id === id)
return def;
return null;
}
function addColumn(id: string) {
const def = root.defFor(id);
Config.dashboard.processColumns.insert({
id: id,
width: def ? def.width : 100
});
}
function removeColumn(index: int) {
const entry = Config.dashboard.processColumns.at(index);
if (entry && entry.id === root.pinnedColumnId)
return;
Config.dashboard.processColumns.remove(index);
}
function moveColumns(from: int, to: int) {
const list = Config.dashboard.processColumns;
const entry = list.at(from);
if (!entry)
return;
if (entry.id === root.pinnedColumnId)
return;
if (to === 0 && list.at(0)?.id === root.pinnedColumnId)
return;
list.move(from, to);
const i = root.pinnedIndex();
if (i > 0)
list.move(i, 0);
}
function resizeColumn(index: int, width: real) {
const entry = Config.dashboard.processColumns.at(index);
if (entry)
entry.width = Math.max(50, width);
}
function dampOvershoot(overshoot: real, maxOvershoot: real): real {
return overshoot / (1 + overshoot / maxOvershoot);
}
function formatCell(id: string, value: var): string {
switch (id) {
case "cpuUsage":
return value.toFixed(1) + "%";
case "memoryUsage":
return root.formatBytes(value * 1024);
case "ioReadBytes":
case "ioWriteBytes":
return root.formatBytes(value);
case "elapsed":
return root.formatElapsed(value);
case "sleeping":
return value ? qsTr("Yes") : qsTr("No");
default:
return String(value);
}
}
function formatBytes(bytes: var): string {
if (bytes === undefined || bytes === null || bytes <= 0)
return "0 B";
const units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
let v = Number(bytes);
let i = 0;
while (v >= 1024 && i < units.length - 1) {
v /= 1024;
++i;
}
return (i === 0 ? v.toFixed(0) : v.toFixed(1)) + " " + units[i];
}
function formatElapsed(secs: var): string {
const s = Math.max(0, Math.floor(Number(secs)));
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const ss = s % 60;
if (h > 0)
return h + "h " + m + "m";
if (m > 0)
return m + "m " + ss + "s";
return ss + "s";
}
}
@@ -0,0 +1,665 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Config
import ZShell.Internal
import ZShell.Services
import qs.Components
import qs.Services
Item {
id: root
required property int headerHeight
required property ProcessesList pList
readonly property alias columnList: columnList
readonly property alias columnsModel: columnsData
readonly property bool anyHeld: columnList.contentItem.children.some(c => c.held)
z: 1
implicitHeight: headerHeight
function widthForColumn(id: string, configured: real): real {
for (const item of columnList.contentItem.children) {
if (item.modelData !== undefined && item.modelData.id === id)
return item.liveWidth > 0 ? item.liveWidth : (configured > 0 ? configured : (ProcessesColumns.defFor(id)?.width ?? 100));
}
return configured > 0 ? configured : (ProcessesColumns.defFor(id)?.width ?? 100);
}
function fillMissingWidths() {
const n = columnsData.values.length;
if (n === 0 || columnList.width <= 0)
return;
const spacingTotal = columnList.spacing * Math.max(0, n - 1);
let savedTotal = 0;
const unsavedIndices = [];
for (let i = 0; i < n; i++) {
const w = columnsData.values[i].width;
if (w > 0)
savedTotal += w;
else
unsavedIndices.push(i);
}
if (unsavedIndices.length === 0)
return;
const available = columnList.width - spacingTotal - savedTotal;
if (available <= 0)
return;
const each = Math.max(50, available / unsavedIndices.length);
for (const idx of unsavedIndices)
ProcessesColumns.resizeColumn(idx, each);
}
Connections {
target: columnList
function onWidthChanged() {
root.fillMissingWidths();
}
function onCountChanged() {
resizeOverlay.geometryTick++;
root.fillMissingWidths();
}
function onContentWidthChanged() {
resizeOverlay.geometryTick++;
}
}
Connections {
target: columnsData
function onValuesChanged() {
root.fillMissingWidths();
resizeOverlay.geometryTick++;
}
}
Component.onCompleted: Qt.callLater(fillMissingWidths)
Component.onDestruction: Cursor.clearOverride()
CustomListView {
id: columnList
anchors.fill: parent
orientation: ListView.Horizontal
interactive: false
spacing: Tokens.spacing.extraSmall / 2
add: Transition {
Anim {
from: 0
properties: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
displaced: Transition {
Anim {
properties: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
properties: "x"
}
}
move: Transition {
Anim {
properties: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
properties: "x"
}
}
remove: Transition {
Anim {
to: 0
properties: "opacity"
type: Anim.DefaultEffects
}
}
model: DelegateModel {
id: columnsVisual
delegate: ColumnHeaderCell {
parentModel: columnsVisual
}
model: ScriptModel {
id: columnsData
values: Config.dashboard.processColumns.values
}
}
}
Item {
id: resizeOverlay
property real gapBoost: pressed ? Tokens.spacing.small : Tokens.spacing.extraSmall
property bool pressed: false
property int geometryTick: 0
property int activeGapIndex: -1
anchors.fill: columnList
z: 10
Component.onCompleted: Qt.callLater(() => geometryTick++)
Repeater {
model: Math.max(0, columnsData.values.length - 1)
MouseArea {
id: handleRoot
required property int index
readonly property bool hovered: containsMouse
property ColumnHeaderCell leftCell: {
const left = leftCell;
if (resizing && left)
return left;
const tick = resizeOverlay.geometryTick;
return resolveCell(true);
}
property ColumnHeaderCell rightCell: {
const right = rightCell;
if (resizing && right)
return right;
const tick = resizeOverlay.geometryTick;
return resolveCell(false);
}
property bool resizing: false
property real originX: 0
property real originWidth: 0
property real originRightWidth: 0
property real originAbsX: 0
function resolveCell(left: bool): ColumnHeaderCell {
if (left) { // qmllint disable incompatible-type
const leftItem = columnList.itemAtIndex(index);
if (leftItem)
return leftItem;
else
return null;
} else {
const rightItem = columnList.itemAtIndex(index + 1);
if (rightItem)
return rightItem;
else
return null;
}
}
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
preventStealing: true
hoverEnabled: true
enabled: !root.anyHeld
x: {
if (!leftCell)
return 0;
const gapCenter = leftCell.x + leftCell.width + columnList.spacing / 2;
return gapCenter - width / 2;
}
y: 0
implicitWidth: 16
implicitHeight: columnList.height
function pairAvailableWidth(rightCell) {
let othersWidth = 0;
for (const item of columnList.contentItem.children) {
if (item.modelData === undefined || item === handleRoot.leftCell || item === rightCell)
continue;
othersWidth += item.width;
}
const spacingTotal = columnList.spacing * Math.max(0, columnsData.values.length - 1);
return columnList.width - othersWidth - spacingTotal;
}
CustomRect {
id: handle
readonly property bool shouldBeVisible: handleRoot.hovered || handleRoot.pressed
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: shouldBeVisible ? resizeOverlay.gapBoost : 0
color: Colors.palette.m3secondary
radius: Tokens.rounding.full
opacity: shouldBeVisible ? 1.0 : 0
Behavior on implicitWidth {
Anim {
type: Anim.FastSpatial
}
}
Behavior on opacity {
Anim {}
}
transform: Scale {
origin {
x: handle.implicitWidth / 2
y: handle.height / 2
}
yScale: handle.shouldBeVisible ? 1 : 0.5
xScale: handle.shouldBeVisible ? 1 : 0.5
Behavior on xScale {
Anim {}
}
Behavior on yScale {
Anim {}
}
}
}
onHoveredChanged: resizeOverlay.activeGapIndex = hovered || pressed ? handleRoot.index : (resizeOverlay.activeGapIndex === handleRoot.index ? -1 : resizeOverlay.activeGapIndex)
onPressedChanged: {
resizeOverlay.pressed = pressed;
if (!pressed)
resizeOverlay.activeGapIndex = hovered ? handleRoot.index : resizeOverlay.activeGapIndex === handleRoot.index ? -1 : resizeOverlay.activeGapIndex;
}
onPressed: e => {
if (!handleRoot.leftCell)
return;
Cursor.setOverride(Qt.ClosedHandCursor);
resizing = true;
originAbsX = handleRoot.mapToItem(resizeOverlay, e.x, 0).x;
originWidth = handleRoot.leftCell.implicitWidth;
originRightWidth = handleRoot.rightCell ? handleRoot.rightCell.implicitWidth : 0;
}
onPositionChanged: e => {
if (!resizing || !handleRoot.leftCell)
return;
const nowAbsX = handleRoot.mapToItem(resizeOverlay, e.x, 0).x;
let newLeftWidth = Math.max(50, originWidth + (nowAbsX - originAbsX));
const rightCell = handleRoot.rightCell;
let roundedLeft = Math.round(newLeftWidth);
if (rightCell) {
const available = Math.round(handleRoot.pairAvailableWidth(rightCell));
const maxLeftWidth = Math.max(50, available - 50);
roundedLeft = Math.min(roundedLeft, maxLeftWidth);
rightCell.liveWidth = Math.max(50, available - roundedLeft);
}
handleRoot.leftCell.liveWidth = roundedLeft;
}
onReleased: {
Cursor.clearOverride();
resizing = false;
const cell = handleRoot.leftCell;
const rightCell = handleRoot.rightCell;
if (cell.liveWidth !== 0)
ProcessesColumns.resizeColumn(cell.DelegateModel.itemsIndex, cell.liveWidth);
if (rightCell.liveWidth !== 0)
ProcessesColumns.resizeColumn(rightCell.DelegateModel.itemsIndex, rightCell.liveWidth);
if (cell || rightCell) {
cell.liveWidth = 0;
rightCell.liveWidth = 0;
}
resizeOverlay.geometryTick++;
}
}
}
}
component ColumnHeaderCell: Item {
id: cell
required property var modelData
required property int index
required property DelegateModel parentModel
property int leftRadius: pushSide !== 1 && DelegateModel.itemsIndex === 0 ? cell.implicitHeight / 2 : Tokens.rounding.extraSmall
property int rightRadius: pushSide !== -1 && DelegateModel.itemsIndex === parentModel.count - 1 ? cell.implicitHeight / 2 : Tokens.rounding.extraSmall
property real radiusLerpProg
property bool held
property bool moved: false
property int pressIndex: -1
property point pressPos
property real lastMoveX: 0
property real liveWidth: 0
readonly property bool isActiveSort: Processes.sortField === (ProcessesColumns.defFor(cell.modelData.id)?.field ?? 0)
readonly property int pushSide: {
const gapIdx = resizeOverlay.activeGapIndex;
if (gapIdx < 0)
return 0;
const myIdx = cell.DelegateModel.itemsIndex;
if (myIdx === gapIdx)
return -1;
if (myIdx === gapIdx + 1)
return 1;
return 0;
}
property real gapBoost: resizeOverlay.gapBoost
readonly property real gapShrink: gapBoost / 2
property real gapProg: pushSide !== 0 ? 1 : 0
property real gapX: pushSide === 1 ? 1 : 0
function lerpRadius(a: real, b: real): real {
return a + (b - a) * radiusLerpProg;
}
Behavior on gapBoost {
Anim {
type: Anim.FastSpatial
}
}
Behavior on gapX {
Anim {
type: Anim.FastSpatial
}
}
Behavior on gapProg {
Anim {
type: Anim.FastSpatial
}
}
implicitHeight: root.headerHeight
implicitWidth: liveWidth > 0 ? liveWidth : (modelData.width > 0 ? modelData.width : (ProcessesColumns.defFor(modelData.id)?.width ?? 100))
z: held || returnAnim.running ? 1 : 0
states: State {
name: "held"
PropertyChanges {
cellBg.color: Colors.palette.m3primaryContainer
elevation.opacity: 1
placeholder.opacity: 0.1
cell.radiusLerpProg: 1
title.color: Colors.palette.m3onPrimaryContainer
removeBtn.inactiveOnColor: Colors.palette.m3onPrimaryContainer
}
}
Behavior on leftRadius {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on rightRadius {
Anim {
type: Anim.DefaultEffects
}
}
transitions: Transition {
Anim {
properties: "opacity,radiusLerpProg"
type: Anim.SlowEffects
}
PropertyAction {
properties: "color,inactiveOnColor"
}
}
state: held ? "held" : ""
CustomRect {
id: placeholder
anchors.fill: parent
color: Colors.palette.m3primary
opacity: 0
topLeftRadius: cell.leftRadius
bottomLeftRadius: cell.leftRadius
topRightRadius: cell.rightRadius
bottomRightRadius: cell.rightRadius
}
DragHandler {
id: dragHandler
property real centerOffset: 0
enabled: cell.modelData.id !== ProcessesColumns.pinnedColumnId
target: null
xAxis.enabled: true
dragThreshold: 10
grabPermissions: PointerHandler.CanTakeOverFromItems
onActiveChanged: {
if (active) {
Cursor.setOverride(Qt.ClosedHandCursor);
returnAnim.stop();
cell.pressIndex = cell.DelegateModel.itemsIndex;
cell.lastMoveX = cell.x;
cell.moved = true;
cell.held = true;
const p = centroid.position;
cell.pressPos = Qt.point(p.x, p.y);
dragHandler.centerOffset = p.x - cell.width / 2;
cellContent.x = Qt.binding(() => {
const maxOvershoot = Tokens.padding.extraLarge;
const xDiff = cell.x - cell.lastMoveX;
const x = dragHandler.centroid.position.x - cell.pressPos.x - xDiff;
const absX = cell.mapToItem(columnList.contentItem, x, 0).x;
const maxX = columnList.contentWidth - cell.width;
if (absX < 0)
return x - absX - ProcessesColumns.dampOvershoot(-absX, maxOvershoot);
if (absX > maxX)
return x - absX + maxX + ProcessesColumns.dampOvershoot(absX - maxX, maxOvershoot);
return x;
});
cellContent.y = Qt.binding(() => {
const maxOvershoot = Tokens.padding.extraLargeIncreased;
const y = dragHandler.centroid.position.y - cell.pressPos.y;
return ProcessesColumns.dampOvershoot(Math.abs(y), maxOvershoot) * Math.sign(y);
});
} else {
Cursor.clearOverride();
if (cell.held) {
cellContent.x = cellContent.x;
cellContent.y = cellContent.y;
cell.held = false;
returnAnim.start();
if (cell.DelegateModel.itemsIndex !== cell.pressIndex) {
ProcessesColumns.moveColumns(cell.pressIndex, cell.DelegateModel.itemsIndex);
}
}
}
}
onCentroidChanged: {
if (!active)
return;
const p = centroid.position;
cell.lastMoveX = cell.x;
const cursorAbsX = cell.mapToItem(columnList.contentItem, p.x, 0).x;
const centerAbsX = cursorAbsX - dragHandler.centerOffset;
const movingRight = p.x > cell.pressPos.x;
const grabbedRight = dragHandler.centerOffset > 0;
const useCursor = movingRight === grabbedRight;
const refX = useCursor ? cursorAbsX : centerAbsX;
const myIndex = cell.DelegateModel.itemsIndex;
const rightItem = columnList.itemAtIndex(myIndex + 1);
if (rightItem) {
const rightMid = rightItem.x + rightItem.width / 2;
if (refX > rightMid) {
columnsVisual.items.move(myIndex, myIndex + 1);
return;
}
}
const leftItem = columnList.itemAtIndex(myIndex - 1);
if (leftItem) {
const leftMid = leftItem.x + leftItem.width / 2;
const pinnedAtZero = columnList.itemAtIndex(0)?.modelData?.id === ProcessesColumns.pinnedColumnId;
if (refX < leftMid && !(myIndex === 1 && pinnedAtZero)) {
columnsVisual.items.move(myIndex, myIndex - 1);
}
}
}
}
Anim {
id: returnAnim
properties: "x,y"
target: cellContent
to: 0
}
Item {
id: cellVisual
x: cell.gapShrink * cell.gapX
y: 0
width: cell.width - cell.gapShrink * cell.gapProg
height: cell.height
Item {
id: cellContent
width: cellVisual.width
height: cellVisual.height
readonly property int topLeftRadius: cell.lerpRadius(cell.leftRadius, cell.implicitHeight / 2)
readonly property int topRightRadius: cell.lerpRadius(cell.rightRadius, cell.implicitHeight / 2)
readonly property int bottomLeftRadius: cell.lerpRadius(cell.leftRadius, cell.implicitHeight / 2)
readonly property int bottomRightRadius: cell.lerpRadius(cell.rightRadius, cell.implicitHeight / 2)
Elevation {
id: elevation
anchors.fill: parent
level: 3
opacity: 0
topLeftRadius: parent.topLeftRadius
topRightRadius: parent.topRightRadius
bottomLeftRadius: parent.bottomLeftRadius
bottomRightRadius: parent.bottomRightRadius
}
CustomRect {
id: cellBg
anchors.fill: parent
color: cell.isActiveSort ? Colors.palette.m3primary : Colors.palette.m3surfaceContainer
topLeftRadius: cell.lerpRadius(cell.leftRadius, cell.implicitHeight / 2)
topRightRadius: cell.lerpRadius(cell.rightRadius, cell.implicitHeight / 2)
bottomLeftRadius: cell.lerpRadius(cell.leftRadius, cell.implicitHeight / 2)
bottomRightRadius: cell.lerpRadius(cell.rightRadius, cell.implicitHeight / 2)
StateLayer {
id: layer
color: cell.isActiveSort ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
manualHoverOverride: cell.held
onClicked: {
Processes.sortBy(ProcessesColumns.defFor(cell.modelData.id)?.field ?? 0);
root.pList.positionViewAtBeginning();
}
}
}
Item {
anchors.fill: parent
anchors.leftMargin: Tokens.padding.medium
anchors.rightMargin: 12
CustomText {
id: title
anchors.left: parent.left
anchors.right: removeBtn.visible ? removeBtn.left : parent.right
anchors.rightMargin: Tokens.spacing.extraSmall
anchors.verticalCenter: parent.verticalCenter
color: cell.isActiveSort ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
elide: CustomText.ElideRight
text: ProcessesColumns.defFor(cell.modelData.id)?.title ?? cell.modelData.id
}
IconButton {
id: removeBtn
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
type: IconButton.Text
icon: "close"
font: Tokens.font.icon.small
inactiveOnColor: Colors.palette.m3onPrimary
visible: cell.modelData.id !== ProcessesColumns.pinnedColumnId
onClicked: ProcessesColumns.removeColumn(cell.DelegateModel.itemsIndex)
}
MaterialIcon {
anchors.right: removeBtn.visible ? removeBtn.left : parent.right
anchors.verticalCenter: parent.verticalCenter
text: "keyboard_arrow_down"
opacity: cell.isActiveSort ? 1 : 0
visible: opacity > 0
rotation: !Processes.sortAscending && opacity > 0 ? 180 : 0
color: Colors.palette.m3onPrimary
Behavior on rotation {
Anim {}
}
Behavior on opacity {
Anim {}
}
}
}
}
}
}
component HiddenColumnItem: MenuItem {
required property var modelData
text: modelData.title
}
}
@@ -0,0 +1,140 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Services
CustomListView {
id: root
required property ProcessesHeader columnHeader
required property int rowHeight
property real rowSpacing: Tokens.spacing.small
model: Processes.list
spacing: root.rowSpacing
clip: true
CustomScrollBar.vertical: CustomScrollBar {
flickable: root
}
delegate: CustomRect {
id: processRow
required property var modelData
required property int index
implicitWidth: ListView.view.width
implicitHeight: rowLayout.implicitHeight
radius: Tokens.rounding.full
StateLayer {
id: rowHover
}
RowLayout {
id: rowLayout
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
spacing: Tokens.spacing.extraSmall / 2
CustomClippingRect {
id: iconClip
Layout.fillHeight: true
Layout.margins: Tokens.padding.medium
implicitWidth: height
radius: Tokens.rounding.full
IconImage {
id: appIcon
anchors.fill: parent
implicitSize: height
source: Quickshell.iconPath(processRow.modelData.iconName, "application-x-executable")
}
}
Repeater {
id: cellRepeater
model: root.columnHeader.columnsModel
Item {
id: rowCell
required property var modelData
required property int index
implicitWidth: root.columnHeader.widthForColumn(modelData.id, modelData.width) - (index === 0 ? iconClip.implicitWidth + iconClip.Layout.margins * 2 + rowLayout.spacing : 0)
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
Layout.preferredWidth: implicitWidth
Layout.fillHeight: true
CustomRect {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Tokens.padding.small
anchors.leftMargin: -(rowLayout.spacing)
implicitWidth: 2
color: Colors.palette.m3outlineVariant
visible: rowCell.index !== 0
}
Item {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.small
anchors.leftMargin: rowCell.index !== 0 ? Tokens.padding.medium : 0
anchors.rightMargin: Tokens.padding.medium
implicitHeight: mainText.implicitHeight + subText.implicitHeight + mainText.anchors.margins * 2 + subText.anchors.topMargin
CustomText {
id: mainText
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
elide: CustomText.ElideRight
text: {
const value = processRow.modelData[rowCell.modelData.id];
return value === undefined ? "" : ProcessesColumns.formatCell(rowCell.modelData.id, value);
}
states: State {
name: "centerAnchored"
when: !subText.visible
AnchorChanges {
target: mainText
anchors.top: undefined
anchors.verticalCenter: layout.verticalCenter
}
}
}
CustomText {
id: subText
anchors.top: mainText.bottom
anchors.left: parent.left
anchors.right: parent.right
visible: rowCell.modelData.id === "command"
elide: CustomText.ElideRight
text: rowCell.modelData.id === "command" ? processRow.modelData["commandLine"] : ""
font: Tokens.font.label.small
color: Colors.palette.m3outline
}
}
}
}
}
}
}
@@ -0,0 +1,86 @@
pragma Singleton
import QtQuick
import Quickshell
import ZShell.Internal
import ZShell.Services
Singleton {
id: root
readonly property int historyLength: 30
readonly property CircularBuffer cpuBuffer: _cpuBuffer
readonly property CircularBuffer memoryBuffer: _memoryBuffer
readonly property list<var> cpuInfo: [
{
label: qsTr("Usage"),
value: `${Math.round(Cpu.percentage * 100)}%`
},
{
label: qsTr("Temp"),
value: `${Math.ceil(Cpu.temperature)}°${"C"}`
}
]
readonly property list<var> memoryInfo: [
{
label: qsTr("Used"),
value: memoryUsageFormat(Memory.used, Memory.total)
},
{
label: qsTr("Cached"),
value: memoryTotalFormat(Memory.cached)
},
{
label: qsTr("Writes"),
value: memoryTotalFormat(Memory.writeTotal)
},
{
label: qsTr("Reads"),
value: memoryTotalFormat(Memory.readTotal)
}
]
function memoryTotalFormat(kib: int): string {
const fmt = UsageFmt.formatKib(kib);
return `${fmt.value.toFixed(1)} ${fmt.unit}`;
}
function memoryUsageFormat(used: int, total: int): string {
const fmt = UsageFmt.formatKib(used, total);
return `${fmt.value.toFixed(1)} / ${Math.floor(fmt.total)} ${fmt.unit}`;
}
Connections {
function onUpdateTriggered(): void {
if (Cpu.percentage >= 0 && isFinite(Cpu.percentage))
_cpuBuffer.push(Cpu.percentage);
}
target: Cpu
}
Connections {
function onUpdateTriggered(): void {
if (Memory.percentage >= 0 && isFinite(Memory.percentage))
_memoryBuffer.push(Memory.percentage);
}
target: Memory
}
CircularBuffer {
id: _cpuBuffer
capacity: root.historyLength + 1
smoothingRadius: 2
}
CircularBuffer {
id: _memoryBuffer
capacity: root.historyLength + 1
smoothingRadius: 2
}
}
+187
View File
@@ -0,0 +1,187 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import ZShell.Components
import ZShell.Services
import qs.Components
import qs.Services
Item {
id: root
required property int radius
implicitHeight: layout.implicitHeight
ServiceRef {
service: Cpu
}
ServiceRef {
service: Memory
}
RowLayout {
id: layout
anchors.fill: parent
spacing: Tokens.spacing.medium
UsageCard {
accent: Colors.palette.m3primary
buffer: ResourceUsage.cpuBuffer
icon: "memory"
label: qsTr("CPU")
values: ResourceUsage.cpuInfo
}
UsageCard {
accent: Colors.palette.m3tertiary
buffer: ResourceUsage.memoryBuffer
icon: "memory_alt"
label: qsTr("Memory")
values: ResourceUsage.memoryInfo
}
}
component UsageCard: CustomRect {
id: card
property color accent
required property var buffer
property string icon
property string label
property list<var> values
color: Colors.tPalette.m3surfaceContainer
radius: root.radius
Layout.fillWidth: true
Layout.fillHeight: true
implicitHeight: mainLayout.implicitHeight + mainLayout.anchors.margins * 2
ColumnLayout {
id: mainLayout
anchors.fill: parent
anchors.margins: Tokens.padding.large
spacing: 0
RowLayout {
spacing: Tokens.spacing.small
MaterialIcon {
color: card.accent
text: card.icon
}
CustomText {
text: card.label
}
}
Item {
id: graphWrapper
Layout.fillHeight: true
Layout.fillWidth: true
implicitHeight: infoColumn.implicitHeight
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: infoColumn.left
SparklineItem {
id: sparkline
anchors.fill: parent
historyLength: ResourceUsage.historyLength
line1: card.buffer
line1Color: card.accent
line1FillAlpha: 0.15
maxValue: 1.0
slideProgress: 1
Connections {
function onValuesChanged(): void {
slideAnim.restart();
}
target: card.buffer
}
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: Colors.palette.m3outline
text: qsTr("Collecting data...")
visible: card.buffer.count < 2
}
}
ColumnLayout {
id: infoColumn
anchors.right: parent.right
anchors.bottom: parent.bottom
width: {
const prefWidth = graphWrapper.width / 3;
const minWidth = graphWrapper.width / 4;
if (prefWidth < implicitWidth)
return implicitWidth;
else if (prefWidth >= implicitWidth * 2)
return minWidth;
else
return prefWidth;
}
Repeater {
model: card.values
Item {
id: moreInfo
required property var modelData
Layout.leftMargin: Tokens.spacing.medium
Layout.fillWidth: true
implicitWidth: label.implicitWidth + value.implicitWidth + label.anchors.margins * 2
implicitHeight: label.implicitHeight
CustomText {
id: label
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onSurfaceVariant
text: moreInfo.modelData.label
}
CustomText {
id: value
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: card.accent
text: moreInfo.modelData.value
}
}
}
}
}
}
}
}
+54
View File
@@ -0,0 +1,54 @@
import QtQuick
import Quickshell
import ZShell.Config
import ZShell.Services
import qs.Modules.Dashboard.Processes
import qs.Components
import qs.Services
Item {
id: root
required property int radius
implicitWidth: Tokens.sizes.dashboard.processesWidth
implicitHeight: Tokens.sizes.dashboard.processesHeight
ServiceRef {
service: Processes
}
ProcessesHeader {
id: header
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
headerHeight: Tokens.sizes.dashboard.processesHeaderHeight
pList: processList
}
ProcessesList {
id: processList
anchors.top: header.bottom
anchors.bottom: sysInfo.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: Tokens.spacing.small
anchors.bottomMargin: Tokens.spacing.small
columnHeader: header
rowHeight: Tokens.sizes.dashboard.processesRowHeight
}
SystemInfo {
id: sysInfo
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
radius: root.radius
}
}

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