Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fc1a123fa | ||
|
|
672cb07547 | ||
|
|
e3b2d33a11 | ||
|
|
2a40696292 | ||
|
|
80ee714e91 | ||
|
|
2c572c3250 | ||
|
|
0912a8e40c | ||
|
|
0d17621180 | ||
|
|
0ae6d6fc55 | ||
|
|
9300da258e | ||
|
|
298cbbf424 | ||
|
|
b19b630a77 | ||
|
|
75f0f2071d | ||
|
|
bfea718547 | ||
|
|
5505ac19ba | ||
|
|
9d2dc281ec | ||
|
|
05c8851143 | ||
|
|
1cbe9a9f8a | ||
|
|
71b25bff49 | ||
|
|
87433334c7 | ||
|
|
6d5609fe5b | ||
|
|
ffefd3e202 | ||
|
|
2db6a57c0b | ||
|
|
0c5871e108 | ||
|
|
6ad9fe5e07 | ||
|
|
8c11aead18 | ||
|
|
d013be0f9a | ||
|
|
69774e7759 | ||
|
|
cdf66bff23 | ||
|
|
562770595f | ||
|
|
9126b04b10 | ||
|
|
55dab8f7ce | ||
|
|
c62695bfff | ||
|
|
2b06a33508 | ||
|
|
4d731c5a65 | ||
|
|
53836aec1a | ||
|
|
355106cb2b | ||
|
|
865b5bda53 | ||
|
|
2b89c8e4a1 | ||
|
|
61019204d8 |
@@ -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 .
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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"
|
|
||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint-format:
|
fmt:
|
||||||
runs-on: alpine
|
runs-on: alpine
|
||||||
container: node:26-alpine
|
container: node:26-alpine
|
||||||
|
|
||||||
@@ -23,11 +23,28 @@ jobs:
|
|||||||
pip install --no-cache-dir ruff
|
pip install --no-cache-dir ruff
|
||||||
|
|
||||||
- name: Format check
|
- name: Format check
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
ruff format --check .
|
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
|
- name: Lint
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
@@ -63,3 +80,30 @@ jobs:
|
|||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
cd cli
|
cd cli
|
||||||
python -m pytest tests/ -v
|
python -m pytest tests/ -v
|
||||||
|
|
||||||
|
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/
|
||||||
@@ -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
|
||||||
+118
-2
@@ -36,13 +36,15 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
set(ENABLE_MODULES "plugin;shell" CACHE STRING "Modules to build/install")
|
set(ENABLE_MODULES "plugin;shell;m3shapes" CACHE STRING "Modules to build/install")
|
||||||
|
|
||||||
set(INSTALL_LIBDIR "usr/lib/ZShell" CACHE STRING "Library install dir")
|
set(INSTALL_LIBDIR "usr/lib/ZShell" CACHE STRING "Library install dir")
|
||||||
set(INSTALL_QMLDIR "usr/lib/qt6/qml" CACHE STRING "QML install dir")
|
set(INSTALL_QMLDIR "usr/lib/qt6/qml" CACHE STRING "QML install dir")
|
||||||
set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell config install dir")
|
set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell config install dir")
|
||||||
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
|
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
|
||||||
|
|
||||||
|
set(CMAKE_INSTALL_MESSAGE NEVER)
|
||||||
|
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
|
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
|
||||||
-Wold-style-cast -Wnull-dereference -Wdouble-promotion
|
-Wold-style-cast -Wnull-dereference -Wdouble-promotion
|
||||||
@@ -52,9 +54,104 @@ 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)
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if("shell" IN_LIST ENABLE_MODULES)
|
if("shell" IN_LIST ENABLE_MODULES)
|
||||||
@@ -71,3 +168,22 @@ if("shell" IN_LIST ENABLE_MODULES)
|
|||||||
# Greeter
|
# Greeter
|
||||||
install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}")
|
install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if("m3shapes" IN_LIST ENABLE_MODULES)
|
||||||
|
message(STATUS "Fetching M3Shapes module")
|
||||||
|
include(FetchContent)
|
||||||
|
set(M3SHAPES_REV bdc327b29f95394a732baf3c9b19658ba23755b6)
|
||||||
|
FetchContent_Declare(
|
||||||
|
m3shapes_external
|
||||||
|
GIT_REPOSITORY https://github.com/soramanew/m3shapes.git
|
||||||
|
GIT_TAG ${M3SHAPES_REV}
|
||||||
|
SOURCE_DIR "${CMAKE_BINARY_DIR}/_deps/m3shapes-${M3SHAPES_REV}"
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(m3shapes_external)
|
||||||
|
message(STATUS "Done fetching M3Shapes module")
|
||||||
|
|
||||||
|
# Fix m3shapes wrong rpath
|
||||||
|
if(TARGET m3shapesplugin)
|
||||||
|
set_target_properties(m3shapesplugin PROPERTIES INSTALL_RPATH "$ORIGIN")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import M3Shapes
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
MaterialShape {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool animated: true
|
||||||
|
property real cRotation
|
||||||
|
property bool containsIcon
|
||||||
|
property real dampingRatio: 0.6
|
||||||
|
property real lRotation
|
||||||
|
property int morphAnimRotation: 60
|
||||||
|
property real morphScale: 0.14
|
||||||
|
property alias rotateAnimDuration: rotateAnim.duration
|
||||||
|
property int shapeIndex
|
||||||
|
property list<int> shapes: {
|
||||||
|
if (containsIcon)
|
||||||
|
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
|
||||||
|
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pentagon, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
|
||||||
|
}
|
||||||
|
readonly property real springDuration: {
|
||||||
|
const wn = Math.sqrt(stiffness);
|
||||||
|
const r = -dampingRatio * wn;
|
||||||
|
const c = 1 / Math.sqrt(1 - dampingRatio * dampingRatio);
|
||||||
|
return Math.log(visibilityThreshold / c) / r;
|
||||||
|
}
|
||||||
|
readonly property real springMaxVelocity: {
|
||||||
|
const wn = Math.sqrt(stiffness);
|
||||||
|
const factor = Math.exp(-z * Math.acos(z) / Math.sqrt(1 - z * z));
|
||||||
|
return wn * factor;
|
||||||
|
}
|
||||||
|
property bool springSettled: true
|
||||||
|
property real stiffness: 180
|
||||||
|
property real thisLRotation
|
||||||
|
property real visibilityThreshold: 0.075
|
||||||
|
|
||||||
|
function spring(t: real): var {
|
||||||
|
const wn = Math.sqrt(stiffness);
|
||||||
|
const za = dampingRatio * wn;
|
||||||
|
|
||||||
|
const wd = wn * Math.sqrt(1 - dampingRatio * dampingRatio);
|
||||||
|
const r = za / wd;
|
||||||
|
const pos = 1 - Math.exp(-za * t) * (Math.cos(wd * t) + r * Math.sin(wd * t));
|
||||||
|
const vel = Math.exp(-za * t) * (wn * wn / wd) * Math.sin(wd * t);
|
||||||
|
|
||||||
|
return [pos, vel];
|
||||||
|
}
|
||||||
|
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
implicitSize: 38
|
||||||
|
toShape: shapes[0]
|
||||||
|
|
||||||
|
RotationAnimation on cRotation {
|
||||||
|
id: rotateAnim
|
||||||
|
|
||||||
|
duration: 4666
|
||||||
|
easing.type: Easing.Linear
|
||||||
|
from: 0
|
||||||
|
loops: Animation.Infinite
|
||||||
|
running: root.animated
|
||||||
|
to: 360
|
||||||
|
}
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElapsedTimer {
|
||||||
|
id: timer
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameAnimation {
|
||||||
|
running: root.animated && !root.springSettled
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
const t = timer.elapsed();
|
||||||
|
|
||||||
|
if (t >= root.springDuration) {
|
||||||
|
root.springSettled = true;
|
||||||
|
} else {
|
||||||
|
const [pos, vel] = root.spring(t);
|
||||||
|
root.morphProgress = Math.min(1, pos); // Overshooting the morph looks weird
|
||||||
|
root.thisLRotation = pos * root.morphAnimRotation;
|
||||||
|
root.scale = 1 + vel * root.morphScale / root.springMaxVelocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 650
|
||||||
|
repeat: true
|
||||||
|
running: root.animated
|
||||||
|
triggeredOnStart: true
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
root.beginBatchUpdate();
|
||||||
|
root.fromShape = root.toShape;
|
||||||
|
root.shapeIndex = (root.shapeIndex + 1) % root.shapes.length;
|
||||||
|
root.toShape = root.shapes[root.shapeIndex];
|
||||||
|
root.morphProgress = 0;
|
||||||
|
|
||||||
|
root.rotation = root.rotation;
|
||||||
|
root.lRotation = (root.lRotation + root.thisLRotation) % 360;
|
||||||
|
root.thisLRotation = 0;
|
||||||
|
root.rotation = Qt.binding(() => root.cRotation + root.lRotation + root.thisLRotation);
|
||||||
|
|
||||||
|
root.springSettled = false;
|
||||||
|
timer.restart();
|
||||||
|
root.endBatchUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -6,25 +6,55 @@ import qs.Effects
|
|||||||
CustomListView {
|
CustomListView {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
|
property real endFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
|
||||||
property real fadeAmount: 0.1
|
property real fadeAmount: 0.1
|
||||||
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
|
readonly property bool horizontal: orientation === ListView.Horizontal
|
||||||
|
property real startFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
|
||||||
|
|
||||||
function fadeShouldBeActive(isStart: bool): bool {
|
function contentSize(): real {
|
||||||
// When content is smaller than flickable size, hide fade when rebound starts
|
return horizontal ? contentWidth : contentHeight;
|
||||||
if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (isStart)
|
|
||||||
return visibleArea.yPosition > 0;
|
|
||||||
return visibleArea.yPosition + visibleArea.heightRatio < 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flickableDirection: Flickable.VerticalFlick
|
function fadeShouldBeActive(isStart: bool): bool {
|
||||||
layer.enabled: true
|
// When content is smaller than flickable size, hide fade when rebound starts.
|
||||||
orientation: ListView.Vertical
|
if (contentSize() + marginStart() + marginEnd() < viewportSize() && rebound.running && ((isStart ? overshootStart() > 0 : overshootStart() < 0))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on bottomFadeOpacity {
|
if (isStart)
|
||||||
|
return visibleStart() > 0;
|
||||||
|
|
||||||
|
return visibleStart() + visibleRatio() < 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function marginEnd(): real {
|
||||||
|
return horizontal ? rightMargin : bottomMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function marginStart(): real {
|
||||||
|
return horizontal ? leftMargin : topMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function overshootStart(): real {
|
||||||
|
return horizontal ? horizontalOvershoot : verticalOvershoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewportSize(): real {
|
||||||
|
return horizontal ? width : height;
|
||||||
|
}
|
||||||
|
|
||||||
|
function visibleRatio(): real {
|
||||||
|
return horizontal ? visibleArea.widthRatio : visibleArea.heightRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
function visibleStart(): real {
|
||||||
|
return horizontal ? visibleArea.xPosition : visibleArea.yPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
flickableDirection: horizontal ? Flickable.HorizontalFlick : Flickable.VerticalFlick
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
|
Behavior on endFadeOpacity {
|
||||||
Anim {
|
Anim {
|
||||||
type: Anim.SlowEffects
|
type: Anim.SlowEffects
|
||||||
}
|
}
|
||||||
@@ -40,10 +70,10 @@ CustomListView {
|
|||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
gradient: Gradient {
|
gradient: Gradient {
|
||||||
orientation: Gradient.Vertical
|
orientation: root.horizontal ? Gradient.Horizontal : Gradient.Vertical
|
||||||
|
|
||||||
GradientStop {
|
GradientStop {
|
||||||
color: Qt.rgba(0, 0, 0, root.topFadeOpacity)
|
color: Qt.rgba(0, 0, 0, root.startFadeOpacity)
|
||||||
position: 0
|
position: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,13 +88,13 @@ CustomListView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GradientStop {
|
GradientStop {
|
||||||
color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity)
|
color: Qt.rgba(0, 0, 0, root.endFadeOpacity)
|
||||||
position: 1
|
position: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on topFadeOpacity {
|
Behavior on startFadeOpacity {
|
||||||
Anim {
|
Anim {
|
||||||
type: Anim.SlowEffects
|
type: Anim.SlowEffects
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real amplitude: 3
|
||||||
|
property color color: DynamicColors.palette.m3surfaceContainer
|
||||||
|
readonly property real waveHeight: amplitude * 2
|
||||||
|
property int waves: 4
|
||||||
|
|
||||||
|
asynchronous: true
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: root.color
|
||||||
|
strokeColor: "transparent"
|
||||||
|
strokeWidth: 0
|
||||||
|
|
||||||
|
Behavior on fillColor {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PathSvg {
|
||||||
|
path: {
|
||||||
|
const w = root.width;
|
||||||
|
const h = root.height;
|
||||||
|
const a = root.amplitude;
|
||||||
|
const n = Math.max(1, root.waves);
|
||||||
|
const wl = w / n;
|
||||||
|
const half = wl / 2;
|
||||||
|
|
||||||
|
let d = `M 0,${a} `;
|
||||||
|
for (let i = 0; i < n; ++i) {
|
||||||
|
const x = i * wl;
|
||||||
|
d += `Q ${x + half / 2},${-a} ${x + half},${a} `;
|
||||||
|
d += `Q ${x + half + half / 2},${3 * a} ${x + wl},${a} `;
|
||||||
|
}
|
||||||
|
d += `L ${w},${h} L 0,${h} Z`;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
|
|
||||||
Text {
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
textFormat: Text.PlainText
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
|
||||||
@@ -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
@@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
pragma Singleton
|
|
||||||
|
|
||||||
import Quickshell
|
|
||||||
|
|
||||||
Singleton {
|
|
||||||
}
|
|
||||||
+31
-3
@@ -1,12 +1,12 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQml
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Services.Mpris
|
import Quickshell.Services.Mpris
|
||||||
import QtQml
|
|
||||||
import ZShell
|
import ZShell
|
||||||
import qs.Config
|
|
||||||
import qs.Components
|
import qs.Components
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -15,7 +15,24 @@ Singleton {
|
|||||||
readonly property list<MprisPlayer> list: Mpris.players.values
|
readonly property list<MprisPlayer> list: Mpris.players.values
|
||||||
property alias manualActive: props.manualActive
|
property alias manualActive: props.manualActive
|
||||||
|
|
||||||
|
function getArtUrl(player: MprisPlayer): string {
|
||||||
|
if (!player)
|
||||||
|
return "";
|
||||||
|
if (player.trackArtUrl)
|
||||||
|
return player.trackArtUrl;
|
||||||
|
|
||||||
|
const url = player.metadata["xesam:url"] ?? "";
|
||||||
|
if (url.startsWith("https://www.youtube.com/watch")) {
|
||||||
|
// Fallback for youtube
|
||||||
|
const id = url.match(/[?&]v=([\w-]{11})/)?.[1];
|
||||||
|
return id ? `https://img.youtube.com/vi/${id}/hqdefault.jpg` : "";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
function getIdentity(player: MprisPlayer): string {
|
function getIdentity(player: MprisPlayer): string {
|
||||||
|
if (!player)
|
||||||
|
return "";
|
||||||
const alias = Config.services.playerAliases.find(a => a.from === player.identity);
|
const alias = Config.services.playerAliases.find(a => a.from === player.identity);
|
||||||
return alias?.to ?? player.identity;
|
return alias?.to ?? player.identity;
|
||||||
}
|
}
|
||||||
@@ -25,9 +42,12 @@ Singleton {
|
|||||||
if (!Config.utilities.toasts.nowPlaying) {
|
if (!Config.utilities.toasts.nowPlaying) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (root.active.trackArtist != "" && root.active.trackTitle != "") {
|
||||||
|
Toaster.toast(qsTr("Now Playing"), qsTr("%1 - %2").arg(root.active.trackArtist).arg(root.active.trackTitle), "music_note");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target: active
|
target: root.active
|
||||||
}
|
}
|
||||||
|
|
||||||
PersistentProperties {
|
PersistentProperties {
|
||||||
@@ -38,8 +58,10 @@ Singleton {
|
|||||||
reloadableId: "players"
|
reloadableId: "players"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// qmllint disable unresolved-type
|
||||||
CustomShortcut {
|
CustomShortcut {
|
||||||
description: "Toggle media playback"
|
description: "Toggle media playback"
|
||||||
|
// qmllint enable unresolved-type
|
||||||
name: "mediaToggle"
|
name: "mediaToggle"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
@@ -49,8 +71,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// qmllint disable unresolved-type
|
||||||
CustomShortcut {
|
CustomShortcut {
|
||||||
description: "Previous track"
|
description: "Previous track"
|
||||||
|
// qmllint enable unresolved-type
|
||||||
name: "mediaPrev"
|
name: "mediaPrev"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
@@ -60,8 +84,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// qmllint disable unresolved-type
|
||||||
CustomShortcut {
|
CustomShortcut {
|
||||||
description: "Next track"
|
description: "Next track"
|
||||||
|
// qmllint enable unresolved-type
|
||||||
name: "mediaNext"
|
name: "mediaNext"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
@@ -71,8 +97,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// qmllint disable unresolved-type
|
||||||
CustomShortcut {
|
CustomShortcut {
|
||||||
description: "Stop media playback"
|
description: "Stop media playback"
|
||||||
|
// qmllint enable unresolved-type
|
||||||
name: "mediaStop"
|
name: "mediaStop"
|
||||||
|
|
||||||
onPressed: root.active?.stop()
|
onPressed: root.active?.stop()
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
|
|
||||||
Text {
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
textFormat: Text.PlainText
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+107
-32
@@ -1,7 +1,7 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
import Quickshell
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
import ZShell
|
import ZShell
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
|
||||||
@@ -12,15 +12,15 @@ Singleton {
|
|||||||
property var cc
|
property var cc
|
||||||
property string city
|
property string city
|
||||||
readonly property string description: cc?.weatherDesc ?? qsTr("No weather")
|
readonly property string description: cc?.weatherDesc ?? qsTr("No weather")
|
||||||
readonly property string feelsLike: `${cc?.feelsLikeC ?? 0}°C`
|
readonly property string feelsLike: formatTemp(cc?.feelsLikeC)
|
||||||
property list<var> forecast
|
property list<var> forecast
|
||||||
property list<var> hourlyForecast
|
property list<var> hourlyForecast
|
||||||
readonly property int humidity: cc?.humidity ?? 0
|
readonly property int humidity: cc?.humidity ?? 0
|
||||||
readonly property string icon: cc ? Icons.getWeatherIcon(cc.weatherCode) : "cloud_alert"
|
readonly property string icon: cc ? Icons.getWeatherIcon(cc.weatherCode) : "cloud_alert"
|
||||||
property string loc
|
property string loc
|
||||||
readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), "h:mm") : "--:--"
|
readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
|
||||||
readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), "h:mm") : "--:--"
|
readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
|
||||||
readonly property string temp: `${cc?.tempC ?? 0}°C`
|
readonly property string temp: formatTemp(cc?.tempC)
|
||||||
readonly property real windSpeed: cc?.windSpeed ?? 0
|
readonly property real windSpeed: cc?.windSpeed ?? 0
|
||||||
|
|
||||||
function fetchCityFromCoords(coords: string): void {
|
function fetchCityFromCoords(coords: string): void {
|
||||||
@@ -29,29 +29,48 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [lat, lon] = coords.split(",");
|
const [lat, lon] = coords.split(",").map(s => s.trim());
|
||||||
const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=geocodejson`;
|
const lang = Qt.locale().name.split("_")[0] || "en";
|
||||||
Requests.get(url, text => {
|
|
||||||
|
const fallbackToBigDataCloud = () => {
|
||||||
|
const fallbackUrl = `https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lon}&localityLanguage=${lang}`;
|
||||||
|
Requests.get(fallbackUrl, text => {
|
||||||
|
const geo = JSON.parse(text);
|
||||||
|
const geoCity = geo.city || geo.locality;
|
||||||
|
if (geoCity) {
|
||||||
|
city = fixCityName(geoCity);
|
||||||
|
cachedCities.set(coords, city);
|
||||||
|
} else {
|
||||||
|
city = "Unknown City";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const nominatimUrl = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=geocodejson&accept-language=${lang}`;
|
||||||
|
Requests.get(nominatimUrl, text => {
|
||||||
const geo = JSON.parse(text).features?.[0]?.properties.geocoding;
|
const geo = JSON.parse(text).features?.[0]?.properties.geocoding;
|
||||||
if (geo) {
|
if (geo) {
|
||||||
const geoCity = geo.type === "city" ? geo.name : geo.city;
|
const geoCity = geo.type === "city" ? geo.name : geo.city;
|
||||||
city = geoCity;
|
if (geoCity) {
|
||||||
cachedCities.set(coords, geoCity);
|
city = fixCityName(geoCity);
|
||||||
} else {
|
cachedCities.set(coords, city);
|
||||||
city = "Unknown City";
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
fallbackToBigDataCloud();
|
||||||
|
}, fallbackToBigDataCloud);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchCoordsFromCity(cityName: string): void {
|
function fetchCoordsFromCity(cityName: string): void {
|
||||||
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1&language=en&format=json`;
|
const lang = Qt.locale().name.split("_")[0] || "en";
|
||||||
|
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1&language=${lang}&format=json`;
|
||||||
|
|
||||||
Requests.get(url, text => {
|
Requests.get(url, text => {
|
||||||
const json = JSON.parse(text);
|
const json = JSON.parse(text);
|
||||||
if (json.results && json.results.length > 0) {
|
if (json.results && json.results.length > 0) {
|
||||||
const result = json.results[0];
|
const result = json.results[0];
|
||||||
loc = result.latitude + "," + result.longitude;
|
loc = result.latitude + "," + result.longitude;
|
||||||
city = result.name;
|
city = fixCityName(result.name);
|
||||||
} else {
|
} else {
|
||||||
loc = "";
|
loc = "";
|
||||||
reload();
|
reload();
|
||||||
@@ -72,25 +91,21 @@ Singleton {
|
|||||||
cc = {
|
cc = {
|
||||||
weatherCode: json.current.weather_code,
|
weatherCode: json.current.weather_code,
|
||||||
weatherDesc: getWeatherCondition(json.current.weather_code),
|
weatherDesc: getWeatherCondition(json.current.weather_code),
|
||||||
tempC: Math.round(json.current.temperature_2m),
|
tempC: json.current.temperature_2m,
|
||||||
tempF: Math.round(toFahrenheit(json.current.temperature_2m)),
|
feelsLikeC: json.current.apparent_temperature,
|
||||||
feelsLikeC: Math.round(json.current.apparent_temperature),
|
|
||||||
feelsLikeF: Math.round(toFahrenheit(json.current.apparent_temperature)),
|
|
||||||
humidity: json.current.relative_humidity_2m,
|
humidity: json.current.relative_humidity_2m,
|
||||||
windSpeed: json.current.wind_speed_10m,
|
windSpeed: json.current.wind_speed_10m,
|
||||||
isDay: json.current.is_day,
|
isDay: json.current.is_day,
|
||||||
sunrise: json.daily.sunrise[0],
|
sunrise: json.daily.sunrise[0].replace("T", " "),
|
||||||
sunset: json.daily.sunset[0]
|
sunset: json.daily.sunset[0].replace("T", " ")
|
||||||
};
|
};
|
||||||
|
|
||||||
const forecastList = [];
|
const forecastList = [];
|
||||||
for (let i = 0; i < json.daily.time.length; i++)
|
for (let i = 0; i < json.daily.time.length; i++)
|
||||||
forecastList.push({
|
forecastList.push({
|
||||||
date: json.daily.time[i],
|
date: json.daily.time[i].replace(/-/g, "/"),
|
||||||
maxTempC: Math.round(json.daily.temperature_2m_max[i]),
|
maxTempC: json.daily.temperature_2m_max[i],
|
||||||
maxTempF: Math.round(toFahrenheit(json.daily.temperature_2m_max[i])),
|
minTempC: json.daily.temperature_2m_min[i],
|
||||||
minTempC: Math.round(json.daily.temperature_2m_min[i]),
|
|
||||||
minTempF: Math.round(toFahrenheit(json.daily.temperature_2m_min[i])),
|
|
||||||
weatherCode: json.daily.weather_code[i],
|
weatherCode: json.daily.weather_code[i],
|
||||||
icon: Icons.getWeatherIcon(json.daily.weather_code[i])
|
icon: Icons.getWeatherIcon(json.daily.weather_code[i])
|
||||||
});
|
});
|
||||||
@@ -99,7 +114,8 @@ Singleton {
|
|||||||
const hourlyList = [];
|
const hourlyList = [];
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
for (let i = 0; i < json.hourly.time.length; i++) {
|
for (let i = 0; i < json.hourly.time.length; i++) {
|
||||||
const time = new Date(json.hourly.time[i]);
|
const time = new Date(json.hourly.time[i].replace("T", " "));
|
||||||
|
|
||||||
if (time < now)
|
if (time < now)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -107,7 +123,7 @@ Singleton {
|
|||||||
timestamp: json.hourly.time[i],
|
timestamp: json.hourly.time[i],
|
||||||
hour: time.getHours(),
|
hour: time.getHours(),
|
||||||
tempC: Math.round(json.hourly.temperature_2m[i]),
|
tempC: Math.round(json.hourly.temperature_2m[i]),
|
||||||
tempF: Math.round(toFahrenheit(json.hourly.temperature_2m[i])),
|
precipChance: json.hourly.precipitation_probability[i],
|
||||||
weatherCode: json.hourly.weather_code[i],
|
weatherCode: json.hourly.weather_code[i],
|
||||||
icon: Icons.getWeatherIcon(json.hourly.weather_code[i])
|
icon: Icons.getWeatherIcon(json.hourly.weather_code[i])
|
||||||
});
|
});
|
||||||
@@ -116,6 +132,59 @@ Singleton {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fixCityName(cityName: string): string {
|
||||||
|
if (!cityName)
|
||||||
|
return "";
|
||||||
|
const mapping = {
|
||||||
|
// Polish
|
||||||
|
"Poznan": "Poznań",
|
||||||
|
"Wroclaw": "Wrocław",
|
||||||
|
"Krakow": "Kraków",
|
||||||
|
"Gdansk": "Gdańsk",
|
||||||
|
"Lodz": "Łódź",
|
||||||
|
"Rzeszow": "Rzeszów",
|
||||||
|
"Torun": "Toruń",
|
||||||
|
"Bialystok": "Białystok",
|
||||||
|
"Czestochowa": "Częstochowa",
|
||||||
|
"Plock": "Płock",
|
||||||
|
"Ruda Slaska": "Ruda Śląska",
|
||||||
|
"Dabrowa Gornicza": "Dąbrowa Górnicza",
|
||||||
|
"Elblag": "Elbląg",
|
||||||
|
"Gorzow Wielkopolski": "Gorzów Wielkopolski",
|
||||||
|
"Zielona Gora": "Zielona Góra",
|
||||||
|
"Slupsk": "Słupsk",
|
||||||
|
|
||||||
|
// German
|
||||||
|
"Munchen": "München",
|
||||||
|
"Koln": "Köln",
|
||||||
|
"Dusseldorf": "Düsseldorf",
|
||||||
|
"Nurnberg": "Nürnberg",
|
||||||
|
|
||||||
|
// French & Spanish & Portuguese
|
||||||
|
"Sao Paulo": "São Paulo",
|
||||||
|
"Montreal": "Montréal",
|
||||||
|
"Quebec": "Québec",
|
||||||
|
"Bogota": "Bogotá",
|
||||||
|
"Medellin": "Medellín",
|
||||||
|
"Cordoba": "Córdoba",
|
||||||
|
|
||||||
|
// Turkish
|
||||||
|
"Istanbul": "İstanbul",
|
||||||
|
"Izmir": "İzmir",
|
||||||
|
|
||||||
|
// Scandinavian & others
|
||||||
|
"Malmo": "Malmö",
|
||||||
|
"Goteborg": "Göteborg",
|
||||||
|
"Zurich": "Zürich",
|
||||||
|
"Geneve": "Genève"
|
||||||
|
};
|
||||||
|
return mapping[cityName] || cityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTemp(temp: var): string {
|
||||||
|
return Config.services.useFahrenheit ? `${temp !== undefined ? Math.round(toFahrenheit(temp)) : "--"}°F` : `${temp !== undefined ? Math.round(temp) : "--"}°C`;
|
||||||
|
}
|
||||||
|
|
||||||
function getWeatherCondition(code: string): string {
|
function getWeatherCondition(code: string): string {
|
||||||
const conditions = {
|
const conditions = {
|
||||||
"0": "Clear",
|
"0": "Clear",
|
||||||
@@ -154,9 +223,9 @@ Singleton {
|
|||||||
if (!loc || loc.indexOf(",") === -1)
|
if (!loc || loc.indexOf(",") === -1)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
const [lat, lon] = loc.split(",");
|
const [lat, lon] = loc.split(",").map(s => s.trim());
|
||||||
const baseUrl = "https://api.open-meteo.com/v1/forecast";
|
const baseUrl = "https://api.open-meteo.com/v1/forecast";
|
||||||
const params = ["latitude=" + lat, "longitude=" + lon, "hourly=weather_code,temperature_2m", "daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset", "current=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,weather_code,wind_speed_10m", "timezone=auto", "forecast_days=7"];
|
const params = ["latitude=" + lat, "longitude=" + lon, "hourly=weather_code,temperature_2m,precipitation_probability", "daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset", "current=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,weather_code,wind_speed_10m", "timezone=auto", "forecast_days=7"];
|
||||||
|
|
||||||
return baseUrl + "?" + params.join("&");
|
return baseUrl + "?" + params.join("&");
|
||||||
}
|
}
|
||||||
@@ -189,7 +258,14 @@ Singleton {
|
|||||||
|
|
||||||
onLocChanged: fetchWeatherData()
|
onLocChanged: fetchWeatherData()
|
||||||
|
|
||||||
// Refresh current location hourly
|
Connections {
|
||||||
|
function onWeatherLocationChanged(): void {
|
||||||
|
root.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: Config.services
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 3600000 // 1 hour
|
interval: 3600000 // 1 hour
|
||||||
repeat: true
|
repeat: true
|
||||||
@@ -200,6 +276,5 @@ Singleton {
|
|||||||
|
|
||||||
ElapsedTimer {
|
ElapsedTimer {
|
||||||
id: timer
|
id: timer
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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 => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-41
@@ -4,76 +4,66 @@ import qs.Components
|
|||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
|
||||||
RowLayout {
|
CustomClippingRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var lock
|
required property var lock
|
||||||
|
|
||||||
spacing: Appearance.spacing.large * 2
|
implicitHeight: layout.implicitHeight
|
||||||
|
implicitWidth: layout.implicitWidth
|
||||||
|
radius: Appearance.rounding.large
|
||||||
|
|
||||||
ColumnLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
id: layout
|
||||||
spacing: Appearance.spacing.normal
|
|
||||||
|
|
||||||
CustomRect {
|
anchors.fill: parent
|
||||||
|
spacing: Appearance.spacing.large * 2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
spacing: Appearance.spacing.normal
|
||||||
implicitHeight: weather.implicitHeight
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
topLeftRadius: Appearance.rounding.large
|
|
||||||
|
|
||||||
WeatherInfo {
|
WeatherInfo {
|
||||||
id: weather
|
id: weather
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
rootHeight: root.height
|
rootHeight: root.height
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
CustomRect {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
implicitHeight: resources.implicitHeight
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
|
|
||||||
Resources {
|
Resources {
|
||||||
id: resources
|
id: resources
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
CustomClippingRect {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
bottomLeftRadius: Appearance.rounding.large
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
|
|
||||||
Media {
|
Media {
|
||||||
id: media
|
id: media
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
lock: root.lock
|
lock: root.lock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Center {
|
Center {
|
||||||
lock: root.lock
|
lock: root.lock
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Appearance.spacing.normal
|
|
||||||
|
|
||||||
CustomRect {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
bottomRightRadius: Appearance.rounding.large
|
spacing: Appearance.spacing.normal
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
topRightRadius: Appearance.rounding.large
|
|
||||||
|
|
||||||
NotifDock {
|
CustomRect {
|
||||||
lock: root.lock
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
bottomRightRadius: Appearance.rounding.large
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
topRightRadius: Appearance.rounding.large
|
||||||
|
|
||||||
|
NotifDock {
|
||||||
|
lock: root.lock
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+54
-145
@@ -1,201 +1,110 @@
|
|||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import qs.Modules
|
import Quickshell
|
||||||
|
import ZShell.Components
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Helpers
|
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
import qs.Helpers
|
||||||
|
|
||||||
Item {
|
CustomClippingRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var lock
|
required property var lock
|
||||||
|
|
||||||
anchors.fill: parent
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
|
FadeImage {
|
||||||
|
id: image
|
||||||
|
|
||||||
Image {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
opacity: status === Image.Ready ? 1 : 0
|
opacity: status === Image.Ready ? 1 : 0
|
||||||
source: Players.active?.trackArtUrl ?? ""
|
source: Players.getArtUrl(Players.active)
|
||||||
sourceSize.height: height
|
sourceSize: {
|
||||||
sourceSize.width: width
|
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||||
|
return Qt.size(width * dpr, height * dpr);
|
||||||
layer.effect: OpacityMask {
|
|
||||||
maskSource: mask
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {
|
Anim {
|
||||||
duration: Appearance.anim.durations.extraLarge
|
type: Anim.StandardExtraLarge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
CustomRect {
|
||||||
id: mask
|
anchors.fill: parent
|
||||||
|
color: DynamicColors.palette.m3surface
|
||||||
anchors.fill: parent
|
opacity: 0.7
|
||||||
layer.enabled: true
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
gradient: Gradient {
|
|
||||||
orientation: Gradient.Horizontal
|
|
||||||
|
|
||||||
GradientStop {
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.5)
|
|
||||||
position: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
GradientStop {
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.2)
|
|
||||||
position: 0.4
|
|
||||||
}
|
|
||||||
|
|
||||||
GradientStop {
|
|
||||||
color: Qt.rgba(0, 0, 0, 0)
|
|
||||||
position: 0.8
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: layout
|
id: layout
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.left: parent.left
|
||||||
anchors.margins: Appearance.padding.large
|
anchors.margins: Appearance.padding.extraLarge
|
||||||
|
anchors.right: parent.right
|
||||||
CustomText {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
Layout.bottomMargin: Appearance.spacing.larger
|
spacing: Appearance.spacing.extraSmall
|
||||||
Layout.topMargin: Appearance.padding.large
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.family: Appearance.font.family.mono
|
|
||||||
font.weight: 500
|
|
||||||
text: qsTr("Now playing")
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
CustomText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
animate: true
|
animate: true
|
||||||
color: DynamicColors.palette.m3primary
|
color: DynamicColors.palette.m3primary
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font.family: Appearance.font.family.mono
|
font.pointSize: Appearance.font.size.medium
|
||||||
font.pointSize: Appearance.font.size.large
|
|
||||||
font.weight: 600
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: Players.active?.trackArtist ?? qsTr("No media")
|
text: (Players.active?.trackTitle ?? qsTr("Nothing playing")) || qsTr("Unknown track")
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomText {
|
CustomText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
animate: true
|
animate: true
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font.family: Appearance.font.family.mono
|
font.pointSize: Appearance.font.size.small
|
||||||
font.pointSize: Appearance.font.size.larger
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: Players.active?.trackTitle ?? qsTr("No media")
|
text: (Players.active?.trackArtist ?? qsTr("Try playing some music!")) || qsTr("Unknown artist")
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
ButtonRow {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.bottomMargin: Appearance.padding.large
|
Layout.topMargin: Appearance.spacing.small
|
||||||
Layout.topMargin: Appearance.spacing.large * 1.2
|
spacing: Appearance.spacing.extraSmall
|
||||||
spacing: Appearance.spacing.large
|
|
||||||
|
|
||||||
PlayerControl {
|
|
||||||
function onClicked(): void {
|
|
||||||
if (Players.active?.canGoPrevious)
|
|
||||||
Players.active.previous();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
enabled: Players.active?.canGoPrevious
|
||||||
icon: "skip_previous"
|
icon: "skip_previous"
|
||||||
|
isRound: true
|
||||||
|
shapeMorph: true
|
||||||
|
type: IconButton.Tonal
|
||||||
|
|
||||||
|
onClicked: Players.active?.previous()
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerControl {
|
IconButton {
|
||||||
function onClicked(): void {
|
checked: Players.active?.isPlaying ?? false
|
||||||
if (Players.active?.canTogglePlaying)
|
enabled: Players.active?.canTogglePlaying
|
||||||
Players.active.togglePlaying();
|
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
||||||
}
|
implicitWidth: implicitHeight + Appearance.padding.largeIncreased * 2
|
||||||
|
isRound: true
|
||||||
|
shapeMorph: true
|
||||||
|
|
||||||
active: Players.active?.isPlaying ?? false
|
onClicked: Players.active?.togglePlaying()
|
||||||
animate: true
|
|
||||||
icon: active ? "pause" : "play_arrow"
|
|
||||||
level: active ? 2 : 1
|
|
||||||
set_color: "Primary"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerControl {
|
IconButton {
|
||||||
function onClicked(): void {
|
enabled: Players.active?.canGoNext
|
||||||
if (Players.active?.canGoNext)
|
|
||||||
Players.active.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
icon: "skip_next"
|
icon: "skip_next"
|
||||||
}
|
isRound: true
|
||||||
}
|
shapeMorph: true
|
||||||
}
|
type: IconButton.Tonal
|
||||||
|
|
||||||
component PlayerControl: CustomRect {
|
onClicked: Players.active?.next()
|
||||||
id: control
|
|
||||||
|
|
||||||
property bool active
|
|
||||||
property alias animate: controlIcon.animate
|
|
||||||
property alias icon: controlIcon.text
|
|
||||||
property int level: 1
|
|
||||||
property string set_color: "Secondary"
|
|
||||||
|
|
||||||
function onClicked(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.preferredWidth: implicitWidth + (controlState.pressed ? Appearance.padding.normal * 2 : active ? Appearance.padding.small * 2 : 0)
|
|
||||||
color: active ? DynamicColors.palette[`m3${set_color.toLowerCase()}`] : DynamicColors.palette[`m3${set_color.toLowerCase()}Container`]
|
|
||||||
implicitHeight: controlIcon.implicitHeight + Appearance.padding.normal * 2
|
|
||||||
implicitWidth: controlIcon.implicitWidth + Appearance.padding.large * 2
|
|
||||||
radius: active || controlState.pressed ? Appearance.rounding.small : Appearance.rounding.normal
|
|
||||||
|
|
||||||
Behavior on Layout.preferredWidth {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.expressiveFastSpatial
|
|
||||||
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Behavior on radius {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.expressiveFastSpatial
|
|
||||||
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Elevation {
|
|
||||||
anchors.fill: parent
|
|
||||||
level: controlState.containsMouse && !controlState.pressed ? control.level + 1 : control.level
|
|
||||||
radius: parent.radius
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
StateLayer {
|
|
||||||
id: controlState
|
|
||||||
|
|
||||||
color: control.active ? DynamicColors.palette[`m3on${control.set_color}`] : DynamicColors.palette[`m3on${control.set_color}Container`]
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
control.onClicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
id: controlIcon
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: control.active ? DynamicColors.palette[`m3on${control.set_color}`] : DynamicColors.palette[`m3on${control.set_color}Container`]
|
|
||||||
fill: control.active ? 1 : 0
|
|
||||||
font.pointSize: Appearance.font.size.large
|
|
||||||
|
|
||||||
Behavior on fill {
|
|
||||||
Anim {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-47
@@ -1,81 +1,82 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import M3Shapes
|
||||||
import ZShell.Services
|
import ZShell.Services
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Helpers
|
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
import qs.Effects
|
||||||
|
|
||||||
GridLayout {
|
CustomRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
anchors.left: parent.left
|
readonly property real fontScale: {
|
||||||
anchors.margins: Appearance.padding.large
|
const diff = width / 391 - 1; // 391 is the width at 1080 height screen
|
||||||
anchors.right: parent.right
|
return 1 + Math.pow(Math.abs(diff), 0.8) * Math.sign(diff);
|
||||||
columnSpacing: Appearance.spacing.large
|
}
|
||||||
columns: 2
|
|
||||||
rowSpacing: Appearance.spacing.large
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
rows: 1
|
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
|
ServiceRef {
|
||||||
|
service: Cpu
|
||||||
|
}
|
||||||
|
|
||||||
ServiceRef {
|
ServiceRef {
|
||||||
service: Memory
|
service: Memory
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceRef {
|
ServiceRef {
|
||||||
service: Cpu
|
service: Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
Resource {
|
RowLayout {
|
||||||
Layout.bottomMargin: Appearance.padding.large
|
id: layout
|
||||||
Layout.topMargin: Appearance.padding.large
|
|
||||||
fgColor: DynamicColors.palette.m3primary
|
anchors.fill: parent
|
||||||
icon: "memory"
|
anchors.margins: Appearance.padding.large
|
||||||
value: Cpu.percentage
|
spacing: Appearance.spacing.large
|
||||||
|
|
||||||
|
Resource {
|
||||||
|
id: cpu
|
||||||
|
|
||||||
|
fgColor: DynamicColors.palette.m3primary
|
||||||
|
icon: "memory"
|
||||||
|
value: Cpu.percentage
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource {
|
||||||
|
fgColor: DynamicColors.palette.m3tertiary
|
||||||
|
icon: "memory_alt"
|
||||||
|
value: Memory.percentage
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource {
|
||||||
|
fgColor: DynamicColors.palette.m3secondary
|
||||||
|
icon: "hard_disk"
|
||||||
|
value: Storage.percentage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Resource {
|
component Resource: CircularProgress {
|
||||||
Layout.bottomMargin: Appearance.padding.large
|
|
||||||
Layout.topMargin: Appearance.padding.large
|
|
||||||
fgColor: DynamicColors.palette.m3secondary
|
|
||||||
icon: "memory_alt"
|
|
||||||
value: Memory.percentage
|
|
||||||
}
|
|
||||||
|
|
||||||
component Resource: CustomRect {
|
|
||||||
id: res
|
id: res
|
||||||
|
|
||||||
required property color fgColor
|
|
||||||
required property string icon
|
required property string icon
|
||||||
required property real value
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
implicitSize: width
|
||||||
implicitHeight: width
|
|
||||||
radius: Appearance.rounding.large
|
|
||||||
|
|
||||||
Behavior on value {
|
Behavior on clampedVal {
|
||||||
Anim {
|
Anim {
|
||||||
duration: Appearance.anim.durations.large
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CircularProgress {
|
|
||||||
id: circ
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
bgColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
|
|
||||||
fgColor: res.fgColor
|
|
||||||
padding: Appearance.padding.large * 3
|
|
||||||
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
|
|
||||||
value: res.value
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialIcon {
|
MaterialIcon {
|
||||||
id: icon
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: res.fgColor
|
color: res.fgColor
|
||||||
font.pointSize: (circ.arcRadius * 0.7) || 1
|
font.pointSize: Appearance.font.size.extraLarge
|
||||||
font.weight: 600
|
|
||||||
text: res.icon
|
text: res.icon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
|
import qs.Config
|
||||||
|
import qs.Helpers
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property int rootHeight
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
animate: true
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
text: Weather.description
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: temp
|
||||||
|
|
||||||
|
animate: true
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
text: Weather.temp
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
animate: true
|
||||||
|
color: DynamicColors.palette.m3secondary
|
||||||
|
text: Weather.icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
animate: true
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
text: qsTr("Feels like %1").arg(Weather.temp)
|
||||||
|
visible: root.rootHeight > 550
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
animate: true
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
text: {
|
||||||
|
const today = Weather.forecast[0];
|
||||||
|
return qsTr("High %1 • Low %2").arg(Weather.formatTemp(today?.maxTempC)).arg(Weather.formatTemp(today?.minTempC));
|
||||||
|
}
|
||||||
|
visible: root.rootHeight > 550
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import M3Shapes
|
||||||
|
import ZShell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
||||||
|
implicitHeight: header.anchors.margins + header.implicitHeight + Appearance.spacing.small + layout.implicitHeight + layout.anchors.bottomMargin
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: header
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.largeIncreased
|
||||||
|
anchors.top: parent.top
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.topMargin: Math.round(fontInfo.pointSize * 0.12)
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
text: "schedule"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: title
|
||||||
|
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
text: qsTr("Hourly forecast")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalFadeListView {
|
||||||
|
id: layout
|
||||||
|
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: Appearance.padding.largeIncreased
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
anchors.right: parent.right
|
||||||
|
implicitHeight: contentItem.childrenRect.height
|
||||||
|
model: Weather.hourlyForecast
|
||||||
|
orientation: VerticalFadeListView.Horizontal
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
id: hour
|
||||||
|
|
||||||
|
readonly property var cond: modelData
|
||||||
|
required property int index
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
|
MaterialShape {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
color: Qt.alpha(DynamicColors.palette.m3primary, hour.index === 0 ? 1 : 0)
|
||||||
|
implicitSize: temp.implicitHeight + Appearance.padding.normal * 2
|
||||||
|
shape: MaterialShape.Cookie4Sided
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: temp
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: hour.index === 0 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
text: Weather.formatTemp(hour.cond.tempC).slice(0, -1) // Remove C/F
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
color: DynamicColors.palette.m3secondary
|
||||||
|
font.pointSize: Appearance.font.size.extraLarge
|
||||||
|
text: hour.cond.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
text: hour.cond.precipChance + "%"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: Appearance.spacing.extraSmall
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
text: hour.index === 0 ? qsTr("Now") : Qt.formatDateTime(new Date(hour.cond.timestamp.replace("T", " ")), Config.services.useTwelveHourClock ? "ha" : "hh:00")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
-150
@@ -1,162 +1,23 @@
|
|||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import qs.Modules.Lock.Weather
|
||||||
|
import qs.Config
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Config
|
|
||||||
|
|
||||||
ColumnLayout {
|
CustomRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property int rootHeight
|
required property int rootHeight
|
||||||
|
readonly property bool showForecast: rootHeight >= 700
|
||||||
|
|
||||||
anchors.left: parent.left
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
anchors.margins: Appearance.padding.large * 2
|
implicitHeight: {
|
||||||
anchors.right: parent.right
|
const base = brief.implicitHeight + brief.anchors.topMargin;
|
||||||
spacing: Appearance.spacing.small
|
if (showForecast)
|
||||||
|
return base + Appearance.spacing.large + forecast.implicitHeight + forecast.anchors.margins;
|
||||||
Loader {
|
return base + brief.anchors.topMargin;
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.bottomMargin: -Appearance.padding.large
|
|
||||||
Layout.topMargin: Appearance.padding.large * 2
|
|
||||||
active: root.rootHeight > 610
|
|
||||||
visible: active
|
|
||||||
|
|
||||||
sourceComponent: CustomText {
|
|
||||||
color: DynamicColors.palette.m3primary
|
|
||||||
font.pointSize: Appearance.font.size.extraLarge
|
|
||||||
font.weight: 500
|
|
||||||
text: qsTr("Weather")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Appearance.spacing.large
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
animate: true
|
|
||||||
color: DynamicColors.palette.m3secondary
|
|
||||||
font.pointSize: Appearance.font.size.extraLarge * 2.5
|
|
||||||
text: Weather.icon
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
animate: true
|
|
||||||
color: DynamicColors.palette.m3secondary
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pointSize: Appearance.font.size.large
|
|
||||||
font.weight: 500
|
|
||||||
text: Weather.description
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
animate: true
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
text: qsTr("Humidity: %1%").arg(Weather.humidity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
Layout.rightMargin: Appearance.padding.smaller
|
|
||||||
active: root.width > 400
|
|
||||||
visible: active
|
|
||||||
|
|
||||||
sourceComponent: ColumnLayout {
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
animate: true
|
|
||||||
color: DynamicColors.palette.m3primary
|
|
||||||
elide: Text.ElideLeft
|
|
||||||
font.pointSize: Appearance.font.size.extraLarge
|
|
||||||
font.weight: 500
|
|
||||||
horizontalAlignment: Text.AlignRight
|
|
||||||
text: Weather.temp
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
animate: true
|
|
||||||
color: DynamicColors.palette.m3outline
|
|
||||||
elide: Text.ElideLeft
|
|
||||||
font.pointSize: Appearance.font.size.smaller
|
|
||||||
horizontalAlignment: Text.AlignRight
|
|
||||||
text: qsTr("Feels like: %1").arg(Weather.feelsLike)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: forecastLoader
|
|
||||||
|
|
||||||
Layout.bottomMargin: Appearance.padding.large * 2
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: Appearance.spacing.smaller
|
|
||||||
active: root.rootHeight > 820
|
|
||||||
visible: active
|
|
||||||
|
|
||||||
sourceComponent: RowLayout {
|
|
||||||
spacing: Appearance.spacing.large
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: {
|
|
||||||
const forecast = Weather.hourlyForecast;
|
|
||||||
const count = root.width < 320 ? 3 : root.width < 400 ? 4 : 5;
|
|
||||||
if (!forecast)
|
|
||||||
return Array.from({
|
|
||||||
length: count
|
|
||||||
}, () => null);
|
|
||||||
|
|
||||||
return forecast.slice(0, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: forecastHour
|
|
||||||
|
|
||||||
required property var modelData
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: DynamicColors.palette.m3outline
|
|
||||||
font.pointSize: Appearance.font.size.larger
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: {
|
|
||||||
const hour = forecastHour.modelData?.hour ?? 0;
|
|
||||||
return hour > 12 ? `${(hour - 12).toString().padStart(2, "0")} PM` : `${hour.toString().padStart(2, "0")} AM`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
font.pointSize: Appearance.font.size.extraLarge * 1.5
|
|
||||||
font.weight: 500
|
|
||||||
text: forecastHour.modelData?.icon ?? "cloud_alert"
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
color: DynamicColors.palette.m3secondary
|
|
||||||
font.pointSize: Appearance.font.size.larger
|
|
||||||
text: Config.services.useFahrenheit ? `${forecastHour.modelData?.tempF ?? 0}°F` : `${forecastHour.modelData?.tempC ?? 0}°C`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 900000 // 15 minutes
|
interval: 900000 // 15 minutes
|
||||||
@@ -166,4 +27,27 @@ ColumnLayout {
|
|||||||
|
|
||||||
onTriggered: Weather.reload()
|
onTriggered: Weather.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BriefInfo {
|
||||||
|
id: brief
|
||||||
|
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: Appearance.padding.extraLarge
|
||||||
|
rootHeight: root.rootHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: forecast
|
||||||
|
|
||||||
|
active: root.showForecast
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
anchors.right: parent.right
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
sourceComponent: Forecast {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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 = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -32,6 +32,31 @@ Item {
|
|||||||
implicitHeight: width
|
implicitHeight: width
|
||||||
radius: Appearance.rounding.large
|
radius: Appearance.rounding.large
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: opacity > 0
|
||||||
|
anchors.centerIn: parent
|
||||||
|
opacity: img.status === Image.Ready ? 0 : 1
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourceComponent: CustomRect {
|
||||||
|
color: DynamicColors.palette.m3primaryContainer
|
||||||
|
implicitHeight: loadingIndicator.implicitSize + Appearance.padding.large * 2
|
||||||
|
implicitWidth: loadingIndicator.implicitSize + Appearance.padding.large * 2
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
LoadingIndicator {
|
||||||
|
id: loadingIndicator
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
containsIcon: true
|
||||||
|
implicitSize: Math.min(imgWrapper.width, imgWrapper.height) * 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: img
|
id: img
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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) + "%"
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ PageBase {
|
|||||||
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
|
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
|
||||||
active: root.clockFormats.find(item => item.value === Config.general.dateFormat)
|
active: root.clockFormats.find(item => item.value === Config.general.dateFormat)
|
||||||
first: true
|
first: 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")
|
||||||
|
|
||||||
@@ -57,5 +57,15 @@ PageBase {
|
|||||||
Config.general.dateFormat = item.value;
|
Config.general.dateFormat = item.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToggleRow {
|
||||||
|
checked: Config.services.useTwelveHourClock
|
||||||
|
last: true
|
||||||
|
settingAnchor: "bar-clock-twelve-hour"
|
||||||
|
subtext: qsTr("Format timestamps for twelve or twenty-four hours in UI")
|
||||||
|
text: qsTr("Twelve hour clock")
|
||||||
|
|
||||||
|
onToggled: Config.services.useTwelveHourClock = checked
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user