43 Commits
Author SHA1 Message Date
AramJonghu f75fef3f55 chore(template): checklist adjustment
C++ / fmt (pull_request) Successful in 8s
JS/TS / fmt (pull_request) Successful in 14s
JS/TS / lint (pull_request) Successful in 22s
Python / lint (pull_request) Successful in 32s
Python / fmt (pull_request) Successful in 35s
Python / test (pull_request) Successful in 1m4s
Python / typecheck (pull_request) Failing after 1m3s
C++ / build (pull_request) Successful in 1m55s
Rust / build (pull_request) Successful in 1m54s
Rust / fmt (pull_request) Successful in 1m16s
Python / buildcheck (pull_request) Successful in 2m46s
Rust / clippy (pull_request) Successful in 2m40s
C++ / clang-tidy (pull_request) Successful in 5m36s
2026-07-10 01:16:21 +02:00
AramJonghu 246ee67fb3 update(typecheck): added typecheck in workflow and pyproject
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 17s
Python / fmt (pull_request) Successful in 34s
Python / lint (pull_request) Successful in 31s
Python / test (pull_request) Successful in 59s
Python / typecheck (pull_request) Failing after 53s
C++ / build (pull_request) Successful in 1m56s
Rust / fmt (pull_request) Successful in 1m18s
Rust / build (pull_request) Successful in 1m50s
Rust / clippy (pull_request) Successful in 2m7s
Python / buildcheck (pull_request) Successful in 2m42s
C++ / clang-tidy (pull_request) Successful in 3m49s
2026-07-10 01:09:08 +02:00
AramJonghu 5b5f38a48d feat(templates): Added template yaml files 2026-07-10 01:09:01 +02:00
AramJonghu 8d4b910e23 Merge branch 'main' into 135-issue-pr-templates
C++ / fmt (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 20s
Python / fmt (pull_request) Successful in 31s
Python / lint (pull_request) Successful in 29s
Python / test (pull_request) Successful in 1m0s
C++ / build (pull_request) Successful in 3m25s
Rust / fmt (pull_request) Successful in 1m12s
Python / buildcheck (pull_request) Successful in 2m51s
Rust / build (pull_request) Successful in 2m14s
Rust / clippy (pull_request) Successful in 1m52s
C++ / clang-tidy (pull_request) Successful in 6m7s
2026-07-08 22:14:09 +02:00
zach e3b2d33a11 fix: fallback to python wheel if Nuitka is not available 2026-07-08 18:32:21 +02:00
zach 2a40696292 Merge pull request 'chore(ci-image): fix tld in ci-image workflow + additional workflow improvements' (#137) from update-cibuild-tld into main
Reviewed-on: #137
Reviewed-by: zach <zach@brohn.se>
2026-07-08 18:25:10 +02:00
zach 80ee714e91 Merge branch 'main' into update-cibuild-tld
C++ / fmt (pull_request) Successful in 4s
Python / fmt (pull_request) Successful in 58s
JS/TS / lint (pull_request) Successful in 1m14s
JS/TS / fmt (pull_request) Failing after 1m32s
Python / lint (pull_request) Failing after 1m55s
C++ / clang-tidy (pull_request) Successful in 2m55s
C++ / build (pull_request) Successful in 2m58s
Rust / fmt (pull_request) Successful in 1m21s
Python / test (pull_request) Successful in 2m25s
Rust / clippy (pull_request) Successful in 1m31s
Rust / build (pull_request) Successful in 3m5s
Python / buildcheck (pull_request) Successful in 3m25s
2026-07-08 17:50:24 +02:00
zach 2c572c3250 feat: animations in settings search results 2026-07-08 17:49:26 +02:00
AramJonghu 0912a8e40c hotfix(): Forgot to change ruff package as dep
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Successful in 34s
Python / lint (pull_request) Successful in 33s
Python / test (pull_request) Successful in 59s
C++ / build (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 1m12s
Rust / build (pull_request) Successful in 1m58s
Python / buildcheck (pull_request) Successful in 2m44s
Rust / clippy (pull_request) Successful in 1m57s
C++ / clang-tidy (pull_request) Successful in 3m37s
2026-07-08 17:30:46 +02:00
AramJonghu 0d17621180 chore(py formatter): Revert from autopep8 -> ruff after config update
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 20s
Python / fmt (pull_request) Failing after 26s
Python / lint (pull_request) Successful in 29s
Python / test (pull_request) Successful in 1m3s
Rust / fmt (pull_request) Successful in 1m11s
C++ / build (pull_request) Successful in 1m53s
Rust / build (pull_request) Successful in 1m56s
Rust / clippy (pull_request) Successful in 1m45s
Python / buildcheck (pull_request) Successful in 2m44s
C++ / clang-tidy (pull_request) Successful in 3m49s
2026-07-08 17:28:25 +02:00
zach 0ae6d6fc55 chore: format test files
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 19s
Python / lint (pull_request) Successful in 32s
Python / fmt (pull_request) Failing after 37s
Python / test (pull_request) Successful in 54s
C++ / build (pull_request) Successful in 1m57s
Rust / fmt (pull_request) Successful in 1m16s
Rust / build (pull_request) Successful in 1m56s
Python / buildcheck (pull_request) Successful in 2m44s
Rust / clippy (pull_request) Successful in 1m56s
C++ / clang-tidy (pull_request) Successful in 3m46s
2026-07-08 17:01:25 +02:00
zach 9300da258e chore: python code formatted with ruff, ruff formatter + linter, pyproject.toml now in root
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 18s
Python / lint (pull_request) Failing after 32s
Python / fmt (pull_request) Failing after 37s
Python / test (pull_request) Successful in 52s
C++ / build (pull_request) Successful in 1m56s
Rust / fmt (pull_request) Successful in 1m22s
Rust / build (pull_request) Successful in 2m5s
Python / buildcheck (pull_request) Successful in 2m39s
Rust / clippy (pull_request) Successful in 2m1s
C++ / clang-tidy (pull_request) Successful in 3m46s
2026-07-08 16:54:41 +02:00
zach 298cbbf424 Merge branch 'main' into update-cibuild-tld
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 14s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Failing after 30s
Python / lint (pull_request) Successful in 33s
Python / test (pull_request) Successful in 54s
C++ / build (pull_request) Successful in 1m47s
Rust / fmt (pull_request) Successful in 1m16s
Rust / build (pull_request) Successful in 2m0s
Python / buildcheck (pull_request) Successful in 2m41s
Rust / clippy (pull_request) Successful in 1m54s
C++ / clang-tidy (pull_request) Successful in 3m41s
2026-07-08 16:18:15 +02:00
zach b19b630a77 better ddcutil handling, revert to using onMoved brightness slider, fix osd region 2026-07-08 16:17:37 +02:00
AramJonghu 75f0f2071d fix(ci): include zshell python package for buildcheck
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 18s
Python / fmt (pull_request) Failing after 32s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 52s
Rust / fmt (pull_request) Successful in 1m11s
C++ / build (pull_request) Successful in 1m57s
Rust / build (pull_request) Successful in 1m49s
Python / buildcheck (pull_request) Successful in 2m39s
Rust / clippy (pull_request) Successful in 1m45s
C++ / clang-tidy (pull_request) Successful in 3m43s
2026-07-08 14:52:42 +02:00
AramJonghu bfea718547 fix(ci): clang-format pass echo, nuitka full package compile
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 15s
Python / fmt (pull_request) Failing after 34s
Python / lint (pull_request) Successful in 33s
Python / test (pull_request) Successful in 51s
C++ / build (pull_request) Successful in 1m37s
Rust / fmt (pull_request) Successful in 1m8s
Rust / build (pull_request) Successful in 1m52s
Python / buildcheck (pull_request) Successful in 2m37s
Rust / clippy (pull_request) Successful in 1m55s
C++ / clang-tidy (pull_request) Successful in 3m37s
2026-07-08 14:49:02 +02:00
AramJonghu 5505ac19ba refactor(ci): add python workflow, split cpp jobs, add clang-format
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 18s
JS/TS / lint (pull_request) Successful in 17s
Python / fmt (pull_request) Failing after 29s
Python / lint (pull_request) Successful in 35s
Python / test (pull_request) Successful in 56s
C++ / build (pull_request) Successful in 1m49s
Rust / fmt (pull_request) Successful in 1m9s
Rust / build (pull_request) Successful in 1m51s
Python / buildcheck (pull_request) Successful in 2m31s
Rust / clippy (pull_request) Successful in 1m47s
C++ / clang-tidy (pull_request) Successful in 3m29s
2026-07-08 14:37:35 +02:00
AramJonghu 9d2dc281ec refactor(ci): restructure workflows to multi-job per language
JS/TS / fmt (pull_request) Successful in 14s
JS/TS / lint (pull_request) Successful in 14s
Rust / fmt (pull_request) Successful in 1m30s
Rust / build (pull_request) Successful in 1m53s
Rust / clippy (pull_request) Successful in 2m20s
C++ / build (pull_request) Successful in 2m54s
2026-07-08 14:28:51 +02:00
AramJonghu 05c8851143 Merge branch 'main' into update-cibuild-tld
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 22s
Python / lint-format (pull_request) Successful in 27s
Python / test (pull_request) Successful in 50s
C++ / build (pull_request) Successful in 2m31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m34s
2026-07-08 13:51:16 +02:00
zach 1cbe9a9f8a Merge pull request 'Add ahead-of-time compilation for cli and optimize thumbnail caching' (#136) from feat/aot-compilation-cli into main
Reviewed-on: #136
Reviewed-by: AramJonghu <aramjonghu@tutamail.com>
2026-07-08 13:41:18 +02:00
AramJonghu 71b25bff49 chore(ci-image): fix tld in ci-image workflow
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 45s
Python / lint-format (pull_request) Successful in 52s
Python / test (pull_request) Successful in 44s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m50s
C++ / build (pull_request) Successful in 3m40s
2026-07-08 13:21:19 +02:00
zach 87433334c7 fix cmake compilation of python code
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 31s
Python / test (pull_request) Successful in 41s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m41s
C++ / build (pull_request) Successful in 2m27s
2026-07-08 13:01:53 +02:00
zach 6d5609fe5b Add ahead-of-time compilation for cli and optimize thumbnail caching
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 46s
Python / lint-format (pull_request) Successful in 49s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m42s
C++ / build (pull_request) Successful in 3m7s
2026-07-08 12:23:45 +02:00
zach ffefd3e202 clip settings at bar bounds 2026-07-07 18:47:48 +02:00
zach 2db6a57c0b fix: shift background in accordance with content 2026-07-06 19:40:57 +02:00
zach 0c5871e108 add burn-in prevention for lock screen 2026-07-06 14:12:07 +02:00
zach 6ad9fe5e07 settings highlight optimized 2026-07-05 18:01:52 +02:00
zach 8c11aead18 fix: osd stays open when hovered 2026-07-04 15:54:44 +02:00
AramJonghu 5e78b3b492 init(new branch): New branch
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 1m3s
Lint & Format (Rust) / lint-format (pull_request) Successful in 2m36s
C++ / build (pull_request) Successful in 6m21s
2026-07-04 04:54:28 +02:00
zach d013be0f9a fix: include spacing for expand icon tray menu 2026-07-03 17:40:45 +02:00
zach 69774e7759 feat: greeter support hyprland lua 2026-07-03 15:52:27 +02:00
zach cdf66bff23 fix: better width + height calculations for tray menu popouts 2026-07-03 15:02:00 +02:00
zach 562770595f fix: add generic gpu vram support 2026-07-03 12:28:54 +02:00
zach 9126b04b10 fix: close tray menus when hovering bar + showOnHover enabled 2026-07-02 18:50:14 +02:00
zach 55dab8f7ce HyprlandFocusGrab on traymenu stackdepth > 1 2026-07-02 16:15:18 +02:00
zach c62695bfff chore: cleanup more unused files/imports, use new buttonrow for polkit 2026-07-02 13:35:17 +02:00
zach 2b06a33508 chore: cleanup unused files/imports 2026-07-02 12:49:47 +02:00
zach 4d731c5a65 feat: add option for recoloring tray icons + show popout on hover 2026-07-02 11:33:43 +02:00
zach 53836aec1a fix: anim property for record tool 2026-07-01 22:35:58 +02:00
zach 355106cb2b add radius anim to search results 2026-06-30 22:40:08 +02:00
zach 865b5bda53 chore: cleanup comments 2026-06-30 22:27:37 +02:00
zach 2b89c8e4a1 implement search bar functionality in settings 2026-06-30 22:20:48 +02:00
zach 61019204d8 formatter 2026-06-30 15:35:25 +02:00
127 changed files with 3906 additions and 5945 deletions
+100
View File
@@ -0,0 +1,100 @@
name: Bug Report
about: Something broke
title: "bug: "
labels:
- bug
body:
- type: textarea
id: description
attributes:
label: What happened
placeholder: Describe the bug
validations:
required: true
- type: textarea
id: expected
attributes:
label: What should happen
placeholder: Expected behavior
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
placeholder: |
1.
2.
3.
render: shell
validations:
required: true
- type: dropdown
id: os
attributes:
label: OS
options:
- NixOS
- Arch
- Debian / Ubuntu
- Fedora
- Alpine
- Artix
- Gentoo
- Other
validations:
required: false
- type: input
id: other-os
attributes:
label: Other OS (if "Other" above)
placeholder: e.g. Void, Gentoo, FreeBSD
validations:
required: false
- type: dropdown
id: init
attributes:
label: Init system
description: Relevant for UWSM configuration
options:
- systemd
- non-systemd (openrc / runit / s6 / ...)
validations:
required: false
- type: input
id: qt-version
attributes:
label: Qt version
placeholder: 6.7
validations:
required: false
- type: input
id: commit
attributes:
label: Branch / commit
placeholder: main / abc1234
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs / backtrace / screenshots
render: shell
validations:
required: false
- type: textarea
id: workaround
attributes:
label: Found a workaround?
validations:
required: false
+51
View File
@@ -0,0 +1,51 @@
name: Feature Request
about: Idea or improvement
title: "feat: "
labels:
- enhancement
body:
- type: textarea
id: problem
attributes:
label: What's missing / annoying
placeholder: The use case or pain point
validations:
required: true
- type: textarea
id: solution
attributes:
label: How should it work
placeholder: Describe the desired behavior
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Other approaches considered
validations:
required: false
- type: dropdown
id: scope
attributes:
label: Area
options:
- Core / Daemon
- UI / Components
- Plugin system
- CMake / Build
- CI / Tooling
- Documentation
- Other
validations:
required: false
- type: textarea
id: context
attributes:
label: Mockups / references / notes
validations:
required: false
+59
View File
@@ -0,0 +1,59 @@
name: Pull Request
about: Submit changes
title: ""
labels: []
body:
- type: textarea
id: description
attributes:
label: What & why
placeholder: Describe the change and motivation
validations:
required: true
- type: input
id: issue
attributes:
label: Related issue
placeholder: "#123"
validations:
required: false
- type: dropdown
id: type
attributes:
label: Type
options:
- Bug fix
- New feature
- Refactor / style
- CI / build / tooling
- Documentation
validations:
required: true
- type: checkboxes
id: tested
attributes:
label: Tested
options:
- label: Build succeeds
- label: Existing tests pass
- label: Manually verified
- type: textarea
id: breaking
attributes:
label: Breaking changes?
placeholder: API/ABI breakage? Migration path?
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: CI passes (format, lint, typecheck)
- label: Clean commits
- label: No new warnings
- label: Docs updated if needed
+3 -3
View File
@@ -2,7 +2,7 @@ name: Rebuild CI Image
on: on:
schedule: schedule:
- cron: '0 6 * * 1' - cron: "0 6 * * 1"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@@ -11,7 +11,7 @@ jobs:
container: container:
image: node:26-alpine image: node:26-alpine
env: env:
IMAGE: git.aramjonghu.nl/aramjonghu/zshell-ci:latest IMAGE: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps: steps:
- name: Checkout - name: Checkout
@@ -21,7 +21,7 @@ jobs:
run: apk add --no-cache docker-cli run: apk add --no-cache docker-cli
- name: Login to registry - name: Login to registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.nl --username aramjonghu --password-stdin run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.dev --username aramjonghu --password-stdin
- name: Build image - name: Build image
run: docker build -t "$IMAGE" -f ci/Dockerfile . run: docker build -t "$IMAGE" -f ci/Dockerfile .
+34 -1
View File
@@ -4,10 +4,43 @@ on:
pull_request: pull_request:
jobs: jobs:
fmt:
runs-on: alpine
container:
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Format check
run: |
find . \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) \
-not -path './build/*' \
-exec clang-format -i --style=file {} +
git diff --exit-code && echo "clang-format: passed"
build: build:
runs-on: alpine runs-on: alpine
container: container:
image: git.aramjonghu.nl/aramjonghu/zshell-ci:latest image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
- name: Build
run: ninja -C build
clang-tidy:
runs-on: alpine
container:
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps: steps:
- name: Checkout - name: Checkout
@@ -1,10 +1,10 @@
name: Lint & Format (JS/TS) name: JS/TS
on: on:
pull_request: pull_request:
jobs: jobs:
lint-format: fmt:
runs-on: alpine runs-on: alpine
container: node:26-alpine container: node:26-alpine
@@ -18,7 +18,6 @@ jobs:
git git
- name: Prettier - name: Prettier
continue-on-error: true
run: | run: |
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
npx --yes prettier --check "**/*.{js,jsx,ts,tsx,mjs,cjs}" --ignore-path .prettierignore npx --yes prettier --check "**/*.{js,jsx,ts,tsx,mjs,cjs}" --ignore-path .prettierignore
@@ -26,6 +25,19 @@ jobs:
echo "No JS/TS files found" echo "No JS/TS files found"
fi fi
lint:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git
- name: ESLint - name: ESLint
run: | run: |
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
-65
View File
@@ -1,65 +0,0 @@
name: Python
on:
pull_request:
jobs:
lint-format:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir ruff
- name: Format check
continue-on-error: true
run: |
. .venv/bin/activate
ruff format --check .
- name: Lint
run: |
. .venv/bin/activate
ruff check .
test:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip \
py3-pillow \
build-base
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir \
typer \
pillow \
materialyoucolor \
jinja2 \
pytest
- name: Test
run: |
. .venv/bin/activate
cd cli
python -m pytest tests/ -v
-85
View File
@@ -1,85 +0,0 @@
name: Lint & Format (Rust)
on:
pull_request:
jobs:
lint-format:
runs-on: alpine
container: node:26-alpine
env:
CARGO_HOME: ${{ github.workspace }}/.cargo
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache cargo packages
uses: actions/cache@v4
env:
cache-name: cache-cargo-packages
with:
path: |
.cargo/registry
.cargo/git
target
key: rust-${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
rust-${{ runner.os }}-build-${{ env.cache-name }}-
rust-${{ runner.os }}-build-
rust-
- name: Install tools
run: |
apk add --no-cache \
git \
cargo \
rust \
rustfmt \
rust-clippy
- id: format-check
name: Format check
continue-on-error: true
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do
cargo fmt --manifest-path "$manifest" --check && \
echo "$manifest: formatting OK" || \
{ echo "$manifest: needs formatting"; status=1; }
done
exit $status
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
else
echo "No Rust project found"
fi
- id: clippy
name: Clippy
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do
cargo clippy --manifest-path "$manifest" --all-targets --all-features -- -D warnings && \
echo "$manifest: Clippy passed" || \
{ echo "$manifest: Clippy failed"; status=1; }
done
exit $status
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
else
echo "No Rust project found"
fi
- name: Check results
if: always()
run: |
if [ "${{ steps.format-check.outcome }}" = "failure" ] || [ "${{ steps.clippy.outcome }}" = "failure" ]; then
echo "One or more checks failed"
exit 1
fi
echo "All checks passed"
+132
View File
@@ -0,0 +1,132 @@
name: Python
on:
pull_request:
jobs:
fmt:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir ruff
- name: Format check
run: |
. .venv/bin/activate
ruff format --check .
lint:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir ruff
- name: Lint
run: |
. .venv/bin/activate
ruff check .
test:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip \
py3-pillow \
build-base
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir \
typer \
pillow \
materialyoucolor \
jinja2 \
pytest
- name: Test
run: |
. .venv/bin/activate
cd cli
python -m pytest tests/ -v
typecheck:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir basedpyright
- name: Type check
run: |
. .venv/bin/activate
basedpyright
buildcheck:
runs-on: alpine
container: node:26-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git \
python3 \
py3-pip \
build-base \
python3-dev \
gcc \
g++
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir nuitka
- name: Nuitka module check
run: |
. .venv/bin/activate
nuitka --module --include-package=zshell cli/src/zshell/
+152
View File
@@ -0,0 +1,152 @@
name: Rust
on:
pull_request:
jobs:
build:
runs-on: alpine
container: node:26-alpine
env:
CARGO_HOME: ${{ github.workspace }}/.cargo
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache cargo packages
uses: actions/cache@v4
env:
cache-name: cache-cargo-packages
with:
path: |
.cargo/registry
.cargo/git
target
key: rust-build-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
rust-${{ runner.os }}-build-${{ env.cache-name }}-
rust-${{ runner.os }}-build-
rust-
- name: Install tools
run: |
apk add --no-cache \
git \
cargo \
rust
- name: Cargo check
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
for manifest in $(find . -name "Cargo.toml"); do
cargo check --manifest-path "$manifest" && \
echo "$manifest: check passed" || \
{ echo "$manifest: check failed"; exit 1; }
done
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
else
echo "No Rust project found"
fi
fmt:
runs-on: alpine
container: node:26-alpine
env:
CARGO_HOME: ${{ github.workspace }}/.cargo
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache cargo packages
uses: actions/cache@v4
env:
cache-name: cache-cargo-packages
with:
path: |
.cargo/registry
.cargo/git
target
key: rust-fmt-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
rust-${{ runner.os }}-build-${{ env.cache-name }}-
rust-${{ runner.os }}-build-
rust-
- name: Install tools
run: |
apk add --no-cache \
git \
cargo \
rust \
rustfmt
- name: Format check
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do
cargo fmt --manifest-path "$manifest" --check && \
echo "$manifest: formatting OK" || \
{ echo "$manifest: needs formatting"; status=1; }
done
exit $status
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
else
echo "No Rust project found"
fi
clippy:
runs-on: alpine
container: node:26-alpine
env:
CARGO_HOME: ${{ github.workspace }}/.cargo
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache cargo packages
uses: actions/cache@v4
env:
cache-name: cache-cargo-packages
with:
path: |
.cargo/registry
.cargo/git
target
key: rust-clippy-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
rust-${{ runner.os }}-build-${{ env.cache-name }}-
rust-${{ runner.os }}-build-
rust-
- name: Install tools
run: |
apk add --no-cache \
git \
cargo \
rust \
rust-clippy
- name: Clippy
run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do
cargo clippy --manifest-path "$manifest" --all-targets --all-features -- -D warnings && \
echo "$manifest: Clippy passed" || \
{ echo "$manifest: Clippy failed"; status=1; }
done
exit $status
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
else
echo "No Rust project found"
fi
+96
View File
@@ -52,6 +52,102 @@ add_compile_options(
) )
if("shell" 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
set(ZSHELL_CLI_BUILD_DIR "${CMAKE_BINARY_DIR}/zshell-cli")
set(ZSHELL_CLI_DIST "${ZSHELL_CLI_BUILD_DIR}/zshell.dist")
set(ZSHELL_CLI_SRC "${CMAKE_SOURCE_DIR}/cli/src/zshell")
find_program(NUITKA_EXECUTABLE nuitka)
if(NUITKA_EXECUTABLE)
file(GLOB_RECURSE ZSHELL_CLI_SOURCES CONFIGURE_DEPENDS
"${ZSHELL_CLI_SRC}/*.py"
)
file(GLOB_RECURSE ZSHELL_CLI_ASSETS CONFIGURE_DEPENDS
"${ZSHELL_CLI_SRC}/assets/*"
)
add_custom_command(
OUTPUT "${ZSHELL_CLI_DIST}/zshell-cli"
COMMAND ${CMAKE_COMMAND} -E make_directory "${ZSHELL_CLI_BUILD_DIR}"
COMMAND ${CMAKE_COMMAND} -E rm -rf "${ZSHELL_CLI_DIST}"
COMMAND
${NUITKA_EXECUTABLE}
--standalone
--include-data-dir=${CMAKE_SOURCE_DIR}/cli/src/zshell/assets=zshell/assets
--output-dir=${ZSHELL_CLI_BUILD_DIR}
--output-filename=zshell-cli
${CMAKE_SOURCE_DIR}/cli/src/zshell/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/cli
DEPENDS ${ZSHELL_CLI_SOURCES} ${ZSHELL_CLI_ASSETS}
)
add_custom_target(zshell-cli ALL DEPENDS "${ZSHELL_CLI_DIST}/zshell-cli")
install(PROGRAMS "${ZSHELL_CLI_DIST}/zshell-cli" DESTINATION "${INSTALL_LIBDIR}/zshell-cli")
install(DIRECTORY "${ZSHELL_CLI_DIST}/" DESTINATION "${INSTALL_LIBDIR}/zshell-cli" PATTERN "zshell-cli" EXCLUDE)
configure_file(
"${CMAKE_SOURCE_DIR}/Plugins/cmake/zshell-cli.cmake.in"
"${CMAKE_BINARY_DIR}/Plugins/cmake/zshell-cli.cmake"
@ONLY
)
install(SCRIPT "${CMAKE_BINARY_DIR}/Plugins/cmake/zshell-cli.cmake")
else()
message(STATUS "Nuitka not found, building zshell-cli as a Python wheel instead")
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(ZSHELL_CLI_DIR "${CMAKE_SOURCE_DIR}")
set(ZSHELL_CLI_DIST_DIR "${ZSHELL_CLI_DIR}/dist")
set(ZSHELL_CLI_WHEEL_STAMP "${CMAKE_BINARY_DIR}/zshell-cli/wheel.stamp")
file(GLOB_RECURSE ZSHELL_CLI_SOURCES CONFIGURE_DEPENDS
"${ZSHELL_CLI_DIR}/src/zshell/*.py"
)
file(GLOB_RECURSE ZSHELL_CLI_ASSETS CONFIGURE_DEPENDS
"${ZSHELL_CLI_DIR}/src/zshell/assets/*"
)
add_custom_command(
OUTPUT "${ZSHELL_CLI_WHEEL_STAMP}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/zshell-cli"
COMMAND ${CMAKE_COMMAND} -E rm -rf "${ZSHELL_CLI_DIST_DIR}"
COMMAND ${Python3_EXECUTABLE} -m build --wheel --no-isolation
COMMAND ${CMAKE_COMMAND} -E touch "${ZSHELL_CLI_WHEEL_STAMP}"
WORKING_DIRECTORY "${ZSHELL_CLI_DIR}"
DEPENDS ${ZSHELL_CLI_SOURCES} ${ZSHELL_CLI_ASSETS}
COMMENT "Building zshell-cli python wheel"
)
add_custom_target(zshell-cli ALL DEPENDS "${ZSHELL_CLI_WHEEL_STAMP}")
configure_file(
"${CMAKE_SOURCE_DIR}/Plugins/cmake/zshell-cli-python.cmake.in"
"${CMAKE_BINARY_DIR}/Plugins/cmake/zshell-cli-python.cmake"
@ONLY
)
install(SCRIPT "${CMAKE_BINARY_DIR}/Plugins/cmake/zshell-cli-python.cmake")
endif()
endif()
if("plugin" IN_LIST ENABLE_MODULES) if("plugin" IN_LIST ENABLE_MODULES)
add_subdirectory(Plugins) add_subdirectory(Plugins)
-135
View File
@@ -1,135 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
ColumnLayout {
id: root
default property alias content: contentColumn.data
property string description: ""
property bool expanded: false
property bool nested: false
property bool showBackground: false
required property string title
signal toggleRequested
Layout.fillWidth: true
spacing: Appearance.spacing.small
Item {
id: sectionHeaderItem
Layout.fillWidth: true
Layout.preferredHeight: Math.max(titleRow.implicitHeight + Appearance.padding.normal * 2, 48)
RowLayout {
id: titleRow
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
font.pointSize: Appearance.font.size.larger
font.weight: 500
text: root.title
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
rotation: root.expanded ? 180 : 0
text: "expand_more"
Behavior on rotation {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
}
StateLayer {
anchors.fill: parent
color: DynamicColors.palette.m3onSurface
radius: Appearance.rounding.normal
showHoverBackground: false
onClicked: {
root.toggleRequested();
root.expanded = !root.expanded;
}
}
}
Item {
id: contentWrapper
Layout.fillWidth: true
Layout.preferredHeight: root.expanded ? (contentColumn.implicitHeight + Appearance.spacing.small * 2) : 0
clip: true
Behavior on Layout.preferredHeight {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomRect {
id: backgroundRect
anchors.fill: parent
color: DynamicColors.transparency.enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, root.nested ? 3 : 2) : (root.nested ? DynamicColors.palette.m3surfaceContainerHigh : DynamicColors.palette.m3surfaceContainer)
opacity: root.showBackground && root.expanded ? 1.0 : 0.0
radius: Appearance.rounding.normal
visible: root.showBackground
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
ColumnLayout {
id: contentColumn
anchors.bottomMargin: Appearance.spacing.small
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
opacity: root.expanded ? 1.0 : 0.0
spacing: Appearance.spacing.small
y: Appearance.spacing.small
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomText {
id: descriptionText
Layout.bottomMargin: root.description !== "" ? Appearance.spacing.small : 0
Layout.fillWidth: true
Layout.topMargin: root.description !== "" ? Appearance.spacing.smaller : 0
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: root.description
visible: root.description !== ""
wrapMode: Text.Wrap
}
}
}
}
-70
View File
@@ -1,70 +0,0 @@
import QtQuick
import QtQuick.Templates
import qs.Config
Slider {
id: root
property color nonPeakColor: DynamicColors.tPalette.m3primary
required property real peak
property color peakColor: DynamicColors.palette.m3primary
background: Item {
CustomRect {
anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 3
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: root.implicitHeight / 3
bottomRightRadius: root.implicitHeight / 15
color: root.nonPeakColor
implicitWidth: root.handle.x - root.implicitHeight
radius: Appearance.rounding.full
topRightRadius: root.implicitHeight / 15
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
bottomRightRadius: root.implicitHeight / 15
color: root.peakColor
implicitWidth: parent.width * root.peak
radius: Appearance.rounding.full
topRightRadius: root.implicitHeight / 15
Behavior on implicitWidth {
Anim {
duration: 50
}
}
}
}
CustomRect {
anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 3
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: root.implicitHeight / 3
bottomLeftRadius: root.implicitHeight / 15
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: root.implicitWidth - root.handle.x - root.handle.implicitWidth - root.implicitHeight
radius: Appearance.rounding.full
topLeftRadius: root.implicitHeight / 15
}
}
handle: CustomRect {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
implicitHeight: 15
implicitWidth: 5
radius: Appearance.rounding.full
x: root.visualPosition * root.availableWidth - implicitWidth / 2
MouseArea {
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
}
}
-69
View File
@@ -1,69 +0,0 @@
import QtQuick
import QtQuick.Controls.Basic
BusyIndicator {
id: control
property int busySize: 64
property color color: delegate.color
contentItem: Item {
implicitHeight: control.busySize
implicitWidth: control.busySize
Item {
id: item
height: control.busySize
opacity: control.running ? 1 : 0
width: control.busySize
x: parent.width / 2 - (control.busySize / 2)
y: parent.height / 2 - (control.busySize / 2)
Behavior on opacity {
OpacityAnimator {
duration: 250
}
}
RotationAnimator {
duration: 1250
from: 0
loops: Animation.Infinite
running: control.visible && control.running
target: item
to: 360
}
Repeater {
id: repeater
model: 6
CustomRect {
id: delegate
required property int index
color: control.color
implicitHeight: 10
implicitWidth: 10
radius: 5
x: item.width / 2 - width / 2
y: item.height / 2 - height / 2
transform: [
Translate {
y: -Math.min(item.width, item.height) * 0.5 + 5
},
Rotation {
angle: delegate.index / repeater.count * 360
origin.x: 5
origin.y: 5
}
]
}
}
}
}
}
-32
View File
@@ -1,32 +0,0 @@
import QtQuick
import QtQuick.Controls
import qs.Config
Button {
id: control
property color bgColor: DynamicColors.palette.m3primary
property int radius: Appearance.rounding.smallest / 2
property color textColor: DynamicColors.palette.m3onPrimary
background: CustomRect {
color: control.bgColor
opacity: control.enabled ? 1.0 : 0.5
radius: control.radius
}
contentItem: CustomText {
color: control.textColor
horizontalAlignment: Text.AlignHCenter
opacity: control.enabled ? 1.0 : 0.5
text: control.text
verticalAlignment: Text.AlignVCenter
}
StateLayer {
radius: control.radius
onClicked: {
control.clicked();
}
}
}
-169
View File
@@ -1,169 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import qs.Config
ComboBox {
id: root
property int cornerRadius: Appearance.rounding.normal
property int fieldHeight: 42
property bool filled: true
property real focusRingOpacity: 0.70
property int hPadding: 16
property int menuCornerRadius: 16
property int menuRowHeight: 46
property int menuVisibleRows: 7
property bool preferPopupWindow: false
hoverEnabled: true
implicitHeight: fieldHeight
implicitWidth: 240
spacing: 8
// ---------- Field background (filled/outlined + state layers + focus ring) ----------
background: Item {
anchors.fill: parent
CustomRect {
id: container
anchors.fill: parent
color: DynamicColors.palette.m3surfaceVariant
radius: root.cornerRadius
StateLayer {
}
}
}
// ---------- Content ----------
contentItem: RowLayout {
anchors.fill: parent
anchors.leftMargin: root.hPadding
anchors.rightMargin: root.hPadding
spacing: 12
// Display text
CustomText {
Layout.fillWidth: true
color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pixelSize: 16
font.weight: Font.Medium
text: root.currentText
verticalAlignment: Text.AlignVCenter
}
// Indicator chevron (simple, replace with your icon system)
CustomText {
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurfaceVariant
rotation: root.popup.visible ? 180 : 0
text: "▾"
transformOrigin: Item.Center
verticalAlignment: Text.AlignVCenter
Behavior on rotation {
NumberAnimation {
duration: 140
easing.type: Easing.OutCubic
}
}
}
}
popup: Popup {
id: p
implicitHeight: list.contentItem.height + Appearance.padding.small * 2
implicitWidth: root.width
modal: true
popupType: root.preferPopupWindow ? Popup.Window : Popup.Item
y: -list.currentIndex * (root.menuRowHeight + Appearance.spacing.small) - Appearance.padding.small
background: CustomRect {
color: DynamicColors.palette.m3surface
radius: root.menuCornerRadius
}
contentItem: ListView {
id: list
anchors.bottomMargin: Appearance.padding.small
anchors.fill: parent
anchors.topMargin: Appearance.padding.small
clip: true
currentIndex: root.currentIndex
model: root.delegateModel
spacing: Appearance.spacing.small
delegate: CustomRect {
required property int index
required property var modelData
anchors.horizontalCenter: parent.horizontalCenter
color: (index === root.currentIndex) ? DynamicColors.palette.m3primary : "transparent"
implicitHeight: root.menuRowHeight
implicitWidth: p.implicitWidth - Appearance.padding.small * 2
radius: Appearance.rounding.normal - Appearance.padding.small
RowLayout {
anchors.fill: parent
spacing: 10
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurface
elide: Text.ElideRight
font.pixelSize: 15
text: modelData
verticalAlignment: Text.AlignVCenter
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
text: "✓"
verticalAlignment: Text.AlignVCenter
visible: index === root.currentIndex
}
}
StateLayer {
onClicked: {
root.currentIndex = index;
p.close();
}
}
}
}
// Expressive-ish open/close motion: subtle scale+fade (tune to taste). :contentReference[oaicite:5]{index=5}
enter: Transition {
Anim {
from: 0
property: "opacity"
to: 1
}
Anim {
from: 0.98
property: "scale"
to: 1.0
}
}
exit: Transition {
Anim {
from: 1
property: "opacity"
to: 0
}
}
Elevation {
anchors.fill: parent
level: 2
radius: root.menuCornerRadius
z: -1
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ ListView {
property bool doneFakeFlick property bool doneFakeFlick
interactive: !Visibilities.getForActive().isDrawing interactive: !Visibilities.getForActive()?.isDrawing
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
-25
View File
@@ -1,25 +0,0 @@
import QtQuick
import QtQuick.Controls
import qs.Components
ToolTip {
id: root
property bool alternativeVisibleCondition: false
property bool extraVisibleCondition: true
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
background: null
horizontalPadding: 10
verticalPadding: 5
visible: internalVisibleCondition
contentItem: CustomTooltipContent {
id: contentItem
horizontalPadding: root.horizontalPadding
shown: root.internalVisibleCondition
text: root.text
verticalPadding: root.verticalPadding
}
}
-54
View File
@@ -1,54 +0,0 @@
import QtQuick
import qs.Components
import qs.Config
Item {
id: root
property real horizontalPadding: 10
property bool isVisible: backgroundRectangle.implicitHeight > 0
property bool shown: false
required property string text
property real verticalPadding: 5
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
Rectangle {
id: backgroundRectangle
clip: true
color: DynamicColors.tPalette.m3inverseSurface ?? "#3C4043"
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
opacity: shown ? 1 : 0
radius: Appearance.rounding.smallest
Behavior on implicitHeight {
Anim {
}
}
Behavior on implicitWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
anchors {
bottom: root.bottom
horizontalCenter: root.horizontalCenter
}
CustomText {
id: tooltipTextObject
anchors.centerIn: parent
color: DynamicColors.palette.m3inverseOnSurface ?? "#FFFFFF"
text: root.text
wrapMode: Text.Wrap
}
}
}
-44
View File
@@ -1,44 +0,0 @@
import qs.Config
import QtQuick
CustomRect {
required property int extra
anchors.margins: 8
anchors.right: parent.right
color: DynamicColors.palette.m3tertiary
implicitHeight: count.implicitHeight + 4 * 2
implicitWidth: count.implicitWidth + 8 * 2
opacity: extra > 0 ? 1 : 0
radius: Appearance.rounding.smallest
scale: extra > 0 ? 1 : 0.5
Behavior on opacity {
Anim {
duration: MaterialEasing.expressiveEffectsTime
}
}
Behavior on scale {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Elevation {
anchors.fill: parent
level: 2
opacity: parent.opacity
radius: parent.radius
z: -1
}
CustomText {
id: count
anchors.centerIn: parent
animate: parent.opacity > 0
color: DynamicColors.palette.m3onTertiary
text: qsTr("+%1").arg(parent.extra)
}
}
-33
View File
@@ -1,33 +0,0 @@
import QtQuick
import QtQuick.Controls
import qs.Config
IconButton {
id: root
required property bool shouldBeVisible
opacity: 0
scale: 0
visible: root.scale > 0
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
}
Behavior on scale {
Anim {
}
}
onShouldBeVisibleChanged: {
if (root.shouldBeVisible) {
root.opacity = 1;
root.scale = 1;
} else {
root.opacity = 0;
root.scale = 0;
}
}
}
-309
View File
@@ -1,309 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Config
Item {
id: root
enum Variant {
Outlined,
Filled
}
readonly property color accent: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline))
readonly property real disabledOpacity: 0.38
readonly property string effectiveTrailingIcon: root.trailingIcon.length > 0 ? root.trailingIcon : (root.password ? (field.echoMode === TextInput.Password ? "visibility" : "visibility_off") : (root.isError ? "error" : ""))
property string errorText: ""
readonly property alias field: field
readonly property bool floating: focused || hasContent || prefixText.length > 0
readonly property bool focused: field.activeFocus
readonly property bool hasContent: field.text.length > 0
readonly property color iconColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
property int inputMethodHints: Qt.ImhNone
property bool isError: false
property string label: ""
readonly property color labelColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
property string leadingIcon: ""
property int maxLength: 0
property color notchColor: DynamicColors.palette.m3surface
readonly property bool outlined: variant === M3TextField.Variant.Outlined
property bool password: false
property string placeholder: ""
property string prefixText: ""
property string suffixText: ""
readonly property color supportColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant)
property string supportingText: ""
property alias text: field.text
property string trailingIcon: ""
readonly property bool trailingInteractive: root.password && root.trailingIcon.length === 0
property var validator: null
property int variant: M3TextField.Variant.Outlined
signal accepted
function forceFieldFocus(): void {
field.forceActiveFocus();
}
implicitHeight: 56 + (supportRow.visible ? supportRow.implicitHeight + Appearance.spacing.extraSmall : 0)
implicitWidth: 240
CustomRect {
id: filledBg
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: root.enabled ? (root.focused ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) : DynamicColors.palette.m3surfaceContainerHigh) : DynamicColors.palette.m3onSurface
implicitHeight: 56
opacity: root.enabled ? 1 : 0.04 // M3 filled disabled container @ 4%
topLeftRadius: Appearance.rounding.small
topRightRadius: Appearance.rounding.small
visible: !root.outlined
Behavior on color {
CAnim {
}
}
StateLayer {
anchors.fill: parent
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
enabled: root.enabled
topLeftRadius: Appearance.rounding.small
topRightRadius: Appearance.rounding.small
onClicked: field.forceActiveFocus()
}
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: root.accent
implicitHeight: root.focused || root.isError ? 2 : 1
opacity: root.enabled ? 1 : root.disabledOpacity
Behavior on color {
CAnim {
}
}
Behavior on implicitHeight {
Anim {
type: Anim.StandardSmall
}
}
}
}
CustomRect {
id: outline
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
border.color: root.accent
border.width: root.focused || root.isError ? 2 : 1
color: "transparent"
implicitHeight: 56
opacity: root.enabled ? 1 : root.disabledOpacity
radius: Appearance.rounding.small
visible: root.outlined
Behavior on border.color {
CAnim {
}
}
Behavior on border.width {
Anim {
type: Anim.StandardSmall
}
}
StateLayer {
anchors.fill: parent
anchors.margins: outline.border.width
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
enabled: root.enabled
radius: Appearance.rounding.small
onClicked: field.forceActiveFocus()
}
}
Rectangle {
color: root.notchColor
height: outline.border.width
opacity: root.enabled ? 1 : root.disabledOpacity
visible: root.outlined && root.floating && root.label.length > 0
width: labelText.width + Appearance.spacing.extraSmall
x: labelText.x - Appearance.spacing.extraSmall / 2
y: 0
}
CustomText {
id: labelText
color: root.labelColor
font.pointSize: root.floating ? Appearance.font.size.small : Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.label
visible: root.label.length > 0
x: root.floating ? (Appearance.padding.larger + Appearance.spacing.extraSmall / 2) : (leading.visible ? leading.x + leading.width + Appearance.spacing.small : Appearance.padding.larger)
y: root.floating ? (root.outlined ? -height / 2 : Appearance.padding.extraSmall) : (56 - height) / 2
Behavior on color {
CAnim {
}
}
Behavior on x {
Anim {
type: Anim.StandardSmall
}
}
Behavior on y {
Anim {
type: Anim.StandardSmall
}
}
}
MaterialIcon {
id: leading
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: (56 - height) / 2
color: root.iconColor
font.pointSize: Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.leadingIcon
visible: root.leadingIcon.length > 0
Behavior on color {
CAnim {
}
}
}
MaterialIcon {
id: trailing
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: (56 - height) / 2
color: root.isError ? DynamicColors.palette.m3error : (root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface)
font.pointSize: Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.effectiveTrailingIcon
visible: root.effectiveTrailingIcon.length > 0
MouseArea {
anchors.fill: parent
anchors.margins: -Appearance.padding.small
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: root.trailingInteractive && root.enabled
onClicked: field.echoMode = field.echoMode === TextInput.Password ? TextInput.Normal : TextInput.Password
}
}
CustomText {
id: prefix
anchors.left: leading.visible ? leading.right : parent.left
anchors.leftMargin: leading.visible ? Appearance.spacing.small : Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.medium
height: 56
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.prefixText
verticalAlignment: Text.AlignVCenter
visible: root.prefixText.length > 0 && root.floating
}
CustomText {
id: suffix
anchors.right: trailing.visible ? trailing.left : parent.right
anchors.rightMargin: trailing.visible ? Appearance.spacing.small : Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.medium
height: 56
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.suffixText
verticalAlignment: Text.AlignVCenter
visible: root.suffixText.length > 0 && root.floating
}
CustomTextField {
id: field
anchors.left: prefix.visible ? prefix.right : (leading.visible ? leading.right : parent.left)
anchors.leftMargin: prefix.visible ? Appearance.spacing.extraSmall : (leading.visible ? Appearance.spacing.small : Appearance.padding.larger)
anchors.right: suffix.visible ? suffix.left : (trailing.visible ? trailing.left : parent.right)
anchors.rightMargin: suffix.visible ? Appearance.spacing.extraSmall : (trailing.visible ? Appearance.spacing.small : Appearance.padding.larger)
anchors.top: parent.top
color: DynamicColors.palette.m3onSurface
echoMode: root.password ? TextInput.Password : TextInput.Normal
enabled: root.enabled
font.pointSize: Appearance.font.size.medium
height: 56
inputMethodHints: root.inputMethodHints
maximumLength: root.maxLength > 0 ? root.maxLength : 32767
opacity: root.enabled ? 1 : root.disabledOpacity
placeholderText: root.focused ? root.placeholder : ""
topPadding: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
validator: root.validator
verticalAlignment: TextInput.AlignVCenter
onAccepted: root.accepted()
onTextEdited: if (root.isError)
root.isError = false
}
Item {
id: supportRow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 56 + Appearance.spacing.extraSmall
implicitHeight: Math.max(supportText.implicitHeight, counter.implicitHeight)
visible: (root.isError && root.errorText.length > 0) || root.supportingText.length > 0 || root.maxLength > 0
CustomText {
id: supportText
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.larger
anchors.right: counter.visible ? counter.left : parent.right
anchors.rightMargin: Appearance.spacing.small
color: root.supportColor
font.pointSize: Appearance.font.size.small
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.isError && root.errorText.length > 0 ? root.errorText : root.supportingText
wrapMode: Text.WordWrap
}
CustomText {
id: counter
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
color: root.supportColor
font.pointSize: Appearance.font.size.small
opacity: root.enabled ? 1 : root.disabledOpacity
text: `${root.text.length}/${root.maxLength}`
visible: root.maxLength > 0
}
}
}
-76
View File
@@ -1,76 +0,0 @@
import QtQuick
Path {
id: root
required property real viewHeight
required property real viewWidth
startX: root.viewWidth / 2
startY: 0
PathAttribute {
name: "itemOpacity"
value: 0.25
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (1 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.45
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (2 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.70
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (3 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 1.00
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (4 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.70
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (5 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.45
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight
}
PathAttribute {
name: "itemOpacity"
value: 0.25
}
}
-180
View File
@@ -1,180 +0,0 @@
import QtQuick
import QtQuick.Effects
import qs.Config
Elevation {
id: root
required property int currentIndex
property bool expanded
required property int from
property color insideTextColor: DynamicColors.palette.m3onPrimary
property int itemHeight
property int listHeight: 200
property color outsideTextColor: DynamicColors.palette.m3onSurfaceVariant
readonly property var spinnerModel: root.range(root.from, root.to)
required property int to
property Item triggerItem
signal itemSelected(item: int)
function range(first, last) {
let out = [];
for (let i = first; i <= last; ++i)
out.push(i);
return out;
}
implicitHeight: root.expanded ? view.implicitHeight : 0
level: root.expanded ? 2 : 0
radius: itemHeight / 2
visible: implicitHeight > 0
z: root.expanded ? 100 : 0
Behavior on implicitHeight {
Anim {
}
}
onExpandedChanged: {
if (!root.expanded)
root.itemSelected(view.currentIndex + 1);
}
Component {
id: spinnerDelegate
Item {
id: wrapper
readonly property color delegateTextColor: wrapper.PathView.view ? wrapper.PathView.view.delegateTextColor : "white"
required property var modelData
height: root.itemHeight
opacity: wrapper.PathView.itemOpacity
visible: wrapper.PathView.onPath
width: wrapper.PathView.view ? wrapper.PathView.view.width : 0
z: wrapper.PathView.isCurrentItem ? 100 : Math.round(wrapper.PathView.itemScale * 100)
CustomText {
anchors.centerIn: parent
color: wrapper.delegateTextColor
font.pointSize: Appearance.font.size.large
text: wrapper.modelData
}
}
}
CustomClippingRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
radius: parent.radius
z: root.z
// Main visible spinner: normal/outside text color
PathView {
id: view
property color delegateTextColor: root.outsideTextColor
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
currentIndex: root.currentIndex - 1
delegate: spinnerDelegate
dragMargin: width
highlightRangeMode: PathView.StrictlyEnforceRange
implicitHeight: root.listHeight
model: root.spinnerModel
pathItemCount: 7
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
snapMode: PathView.SnapToItem
path: PathMenu {
viewHeight: view.height
viewWidth: view.width
}
}
// The selection rectangle itself
CustomRect {
id: selectionRect
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
height: root.itemHeight
radius: root.itemHeight / 2
width: parent.width
z: 2
}
// Hidden source: same PathView, but with the "inside selection" text color
Item {
id: selectedTextSource
anchors.fill: parent
layer.enabled: true
visible: false
PathView {
id: selectedTextView
property color delegateTextColor: root.insideTextColor
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
currentIndex: view.currentIndex
delegate: spinnerDelegate
dragMargin: view.dragMargin
highlightRangeMode: view.highlightRangeMode
implicitHeight: root.listHeight
interactive: false
model: view.model
// Keep this PathView visually locked to the real one
offset: view.offset
pathItemCount: view.pathItemCount
preferredHighlightBegin: view.preferredHighlightBegin
preferredHighlightEnd: view.preferredHighlightEnd
snapMode: view.snapMode
path: PathMenu {
viewHeight: selectedTextView.height
viewWidth: selectedTextView.width
}
}
}
// Mask matching the selection rectangle
Item {
id: selectionMask
anchors.fill: parent
layer.enabled: true
visible: false
CustomRect {
color: "white"
height: selectionRect.height
radius: selectionRect.radius
width: selectionRect.width
x: selectionRect.x
y: selectionRect.y
}
}
// Only show the "inside selection" text where the mask exists
MultiEffect {
anchors.fill: selectedTextSource
maskEnabled: true
maskInverted: false
maskSource: selectionMask
source: selectedTextSource
z: 3
}
}
}
-50
View File
@@ -1,50 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
CustomRect {
id: root
required property string label
required property real max
required property real min
property var onValueModified: function (value) {}
property real step: 1
required property real value
Layout.fillWidth: true
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
implicitHeight: row.implicitHeight + Appearance.padding.large * 2
radius: Appearance.rounding.normal
Behavior on implicitHeight {
Anim {
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
Layout.fillWidth: true
text: root.label
}
CustomSpinBox {
max: root.max
min: root.min
step: root.step
value: root.value
onValueModified: value => {
root.onValueModified(value);
}
}
}
}
+1 -1
View File
@@ -52,7 +52,7 @@ MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: !enabled ? undefined : Qt.PointingHandCursor cursorShape: !enabled ? undefined : Qt.PointingHandCursor
enabled: parent.enabled && !Visibilities.getForActive().isDrawing enabled: parent.enabled && !Visibilities.getForActive()?.isDrawing
hoverEnabled: true hoverEnabled: true
Behavior on stateOpacity { Behavior on stateOpacity {
+2
View File
@@ -72,10 +72,12 @@ JsonObject {
property bool upower: true property bool upower: true
} }
component Tray: JsonObject { component Tray: JsonObject {
property bool recolorIcons: false
property bool showAudio: true property bool showAudio: true
property bool showBluetooth: false property bool showBluetooth: false
property bool showMicrophone: true property bool showMicrophone: true
property bool showNetwork: false property bool showNetwork: false
property bool showOnHover: false
property bool showPower: true property bool showPower: true
property bool showWifi: false property bool showWifi: false
property int trayIconSize: 24 property int trayIconSize: 24
+1
View File
@@ -17,6 +17,7 @@ JsonObject {
property bool showMemory: true property bool showMemory: true
property bool showNetwork: true property bool showNetwork: true
property bool showStorage: true property bool showStorage: true
property bool showVram: true
} }
component Sizes: JsonObject { component Sizes: JsonObject {
readonly property int dateTimeWidth: 110 readonly property int dateTimeWidth: 110
+3 -14
View File
@@ -170,7 +170,7 @@ Item {
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
} }
if (!root.popouts.currentName.startsWith("traymenu")) { if (!root.popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} }
@@ -196,7 +196,7 @@ Item {
if (!root.visibilities.bar && Config.bar.autoHide && y < root.bar.implicitHeight) if (!root.visibilities.bar && Config.bar.autoHide && y < root.bar.implicitHeight)
root.bar.isHovered = true; root.bar.isHovered = true;
if (root.panels.sidebar.width === 0) { if (root.panels.sidebar.offsetScale === 1) {
const showOsd = root.inRightPanel(root.panels.osdWrapper, x, y); const showOsd = root.inRightPanel(root.panels.osdWrapper, x, y);
if (showOsd) { if (showOsd) {
@@ -204,7 +204,7 @@ Item {
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
} }
} else { } else {
const outOfSidebar = x < root.width - root.panels.sidebar.width; const outOfSidebar = x < root.width - root.panels.sidebar.width * (1 - root.panels.sidebar.offsetScale);
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) {
@@ -311,17 +311,6 @@ Item {
} }
} }
function onUtilitiesChanged() {
if (root.visibilities.utilities) {
const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY);
if (!inUtilitiesArea) {
root.utilitiesShortcutActive = true;
}
} else {
root.utilitiesShortcutActive = false;
}
}
target: root.visibilities target: root.visibilities
} }
} }
+3 -7
View File
@@ -184,18 +184,14 @@ Item {
Item { Item {
id: settingsWrapper id: settingsWrapper
anchors.fill: parent
clip: true clip: true
implicitHeight: settings.implicitHeight
implicitWidth: settings.implicitWidth
x: (root.width - settings.implicitWidth) / 2
y: (settings.implicitHeight + (root.height - root.bar.implicitHeight - settings.implicitHeight) / 2) * (1 - settings.offsetScale) - settings.implicitHeight - 5
Settings.Wrapper { Settings.Wrapper {
id: settings id: settings
anchors.horizontalCenter: parent.horizontalCenter anchors.centerIn: parent
anchors.top: parent.top anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale
// anchors.centerIn: parent
panels: root panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
+1 -2
View File
@@ -38,7 +38,6 @@ Region {
R { R {
panel: root.panels.osdWrapper panel: root.panels.osdWrapper
width: panel.width * (1 - root.panels.osd.offsetScale) + root.borderThickness width: panel.width * (1 - root.panels.osd.offsetScale) + root.borderThickness
x: root.win.width - width
} }
R { R {
@@ -60,7 +59,7 @@ Region {
} }
R { R {
panel: root.panels.settingsWrapper panel: root.panels.settings
} }
R { R {
+5 -18
View File
@@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -25,7 +26,7 @@ CustomWindow {
if (focusGrab.active) if (focusGrab.active)
return 0; return 0;
if (monitor?.lastIpcObject.specialWorkspace?.name || monitor?.activeWorkspace.lastIpcObject.windows > 0) if (monitor?.lastIpcObject.specialWorkspace?.name || monitor?.activeWorkspace?.lastIpcObject.windows > 0)
return 0; return 0;
return 100; return 100;
@@ -127,7 +128,7 @@ 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")) 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))
windows: [root] windows: [root]
onCleared: { onCleared: {
@@ -161,14 +162,6 @@ CustomWindow {
Component.onCompleted: Visibilities.load(root.screen, this) Component.onCompleted: Visibilities.load(root.screen, this)
} }
IpcHandler {
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
}
target: "visibilities"
}
Binding { Binding {
property: "bar" property: "bar"
target: visibilities target: visibilities
@@ -305,15 +298,9 @@ CustomWindow {
PanelBg { PanelBg {
id: settingsBg id: settingsBg
property real extraHeight: 0
deformAmount: 0.03 deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight) panel: panels.settings
implicitWidth: panels.settings.width
panel: panels.settingsWrapper
radius: Appearance.rounding.large + Appearance.padding.normal radius: Appearance.rounding.large + Appearance.padding.normal
x: panels.settingsWrapper.x + panels.settings.x + root.borderThickness
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
} }
PanelBg { PanelBg {
@@ -421,7 +408,7 @@ CustomWindow {
resources.transform: Matrix4x4 { resources.transform: Matrix4x4 {
matrix: resourcesBg.deformMatrix matrix: resourcesBg.deformMatrix
} }
settingsWrapper.transform: Matrix4x4 { settings.transform: Matrix4x4 {
matrix: settingsBg.deformMatrix matrix: settingsBg.deformMatrix
} }
sidebar.transform: Matrix4x4 { sidebar.transform: Matrix4x4 {
-12
View File
@@ -30,18 +30,6 @@ RowLayout {
} }
} }
// CustomRect {
// Layout.fillWidth: true
// color: DynamicColors.tPalette.m3surfaceContainer
// implicitHeight: resources.implicitHeight
// radius: Appearance.rounding.small
//
// Resources {
// id: resources
//
// }
// }
CustomClippingRect { CustomClippingRect {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
-17
View File
@@ -1,17 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
id: root
function getTrayIcon(id: string, icon: string): string {
if (icon.includes("?path=")) {
const [name, path] = icon.split("?path=");
icon = Qt.resolvedUrl(`${path}/${name.slice(name.lastIndexOf("/") + 1)}`);
} else if (icon.includes("qspixmap") && id === "chrome_status_icon_1") {
icon = icon.replace("qspixmap", "icon/discord-tray");
}
return icon;
}
}
-60
View File
@@ -1,60 +0,0 @@
pragma Singleton
import QtQuick
QtObject {
id: root
property Item activeMenu: null
property Item activeTrigger: null
function close(menu) {
if (!menu)
return;
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
menu.expanded = false;
}
function closeActive() {
if (activeMenu)
activeMenu.expanded = false;
activeMenu = null;
activeTrigger = null;
}
function forget(menu) {
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
}
function hit(item, scenePos) {
if (!item || !item.visible)
return false;
const p = item.mapFromItem(null, scenePos.x, scenePos.y);
return item.contains(p);
}
function open(menu, trigger) {
if (activeMenu && activeMenu !== menu)
activeMenu.expanded = false;
activeMenu = menu;
activeTrigger = trigger || null;
menu.expanded = true;
}
function toggle(menu, trigger) {
if (activeMenu === menu && menu.expanded)
close(menu);
else
open(menu, trigger);
}
}
-409
View File
@@ -1,409 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
import qs.Config
Singleton {
id: root
property string autoGpuType: "NONE"
property string cpuName: ""
property real cpuPerc
property real cpuTemp
// Individual disks: Array of { mount, used, total, free, perc }
property var disks: []
property real gpuMemTotal: 0
property real gpuMemUsed
property real gpuPerc
property real gpuTemp
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
property real lastCpuIdle
property real lastCpuTotal
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
property real memTotal
property real memUsed
property int refCount
readonly property real storagePerc: {
let totalUsed = 0;
let totalSize = 0;
for (const disk of disks) {
totalUsed += disk.used;
totalSize += disk.total;
}
return totalSize > 0 ? totalUsed / totalSize : 0;
}
function cleanCpuName(name: string): string {
return name.replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/CPU/gi, "").replace(/\d+th Gen /gi, "").replace(/\d+nd Gen /gi, "").replace(/\d+rd Gen /gi, "").replace(/\d+st Gen /gi, "").replace(/Core /gi, "").replace(/Processor/gi, "").replace(/\s+/g, " ").trim();
}
function cleanGpuName(name: string): string {
return name.replace(/NVIDIA GeForce /gi, "").replace(/NVIDIA /gi, "").replace(/AMD Radeon /gi, "").replace(/AMD /gi, "").replace(/Intel /gi, "").replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/Graphics/gi, "").replace(/\s+/g, " ").trim();
}
function formatKib(kib: real): var {
const mib = 1024;
const gib = 1024 ** 2;
const tib = 1024 ** 3;
if (kib >= tib)
return {
value: kib / tib,
unit: "TiB"
};
if (kib >= gib)
return {
value: kib / gib,
unit: "GiB"
};
if (kib >= mib)
return {
value: kib / mib,
unit: "MiB"
};
return {
value: kib,
unit: "KiB"
};
}
Timer {
interval: Config.dashboard.resourceUpdateInterval
repeat: true
running: root.refCount > 0
triggeredOnStart: true
onTriggered: {
stat.reload();
meminfo.reload();
if (root.gpuType === "GENERIC")
gpuUsage.running = true;
}
}
Timer {
interval: 60000 * 120
repeat: true
running: true
triggeredOnStart: true
onTriggered: {
storage.running = true;
}
}
Timer {
interval: Config.dashboard.resourceUpdateInterval * 5
repeat: true
running: root.refCount > 0
triggeredOnStart: true
onTriggered: {
sensors.running = true;
}
}
FileView {
id: cpuinfoInit
path: "/proc/cpuinfo"
onLoaded: {
const nameMatch = text().match(/model name\s*:\s*(.+)/);
if (nameMatch)
root.cpuName = root.cleanCpuName(nameMatch[1]);
}
}
FileView {
id: stat
path: "/proc/stat"
onLoaded: {
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
if (data) {
const stats = data.slice(1).map(n => parseInt(n, 10));
const total = stats.reduce((a, b) => a + b, 0);
const idle = stats[3] + (stats[4] ?? 0);
const totalDiff = total - root.lastCpuTotal;
const idleDiff = idle - root.lastCpuIdle;
const newCpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
root.lastCpuTotal = total;
root.lastCpuIdle = idle;
if (Math.abs(newCpuPerc - root.cpuPerc) >= 0.01)
root.cpuPerc = newCpuPerc;
}
}
}
FileView {
id: meminfo
path: "/proc/meminfo"
onLoaded: {
const data = text();
const total = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
const used = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
if (root.memTotal !== total)
root.memTotal = total;
if (Math.abs(used - root.memUsed) >= 16384)
root.memUsed = used;
}
}
Process {
id: storage
command: ["lsblk", "-b", "-o", "NAME,SIZE,TYPE,FSUSED,FSSIZE", "-P"]
stdout: StdioCollector {
onStreamFinished: {
const diskMap = {}; // Map disk name -> { name, totalSize, used, fsTotal }
const lines = text.trim().split("\n");
for (const line of lines) {
if (line.trim() === "")
continue;
const nameMatch = line.match(/NAME="([^"]+)"/);
const sizeMatch = line.match(/SIZE="([^"]+)"/);
const typeMatch = line.match(/TYPE="([^"]+)"/);
const fsusedMatch = line.match(/FSUSED="([^"]*)"/);
const fssizeMatch = line.match(/FSSIZE="([^"]*)"/);
if (!nameMatch || !typeMatch)
continue;
const name = nameMatch[1];
const type = typeMatch[1];
const size = parseInt(sizeMatch?.[1] || "0", 10);
const fsused = parseInt(fsusedMatch?.[1] || "0", 10);
const fssize = parseInt(fssizeMatch?.[1] || "0", 10);
if (type === "disk") {
// Skip zram (swap) devices
if (name.startsWith("zram"))
continue;
// Initialize disk entry
if (!diskMap[name]) {
diskMap[name] = {
name: name,
totalSize: size,
used: 0,
fsTotal: 0
};
}
} else if (type === "part") {
// Find parent disk (remove trailing numbers/p+numbers)
let parentDisk = name.replace(/p?\d+$/, "");
// For nvme devices like nvme0n1p1, parent is nvme0n1
if (name.match(/nvme\d+n\d+p\d+/))
parentDisk = name.replace(/p\d+$/, "");
// Aggregate partition usage to parent disk
if (diskMap[parentDisk]) {
diskMap[parentDisk].used += fsused;
diskMap[parentDisk].fsTotal += fssize;
}
}
}
const diskList = [];
let totalUsed = 0;
let totalSize = 0;
for (const diskName of Object.keys(diskMap).sort()) {
const disk = diskMap[diskName];
// Use filesystem total if available, otherwise use disk size
const total = disk.fsTotal > 0 ? disk.fsTotal : disk.totalSize;
const used = disk.used;
const perc = total > 0 ? used / total : 0;
// Convert bytes to KiB for consistency with formatKib
diskList.push({
mount: disk.name // Using 'mount' property for compatibility
,
used: used / 1024,
total: total / 1024,
free: (total - used) / 1024,
perc: perc
});
totalUsed += used;
totalSize += total;
}
root.disks = diskList;
}
}
}
Process {
id: gpuNameDetect
command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || lspci 2>/dev/null | grep -i 'vga\\|3d\\|display' | head -1"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const output = text.trim();
if (!output)
return;
// Check if it's from nvidia-smi (clean GPU name)
if (output.toLowerCase().includes("nvidia") || output.toLowerCase().includes("geforce") || output.toLowerCase().includes("rtx") || output.toLowerCase().includes("gtx")) {
root.gpuName = root.cleanGpuName(output);
} else {
// Parse lspci output: extract name from brackets or after colon
const bracketMatch = output.match(/\[([^\]]+)\]/);
if (bracketMatch) {
root.gpuName = root.cleanGpuName(bracketMatch[1]);
} else {
const colonMatch = output.match(/:\s*(.+)/);
if (colonMatch)
root.gpuName = root.cleanGpuName(colonMatch[1]);
}
}
}
}
}
Process {
id: gpuTypeCheck
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
running: !Config.services.gpuType
stdout: StdioCollector {
onStreamFinished: root.autoGpuType = text.trim()
}
}
Process {
id: oneshotMem
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
running: root.gpuType === "NVIDIA" && root.gpuMemTotal === 0
stdout: StdioCollector {
onStreamFinished: {
root.gpuMemTotal = Number(this.text.trim());
oneshotMem.running = false;
}
}
}
Process {
id: gpuUsageNvidia
command: ["/usr/bin/nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu,memory.used", "--format=csv,noheader,nounits", "-lms", "1000"]
running: root.refCount > 0 && root.gpuType === "NVIDIA"
stdout: SplitParser {
onRead: data => {
const parts = String(data).trim().split(/\s*,\s*/);
if (parts.length < 3)
return;
const usageRaw = parseInt(parts[0], 10);
const tempRaw = parseInt(parts[1], 10);
const memRaw = parseInt(parts[2], 10);
if (!Number.isFinite(usageRaw) || !Number.isFinite(tempRaw) || !Number.isFinite(memRaw))
return;
const newGpuPerc = Math.max(0, Math.min(1, usageRaw / 100));
const newGpuTemp = tempRaw;
const newGpuMemUsed = root.gpuMemTotal > 0 ? Math.max(0, Math.min(1, memRaw / root.gpuMemTotal)) : 0;
// Only publish meaningful changes to avoid needless binding churn / repaints
if (Math.abs(root.gpuPerc - newGpuPerc) >= 0.01)
root.gpuPerc = newGpuPerc;
if (Math.abs(root.gpuTemp - newGpuTemp) >= 1)
root.gpuTemp = newGpuTemp;
if (Math.abs(root.gpuMemUsed - newGpuMemUsed) >= 0.01)
root.gpuMemUsed = newGpuMemUsed;
}
}
}
Process {
id: gpuUsage
command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : ["echo"]
stdout: StdioCollector {
onStreamFinished: {
if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
root.gpuPerc = sum / percs.length / 100;
} else {
root.gpuPerc = 0;
root.gpuTemp = 0;
}
}
}
}
Process {
id: sensors
command: ["sensors"]
environment: ({
LANG: "C.UTF-8",
LC_ALL: "C.UTF-8"
})
stdout: StdioCollector {
onStreamFinished: {
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
if (!cpuTemp)
// If AMD Tdie pattern failed, try fallback on Tctl
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
if (cpuTemp && Math.abs(parseFloat(cpuTemp[1]) - root.cpuTemp) >= 0.5)
root.cpuTemp = parseFloat(cpuTemp[1]);
if (root.gpuType !== "GENERIC")
return;
let eligible = false;
let sum = 0;
let count = 0;
for (const line of text.trim().split("\n")) {
if (line === "Adapter: PCI adapter")
eligible = true;
else if (line === "")
eligible = false;
else if (eligible) {
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (!match)
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (match) {
sum += parseFloat(match[2]);
count++;
}
}
}
root.gpuTemp = count > 0 ? sum / count : 0;
}
}
}
}
-6
View File
@@ -1,6 +0,0 @@
import QtQuick
Text {
renderType: Text.NativeRendering
textFormat: Text.PlainText
}
-288
View File
@@ -1,288 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
property list<DesktopEntry> entryList: []
property var preppedIcons: []
property var preppedIds: []
property var preppedNames: []
// Dynamic fixups
property var regexSubstitutions: [
{
"regex": /^steam_app_(\d+)$/,
"replace": "steam_icon_$1"
},
{
"regex": /Minecraft.*/,
"replace": "minecraft-launcher"
},
{
"regex": /.*polkit.*/,
"replace": "system-lock-screen"
},
{
"regex": /gcr.prompter/,
"replace": "system-lock-screen"
}
]
property real scoreThreshold: 0.2
// Manual overrides for tricky apps
property var substitutions: ({
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot"
})
function checkCleanMatch(str) {
if (!str || str.length <= 3)
return null;
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
// Aggressive fallback: strip all separators
const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
const list = Array.from(entryList);
for (let i = 0; i < list.length; i++) {
const entry = list[i];
const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, '');
if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) {
return entry;
}
}
return null;
}
function checkFuzzySearch(str) {
if (typeof FuzzySort === 'undefined')
return null;
// Check filenames (IDs) first
if (preppedIds.length > 0) {
let results = fuzzyQuery(str, preppedIds);
if (results.length === 0) {
const underscored = str.replace(/-/g, '_').toLowerCase();
if (underscored !== str)
results = fuzzyQuery(underscored, preppedIds);
}
if (results.length > 0)
return results[0];
}
// Then icons
if (preppedIcons.length > 0) {
const results = fuzzyQuery(str, preppedIcons);
if (results.length > 0)
return results[0];
}
// Then names
if (preppedNames.length > 0) {
const results = fuzzyQuery(str, preppedNames);
if (results.length > 0)
return results[0];
}
return null;
}
// --- Lookup Helpers ---
function checkHeuristic(str) {
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
const entry = DesktopEntries.heuristicLookup(str);
if (entry)
return entry;
}
return null;
}
function checkRegex(str) {
for (let i = 0; i < regexSubstitutions.length; i++) {
const sub = regexSubstitutions[i];
const replaced = str.replace(sub.regex, sub.replace);
if (replaced !== str) {
return findAppEntry(replaced);
}
}
return null;
}
function checkSimpleTransforms(str) {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
const lower = str.toLowerCase();
const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()];
for (let i = 0; i < variants.length; i++) {
const variant = variants[i];
if (variant) {
const entry = DesktopEntries.byId(variant);
if (entry)
return entry;
}
}
return null;
}
function checkSubstitutions(str) {
let effectiveStr = substitutions[str];
if (!effectiveStr)
effectiveStr = substitutions[str.toLowerCase()];
if (effectiveStr && effectiveStr !== str) {
return findAppEntry(effectiveStr);
}
return null;
}
function distroLogoPath() {
try {
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
} catch (e) {
return "";
}
}
// Robust lookup strategy
function findAppEntry(str) {
if (!str || str.length === 0)
return null;
let result = null;
if (result = checkHeuristic(str))
return result;
if (result = checkSubstitutions(str))
return result;
if (result = checkRegex(str))
return result;
if (result = checkSimpleTransforms(str))
return result;
if (result = checkFuzzySearch(str))
return result;
if (result = checkCleanMatch(str))
return result;
return null;
}
function fuzzyQuery(search, preppedData) {
if (!search || !preppedData || preppedData.length === 0)
return [];
return FuzzySort.go(search, preppedData, {
all: true,
key: "name"
}).map(r => r.obj.entry);
}
function getFromReverseDomain(str) {
if (!str)
return "";
return str.split('.').slice(-1)[0];
}
// Deprecated shim
function guessIcon(str) {
const entry = findAppEntry(str);
return entry ? entry.icon : "image-missing";
}
function iconExists(iconName) {
if (!iconName || iconName.length === 0)
return false;
if (iconName.startsWith("/"))
return true;
const path = Quickshell.iconPath(iconName, true);
return path && path.length > 0 && !path.includes("image-missing");
}
function iconForAppId(appId, fallbackName) {
const fallback = fallbackName || "application-x-executable";
if (!appId)
return iconFromName(fallback, fallback);
const entry = findAppEntry(appId);
if (entry) {
return iconFromName(entry.icon, fallback);
}
return iconFromName(appId, fallback);
}
function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable";
try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback);
if (p && p !== "")
return p;
}
} catch (e) {}
try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
} catch (e2) {
return "";
}
}
function normalizeWithHyphens(str) {
if (!str)
return "";
return str.toLowerCase().replace(/\s+/g, "-");
}
function refreshEntries() {
if (typeof DesktopEntries === 'undefined')
return;
const values = Array.from(DesktopEntries.applications.values);
if (values) {
entryList = values.sort((a, b) => a.name.localeCompare(b.name));
updatePreppedData();
}
}
function updatePreppedData() {
if (typeof FuzzySort === 'undefined')
return;
const list = Array.from(entryList);
preppedNames = list.map(a => ({
name: FuzzySort.prepare(`${a.name} `),
entry: a
}));
preppedIcons = list.map(a => ({
name: FuzzySort.prepare(`${a.icon} `),
entry: a
}));
preppedIds = list.map(a => ({
name: FuzzySort.prepare(`${a.id} `),
entry: a
}));
}
Component.onCompleted: refreshEntries()
Connections {
function onValuesChanged() {
refreshEntries();
}
target: DesktopEntries.applications
}
}
-77
View File
@@ -1,77 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Helpers
import qs.Config
GridLayout {
id: root
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
columnSpacing: Appearance.spacing.large
columns: 2
rowSpacing: Appearance.spacing.large
rows: 1
Ref {
service: SystemUsage
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
colour: DynamicColors.palette.m3primary
icon: "memory"
value: SystemUsage.cpuPerc
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
colour: DynamicColors.palette.m3secondary
icon: "memory_alt"
value: SystemUsage.memPerc
}
component Resource: CustomRect {
id: res
required property color colour
required property string icon
required property real value
Layout.fillWidth: true
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: width
radius: Appearance.rounding.large
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
CircularProgress {
id: circ
anchors.fill: parent
bgColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
fgColour: res.colour
padding: Appearance.padding.large * 3
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
value: res.value
}
MaterialIcon {
id: icon
anchors.centerIn: parent
color: res.colour
font.pointSize: (circ.arcRadius * 0.7) || 1
font.weight: 600
text: res.icon
}
}
}
+1 -1
View File
@@ -9,5 +9,5 @@ export EGL_PLATFORM=gbm
if command -v start-hyprland >/dev/null 2>&1; then if command -v start-hyprland >/dev/null 2>&1; then
exec start-hyprland -- -c /etc/zshell-greeter/zshell-hyprland.conf exec start-hyprland -- -c /etc/zshell-greeter/zshell-hyprland.conf
else else
exec Hyprland -c /etc/zshell-greeter/zshell-hyprland.conf exec Hyprland -c /etc/zshell-greeter/zshell-hyprland.lua
fi fi
-12
View File
@@ -1,12 +0,0 @@
monitor = ,preferred,auto,1
env = XDG_SESSION_TYPE,wayland
env = QT_QPA_PLATFORM,wayland
env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
misc {
disable_hyprland_logo = true
disable_splash_rendering = true
}
exec = sh -lc 'qs -c zshell-greeter; hyprctl dispatch exit'
+14
View File
@@ -0,0 +1,14 @@
hl.env("XDG_SESSION_TYPE", "wayland")
hl.env("QT_QPA_PLATFORM", "wayland")
hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
hl.config({
misc = {
disable_hyprland_logo = true,
disable_splash_rendering = true,
},
})
hl.on("hyprland.start", function()
hl.exec_cmd("sh -lc 'qs -c zshell-greeter; hyprctl dispatch exit'")
end)
+15 -42
View File
@@ -11,8 +11,13 @@ Singleton {
id: root id: root
property bool appleDisplayPresent: false property bool appleDisplayPresent: false
readonly property var ddcMonitorMap: {
const map = {};
for (const m of ddcMonitors)
map[m.connector] = m;
return map;
}
property list<var> ddcMonitors: [] property list<var> ddcMonitors: []
property list<var> ddcServiceMon: []
readonly property list<Monitor> monitors: variants.instances readonly property list<Monitor> monitors: variants.instances
function decreaseBrightness(): void { function decreaseBrightness(): void {
@@ -56,8 +61,6 @@ Singleton {
onMonitorsChanged: { onMonitorsChanged: {
ddcMonitors = []; ddcMonitors = [];
ddcServiceMon = [];
ddcServiceProc.running = true;
ddcProc.running = true; ddcProc.running = true;
} }
@@ -92,26 +95,6 @@ Singleton {
} }
} }
Process {
id: ddcServiceProc
command: ["ddcutil-client", "detect"]
// running: true
stdout: StdioCollector {
onStreamFinished: {
const t = text.replace(/\r\n/g, "\n").trim();
const output = ("\n" + t).split(/\n(?=display:\s*\d+\s*\n)/).filter(b => b.startsWith("display:")).map(b => ({
display: Number(b.match(/^display:\s*(\d+)/m)?.[1] ?? -1),
name: (b.match(/^\s*product_name:\s*(.*)$/m)?.[1] ?? "").trim()
})).filter(d => d.display > 0);
root.ddcServiceMon = output;
}
}
}
CustomShortcut { CustomShortcut {
description: "Increase brightness" description: "Increase brightness"
name: "brightnessUp" name: "brightnessUp"
@@ -183,16 +166,12 @@ Singleton {
id: monitor id: monitor
property real brightness property real brightness
readonly property string busNum: root.ddcMonitors.find(m => m.connector === modelData.name)?.busNum ?? "" readonly property string busNum: ddcInfo?.busNum ?? ""
readonly property string displayNum: root.ddcServiceMon.find(m => m.name === modelData.model)?.display ?? "" readonly property var ddcInfo: root.ddcMonitorMap[modelData.name] ?? null
readonly property Process initProc: Process { readonly property Process initProc: Process {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (monitor.isDdcService) { if (monitor.isAppleDisplay) {
const output = text.split("\n").filter(o => o.startsWith("vcp_current_value:"))[0].split(":")[1];
const val = parseInt(output.trim());
monitor.brightness = val / 100;
} else if (monitor.isAppleDisplay) {
const val = parseInt(text.trim()); const val = parseInt(text.trim());
monitor.brightness = val / 101; monitor.brightness = val / 101;
} else { } else {
@@ -203,12 +182,11 @@ Singleton {
} }
} }
readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay") readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay")
readonly property bool isDdc: root.ddcMonitors.some(m => m.connector === modelData.name) readonly property bool isDdc: ddcInfo !== null
readonly property bool isDdcService: Config.services.ddcutilService
required property ShellScreen modelData required property ShellScreen modelData
property real queuedBrightness: NaN property real queuedBrightness: NaN
readonly property Timer timer: Timer { readonly property Timer timer: Timer {
interval: 500 interval: 400
onTriggered: { onTriggered: {
if (!isNaN(monitor.queuedBrightness)) { if (!isNaN(monitor.queuedBrightness)) {
@@ -219,9 +197,7 @@ Singleton {
} }
function initBrightness(): void { function initBrightness(): void {
if (isDdcService) if (isAppleDisplay)
initProc.command = ["ddcutil-client", "-d", displayNum, "getvcp", "10"];
else if (isAppleDisplay)
initProc.command = ["asdbctl", "get"]; initProc.command = ["asdbctl", "get"];
else if (isDdc) else if (isDdc)
initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"]; initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"];
@@ -237,28 +213,25 @@ Singleton {
if (Math.round(brightness * 100) === rounded) if (Math.round(brightness * 100) === rounded)
return; return;
if ((isDdc || isDdcService) && timer.running) { if (isDdc && timer.running) {
queuedBrightness = value; queuedBrightness = value;
return; return;
} }
brightness = value; brightness = value;
if (isDdcService) if (isAppleDisplay)
Quickshell.execDetached(["ddcutil-client", "-d", displayNum, "setvcp", "10", rounded]);
else if (isAppleDisplay)
Quickshell.execDetached(["asdbctl", "set", rounded]); Quickshell.execDetached(["asdbctl", "set", rounded]);
else if (isDdc) else if (isDdc)
Quickshell.execDetached(["ddcutil", "--disable-dynamic-sleep", "--sleep-multiplier", ".1", "--skip-ddc-checks", "-b", busNum, "setvcp", "10", rounded]); Quickshell.execDetached(["ddcutil", "--disable-dynamic-sleep", "--sleep-multiplier", ".1", "--skip-ddc-checks", "-b", busNum, "setvcp", "10", rounded]);
else else
Quickshell.execDetached(["brightnessctl", "s", `${rounded}%`]); Quickshell.execDetached(["brightnessctl", "s", `${rounded}%`]);
if (isDdc || isDdcService) if (isDdc)
timer.restart(); timer.restart();
} }
Component.onCompleted: initBrightness() Component.onCompleted: initBrightness()
onBusNumChanged: initBrightness() onBusNumChanged: initBrightness()
onDisplayNumChanged: initBrightness()
} }
} }
-97
View File
@@ -1,97 +0,0 @@
pragma Singleton
import Quickshell
import qs.Helpers
Singleton {
id: root
property int displayMonth: new Date().getMonth()
property int displayYear: new Date().getFullYear()
readonly property int weekStartDay: 1 // 0 = Sunday, 1 = Monday
function getISOWeekNumber(date: var): int {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}
function getWeekNumbers(month: int, year: int): var {
const days = getWeeksForMonth(month, year);
const weekNumbers = [];
let lastWeekNumber = -1;
for (let i = 0; i < days.length; i++) {
// Only add week numbers for days that belong to the current month
if (days[i].isCurrentMonth) {
const dayDate = new Date(days[i].year, days[i].month, days[i].day);
const weekNumber = getISOWeekNumber(dayDate);
// Only push if this is a new week
if (weekNumber !== lastWeekNumber) {
weekNumbers.push(weekNumber);
lastWeekNumber = weekNumber;
}
}
}
return weekNumbers;
}
function getWeekStartIndex(month: int, year: int): int {
const today = new Date();
if (today.getMonth() !== month || today.getFullYear() !== year) {
return 0;
}
const days = getWeeksForMonth(month, year);
for (let i = 0; i < days.length; i++) {
if (days[i].isToday) {
// Return the start index of the week containing today
return Math.floor(i / 7) * 7;
}
}
return 0;
}
function getWeeksForMonth(month: int, year: int): var {
const firstDayOfMonth = new Date(year, month, 1);
const lastDayOfMonth = new Date(year, month + 1, 0);
const days = [];
let currentDate = new Date(year, month, 1);
// Back up to the start of the first week (Sunday or Monday based on locale)
const dayOfWeek = firstDayOfMonth.getDay();
const daysToBackup = (dayOfWeek - root.weekStartDay + 7) % 7;
currentDate.setDate(currentDate.getDate() - daysToBackup);
// Collect all days, including padding from previous/next month to complete the weeks
while (true) {
days.push({
day: currentDate.getDate(),
month: currentDate.getMonth(),
year: currentDate.getFullYear(),
isCurrentMonth: currentDate.getMonth() === month,
isToday: isDateToday(currentDate)
});
currentDate.setDate(currentDate.getDate() + 1);
// Stop after we've completed a full week following the last day of the month
if (currentDate > lastDayOfMonth && days.length % 7 === 0) {
break;
}
}
return days;
}
function isDateToday(date: var): bool {
const today = new Date();
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
}
}
-17
View File
@@ -1,17 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
id: root
function getTrayIcon(id: string, icon: string): string {
if (icon.includes("?path=")) {
const [name, path] = icon.split("?path=");
icon = Qt.resolvedUrl(`${path}/${name.slice(name.lastIndexOf("/") + 1)}`);
} else if (icon.includes("qspixmap") && id === "chrome_status_icon_1") {
icon = icon.replace("qspixmap", "icon/discord-tray");
}
return icon;
}
}
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Hyprland
import qs.Helpers
Singleton {
function getInitialTitle(callback) {
let activeWindow = Hypr.activeToplevel.title;
let activeClass = Hypr.activeToplevel.lastIpcObject.class.toString();
let regex = new RegExp(activeClass, "i");
const evalTitle = activeWindow.match(regex);
callback(evalTitle);
}
}
+1 -7
View File
@@ -9,31 +9,25 @@ import qs.Config
Singleton { Singleton {
id: root id: root
// Private properties
property real _downloadSpeed: 0 property real _downloadSpeed: 0
property real _downloadTotal: 0 property real _downloadTotal: 0
// Initial readings for calculating totals
property real _initialRxBytes: 0 property real _initialRxBytes: 0
property real _initialTxBytes: 0 property real _initialTxBytes: 0
property bool _initialized: false property bool _initialized: false
// Previous readings for calculating speed
property real _prevRxBytes: 0 property real _prevRxBytes: 0
property real _prevTimestamp: 0 property real _prevTimestamp: 0
property real _prevTxBytes: 0 property real _prevTxBytes: 0
property real _uploadSpeed: 0 property real _uploadSpeed: 0
property real _uploadTotal: 0 property real _uploadTotal: 0
// History buffers for sparkline
readonly property CircularBuffer downloadBuffer: _downloadBuffer readonly property CircularBuffer downloadBuffer: _downloadBuffer
// Current speeds in bytes per second
readonly property real downloadSpeed: _downloadSpeed readonly property real downloadSpeed: _downloadSpeed
// Total bytes transferred since tracking started
readonly property real downloadTotal: _downloadTotal readonly property real downloadTotal: _downloadTotal
readonly property int historyLength: 30 readonly property int historyLength: Config.dashboard.performance.showVram ? 70 : 30
property int refCount: 0 property int refCount: 0
readonly property CircularBuffer uploadBuffer: _uploadBuffer readonly property CircularBuffer uploadBuffer: _uploadBuffer
readonly property real uploadSpeed: _uploadSpeed readonly property real uploadSpeed: _uploadSpeed
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
Singleton {
id: root
property alias centerX: notifCenterSpacing.centerX
JsonAdapter {
id: notifCenterSpacing
property int centerX
}
}
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell.Io
import Quickshell
Singleton {
id: root
property alias notifPath: storage.notifPath
JsonAdapter {
id: storage
property string notifPath: Quickshell.statePath("notifications.json")
}
}
-6
View File
@@ -1,6 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
}
-49
View File
@@ -1,49 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import ZShell.Models
import qs.Config
import qs.Modules
import qs.Helpers
import qs.Paths
Searcher {
id: root
property string actualCurrent: WallpaperPath.currentWallpaperPath
readonly property string current: showPreview ? previewPath : actualCurrent
property string previewPath
property bool showPreview: false
function preview(path: string): void {
previewPath = path;
showPreview = true;
}
function setWallpaper(path: string): void {
actualCurrent = path;
WallpaperPath.currentWallpaperPath = path;
Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/LockScreenBg.py")} --input_image=${root.actualCurrent} --output_path=${Paths.state}/lockscreen_bg.png`]);
}
function stopPreview(): void {
showPreview = false;
Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/SchemeColorGen.py")} --path=${root.actualCurrent} --thumbnail=${Paths.cache}/imagecache/thumbnail.jpg --output=${Paths.state}/scheme.json --scheme=${Config.colors.schemeType}`]);
}
extraOpts: useFuzzy ? ({}) : ({
forward: false
})
key: "relativePath"
list: wallpapers.entries
useFuzzy: true
FileSystemModel {
id: wallpapers
filter: FileSystemModel.Images
path: Config.general.wallpaperPath
recursive: true
}
}
-60
View File
@@ -1,60 +0,0 @@
pragma Singleton
import QtQuick
QtObject {
id: root
property Item activeMenu: null
property Item activeTrigger: null
function close(menu) {
if (!menu)
return;
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
menu.expanded = false;
}
function closeActive() {
if (activeMenu)
activeMenu.expanded = false;
activeMenu = null;
activeTrigger = null;
}
function forget(menu) {
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
}
function hit(item, scenePos) {
if (!item || !item.visible)
return false;
const p = item.mapFromItem(null, scenePos.x, scenePos.y);
return item.contains(p);
}
function open(menu, trigger) {
if (activeMenu && activeMenu !== menu)
activeMenu.expanded = false;
activeMenu = menu;
activeTrigger = trigger || null;
menu.expanded = true;
}
function toggle(menu, trigger) {
if (activeMenu === menu && menu.expanded)
close(menu);
else
open(menu, trigger);
}
}
-27
View File
@@ -1,27 +0,0 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
property string highlightedSetting: ""
function clear() {
highlightedSetting = "";
}
function highlight(settingName: string) {
highlightedSetting = settingName;
highlightTimer.restart();
}
Timer {
id: highlightTimer
interval: 2000
onTriggered: root.clear()
}
}
-6
View File
@@ -1,6 +0,0 @@
import QtQuick
Text {
renderType: Text.NativeRendering
textFormat: Text.PlainText
}
-288
View File
@@ -1,288 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
property list<DesktopEntry> entryList: []
property var preppedIcons: []
property var preppedIds: []
property var preppedNames: []
// Dynamic fixups
property var regexSubstitutions: [
{
"regex": /^steam_app_(\d+)$/,
"replace": "steam_icon_$1"
},
{
"regex": /Minecraft.*/,
"replace": "minecraft-launcher"
},
{
"regex": /.*polkit.*/,
"replace": "system-lock-screen"
},
{
"regex": /gcr.prompter/,
"replace": "system-lock-screen"
}
]
property real scoreThreshold: 0.2
// Manual overrides for tricky apps
property var substitutions: ({
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot"
})
function checkCleanMatch(str) {
if (!str || str.length <= 3)
return null;
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
// Aggressive fallback: strip all separators
const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
const list = Array.from(entryList);
for (let i = 0; i < list.length; i++) {
const entry = list[i];
const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, '');
if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) {
return entry;
}
}
return null;
}
function checkFuzzySearch(str) {
if (typeof FuzzySort === 'undefined')
return null;
// Check filenames (IDs) first
if (preppedIds.length > 0) {
let results = fuzzyQuery(str, preppedIds);
if (results.length === 0) {
const underscored = str.replace(/-/g, '_').toLowerCase();
if (underscored !== str)
results = fuzzyQuery(underscored, preppedIds);
}
if (results.length > 0)
return results[0];
}
// Then icons
if (preppedIcons.length > 0) {
const results = fuzzyQuery(str, preppedIcons);
if (results.length > 0)
return results[0];
}
// Then names
if (preppedNames.length > 0) {
const results = fuzzyQuery(str, preppedNames);
if (results.length > 0)
return results[0];
}
return null;
}
// --- Lookup Helpers ---
function checkHeuristic(str) {
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
const entry = DesktopEntries.heuristicLookup(str);
if (entry)
return entry;
}
return null;
}
function checkRegex(str) {
for (let i = 0; i < regexSubstitutions.length; i++) {
const sub = regexSubstitutions[i];
const replaced = str.replace(sub.regex, sub.replace);
if (replaced !== str) {
return findAppEntry(replaced);
}
}
return null;
}
function checkSimpleTransforms(str) {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
const lower = str.toLowerCase();
const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()];
for (let i = 0; i < variants.length; i++) {
const variant = variants[i];
if (variant) {
const entry = DesktopEntries.byId(variant);
if (entry)
return entry;
}
}
return null;
}
function checkSubstitutions(str) {
let effectiveStr = substitutions[str];
if (!effectiveStr)
effectiveStr = substitutions[str.toLowerCase()];
if (effectiveStr && effectiveStr !== str) {
return findAppEntry(effectiveStr);
}
return null;
}
function distroLogoPath() {
try {
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
} catch (e) {
return "";
}
}
// Robust lookup strategy
function findAppEntry(str) {
if (!str || str.length === 0)
return null;
let result = null;
if (result = checkHeuristic(str))
return result;
if (result = checkSubstitutions(str))
return result;
if (result = checkRegex(str))
return result;
if (result = checkSimpleTransforms(str))
return result;
if (result = checkFuzzySearch(str))
return result;
if (result = checkCleanMatch(str))
return result;
return null;
}
function fuzzyQuery(search, preppedData) {
if (!search || !preppedData || preppedData.length === 0)
return [];
return FuzzySort.go(search, preppedData, {
all: true,
key: "name"
}).map(r => r.obj.entry);
}
function getFromReverseDomain(str) {
if (!str)
return "";
return str.split('.').slice(-1)[0];
}
// Deprecated shim
function guessIcon(str) {
const entry = findAppEntry(str);
return entry ? entry.icon : "image-missing";
}
function iconExists(iconName) {
if (!iconName || iconName.length === 0)
return false;
if (iconName.startsWith("/"))
return true;
const path = Quickshell.iconPath(iconName, true);
return path && path.length > 0 && !path.includes("image-missing");
}
function iconForAppId(appId, fallbackName) {
const fallback = fallbackName || "application-x-executable";
if (!appId)
return iconFromName(fallback, fallback);
const entry = findAppEntry(appId);
if (entry) {
return iconFromName(entry.icon, fallback);
}
return iconFromName(appId, fallback);
}
function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable";
try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback);
if (p && p !== "")
return p;
}
} catch (e) {}
try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
} catch (e2) {
return "";
}
}
function normalizeWithHyphens(str) {
if (!str)
return "";
return str.toLowerCase().replace(/\s+/g, "-");
}
function refreshEntries() {
if (typeof DesktopEntries === 'undefined')
return;
const values = Array.from(DesktopEntries.applications.values);
if (values) {
entryList = values.sort((a, b) => a.name.localeCompare(b.name));
updatePreppedData();
}
}
function updatePreppedData() {
if (typeof FuzzySort === 'undefined')
return;
const list = Array.from(entryList);
preppedNames = list.map(a => ({
name: FuzzySort.prepare(`${a.name} `),
entry: a
}));
preppedIcons = list.map(a => ({
name: FuzzySort.prepare(`${a.icon} `),
entry: a
}));
preppedIds = list.map(a => ({
name: FuzzySort.prepare(`${a.id} `),
entry: a
}));
}
Component.onCompleted: refreshEntries()
Connections {
function onValuesChanged() {
refreshEntries();
}
target: DesktopEntries.applications
}
}
-56
View File
@@ -1,56 +0,0 @@
import Quickshell.Io
JsonObject {
property list<var> week_0: [0, 0, 0, 0, 0, 0,]
property list<var> week_1: [0, 0, 0, 0, 0, 0,]
property list<var> week_10: [0, 0, 0, 0, 0, 0,]
property list<var> week_11: [0, 0, 0, 0, 0, 0,]
property list<var> week_12: [0, 0, 0, 0, 0, 0,]
property list<var> week_13: [0, 0, 0, 0, 0, 0,]
property list<var> week_14: [0, 0, 0, 0, 0, 0,]
property list<var> week_15: [0, 0, 0, 0, 0, 0,]
property list<var> week_16: [0, 0, 0, 0, 0, 0,]
property list<var> week_17: [0, 0, 0, 0, 0, 0,]
property list<var> week_18: [0, 0, 0, 0, 0, 0,]
property list<var> week_19: [0, 0, 0, 0, 0, 0,]
property list<var> week_2: [0, 0, 0, 0, 0, 0,]
property list<var> week_20: [0, 0, 0, 0, 0, 0,]
property list<var> week_21: [0, 0, 0, 0, 0, 0,]
property list<var> week_22: [0, 0, 0, 0, 0, 0,]
property list<var> week_23: [0, 0, 0, 0, 0, 0,]
property list<var> week_24: [0, 0, 0, 0, 0, 0,]
property list<var> week_25: [0, 0, 0, 0, 0, 0,]
property list<var> week_26: [0, 0, 0, 0, 0, 0,]
property list<var> week_27: [0, 0, 0, 0, 0, 0,]
property list<var> week_28: [0, 0, 0, 0, 0, 0,]
property list<var> week_29: [0, 0, 0, 0, 0, 0,]
property list<var> week_3: [0, 0, 0, 0, 0, 0,]
property list<var> week_30: [0, 0, 0, 0, 0, 0,]
property list<var> week_31: [0, 0, 0, 0, 0, 0,]
property list<var> week_32: [0, 0, 0, 0, 0, 0,]
property list<var> week_33: [0, 0, 0, 0, 0, 0,]
property list<var> week_34: [0, 0, 0, 0, 0, 0,]
property list<var> week_35: [0, 0, 0, 0, 0, 0,]
property list<var> week_36: [0, 0, 0, 0, 0, 0,]
property list<var> week_37: [0, 0, 0, 0, 0, 0,]
property list<var> week_38: [0, 0, 0, 0, 0, 0,]
property list<var> week_39: [0, 0, 0, 0, 0, 0,]
property list<var> week_4: [0, 0, 0, 0, 0, 0,]
property list<var> week_40: [0, 0, 0, 0, 0, 0,]
property list<var> week_41: [0, 0, 0, 0, 0, 0,]
property list<var> week_42: [0, 0, 0, 0, 0, 0,]
property list<var> week_43: [0, 0, 0, 0, 0, 0,]
property list<var> week_44: [0, 0, 0, 0, 0, 0,]
property list<var> week_45: [0, 0, 0, 0, 0, 0,]
property list<var> week_46: [0, 0, 0, 0, 0, 0,]
property list<var> week_47: [0, 0, 0, 0, 0, 0,]
property list<var> week_48: [0, 0, 0, 0, 0, 0,]
property list<var> week_49: [0, 0, 0, 0, 0, 0,]
property list<var> week_5: [0, 0, 0, 0, 0, 0,]
property list<var> week_50: [0, 0, 0, 0, 0, 0,]
property list<var> week_51: [0, 0, 0, 0, 0, 0,]
property list<var> week_6: [0, 0, 0, 0, 0, 0,]
property list<var> week_7: [0, 0, 0, 0, 0, 0,]
property list<var> week_8: [0, 0, 0, 0, 0, 0,]
property list<var> week_9: [0, 0, 0, 0, 0, 0,]
}
+3 -19
View File
@@ -3,12 +3,9 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Components
import qs.Modules import qs.Modules
import qs.Config import qs.Config
import qs.Helpers
import qs.Modules.SysTray import qs.Modules.SysTray
import qs.Modules.SysTray.Widgets
import qs.Modules.Network import qs.Modules.Network
import qs.Modules.Updates import qs.Modules.Updates
@@ -26,7 +23,7 @@ RowLayout {
const ch = childAt(x, height / 2) as WrappedLoader; const ch = childAt(x, height / 2) as WrappedLoader;
if (!ch || ch?.id === "spacer") { if (!ch || ch?.id === "spacer") {
if (!popouts.currentName.startsWith("traymenu")) if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover)
popouts.hasCurrent = false; popouts.hasCurrent = false;
return; return;
} }
@@ -48,7 +45,7 @@ RowLayout {
return; return;
} }
if (!popouts.currentName.startsWith("traymenu")) if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover)
popouts.hasCurrent = false; popouts.hasCurrent = false;
} }
@@ -57,19 +54,7 @@ RowLayout {
const item = ch.item; const item = ch.item;
const itemWidth = item.implicitWidth; const itemWidth = item.implicitWidth;
if (id === "audio" && Config.bar.popouts.audio) { if (id === "updates") {
popouts.currentName = "audio";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "network" && Config.bar.popouts.network) {
popouts.currentName = "network";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "upower" && Config.bar.popouts.upower) {
popouts.currentName = "upower";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "updates") {
popouts.currentName = "updates"; popouts.currentName = "updates";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x); popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true; popouts.hasCurrent = true;
@@ -81,7 +66,6 @@ RowLayout {
Repeater { Repeater {
id: repeater id: repeater
// model: Config.bar.entries.filted(n => n.index > 50).sort(n => n.index)
model: Config.bar.entries model: Config.bar.entries
DelegateChooser { DelegateChooser {
+4 -13
View File
@@ -34,21 +34,11 @@ Item {
implicitHeight: 50 implicitHeight: 50
radius: Appearance.rounding.full radius: Appearance.rounding.full
MaterialIcon { SearchBar {
id: searchIcon
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.verticalCenter: parent.verticalCenter
text: "search"
}
CustomTextField {
id: searchField id: searchField
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: searchIcon.right anchors.left: parent.left
anchors.leftMargin: Appearance.spacing.small
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
@@ -170,7 +160,6 @@ Item {
id: lineText id: lineText
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
font.family: ClipHistory.previewIsCode ? Appearance.font.family.mono : Appearance.font.family.sans
height: lineText.paintedHeight + Appearance.padding.extraSmall * 2 height: lineText.paintedHeight + Appearance.padding.extraSmall * 2
text: lineRow.modelData.text.trim() text: lineRow.modelData.text.trim()
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -336,6 +325,8 @@ Item {
} }
model: ScriptModel { model: ScriptModel {
values: ClipHistory.fuzzyQuery(searchField.text) values: ClipHistory.fuzzyQuery(searchField.text)
onValuesChanged: view.currentIndex = 0
} }
onCurrentItemChanged: { onCurrentItemChanged: {
+1 -5
View File
@@ -52,11 +52,7 @@ Item {
placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix) placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
topPadding: Appearance.padding.larger topPadding: Appearance.padding.larger
Component.onCompleted: { Component.onCompleted: forceActiveFocus()
console.log(search.color);
console.log(search.placeholderTextColor);
forceActiveFocus();
}
Keys.onDownPressed: list.currentList?.decrementCurrentIndex() Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
Keys.onEscapePressed: root.visibilities.launcher = false Keys.onEscapePressed: root.visibilities.launcher = false
Keys.onPressed: event => { Keys.onPressed: event => {
+1 -1
View File
@@ -15,7 +15,7 @@ Item {
if (visibilities.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x) if (visibilities.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x)
max -= panels.resources.nonAnimHeight; max -= panels.resources.nonAnimHeight;
if (panels.popouts.hasCurrent) if (panels.popouts.hasCurrent)
if (panels.popouts.current.x + panels.popouts.current.width > root.x && panels.popouts.current.x < root.x + root.width) if (panels.popouts.current?.x + panels.popouts.current?.width > root.x && panels.popouts.current?.x < root.x + root.width)
max -= panels.popouts.nonAnimHeight; max -= panels.popouts.nonAnimHeight;
return max; return max;
} }
+49 -14
View File
@@ -149,36 +149,71 @@ WlSessionLockSurface {
Image { Image {
id: background id: background
anchors.bottomMargin: -8 - lockContent.positions[lockContent.positionIndex].y
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: -8 + lockContent.positions[lockContent.positionIndex].x
anchors.rightMargin: -8 - lockContent.positions[lockContent.positionIndex].x
anchors.topMargin: -8 + lockContent.positions[lockContent.positionIndex].y
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
source: WallpaperPath.lockscreenBg source: WallpaperPath.lockscreenBg
Behavior on anchors.bottomMargin {
Anim {
duration: 5000
}
}
Behavior on anchors.leftMargin {
Anim {
duration: 5000
}
}
Behavior on anchors.rightMargin {
Anim {
duration: 5000
}
}
Behavior on anchors.topMargin {
Anim {
duration: 5000
}
}
} }
Item { Item {
id: lockContent id: lockContent
property int positionIndex: 0
readonly property var positions: [Qt.point(0, 0), Qt.point(4, 0), Qt.point(4, 4), Qt.point(0, 4), Qt.point(-4, 4), Qt.point(-4, 0), Qt.point(-4, -4), Qt.point(0, -4), Qt.point(4, -4),]
readonly property int radius: size / 4 * Appearance.rounding.scale readonly property int radius: size / 4 * Appearance.rounding.scale
readonly property int size: lockIcon.implicitHeight + Appearance.padding.large * 4 readonly property int size: lockIcon.implicitHeight + Appearance.padding.large * 4
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: positions[positionIndex].x
anchors.verticalCenterOffset: positions[positionIndex].y
implicitHeight: size implicitHeight: size
implicitWidth: size implicitWidth: size
scale: 0 scale: 0
// MultiEffect { Behavior on anchors.horizontalCenterOffset {
// anchors.fill: lockBg Anim {
// autoPaddingEnabled: false duration: 5000
// blur: 1 }
// blurEnabled: true }
// blurMax: 64 Behavior on anchors.verticalCenterOffset {
// maskEnabled: true Anim {
// maskSource: lockBg duration: 5000
// }
// source: ShaderEffectSource { }
// sourceItem: background
// sourceRect: Qt.rect(lockBg.x, lockBg.y, lockBg.width, lockBg, height) Timer {
// } interval: 120000
// } repeat: true
running: true
onTriggered: {
lockContent.positionIndex = (lockContent.positionIndex + 1) % lockContent.positions.length;
}
}
CustomRect { CustomRect {
id: lockBg id: lockBg
@@ -133,7 +133,7 @@ CustomRect {
ParallelAnimation { ParallelAnimation {
Anim { Anim {
duration: Appearance.anim.durations.small duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardAccel easing.bezierCurve: Appearance.anim.curves.standardAccel
property: "scale" property: "scale"
target: listOrControls target: listOrControls
to: 0.7 to: 0.7
@@ -141,7 +141,7 @@ CustomRect {
Anim { Anim {
duration: Appearance.anim.durations.small duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardAccel easing.bezierCurve: Appearance.anim.curves.standardAccel
property: "opacity" property: "opacity"
target: listOrControls target: listOrControls
to: 0 to: 0
@@ -166,7 +166,7 @@ CustomRect {
ParallelAnimation { ParallelAnimation {
Anim { Anim {
duration: Appearance.anim.durations.small duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardDecel easing.bezierCurve: Appearance.anim.curves.standardDecel
property: "scale" property: "scale"
target: listOrControls target: listOrControls
to: 1 to: 1
@@ -174,7 +174,7 @@ CustomRect {
Anim { Anim {
duration: Appearance.anim.durations.small duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardDecel easing.bezierCurve: Appearance.anim.curves.standardDecel
property: "opacity" property: "opacity"
target: listOrControls target: listOrControls
to: 1 to: 1
@@ -217,14 +217,14 @@ CustomRect {
Anim { Anim {
duration: Appearance.anim.durations.large duration: Appearance.anim.durations.large
easing: Appearance.anim.curves.emphasizedAccel easing.bezierCurve: Appearance.anim.curves.emphasizedAccel
from: 1 from: 1
to: 0 to: 0
} }
Anim { Anim {
duration: Appearance.anim.durations.extraLarge duration: Appearance.anim.durations.extraLarge
easing: Appearance.anim.curves.emphasizedDecel easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
from: 0 from: 0
to: 1 to: 1
} }
+6 -8
View File
@@ -102,15 +102,13 @@ Item {
to: 1.0 to: 1.0
value: root.brightness value: root.brightness
onPressedChanged: { onMoved: {
if (!pressed) { if (Config.osd.allMonBrightness) {
if (Config.osd.allMonBrightness) { for (const mon of Brightness.monitors) {
for (const mon of Brightness.monitors) { mon.setBrightness(value);
mon.setBrightness(value);
}
} else {
root.monitor?.setBrightness(value);
} }
} else {
root.monitor?.setBrightness(value);
} }
} }
} }
+48 -40
View File
@@ -1,8 +1,8 @@
import Quickshell import Quickshell
import Quickshell.Services.Polkit import Quickshell.Services.Polkit
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Widgets import Quickshell.Widgets
import ZShell.Components
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
@@ -28,9 +28,7 @@ Scope {
visible: false visible: false
Connections { Connections {
target: root function onShouldShowChanged(): void {
onShouldShowChanged: {
if (root.shouldShow) { if (root.shouldShow) {
panelWindow.visible = true; panelWindow.visible = true;
openAnim.start(); openAnim.start();
@@ -38,6 +36,8 @@ Scope {
closeAnim.start(); closeAnim.start();
} }
} }
target: root
} }
Anim { Anim {
@@ -243,17 +243,16 @@ Scope {
Layout.preferredWidth: contentRow.implicitWidth Layout.preferredWidth: contentRow.implicitWidth
spacing: 8 spacing: 8
CustomButton { IconTextButton {
id: detailsButton id: detailsButton
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.preferredHeight: 40 icon: "info"
Layout.preferredWidth: 92 inactiveColor: DynamicColors.palette.m3surfaceContainer
bgColor: DynamicColors.palette.m3surfaceContainer inactiveOnColor: DynamicColors.palette.m3onSurface
enabled: true isRound: true
radius: Appearance.rounding.full shapeMorph: true
text: "Details" text: "Details"
textColor: DynamicColors.palette.m3onSurface
onClicked: { onClicked: {
panelWindow.detailsOpen = !panelWindow.detailsOpen; panelWindow.detailsOpen = !panelWindow.detailsOpen;
@@ -266,41 +265,50 @@ Scope {
Layout.fillWidth: true Layout.fillWidth: true
} }
CustomButton { ButtonRow {
id: okButton
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 40 spacing: Appearance.spacing.normal
Layout.preferredWidth: 76
bgColor: DynamicColors.palette.m3primary
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
radius: Appearance.rounding.full
text: "OK"
textColor: DynamicColors.palette.m3onPrimary
onClicked: { IconTextButton {
polkitAgent.flow.submit(passInput.text); id: okButton
passInput.text = "";
passInput.forceActiveFocus(); enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
horizontalPadding: Appearance.padding.large
icon: "check"
inactiveColor: DynamicColors.palette.m3primary
inactiveOnColor: DynamicColors.palette.m3onPrimary
isRound: true
isToggle: false
shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
text: "OK"
onClicked: {
polkitAgent.flow.submit(passInput.text);
passInput.text = "";
passInput.forceActiveFocus();
}
} }
}
CustomButton { IconTextButton {
id: cancelButton id: cancelButton
Layout.alignment: Qt.AlignRight enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
Layout.preferredHeight: 40 horizontalPadding: Appearance.padding.large
Layout.preferredWidth: 76 icon: "close"
bgColor: DynamicColors.palette.m3surfaceContainer inactiveColor: DynamicColors.palette.m3surfaceContainer
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired inactiveOnColor: DynamicColors.palette.m3onSurface
radius: Appearance.rounding.full isRound: true
text: "Cancel" isToggle: false
textColor: DynamicColors.palette.m3onSurface shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
text: "Cancel"
onClicked: { onClicked: {
root.shouldShow = false; root.shouldShow = false;
polkitAgent.flow.cancelAuthenticationRequest(); polkitAgent.flow.cancelAuthenticationRequest();
passInput.text = ""; passInput.text = "";
}
} }
} }
} }
+1
View File
@@ -9,6 +9,7 @@ CustomRect {
readonly property color accent: DynamicColors.palette.m3tertiary readonly property color accent: DynamicColors.palette.m3tertiary
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + Appearance.padding.large * 2 implicitHeight: layout.implicitHeight + Appearance.padding.large * 2
implicitWidth: layout.implicitWidth + Appearance.padding.extraLargeIncreased * 2 implicitWidth: layout.implicitWidth + Appearance.padding.extraLargeIncreased * 2
-3
View File
@@ -1,7 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import ZShell.Internal import ZShell.Internal
import qs.Modules.Resources
import qs.Helpers import qs.Helpers
import qs.Components import qs.Components
import qs.Config import qs.Config
@@ -9,8 +8,6 @@ import qs.Config
CustomRect { CustomRect {
id: root id: root
required property Wrapper wrapper
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 220 implicitHeight: 220
implicitWidth: 300 implicitWidth: 300
+89
View File
@@ -0,0 +1,89 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Services
import qs.Components
import qs.Config
CustomRect {
id: root
readonly property color accent: DynamicColors.palette.m3tertiary
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + Appearance.padding.large * 2
implicitWidth: layout.implicitWidth + Appearance.padding.extraLargeIncreased
radius: Appearance.rounding.medium
ServiceRef {
service: Gpu
}
ColumnLayout {
id: layout
anchors.centerIn: parent
spacing: Appearance.spacing.extraSmall
RowLayout {
Layout.leftMargin: -Appearance.padding.extraSmall
spacing: Appearance.spacing.small
MaterialIcon {
color: root.accent
fill: 1
text: "memory_alt"
}
CustomText {
text: qsTr("Video memory")
}
}
CircularProgress {
id: circularIndicator
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Appearance.spacing.large
fgColor: root.accent
implicitSize: usageColumn.implicitHeight + thickness + Appearance.padding.largeIncreased * 2
startAngle: -225
sweepAngle: 270
value: Gpu.memoryUsed / Gpu.memoryTotal
Behavior on clampedVal {
Anim {
}
}
ColumnLayout {
id: usageColumn
anchors.centerIn: parent
anchors.verticalCenterOffset: Appearance.padding.extraSmall
spacing: 0
CustomText {
Layout.alignment: Qt.AlignHCenter
color: root.accent
font.pointSize: Appearance.font.size.large
text: Math.round(circularIndicator.value * 100) + "%"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
text: qsTr("Used")
}
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
text: {
const fmt = UsageFmt.formatKib(Gpu.memoryUsed, Gpu.memoryTotal);
return `${fmt.value.toFixed(1)} / ${Math.floor(fmt.total)} ${fmt.unit}`;
}
}
}
}
+21 -4
View File
@@ -74,7 +74,7 @@ Item {
RowLayout { RowLayout {
spacing: Appearance.spacing.normal spacing: Appearance.spacing.normal
visible: storageCard.active || networkCard.active || memoryCard.active visible: storageCard.active || memoryCard.active || vramCard.active || networkCard1.active
WrappedLoader { WrappedLoader {
id: storageCard id: storageCard
@@ -95,15 +95,32 @@ Item {
} }
WrappedLoader { WrappedLoader {
id: networkCard id: vramCard
active: Config.dashboard.performance.showNetwork active: Config.dashboard.performance.showVram
sourceComponent: VramCard {
}
}
WrappedLoader {
id: networkCard1
active: Config.dashboard.performance.showNetwork && !vramCard.active
sourceComponent: NetworkCard { sourceComponent: NetworkCard {
wrapper: root.wrapper
} }
} }
} }
WrappedLoader {
id: networkCard2
active: Config.dashboard.performance.showNetwork && vramCard.active
sourceComponent: NetworkCard {
}
}
} }
WrappedLoader { WrappedLoader {
+52
View File
@@ -3,12 +3,64 @@ import qs.Components
import qs.Config import qs.Config
CustomRect { CustomRect {
id: root
property bool first property bool first
property bool last property bool last
property string settingAnchor
function flashHighlight(): void {
flash.restart();
}
bottomLeftRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall bottomLeftRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
bottomRightRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall bottomRightRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
topLeftRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall topLeftRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
topRightRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall topRightRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
CustomRect {
id: highlight
anchors.fill: parent
bottomLeftRadius: parent.bottomLeftRadius
bottomRightRadius: parent.bottomRightRadius
color: DynamicColors.palette.m3primary
opacity: 0
radius: parent.radius
topLeftRadius: parent.topLeftRadius
topRightRadius: parent.topRightRadius
SequentialAnimation {
id: flash
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: highlight
to: 0.2
}
Anim {
duration: Appearance.anim.durations.normal
property: "opacity"
target: highlight
to: 0.08
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: highlight
to: 0.2
}
Anim {
duration: Appearance.anim.durations.extraLarge
property: "opacity"
target: highlight
to: 0
}
}
}
} }
+100 -2
View File
@@ -9,6 +9,7 @@ import qs.Config
ColumnLayout { ColumnLayout {
id: root id: root
property bool animateScroll: false
readonly property int cappedWidth: Math.min(800, width) readonly property int cappedWidth: Math.min(800, width)
default property Item contentChild default property Item contentChild
readonly property alias flickable: flickable readonly property alias flickable: flickable
@@ -16,11 +17,100 @@ ColumnLayout {
required property SettingsState sState required property SettingsState sState
required property string title required property string title
function applySearchAnchor(): void {
if (!sState.searchAnchor)
return;
scrollRetry.tries = 0;
scrollRetry.lastHeight = -1;
scrollRetry.stableFrames = 0;
scrollRetry.restart();
}
function findAnchor(item: Item, anchor: string): Item {
if (!item)
return null;
if (item.settingAnchor !== undefined && item.settingAnchor === anchor)
return item;
const kids = item.children;
for (let i = 0; i < kids.length; i++) {
const found = findAnchor(kids[i], anchor);
if (found)
return found;
}
return null;
}
function highlightAnchor(anchor: string): void {
const row = findAnchor(contentChild, anchor);
if (row && row.flashHighlight !== undefined)
row.flashHighlight();
}
function scrollToAnchor(anchor: string): bool {
if (!anchor || !contentChild)
return false;
const row = findAnchor(contentChild, anchor);
if (!row)
return false;
const pos = row.mapToItem(flickable.contentItem, 0, 0);
const inset = flickable.height * flickable.fadeAmount + Appearance.padding.large;
const minY = -flickable.topMargin;
const maxY = Math.max(minY, flickable.contentHeight + flickable.bottomMargin - flickable.height);
const target = Math.max(minY, Math.min(pos.y - inset, maxY));
root.animateScroll = true;
flickable.contentY = target;
Qt.callLater(() => root.animateScroll = false);
if (row.flashHighlight !== undefined)
row.flashHighlight();
return true;
}
spacing: Appearance.spacing.large spacing: Appearance.spacing.large
MouseArea { // Prevent clicks from reaching flickable Component.onCompleted: applySearchAnchor()
Layout.bottomMargin: -flickable.topMargin // Extra height to block clicks on flickable top margin
Timer {
id: scrollRetry
property real lastHeight: -1
property int stableFrames: 0
property int tries: 0
interval: 16
repeat: true
onTriggered: {
const h = flickable.contentHeight;
if (h === lastHeight && h > flickable.height)
stableFrames++;
else
stableFrames = 0;
lastHeight = h;
const ready = stableFrames >= 3 || tries >= 30;
if (ready) {
if (root.scrollToAnchor(root.sState.searchAnchor))
root.sState.searchAnchor = "";
stop();
}
tries++;
}
}
Connections {
function onHighlightSetting(anchor: string): void {
root.highlightAnchor(anchor);
}
function onSearchAnchorChanged(): void {
root.applySearchAnchor();
}
target: root.sState
}
MouseArea {
Layout.bottomMargin: -flickable.topMargin
implicitHeight: header.implicitHeight - Layout.bottomMargin implicitHeight: header.implicitHeight - Layout.bottomMargin
implicitWidth: header.implicitWidth implicitWidth: header.implicitWidth
z: 1 z: 1
@@ -69,6 +159,14 @@ ColumnLayout {
fadeAmount: 0.1 fadeAmount: 0.1
topMargin: Appearance.padding.large topMargin: Appearance.padding.large
Behavior on contentY {
enabled: root.animateScroll
Anim {
type: Anim.DefaultSpatial
}
}
TapHandler { TapHandler {
onTapped: flickable.focus = true onTapped: flickable.focus = true
} }
+5
View File
@@ -9,8 +9,13 @@ CustomSwitch {
readonly property alias bg: bg readonly property alias bg: bg
property alias first: bg.first property alias first: bg.first
property alias last: bg.last property alias last: bg.last
property string settingAnchor
property string subtext property string subtext
function flashHighlight(): void {
bg.flashHighlight();
}
Layout.fillWidth: true Layout.fillWidth: true
cLayer: 2 cLayer: 2
horizontalPadding: Appearance.padding.largeIncreased horizontalPadding: Appearance.padding.largeIncreased
@@ -267,8 +267,6 @@ Item {
function restoreFromData() { function restoreFromData() {
let data = Wallpapers.getCrop(wrapper.currentScreen.name); let data = Wallpapers.getCrop(wrapper.currentScreen.name);
console.log(data.x, data.y);
if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) { if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) {
zoom = data.zoom > 0 ? data.zoom : 1.0; zoom = data.zoom > 0 ? data.zoom : 1.0;
x = imageX + (data.x * scaledImg.paintedWidth); x = imageX + (data.x * scaledImg.paintedWidth);
+6
View File
@@ -34,6 +34,12 @@ ColumnLayout {
target: root.sState target: root.sState
value: searchField.text.length > 0 value: searchField.text.length > 0
} }
Binding {
property: "searchText"
target: root.sState
value: searchField.text
}
} }
NavLocations { NavLocations {
+236 -3
View File
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Modules.Settings import qs.Modules.Settings
@@ -9,7 +10,33 @@ import qs.Modules.Settings
VerticalFadeFlickable { VerticalFadeFlickable {
id: root id: root
readonly property var groups: {
const out = [];
const byPage = ({});
for (const e of results) {
const key = e.pageIdx;
if (byPage[key] === undefined) {
byPage[key] = {
"pageIdx": e.pageIdx,
"page": e.crumbLabels[0],
"icon": e.crumbIcons[0],
"entries": []
};
out.push(byPage[key]);
}
byPage[key].entries.push(e);
}
return out;
}
readonly property var results: {
if (!searching)
return [];
const all = SettingsSearcher.query(search);
return all;
}
required property SettingsState sState required property SettingsState sState
readonly property string search: sState.searchText
readonly property bool searching: search.length > 0
bottomMargin: Appearance.padding.large bottomMargin: Appearance.padding.large
contentHeight: content.implicitHeight contentHeight: content.implicitHeight
@@ -30,14 +57,14 @@ VerticalFadeFlickable {
Repeater { Repeater {
id: list id: list
model: PageRegistry.pages model: root.searching ? [] : PageRegistry.pages
CustomRect { CustomRect {
id: item id: item
required property int index required property int index
readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1].category !== modelData.category readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1]?.category !== modelData.category
readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1].category !== modelData.category readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1]?.category !== modelData.category
readonly property bool isCurrentPage: index === root.sState.currentPageIdx readonly property bool isCurrentPage: index === root.sState.currentPageIdx
required property var modelData required property var modelData
@@ -120,6 +147,212 @@ VerticalFadeFlickable {
} }
} }
} }
Column {
id: resultList
Layout.fillWidth: true
spacing: Appearance.padding.large
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
move: Transition {
Anim {
properties: "x,y"
}
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
Repeater {
model: ScriptModel {
objectProp: "pageIdx"
values: root.groups
}
ColumnLayout {
id: group
required property int index
required property var modelData
spacing: Appearance.spacing.small
width: resultList.width
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
spacing: Appearance.spacing.small
MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
font.pointSize: Appearance.font.size.large
text: group.modelData.icon
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3secondary
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: group.modelData.page
}
}
Column {
id: cardList
Layout.fillWidth: true
spacing: Appearance.spacing.extraSmall / 2
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
move: Transition {
Anim {
properties: "x,y"
}
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
Repeater {
model: ScriptModel {
objectProp: "anchor"
values: group.modelData.entries
}
CustomRect {
id: result
required property int index
readonly property bool isFirst: index === 0
readonly property bool isLast: index === group.modelData.entries.length - 1
required property var modelData
bottomLeftRadius: layer.pressed ? Appearance.rounding.medium : isLast ? Appearance.rounding.large : Appearance.rounding.extraSmall
bottomRightRadius: layer.pressed ? Appearance.rounding.medium : isLast ? Appearance.rounding.large : Appearance.rounding.extraSmall
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: {
const h = resultLayout.implicitHeight + resultLayout.anchors.margins * 2;
return h % 2 === 0 ? h : h + 1;
}
topLeftRadius: layer.pressed ? Appearance.rounding.medium : isFirst ? Appearance.rounding.large : Appearance.rounding.extraSmall
topRightRadius: layer.pressed ? Appearance.rounding.medium : isFirst ? Appearance.rounding.large : Appearance.rounding.extraSmall
width: cardList.width
RadiusBehavior on bottomLeftRadius {
}
RadiusBehavior on bottomRightRadius {
}
RadiusBehavior on topLeftRadius {
}
RadiusBehavior on topRightRadius {
}
ColumnLayout {
id: resultLayout
anchors.fill: parent
anchors.margins: Appearance.padding.large
anchors.rightMargin: result.modelData.togglePath ? toggle.width + Appearance.padding.large * 2 : Appearance.padding.large
spacing: Appearance.spacing.small / 2
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: {
const labels = result.modelData.crumbLabels.slice(1);
const section = result.modelData.section;
const parts = section && section !== labels[labels.length - 1] ? labels.concat(section) : labels;
return parts.join(" \u203a ");
}
visible: text.length > 0
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurface
elide: Text.ElideRight
font.pointSize: Appearance.font.size.medium
text: SettingsSearcher.highlight(result.modelData.title, root.search, DynamicColors.palette.m3primary)
textFormat: text.includes("<font") ? Text.StyledText : Text.PlainText
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: SettingsSearcher.highlight(result.modelData.subtext, root.search, DynamicColors.palette.m3primary)
textFormat: text.includes("<font") ? Text.StyledText : Text.PlainText
visible: result.modelData.subtext.length > 0
}
}
StateLayer {
id: layer
z: 1
onClicked: {
root.sState.jumpToSetting(result.modelData.pageIdx, result.modelData.subPath, result.modelData.anchor);
}
}
CustomSwitch {
id: toggle
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.large
anchors.verticalCenter: parent.verticalCenter
cLayer: 3
checked: result.modelData.toggleValue
scale: 0.85
transformOrigin: Item.Right
visible: result.modelData.togglePath
z: 2
onToggled: result.modelData.setToggle(checked)
}
}
}
}
}
}
}
CustomText {
Layout.fillWidth: true
Layout.topMargin: Appearance.padding.large
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
horizontalAlignment: Text.AlignHCenter
text: qsTr("No matching settings")
visible: root.searching && root.results.length === 0
}
} }
component RadiusBehavior: Behavior { component RadiusBehavior: Behavior {
+5
View File
@@ -30,6 +30,7 @@ PageBase {
DefaultRow { DefaultRow {
first: true first: true
icon: "terminal" icon: "terminal"
settingAnchor: "apps-default-terminal"
status: Config.general.apps.terminal.join(" ") status: Config.general.apps.terminal.join(" ")
text: qsTr("Terminal") text: qsTr("Terminal")
@@ -38,6 +39,7 @@ PageBase {
DefaultRow { DefaultRow {
icon: "volume_up" icon: "volume_up"
settingAnchor: "apps-default-audio"
status: Config.general.apps.audio.join(" ") status: Config.general.apps.audio.join(" ")
text: qsTr("Audio") text: qsTr("Audio")
@@ -46,6 +48,7 @@ PageBase {
DefaultRow { DefaultRow {
icon: "play_circle" icon: "play_circle"
settingAnchor: "apps-default-playback"
status: Config.general.apps.playback.join(" ") status: Config.general.apps.playback.join(" ")
text: qsTr("Media playback") text: qsTr("Media playback")
@@ -55,6 +58,7 @@ PageBase {
DefaultRow { DefaultRow {
icon: "folder" icon: "folder"
last: true last: true
settingAnchor: "apps-default-file-manager"
status: Config.general.apps.explorer.join(" ") status: Config.general.apps.explorer.join(" ")
text: qsTr("File manager") text: qsTr("File manager")
@@ -70,6 +74,7 @@ PageBase {
first: true first: true
icon: "apps" icon: "apps"
last: true last: true
settingAnchor: "apps-all-apps"
status: qsTr("Browse installed apps, set favorites and hidden") status: qsTr("Browse installed apps, set favorites and hidden")
text: qsTr("All apps") text: qsTr("All apps")
+2
View File
@@ -25,6 +25,7 @@ PageBase {
first: true first: true
icon: Icons.getVolumeIcon(Audio.volume, Audio.muted) icon: Icons.getVolumeIcon(Audio.volume, Audio.muted)
label: qsTr("Output") label: qsTr("Output")
settingAnchor: "audio-output"
value: Audio.volume value: Audio.volume
valueLabel: Math.round(value * 100) + "%" valueLabel: Math.round(value * 100) + "%"
@@ -55,6 +56,7 @@ PageBase {
first: true first: true
icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted) icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted)
label: qsTr("Input") label: qsTr("Input")
settingAnchor: "audio-input"
value: Audio.sourceVolume value: Audio.sourceVolume
valueLabel: Math.round(value * 100) + "%" valueLabel: Math.round(value * 100) + "%"
@@ -50,6 +50,7 @@ PageBase {
first: true first: true
last: true last: true
menuItems: root.clockFormats menuItems: root.clockFormats
settingAnchor: "bar-clock-format"
subtext: qsTr("Change how time is displayed in the widget") subtext: qsTr("Change how time is displayed in the widget")
text: qsTr("Time format") text: qsTr("Time format")
@@ -25,6 +25,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.tray.showAudio checked: Config.bar.tray.showAudio
first: true first: true
settingAnchor: "bar-status-speakers"
text: qsTr("Speakers") text: qsTr("Speakers")
onToggled: Config.bar.tray.showAudio = checked onToggled: Config.bar.tray.showAudio = checked
@@ -32,6 +33,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.tray.showMicrophone checked: Config.bar.tray.showMicrophone
settingAnchor: "bar-status-mic"
text: qsTr("Microphone") text: qsTr("Microphone")
onToggled: Config.bar.tray.showMicrophone = checked onToggled: Config.bar.tray.showMicrophone = checked
@@ -62,6 +64,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.tray.showPower checked: Config.bar.tray.showPower
last: true last: true
settingAnchor: "bar-status-power"
text: qsTr("Battery") text: qsTr("Battery")
onToggled: Config.bar.tray.showPower = checked onToggled: Config.bar.tray.showPower = checked
@@ -75,6 +78,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.popouts.audio checked: Config.bar.popouts.audio
first: true first: true
settingAnchor: "bar-status-audio-popout"
subtext: qsTr("Show a details popout when hovering the audio icons") subtext: qsTr("Show a details popout when hovering the audio icons")
text: qsTr("Audio popout on hover") text: qsTr("Audio popout on hover")
@@ -84,6 +88,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.bar.popouts.upower checked: Config.bar.popouts.upower
last: true last: true
settingAnchor: "bar-status-power-popout"
subtext: qsTr("Show a details popout when hovering the power icon") subtext: qsTr("Show a details popout when hovering the power icon")
text: qsTr("Power popout on hover") text: qsTr("Power popout on hover")
+20 -1
View File
@@ -19,7 +19,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 12 from: 12
last: true settingAnchor: "bar-tray-iconsize"
stepSize: 1 stepSize: 1
text: qsTr("Icon size") text: qsTr("Icon size")
to: 24 to: 24
@@ -27,5 +27,24 @@ PageBase {
onMoved: value => Config.bar.tray.trayIconSize = value onMoved: value => Config.bar.tray.trayIconSize = value
} }
ToggleRow {
checked: Config.bar.tray.showOnHover
settingAnchor: "bar-tray-show-popout-on-hover"
subtext: Config.bar.tray.showOnHover ? qsTr("Will show context menu on hover") : qsTr("Will show context menu on right-click")
text: qsTr("Show popout on hover")
onToggled: Config.bar.tray.showOnHover = checked
}
ToggleRow {
checked: Config.bar.tray.recolorIcons
last: true
settingAnchor: "bar-tray-recolor-icons"
subtext: qsTr("Recolors icons to fit current scheme")
text: qsTr("Recolor icons")
onToggled: Config.bar.tray.recolorIcons = checked
}
} }
} }
@@ -26,6 +26,7 @@ PageBase {
checked: Config.bar.autoHide checked: Config.bar.autoHide
first: true first: true
last: true last: true
settingAnchor: "bar-autohide"
subtext: qsTr("Hide the bar, reveal on hover") subtext: qsTr("Hide the bar, reveal on hover")
text: qsTr("Auto hide") text: qsTr("Auto hide")
@@ -40,6 +41,7 @@ PageBase {
NavRow { NavRow {
first: true first: true
icon: "widgets" icon: "widgets"
settingAnchor: "bar-tray"
status: qsTr("System tray icons") status: qsTr("System tray icons")
text: qsTr("Tray") text: qsTr("Tray")
@@ -48,6 +50,7 @@ PageBase {
NavRow { NavRow {
icon: "signal_cellular_alt" icon: "signal_cellular_alt"
settingAnchor: "bar-statusicons"
status: qsTr("Visible indicators") status: qsTr("Visible indicators")
text: qsTr("Status icons") text: qsTr("Status icons")
@@ -57,6 +60,7 @@ PageBase {
NavRow { NavRow {
icon: "schedule" icon: "schedule"
last: true last: true
settingAnchor: "bar-clock"
status: qsTr("Date, icon, background") status: qsTr("Date, icon, background")
text: qsTr("Clock") text: qsTr("Clock")
@@ -28,6 +28,7 @@ PageBase {
checked: Config.dashboard.enabled checked: Config.dashboard.enabled
first: true first: true
last: true last: true
settingAnchor: "dashboard-enabled"
text: qsTr("Enabled") text: qsTr("Enabled")
onToggled: Config.dashboard.enabled = checked onToggled: Config.dashboard.enabled = checked
@@ -28,6 +28,7 @@ PageBase {
checked: Config.launcher.enabled checked: Config.launcher.enabled
first: true first: true
last: true last: true
settingAnchor: "launcher-enabled"
text: qsTr("Enabled") text: qsTr("Enabled")
onToggled: Config.launcher.enabled = checked onToggled: Config.launcher.enabled = checked
@@ -41,6 +42,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 1 from: 1
settingAnchor: "launcher-max-items-shown"
stepSize: 1 stepSize: 1
text: qsTr("Max items shown") text: qsTr("Max items shown")
to: 20 to: 20
@@ -51,6 +53,7 @@ PageBase {
SpinRow { SpinRow {
from: 1 from: 1
settingAnchor: "launcher-max-wallpapers"
stepSize: 1 stepSize: 1
text: qsTr("Max wallpapers") text: qsTr("Max wallpapers")
to: 30 to: 30
@@ -62,6 +65,7 @@ PageBase {
SpinRow { SpinRow {
from: 0 from: 0
last: true last: true
settingAnchor: "launcher-drag-threshold"
stepSize: 5 stepSize: 5
subtext: qsTr("Pixels dragged before the launcher opens") subtext: qsTr("Pixels dragged before the launcher opens")
text: qsTr("Drag threshold") text: qsTr("Drag threshold")
@@ -80,6 +84,7 @@ PageBase {
checked: Config.launcher.enableDangerousActions checked: Config.launcher.enableDangerousActions
first: true first: true
last: true last: true
settingAnchor: "launcher-enable-dangerous-actions"
subtext: qsTr("Allow actions that shut down or log out") subtext: qsTr("Allow actions that shut down or log out")
text: qsTr("Enable dangerous actions") text: qsTr("Enable dangerous actions")
@@ -94,6 +99,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.launcher.useFuzzy.apps checked: Config.launcher.useFuzzy.apps
first: true first: true
settingAnchor: "launcher-apps"
text: qsTr("Apps") text: qsTr("Apps")
onToggled: Config.launcher.useFuzzy.apps = checked onToggled: Config.launcher.useFuzzy.apps = checked
@@ -101,20 +107,15 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.launcher.useFuzzy.actions checked: Config.launcher.useFuzzy.actions
settingAnchor: "launcher-actions"
text: qsTr("Actions") text: qsTr("Actions")
onToggled: Config.launcher.useFuzzy.actions = checked onToggled: Config.launcher.useFuzzy.actions = checked
} }
ToggleRow {
checked: Config.launcher.useFuzzy.schemes
text: qsTr("Schemes")
onToggled: Config.launcher.useFuzzy.schemes = checked
}
ToggleRow { ToggleRow {
checked: Config.launcher.useFuzzy.variants checked: Config.launcher.useFuzzy.variants
settingAnchor: "launcher-variants"
text: qsTr("Variants") text: qsTr("Variants")
onToggled: Config.launcher.useFuzzy.variants = checked onToggled: Config.launcher.useFuzzy.variants = checked
@@ -123,6 +124,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.launcher.useFuzzy.wallpapers checked: Config.launcher.useFuzzy.wallpapers
last: true last: true
settingAnchor: "launcher-wallpapers"
text: qsTr("Wallpapers") text: qsTr("Wallpapers")
onToggled: Config.launcher.useFuzzy.wallpapers = checked onToggled: Config.launcher.useFuzzy.wallpapers = checked
@@ -28,6 +28,7 @@ PageBase {
checked: Config.dashboard.performance.enabled checked: Config.dashboard.performance.enabled
first: true first: true
last: true last: true
settingAnchor: "resources-enabled"
text: qsTr("Enabled") text: qsTr("Enabled")
onToggled: Config.dashboard.performance.enabled = checked onToggled: Config.dashboard.performance.enabled = checked
@@ -41,6 +42,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showBattery checked: Config.dashboard.performance.showBattery
first: true first: true
settingAnchor: "resources-battery"
text: qsTr("Battery") text: qsTr("Battery")
onToggled: Config.dashboard.performance.showBattery = checked onToggled: Config.dashboard.performance.showBattery = checked
@@ -48,13 +50,23 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showGpu checked: Config.dashboard.performance.showGpu
settingAnchor: "resources-gpu"
text: qsTr("GPU") text: qsTr("GPU")
onToggled: Config.dashboard.performance.showGpu = checked onToggled: Config.dashboard.performance.showGpu = checked
} }
ToggleRow {
checked: Config.dashboard.performance.showVram
settingAnchor: "resources-vram"
text: qsTr("Video memory")
onToggled: Config.dashboard.performance.showVram = checked
}
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showCpu checked: Config.dashboard.performance.showCpu
settingAnchor: "resources-cpu"
text: qsTr("CPU") text: qsTr("CPU")
onToggled: Config.dashboard.performance.showCpu = checked onToggled: Config.dashboard.performance.showCpu = checked
@@ -62,6 +74,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showMemory checked: Config.dashboard.performance.showMemory
settingAnchor: "resources-memory"
text: qsTr("Memory") text: qsTr("Memory")
onToggled: Config.dashboard.performance.showMemory = checked onToggled: Config.dashboard.performance.showMemory = checked
@@ -69,6 +82,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showStorage checked: Config.dashboard.performance.showStorage
settingAnchor: "resources-storage"
text: qsTr("Storage") text: qsTr("Storage")
onToggled: Config.dashboard.performance.showStorage = checked onToggled: Config.dashboard.performance.showStorage = checked
@@ -77,6 +91,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.dashboard.performance.showNetwork checked: Config.dashboard.performance.showNetwork
last: true last: true
settingAnchor: "resources-network"
text: qsTr("Network") text: qsTr("Network")
onToggled: Config.dashboard.performance.showNetwork = checked onToggled: Config.dashboard.performance.showNetwork = checked
@@ -26,6 +26,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.sidebar.enabled checked: Config.sidebar.enabled
first: true first: true
settingAnchor: "sidebar-enabled"
text: qsTr("Enabled") text: qsTr("Enabled")
onToggled: Config.sidebar.enabled = checked onToggled: Config.sidebar.enabled = checked
@@ -34,6 +35,7 @@ PageBase {
SpinRow { SpinRow {
from: 0 from: 0
last: true last: true
settingAnchor: "sidebar-drag-threshold"
stepSize: 5 stepSize: 5
subtext: qsTr("Pixels dragged before the sidebar opens") subtext: qsTr("Pixels dragged before the sidebar opens")
text: qsTr("Drag threshold") text: qsTr("Drag threshold")
+5
View File
@@ -16,6 +16,7 @@ PageBase {
NavRow { NavRow {
first: true first: true
icon: "dock_to_bottom" icon: "dock_to_bottom"
settingAnchor: "panels-bar"
status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover") status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover")
text: qsTr("Bar") text: qsTr("Bar")
@@ -24,6 +25,7 @@ PageBase {
NavRow { NavRow {
icon: "dashboard" icon: "dashboard"
settingAnchor: "panels-dashboard"
status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled") status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Dashboard") text: qsTr("Dashboard")
@@ -32,6 +34,7 @@ PageBase {
NavRow { NavRow {
icon: "insert_chart" icon: "insert_chart"
settingAnchor: "panels-resources"
status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled") status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Resources") text: qsTr("Resources")
@@ -40,6 +43,7 @@ PageBase {
NavRow { NavRow {
icon: "apps" icon: "apps"
settingAnchor: "panels-launcher"
status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled") status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Launcher") text: qsTr("Launcher")
@@ -49,6 +53,7 @@ PageBase {
NavRow { NavRow {
icon: "dock_to_right" icon: "dock_to_right"
last: true last: true
settingAnchor: "panels-sidebar"
status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled") status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Sidebar") text: qsTr("Sidebar")
@@ -28,6 +28,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.screenshot.enable_pp checked: Config.screenshot.enable_pp
first: true first: true
settingAnchor: "screenshot-enable"
text: qsTr("Enable effects") text: qsTr("Enable effects")
onToggled: Config.screenshot.enable_pp = checked onToggled: Config.screenshot.enable_pp = checked
@@ -37,6 +38,7 @@ PageBase {
active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1] active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1]
enabled: Config.screenshot.enable_pp enabled: Config.screenshot.enable_pp
last: true last: true
settingAnchor: "screenshot-mode"
subtext: qsTr("Automatic or manual effect values") subtext: qsTr("Automatic or manual effect values")
text: qsTr("Effects mode") text: qsTr("Effects mode")
@@ -67,6 +69,7 @@ PageBase {
checked: Config.screenshot.rounding checked: Config.screenshot.rounding
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true first: true
settingAnchor: "screenshot-enable-rounded-corners"
text: qsTr("Enable rounded corners") text: qsTr("Enable rounded corners")
onToggled: Config.screenshot.rounding = checked onToggled: Config.screenshot.rounding = checked
@@ -76,6 +79,7 @@ PageBase {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding
from: 0 from: 0
last: true last: true
settingAnchor: "screenshot-corner-radius"
stepSize: 1 stepSize: 1
text: qsTr("Corner radius") text: qsTr("Corner radius")
to: 50 to: 50
@@ -95,6 +99,7 @@ PageBase {
checked: Config.screenshot.shadow checked: Config.screenshot.shadow
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true first: true
settingAnchor: "screenshot-enable-shadow"
text: qsTr("Enable shadow") text: qsTr("Enable shadow")
onToggled: Config.screenshot.shadow = checked onToggled: Config.screenshot.shadow = checked
@@ -103,6 +108,7 @@ PageBase {
SpinRow { SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: 0 from: 0
settingAnchor: "screenshot-shadow-blur-amount"
stepSize: 1 stepSize: 1
text: qsTr("Shadow blur amount") text: qsTr("Shadow blur amount")
to: 100 to: 100
@@ -117,6 +123,7 @@ PageBase {
SpinRow { SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100 from: -100
settingAnchor: "screenshot-shadow-horizontal-offset"
stepSize: 10 stepSize: 10
text: qsTr("Shadow horizontal offset") text: qsTr("Shadow horizontal offset")
to: 100 to: 100
@@ -132,6 +139,7 @@ PageBase {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100 from: -100
last: true last: true
settingAnchor: "screenshot-shadow-vertical-offset"
stepSize: 10 stepSize: 10
text: qsTr("Shadow vertical offset") text: qsTr("Shadow vertical offset")
to: 100 to: 100
@@ -25,6 +25,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.notifs.showInFullscreen checked: Config.notifs.showInFullscreen
first: true first: true
settingAnchor: "notif-show-in-fullscreen"
subtext: qsTr("Whether notifications appear over fullscreen apps") subtext: qsTr("Whether notifications appear over fullscreen apps")
text: qsTr("Show in fullscreen") text: qsTr("Show in fullscreen")
@@ -33,6 +34,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.notifs.expire checked: Config.notifs.expire
settingAnchor: "notif-expire-automatically"
subtext: qsTr("Dismiss notifications after their timeout") subtext: qsTr("Dismiss notifications after their timeout")
text: qsTr("Expire automatically") text: qsTr("Expire automatically")
@@ -41,6 +43,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.notifs.openExpanded checked: Config.notifs.openExpanded
settingAnchor: "notif-open-expanded"
subtext: qsTr("Show notifications expanded by default") subtext: qsTr("Show notifications expanded by default")
text: qsTr("Open expanded") text: qsTr("Open expanded")
@@ -49,6 +52,7 @@ PageBase {
SpinRow { SpinRow {
from: 1000 from: 1000
settingAnchor: "notif-default-timeout"
stepSize: 500 stepSize: 500
subtext: qsTr("Time before a notification dismisses (ms)") subtext: qsTr("Time before a notification dismisses (ms)")
text: qsTr("Default timeout") text: qsTr("Default timeout")
@@ -61,6 +65,7 @@ PageBase {
SpinRow { SpinRow {
from: 1 from: 1
last: true last: true
settingAnchor: "notif-group-preview-count"
stepSize: 1 stepSize: 1
subtext: qsTr("Notifications shown per group before collapsing") subtext: qsTr("Notifications shown per group before collapsing")
text: qsTr("Group preview count") text: qsTr("Group preview count")
@@ -78,6 +83,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 1 from: 1
settingAnchor: "notif-visible-toasts"
stepSize: 1 stepSize: 1
subtext: qsTr("Maximum number of toasts shown at once") subtext: qsTr("Maximum number of toasts shown at once")
text: qsTr("Visible toasts") text: qsTr("Visible toasts")
@@ -89,6 +95,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.utilities.toasts.chargingChanged checked: Config.utilities.toasts.chargingChanged
settingAnchor: "notif-charging-changes"
text: qsTr("Charging changes") text: qsTr("Charging changes")
onToggled: Config.utilities.toasts.chargingChanged = checked onToggled: Config.utilities.toasts.chargingChanged = checked
@@ -96,6 +103,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.utilities.toasts.gameModeChanged checked: Config.utilities.toasts.gameModeChanged
settingAnchor: "notif-game-mode-changes"
text: qsTr("Game mode changes") text: qsTr("Game mode changes")
onToggled: Config.utilities.toasts.gameModeChanged = checked onToggled: Config.utilities.toasts.gameModeChanged = checked
@@ -110,6 +118,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.utilities.toasts.audioOutputChanged checked: Config.utilities.toasts.audioOutputChanged
settingAnchor: "notif-audio-output-changes"
text: qsTr("Audio output changes") text: qsTr("Audio output changes")
onToggled: Config.utilities.toasts.audioOutputChanged = checked onToggled: Config.utilities.toasts.audioOutputChanged = checked
@@ -118,6 +127,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.utilities.toasts.audioInputChanged checked: Config.utilities.toasts.audioInputChanged
last: true last: true
settingAnchor: "notif-audio-input-changes"
text: qsTr("Audio input changes") text: qsTr("Audio input changes")
onToggled: Config.utilities.toasts.audioInputChanged = checked onToggled: Config.utilities.toasts.audioInputChanged = checked
+11
View File
@@ -71,6 +71,7 @@ PageBase {
first: true first: true
icon: "notifications" icon: "notifications"
last: true last: true
settingAnchor: "services-notifications"
status: qsTr("Notifications, toasts, timeouts") status: qsTr("Notifications, toasts, timeouts")
text: qsTr("Notifications") text: qsTr("Notifications")
@@ -85,6 +86,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 100 from: 100
settingAnchor: "services-media-refresh"
stepSize: 50 stepSize: 50
subtext: qsTr("How often the media position updates (ms)") subtext: qsTr("How often the media position updates (ms)")
text: qsTr("Media refresh") text: qsTr("Media refresh")
@@ -97,6 +99,7 @@ PageBase {
SpinRow { SpinRow {
from: 0.5 from: 0.5
last: true last: true
settingAnchor: "services-system-stats-refresh"
stepSize: 0.5 stepSize: 0.5
subtext: qsTr("CPU, memory and GPU update interval (seconds)") subtext: qsTr("CPU, memory and GPU update interval (seconds)")
text: qsTr("System stats refresh") text: qsTr("System stats refresh")
@@ -118,6 +121,7 @@ PageBase {
first: true first: true
last: true last: true
menuItems: playerVariants.instances menuItems: playerVariants.instances
settingAnchor: "services-default-player"
subtext: qsTr("Preferred media player when several are open") subtext: qsTr("Preferred media player when several are open")
text: qsTr("Default player") text: qsTr("Default player")
@@ -132,6 +136,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 1 from: 1
settingAnchor: "services-volume-step"
stepSize: 1 stepSize: 1
subtext: qsTr("Amount the volume changes per input (%)") subtext: qsTr("Amount the volume changes per input (%)")
text: qsTr("Volume step") text: qsTr("Volume step")
@@ -143,6 +148,7 @@ PageBase {
SpinRow { SpinRow {
from: 1 from: 1
settingAnchor: "services-brightness-step"
stepSize: 1 stepSize: 1
subtext: qsTr("Amount the brightness changes per input (%)") subtext: qsTr("Amount the brightness changes per input (%)")
text: qsTr("Brightness step") text: qsTr("Brightness step")
@@ -154,6 +160,7 @@ PageBase {
SpinRow { SpinRow {
from: 1 from: 1
settingAnchor: "services-brightness-minimum"
stepSize: 1 stepSize: 1
subtext: qsTr("Lowest allowed brightness (%)") subtext: qsTr("Lowest allowed brightness (%)")
text: qsTr("Brightness minimum") text: qsTr("Brightness minimum")
@@ -166,6 +173,7 @@ PageBase {
SpinRow { SpinRow {
from: 50 from: 50
last: true last: true
settingAnchor: "services-max-volume"
stepSize: 5 stepSize: 5
subtext: qsTr("Upper limit for output volume (%)") subtext: qsTr("Upper limit for output volume (%)")
text: qsTr("Max volume") text: qsTr("Max volume")
@@ -183,6 +191,7 @@ PageBase {
SpinRow { SpinRow {
first: true first: true
from: 10 from: 10
settingAnchor: "services-visualizer-resolution"
stepSize: 2 stepSize: 2
subtext: qsTr("Resolution of the audio visualizer") subtext: qsTr("Resolution of the audio visualizer")
text: qsTr("Visualizer resolution") text: qsTr("Visualizer resolution")
@@ -194,6 +203,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.general.color.smart checked: Config.general.color.smart
settingAnchor: "services-smart-color-scheme"
subtext: qsTr("Derive theme mode from the wallpaper") subtext: qsTr("Derive theme mode from the wallpaper")
text: qsTr("Smart color scheme") text: qsTr("Smart color scheme")
@@ -205,6 +215,7 @@ PageBase {
last: true last: true
menuItems: root.gpuItems menuItems: root.gpuItems
menuOnTop: true menuOnTop: true
settingAnchor: "services-gpu"
subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type") subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type")
text: qsTr("GPU") text: qsTr("GPU")
+5
View File
@@ -49,6 +49,7 @@ PageBase {
ToggleRow { ToggleRow {
checked: Config.background.enabled checked: Config.background.enabled
first: true first: true
settingAnchor: "style-display-wallpaper"
text: qsTr("Display wallpaper") text: qsTr("Display wallpaper")
onToggled: Config.background.enabled = checked onToggled: Config.background.enabled = checked
@@ -57,6 +58,7 @@ PageBase {
ToggleRow { ToggleRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: DynamicColors.transparency.enabled checked: DynamicColors.transparency.enabled
settingAnchor: "style-transparency"
subtext: qsTr("Base %1, layers %2").arg(DynamicColors.transparency.base).arg(DynamicColors.transparency.layers) subtext: qsTr("Base %1, layers %2").arg(DynamicColors.transparency.base).arg(DynamicColors.transparency.layers)
text: qsTr("Transparency") text: qsTr("Transparency")
@@ -67,6 +69,7 @@ PageBase {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: !DynamicColors.light checked: !DynamicColors.light
last: true last: true
settingAnchor: "style-dark-theme"
text: qsTr("Dark theme") text: qsTr("Dark theme")
onToggled: DynamicColors.setMode(checked ? "dark" : "light") onToggled: DynamicColors.setMode(checked ? "dark" : "light")
@@ -88,6 +91,7 @@ PageBase {
checked: Config.general.color.scheduleDark checked: Config.general.color.scheduleDark
first: true first: true
settingAnchor: "style-schedule-dark-mode"
subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule dark mode") text: qsTr("Schedule dark mode")
@@ -129,6 +133,7 @@ PageBase {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: Config.general.color.scheduleHyprsunset checked: Config.general.color.scheduleHyprsunset
last: true last: true
settingAnchor: "style-schedule-hyprsunset"
subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule hyprsunset") text: qsTr("Schedule hyprsunset")
+178
View File
@@ -0,0 +1,178 @@
pragma Singleton
import "../../scripts/fzf.js" as Fzf
import QtQuick
import Quickshell
import ZShell
import qs.Config
Singleton {
id: root
property var fzfFinder: null
readonly property var highlightCache: ({
"search": "",
"pattern": null
})
property var inverted: ({})
property var ranking: ({})
function highlight(text: string, search: string, colour: color): string {
const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
if (search.length === 0)
return escaped;
const cache = root.highlightCache;
if (search !== cache.search) {
const tokens = tokenize(search);
cache.search = search;
if (tokens.length === 0)
cache.pattern = null;
else {
const escapedTokens = tokens.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
cache.pattern = new RegExp("\\b(" + escapedTokens.join("|") + ")", "gi");
}
}
const pattern = cache.pattern;
if (!pattern)
return escaped;
pattern.lastIndex = 0;
if (!pattern.test(escaped))
return escaped;
pattern.lastIndex = 0;
return escaped.replace(pattern, `<font color="${colour}">$1</font>`);
}
function lookup(token: string): var {
const result = ({});
const exact = root.inverted[token] !== undefined;
const keys = exact ? [token] : Object.keys(root.inverted).filter(k => k.startsWith(token));
for (const key of keys) {
const rank = root.ranking[key] ?? ({});
for (const id of root.inverted[key]) {
const w = rank[id] ?? 0.1;
if (result[id] === undefined || w > result[id])
result[id] = w;
}
}
return result;
}
function query(search: string): list<QtObject> {
const tokens = root.tokenize(search);
if (tokens.length === 0)
return [];
const scores = ({});
const hitCounts = ({});
for (const token of tokens) {
const matches = root.lookup(token);
for (const id in matches) {
scores[id] = (scores[id] ?? 0) + matches[id];
hitCounts[id] = (hitCounts[id] ?? 0) + 1;
}
}
const ranked = Object.keys(scores).filter(id => hitCounts[id] === tokens.length).sort((a, b) => scores[b] - scores[a] || (parseInt(a) - parseInt(b))).slice(0, 25);
const all = entries.instances;
const out = ranked.map(id => all[parseInt(id)]).filter(e => e !== undefined);
if (out.length < 5 && root.fzfFinder) {
const seen = ({});
for (const id of ranked)
seen[id] = true;
const fuzzy = root.fzfFinder.find(search);
for (const r of fuzzy) {
const idx = r.item.idx;
if (seen[idx])
continue;
seen[idx] = true;
const entry = all[idx];
if (entry !== undefined)
out.push(entry);
if (out.length >= 25)
break;
}
}
return out;
}
function tokenize(text: string): var {
return text.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length > 0);
}
Component.onCompleted: {
try {
const data = JSON.parse(ZUtils.settingsIndex());
entries.model = data.entries;
root.inverted = data.inverted ?? {};
root.ranking = data.ranking ?? {};
const docs = data.entries.map((e, i) => ({
idx: i,
text: e.title
}));
root.fzfFinder = new Fzf.Finder(docs, {
selector: d => d.text,
limit: 25
});
} catch (e) {
entries.model = [];
root.inverted = {};
root.ranking = {};
root.fzfFinder = null;
}
}
Variants {
id: entries
SettingEntry {
}
}
component SettingEntry: QtObject {
readonly property string anchor: modelData.anchor ?? ""
readonly property var crumbIcons: modelData.crumbIcons
readonly property var crumbLabels: modelData.crumbLabels
readonly property bool isToggle: togglePath.length > 0
required property var modelData
readonly property int pageIdx: modelData.pageIdx
readonly property string section: modelData.section ?? ""
readonly property var subPath: modelData.subPath
readonly property string subtext: modelData.subtext ?? ""
readonly property string title: modelData.title
readonly property string togglePath: modelData.togglePath ?? ""
readonly property bool toggleValue: {
if (!isToggle)
return false;
let obj = Config;
const parts = togglePath.split(".");
for (const part of parts) {
if (obj === undefined || obj === null)
return false;
obj = obj[part];
}
return obj ?? false;
}
function setToggle(value: bool): void {
if (!isToggle)
return;
const parts = togglePath.split(".");
let obj = Config;
for (let k = 0; k < parts.length - 1; k++) {
if (obj === undefined || obj === null)
return;
obj = obj[parts[k]];
}
if (obj !== undefined && obj !== null)
obj[parts[parts.length - 1]] = value;
}
}
}
+34 -1
View File
@@ -8,14 +8,19 @@ QtObject {
property bool animatingContainer property bool animatingContainer
property int currentPageIdx property int currentPageIdx
property bool isWindow property bool isWindow
property string lastAnchor
property list<int> pendingSubPath
property ShellScreen screen property ShellScreen screen
property string searchAnchor
property bool searchOpen property bool searchOpen
property string searchText
property DesktopEntry selectedApp property DesktopEntry selectedApp
property BluetoothDevice selectedBtDevice property BluetoothDevice selectedBtDevice
property string selectedWallpaperCategory property string selectedWallpaperCategory
property list<int> subPageIdxStack property list<int> subPageIdxStack
signal close signal close
signal highlightSetting(anchor: string)
signal subPageClosed signal subPageClosed
signal subPageOpened(idx: int) signal subPageOpened(idx: int)
@@ -24,10 +29,38 @@ QtObject {
subPageIdxStack.pop(); subPageIdxStack.pop();
} }
function jumpToSetting(pageIdx: int, subPath: var, anchor: string): void {
const samePage = currentPageIdx === pageIdx;
const sameSub = subPageIdxStack.length === subPath.length && subPath.every((v, i) => subPageIdxStack[i] === v);
if (samePage && sameSub && anchor === lastAnchor) {
highlightSetting(anchor);
return;
}
lastAnchor = anchor;
if (samePage && sameSub) {
searchAnchor = "";
searchAnchor = anchor;
return;
}
searchAnchor = anchor;
if (!samePage) {
pendingSubPath = subPath.slice();
currentPageIdx = pageIdx;
} else {
while (subPageIdxStack.length > 0)
closeSubPage();
for (let i = 0; i < subPath.length; i++)
openSubPage(subPath[i]);
}
}
function openSubPage(idx: int): void { function openSubPage(idx: int): void {
subPageIdxStack.push(idx); subPageIdxStack.push(idx);
subPageOpened(idx); subPageOpened(idx);
} }
onCurrentPageIdxChanged: subPageIdxStack.length = 0 onCurrentPageIdxChanged: {
subPageIdxStack = pendingSubPath;
pendingSubPath = [];
}
} }
-2
View File
@@ -39,8 +39,6 @@ Item {
sState.animatingContainer: content.opacity < 1 sState.animatingContainer: content.opacity < 1
sState.currentPageIdx: ["wallpaper"][0] sState.currentPageIdx: ["wallpaper"][0]
sState.screen: root.screen sState.screen: root.screen
onClose: console.log("shouldclose")
} }
} }
} }
+17 -32
View File
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import qs.Components import qs.Components
@@ -12,14 +13,13 @@ import qs.Config
StackView { StackView {
id: root id: root
property int biggestWidth: 0
readonly property int itemHeight: 30 readonly property int itemHeight: 30
readonly property int panelRadius: ((itemHeight / 2) + Appearance.padding.small) * Appearance.rounding.scale readonly property int panelRadius: ((itemHeight / 2) + Appearance.padding.small) * Appearance.rounding.scale
required property PopoutState popouts required property PopoutState popouts
property int rootWidth: 0 property int rootWidth: 0
required property QsMenuHandle trayItem required property QsMenuHandle trayItem
implicitHeight: currentItem.implicitHeight implicitHeight: currentItem.isSubMenu ? currentItem.implicitHeight : currentItem.implicitHeight - currentItem.spacing
implicitWidth: currentItem.implicitWidth implicitWidth: currentItem.implicitWidth
initialItem: SubMenu { initialItem: SubMenu {
@@ -46,7 +46,7 @@ StackView {
duration: 0 duration: 0
} }
} }
component SubMenu: Column { component SubMenu: ColumnLayout {
id: menu id: menu
required property QsMenuHandle handle required property QsMenuHandle handle
@@ -54,9 +54,8 @@ StackView {
property bool shown property bool shown
opacity: shown ? 1 : 0 opacity: shown ? 1 : 0
padding: 0
scale: shown ? 1 : 0.8 scale: shown ? 1 : 0.8
spacing: 4 spacing: Appearance.spacing.extraSmall
Behavior on opacity { Behavior on opacity {
Anim { Anim {
@@ -87,22 +86,27 @@ StackView {
required property int index required property int index
required property QsMenuEntry modelData required property QsMenuEntry modelData
Layout.fillWidth: true
Layout.leftMargin: modelData.isSeparator ? Appearance.padding.normal : 0
Layout.rightMargin: modelData.isSeparator ? Appearance.padding.normal : 0
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent" color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight implicitHeight: modelData.isSeparator ? (visible ? 1 : 0) : childrenLoader.item.implicitHeight
implicitWidth: root.biggestWidth implicitWidth: childrenLoader.item?.implicitWidth ?? 0
radius: Appearance.rounding.full radius: Appearance.rounding.full
visible: index !== (menuOpener.children.values.length - 1) ? true : (modelData.isSeparator ? false : true) visible: index !== (menuOpener.children.values.length - 1) ? true : (modelData.isSeparator ? false : true)
Loader { Loader {
id: children id: childrenLoader
active: !item.modelData.isSeparator active: !item.modelData.isSeparator
anchors.left: parent.left anchors.fill: parent
anchors.right: parent.right
asynchronous: true asynchronous: true
sourceComponent: Item { sourceComponent: Item {
property int iconWidth: icon.active ? icon.width + Appearance.spacing.normal + icon.anchors.rightMargin : 0
implicitHeight: root.itemHeight implicitHeight: root.itemHeight
implicitWidth: label.width + label.anchors.leftMargin * 2 + iconWidth + (expand.item?.width ?? 0)
StateLayer { StateLayer {
enabled: item.modelData.enabled enabled: item.modelData.enabled
@@ -111,8 +115,6 @@ StackView {
onClicked: { onClicked: {
const entry = item.modelData; const entry = item.modelData;
if (entry.hasChildren) { if (entry.hasChildren) {
root.rootWidth = root.biggestWidth;
root.biggestWidth = 0;
root.push(subMenuComp.createObject(null, { root.push(subMenuComp.createObject(null, {
handle: entry, handle: entry,
isSubMenu: true isSubMenu: true
@@ -161,23 +163,7 @@ StackView {
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: labelMetrics.elidedText
}
TextMetrics {
id: labelMetrics
font.family: label.font.family
font.pointSize: label.font.pointSize
text: item.modelData.text text: item.modelData.text
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
if (currentWidth > biggestWidth) {
root.biggestWidth = currentWidth;
}
}
} }
Loader { Loader {
@@ -201,17 +187,17 @@ StackView {
Loader { Loader {
id: loader id: loader
Layout.fillWidth: true
Layout.maximumHeight: active ? implicitHeight : 0
active: menu.isSubMenu active: menu.isSubMenu
asynchronous: true asynchronous: true
sourceComponent: Item { sourceComponent: Item {
implicitHeight: 30 implicitHeight: 30
implicitWidth: back.implicitWidth
Item { Item {
anchors.bottom: parent.bottom anchors.fill: parent
implicitHeight: 30 implicitHeight: 30
implicitWidth: root.biggestWidth
CustomRect { CustomRect {
anchors.fill: parent anchors.fill: parent
@@ -224,7 +210,6 @@ StackView {
onClicked: { onClicked: {
root.pop(); root.pop();
root.biggestWidth = root.rootWidth;
} }
} }
} }
+5 -4
View File
@@ -45,7 +45,8 @@ Item {
CustomRect { CustomRect {
anchors.fill: parent anchors.fill: parent
anchors.margins: 3 anchors.margins: 3
color: icon.layer.enabled && root.current ? DynamicColors.palette.m3primary : "transparent" color: icon.layer.enabled && enabled && root.current ? DynamicColors.palette.m3primary : "transparent"
enabled: !Config.bar.tray.showOnHover
radius: Appearance.rounding.full radius: Appearance.rounding.full
StateLayer { StateLayer {
@@ -57,7 +58,7 @@ Item {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
root.item.activate(); root.item.activate();
console.log(icon.source + "\n" + root.item.id); console.log(icon.source + "\n" + root.item.id);
} else if (mouse.button === Qt.RightButton && Config.bar.popouts.tray) { } else if (mouse.button === Qt.RightButton && Config.bar.popouts.tray && !Config.bar.tray.showOnHover) {
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;
@@ -76,9 +77,9 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
antialiasing: true antialiasing: true
color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface color: root.current && !Config.bar.tray.showOnHover ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
implicitSize: Config.bar.tray.trayIconSize * root.dpr implicitSize: Config.bar.tray.trayIconSize * root.dpr
layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark layer.enabled: Config.bar.tray.recolorIcons
scale: 1 / root.dpr scale: 1 / root.dpr
source: root.resolveIcon(root.item.id, root.item.icon) source: root.resolveIcon(root.item.id, root.item.icon)
} }
+3 -3
View File
@@ -61,12 +61,12 @@ RowLayout {
} }
let trayPos = mapToItem(sysTray, localX, localY); let trayPos = mapToItem(sysTray, localX, localY);
if (sysTray.contains(Qt.point(trayPos.x, trayPos.y))) { if (Config.bar.tray.showOnHover && sysTray.contains(Qt.point(trayPos.x, trayPos.y))) {
let trayRowPos = sysTray.mapToItem(sysRow, trayPos.x, trayPos.y); let trayRowPos = sysTray.mapToItem(sysRow, trayPos.x, trayPos.y);
let child = sysRow.childAt(trayRowPos.x, trayRowPos.y); let child = sysRow.childAt(trayRowPos.x, trayRowPos.y);
if (child && child.hasOwnProperty("popoutId")) { if (child && child.hasOwnProperty("ind")) {
return { return {
id: child.popoutId, id: `traymenu${child.ind}`,
item: child item: child
}; };
} }
+4 -4
View File
@@ -12,10 +12,10 @@ Item {
id: root id: root
property bool completed property bool completed
property real cropHeight: displayData.height ?? 1.0 property real cropHeight: displayData?.height ?? 1.0
property real cropWidth: displayData.width ?? 1.0 property real cropWidth: displayData?.width ?? 1.0
property real cropX: displayData.x ?? 0.0 property real cropX: displayData?.x ?? 0.0
property real cropY: displayData.y ?? 0.0 property real cropY: displayData?.y ?? 0.0
property WallpaperImage current property WallpaperImage current
readonly property var displayData: Wallpapers.getCrop(screen.name) readonly property var displayData: Wallpapers.getCrop(screen.name)
required property ShellScreen screen required property ShellScreen screen
+1 -2
View File
@@ -26,8 +26,7 @@ void BlobGroup::setColor(const QColor& c) {
} }
void BlobGroup::setCornerFill(bool e) { void BlobGroup::setCornerFill(bool e) {
if (m_cornerFill == e) if (m_cornerFill == e) return;
return;
m_cornerFill = e; m_cornerFill = e;
emit cornerFillChanged(); emit cornerFillChanged();
markDirty(); markDirty();
+24 -22
View File
@@ -9,11 +9,15 @@ class BlobShape;
class BlobInvertedRect; class BlobInvertedRect;
class BlobGroup : public QObject { class BlobGroup : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged) Q_PROPERTY(
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFillChanged) smoothingChanged)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(
bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY
cornerFillChanged)
public: public:
explicit BlobGroup(QObject* parent = nullptr); explicit BlobGroup(QObject* parent = nullptr);
@@ -27,14 +31,12 @@ Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFill
void setColor(const QColor& c); void setColor(const QColor& c);
[[nodiscard]] bool cornerFill() const { [[nodiscard]] bool cornerFill() const { return m_cornerFill; }
return m_cornerFill;
}
void setCornerFill(bool e); void setCornerFill(bool e);
void addShape(BlobShape* shape); void addShape(BlobShape* shape);
void removeShape(BlobShape* shape); void removeShape(BlobShape* shape);
void setInvertedRect(BlobInvertedRect* rect); void setInvertedRect(BlobInvertedRect* rect);
void clearInvertedRect(BlobInvertedRect* rect); void clearInvertedRect(BlobInvertedRect* rect);
@@ -49,16 +51,16 @@ void removeShape(BlobShape* shape);
void markShapeDirty(BlobShape* source); void markShapeDirty(BlobShape* source);
void ensurePhysicsUpdated(); void ensurePhysicsUpdated();
signals: signals:
void smoothingChanged(); void smoothingChanged();
void colorChanged(); void colorChanged();
void cornerFillChanged(); void cornerFillChanged();
private: private:
qreal m_smoothing = 32.0; qreal m_smoothing = 32.0;
QColor m_color{ 0x44, 0x88, 0xff }; QColor m_color{0x44, 0x88, 0xff};
bool m_cornerFill = true; bool m_cornerFill = true;
QList<BlobShape*> m_shapes; QList<BlobShape*> m_shapes;
BlobInvertedRect* m_invertedRect = nullptr; BlobInvertedRect* m_invertedRect = nullptr;
bool m_physicsUpdated = false; bool m_physicsUpdated = false;
}; };
+28 -29
View File
@@ -32,11 +32,9 @@ void BlobRect::updatePolish() {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
[this]() { [this]() {
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty(); },
}, Qt::QueuedConnection);
Qt::QueuedConnection
);
} }
} else { } else {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
@@ -184,8 +182,7 @@ bool BlobRect::isExcluded(const BlobShape* other) const {
bool BlobRect::isCornerExcluded(const BlobShape* other) const { bool BlobRect::isCornerExcluded(const BlobShape* other) const {
for (const auto& ptr : m_excludeCorners) { for (const auto& ptr : m_excludeCorners) {
if (ptr == other) if (ptr == other) return true;
return true;
} }
return false; return false;
} }
@@ -203,8 +200,15 @@ QQmlListProperty<BlobRect> BlobRect::exclude() {
} }
QQmlListProperty<BlobRect> BlobRect::excludeCorners() { QQmlListProperty<BlobRect> BlobRect::excludeCorners() {
return QQmlListProperty<BlobRect>(this, nullptr, &excludeCornersAppend, &excludeCornersCount, &excludeCornersAt, return QQmlListProperty<BlobRect>(
&excludeCornersClear, &excludeCornersReplace, &excludeCornersRemoveLast); this,
nullptr,
&excludeCornersAppend,
&excludeCornersCount,
&excludeCornersAt,
&excludeCornersClear,
&excludeCornersReplace,
&excludeCornersRemoveLast);
} }
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) { void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
@@ -249,11 +253,11 @@ void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
emit self->excludeChanged(); emit self->excludeChanged();
} }
void BlobRect::excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) { void BlobRect::excludeCornersAppend(
QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners.append(rect); self->m_excludeCorners.append(rect);
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeCornersChanged(); emit self->excludeCornersChanged();
} }
@@ -262,36 +266,33 @@ qsizetype BlobRect::excludeCornersCount(QQmlListProperty<BlobRect>* prop) {
return self->m_excludeCorners.size(); return self->m_excludeCorners.size();
} }
BlobRect* BlobRect::excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index) { BlobRect* BlobRect::excludeCornersAt(
QQmlListProperty<BlobRect>* prop, qsizetype index) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
return self->m_excludeCorners.at(index); return self->m_excludeCorners.at(index);
} }
void BlobRect::excludeCornersClear(QQmlListProperty<BlobRect>* prop) { void BlobRect::excludeCornersClear(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty()) if (self->m_excludeCorners.isEmpty()) return;
return;
self->m_excludeCorners.clear(); self->m_excludeCorners.clear();
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeCornersChanged(); emit self->excludeCornersChanged();
} }
void BlobRect::excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) { void BlobRect::excludeCornersReplace(
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners[index] = rect; self->m_excludeCorners[index] = rect;
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeCornersChanged(); emit self->excludeCornersChanged();
} }
void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) { void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty()) if (self->m_excludeCorners.isEmpty()) return;
return;
self->m_excludeCorners.removeLast(); self->m_excludeCorners.removeLast();
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeCornersChanged(); emit self->excludeCornersChanged();
} }
@@ -319,11 +320,9 @@ void BlobRect::checkAtRest(float speed) {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
[this]() { [this]() {
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty(); },
}, Qt::QueuedConnection);
Qt::QueuedConnection
);
} }
} }
} }
+60 -41
View File
@@ -8,18 +8,32 @@
#include <qqmllist.h> #include <qqmllist.h>
class BlobRect : public BlobShape { class BlobRect : public BlobShape {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged) Q_PROPERTY(
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged) qreal stiffness READ stiffness WRITE setStiffness NOTIFY
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged) stiffnessChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged) Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> excludeCorners READ excludeCorners NOTIFY excludeCornersChanged) Q_PROPERTY(
Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged) qreal deformScale READ deformScale WRITE setDeformScale NOTIFY
Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged) deformScaleChanged)
Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged) Q_PROPERTY(
Q_PROPERTY( QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged) Q_PROPERTY(
QQmlListProperty<BlobRect> excludeCorners READ excludeCorners NOTIFY
excludeCornersChanged)
Q_PROPERTY(
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
topLeftRadiusChanged)
Q_PROPERTY(
qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY
topRightRadiusChanged)
Q_PROPERTY(
qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius
NOTIFY bottomLeftRadiusChanged)
Q_PROPERTY(
qreal bottomRightRadius READ bottomRightRadius WRITE
setBottomRightRadius NOTIFY bottomRightRadiusChanged)
public: public:
explicit BlobRect(QQuickItem* parent = nullptr); explicit BlobRect(QQuickItem* parent = nullptr);
@@ -52,12 +66,12 @@ Q_PROPERTY(
} }
} }
QQmlListProperty<BlobRect> exclude(); QQmlListProperty<BlobRect> exclude();
QQmlListProperty<BlobRect> excludeCorners(); QQmlListProperty<BlobRect> excludeCorners();
bool isExcluded(const BlobShape* other) const override; bool isExcluded(const BlobShape* other) const override;
bool isCornerExcluded(const BlobShape* other) const override; bool isCornerExcluded(const BlobShape* other) const override;
void cornerRadii(float out[4]) const override; void cornerRadii(float out[4]) const override;
[[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; } [[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
@@ -77,16 +91,16 @@ void cornerRadii(float out[4]) const override;
void setBottomRightRadius(qreal r); void setBottomRightRadius(qreal r);
signals: signals:
void stiffnessChanged(); void stiffnessChanged();
void dampingChanged(); void dampingChanged();
void deformScaleChanged(); void deformScaleChanged();
void excludeChanged(); void excludeChanged();
void excludeCornersChanged(); void excludeCornersChanged();
void topLeftRadiusChanged(); void topLeftRadiusChanged();
void topRightRadiusChanged(); void topRightRadiusChanged();
void bottomLeftRadiusChanged(); void bottomLeftRadiusChanged();
void bottomRightRadiusChanged(); void bottomRightRadiusChanged();
protected: protected:
void updatePolish() override; void updatePolish() override;
@@ -121,20 +135,25 @@ void bottomRightRadiusChanged();
qreal m_bottomLeftRadius = -1; qreal m_bottomLeftRadius = -1;
qreal m_bottomRightRadius = -1; qreal m_bottomRightRadius = -1;
QList<QPointer<BlobRect> > m_exclude; QList<QPointer<BlobRect>> m_exclude;
QList<QPointer<BlobRect> > m_excludeCorners; QList<QPointer<BlobRect>> m_excludeCorners;
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect); static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop); static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
static BlobRect* excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index); static BlobRect* excludeAt(
static void excludeClear(QQmlListProperty<BlobRect>* prop); QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect); static void excludeClear(QQmlListProperty<BlobRect>* prop);
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop); static void excludeReplace(
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
static void excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect); static void excludeCornersAppend(
static qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop); QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static BlobRect* excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index); static qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop);
static void excludeCornersClear(QQmlListProperty<BlobRect>* prop); static BlobRect* excludeCornersAt(
static void excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect); QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop); static void excludeCornersClear(QQmlListProperty<BlobRect>* prop);
static void excludeCornersReplace(
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop);
}; };

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