Compare commits
8
Commits
v0.3.0
..
656bd0a54d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
656bd0a54d | ||
|
|
f01001dcfa | ||
|
|
bc6b0d50ab | ||
|
|
f27c9afc25 | ||
|
|
5962fc8354 | ||
|
|
1adcb9841c | ||
|
|
0e0f8555ed | ||
|
|
1d63e67554 |
@@ -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.dev/aramjonghu/zshell-ci:latest
|
IMAGE: git.aramjonghu.nl/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.dev --username aramjonghu --password-stdin
|
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.nl --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,43 +4,10 @@ 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.dev/aramjonghu/zshell-ci:latest
|
image: git.aramjonghu.nl/aramjonghu/zshell-ci:latest
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Configure
|
|
||||||
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: ninja -C build
|
|
||||||
|
|
||||||
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: JS/TS
|
name: Lint & Format (JS/TS)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
fmt:
|
lint-format:
|
||||||
runs-on: alpine
|
runs-on: alpine
|
||||||
container: node:26-alpine
|
container: node:26-alpine
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ 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
|
||||||
@@ -25,19 +26,6 @@ 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
|
||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
fmt:
|
lint-format:
|
||||||
runs-on: alpine
|
runs-on: alpine
|
||||||
container: node:26-alpine
|
container: node:26-alpine
|
||||||
|
|
||||||
@@ -23,28 +23,11 @@ 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
|
||||||
@@ -80,30 +63,3 @@ 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,85 @@
|
|||||||
|
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"
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -15,3 +15,4 @@ dist/
|
|||||||
**/target/
|
**/target/
|
||||||
**/test-plugins/
|
**/test-plugins/
|
||||||
**/Charts/
|
**/Charts/
|
||||||
|
**/network-dev/
|
||||||
|
|||||||
+2
-104
@@ -36,15 +36,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
set(ENABLE_MODULES "plugin;shell;m3shapes" CACHE STRING "Modules to build/install")
|
set(ENABLE_MODULES "plugin;shell" 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
|
||||||
@@ -53,9 +51,7 @@ add_compile_options(
|
|||||||
-Wunreachable-code
|
-Wunreachable-code
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if("shell" IN_LIST ENABLE_MODULES)
|
if("shell" IN_LIST ENABLE_MODULES)
|
||||||
# Build settings index
|
|
||||||
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||||
set(SETTINGS_INDEX_JSON "${CMAKE_BINARY_DIR}/settings-index.json")
|
set(SETTINGS_INDEX_JSON "${CMAKE_BINARY_DIR}/settings-index.json")
|
||||||
execute_process(
|
execute_process(
|
||||||
@@ -68,90 +64,11 @@ if("shell" IN_LIST ENABLE_MODULES)
|
|||||||
if(NOT SETTINGS_INDEX_RESULT EQUAL 0)
|
if(NOT SETTINGS_INDEX_RESULT EQUAL 0)
|
||||||
message(FATAL_ERROR "Failed to build settings search index")
|
message(FATAL_ERROR "Failed to build settings search index")
|
||||||
endif()
|
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()
|
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)
|
||||||
@@ -168,22 +85,3 @@ 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()
|
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
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 {
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property bool shouldBeVisible
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
scale: 0
|
||||||
|
visible: root.scale > 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.small
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onShouldBeVisibleChanged: {
|
||||||
|
if (root.shouldBeVisible) {
|
||||||
|
root.opacity = 1;
|
||||||
|
root.scale = 1;
|
||||||
|
} else {
|
||||||
|
root.opacity = 0;
|
||||||
|
root.scale = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
enum Variant {
|
||||||
|
Outlined,
|
||||||
|
Filled
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property color accent: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline))
|
||||||
|
readonly property real disabledOpacity: 0.38
|
||||||
|
readonly property string effectiveTrailingIcon: root.trailingIcon.length > 0 ? root.trailingIcon : (root.password ? (field.echoMode === TextInput.Password ? "visibility" : "visibility_off") : (root.isError ? "error" : ""))
|
||||||
|
property string errorText: ""
|
||||||
|
readonly property alias field: field
|
||||||
|
readonly property bool floating: focused || hasContent || prefixText.length > 0
|
||||||
|
readonly property bool focused: field.activeFocus
|
||||||
|
readonly property bool hasContent: field.text.length > 0
|
||||||
|
readonly property color iconColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
|
||||||
|
property int inputMethodHints: Qt.ImhNone
|
||||||
|
property bool isError: false
|
||||||
|
property string label: ""
|
||||||
|
readonly property color labelColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
|
||||||
|
property string leadingIcon: ""
|
||||||
|
property int maxLength: 0
|
||||||
|
property color notchColor: DynamicColors.palette.m3surface
|
||||||
|
readonly property bool outlined: variant === M3TextField.Variant.Outlined
|
||||||
|
property bool password: false
|
||||||
|
property string placeholder: ""
|
||||||
|
property string prefixText: ""
|
||||||
|
property string suffixText: ""
|
||||||
|
readonly property color supportColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant)
|
||||||
|
property string supportingText: ""
|
||||||
|
property alias text: field.text
|
||||||
|
property string trailingIcon: ""
|
||||||
|
readonly property bool trailingInteractive: root.password && root.trailingIcon.length === 0
|
||||||
|
property var validator: null
|
||||||
|
property int variant: M3TextField.Variant.Outlined
|
||||||
|
|
||||||
|
signal accepted
|
||||||
|
|
||||||
|
function forceFieldFocus(): void {
|
||||||
|
field.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: 56 + (supportRow.visible ? supportRow.implicitHeight + Appearance.spacing.extraSmall : 0)
|
||||||
|
implicitWidth: 240
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: filledBg
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
color: root.enabled ? (root.focused ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) : DynamicColors.palette.m3surfaceContainerHigh) : DynamicColors.palette.m3onSurface
|
||||||
|
implicitHeight: 56
|
||||||
|
opacity: root.enabled ? 1 : 0.04 // M3 filled disabled container @ 4%
|
||||||
|
|
||||||
|
topLeftRadius: Appearance.rounding.small
|
||||||
|
topRightRadius: Appearance.rounding.small
|
||||||
|
visible: !root.outlined
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
|
||||||
|
enabled: root.enabled
|
||||||
|
topLeftRadius: Appearance.rounding.small
|
||||||
|
topRightRadius: Appearance.rounding.small
|
||||||
|
|
||||||
|
onClicked: field.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
color: root.accent
|
||||||
|
implicitHeight: root.focused || root.isError ? 2 : 1
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
Anim {
|
||||||
|
type: Anim.StandardSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: outline
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
border.color: root.accent
|
||||||
|
border.width: root.focused || root.isError ? 2 : 1
|
||||||
|
color: "transparent"
|
||||||
|
implicitHeight: 56
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
visible: root.outlined
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on border.width {
|
||||||
|
Anim {
|
||||||
|
type: Anim.StandardSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: outline.border.width
|
||||||
|
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
|
||||||
|
enabled: root.enabled
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
|
onClicked: field.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: root.notchColor
|
||||||
|
height: outline.border.width
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
visible: root.outlined && root.floating && root.label.length > 0
|
||||||
|
width: labelText.width + Appearance.spacing.extraSmall
|
||||||
|
x: labelText.x - Appearance.spacing.extraSmall / 2
|
||||||
|
y: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: labelText
|
||||||
|
|
||||||
|
color: root.labelColor
|
||||||
|
font.pointSize: root.floating ? Appearance.font.size.small : Appearance.font.size.medium
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
text: root.label
|
||||||
|
visible: root.label.length > 0
|
||||||
|
x: root.floating ? (Appearance.padding.larger + Appearance.spacing.extraSmall / 2) : (leading.visible ? leading.x + leading.width + Appearance.spacing.small : Appearance.padding.larger)
|
||||||
|
y: root.floating ? (root.outlined ? -height / 2 : Appearance.padding.extraSmall) : (56 - height) / 2
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on x {
|
||||||
|
Anim {
|
||||||
|
type: Anim.StandardSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
Anim {
|
||||||
|
type: Anim.StandardSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: leading
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Appearance.padding.larger
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: (56 - height) / 2
|
||||||
|
color: root.iconColor
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
text: root.leadingIcon
|
||||||
|
visible: root.leadingIcon.length > 0
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: trailing
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Appearance.padding.larger
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: (56 - height) / 2
|
||||||
|
color: root.isError ? DynamicColors.palette.m3error : (root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface)
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
text: root.effectiveTrailingIcon
|
||||||
|
visible: root.effectiveTrailingIcon.length > 0
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -Appearance.padding.small
|
||||||
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: root.trailingInteractive && root.enabled
|
||||||
|
|
||||||
|
onClicked: field.echoMode = field.echoMode === TextInput.Password ? TextInput.Normal : TextInput.Password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: prefix
|
||||||
|
|
||||||
|
anchors.left: leading.visible ? leading.right : parent.left
|
||||||
|
anchors.leftMargin: leading.visible ? Appearance.spacing.small : Appearance.padding.larger
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
|
||||||
|
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
height: 56
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
text: root.prefixText
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
visible: root.prefixText.length > 0 && root.floating
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: suffix
|
||||||
|
|
||||||
|
anchors.right: trailing.visible ? trailing.left : parent.right
|
||||||
|
anchors.rightMargin: trailing.visible ? Appearance.spacing.small : Appearance.padding.larger
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
|
||||||
|
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
height: 56
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
text: root.suffixText
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
visible: root.suffixText.length > 0 && root.floating
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTextField {
|
||||||
|
id: field
|
||||||
|
|
||||||
|
anchors.left: prefix.visible ? prefix.right : (leading.visible ? leading.right : parent.left)
|
||||||
|
anchors.leftMargin: prefix.visible ? Appearance.spacing.extraSmall : (leading.visible ? Appearance.spacing.small : Appearance.padding.larger)
|
||||||
|
anchors.right: suffix.visible ? suffix.left : (trailing.visible ? trailing.left : parent.right)
|
||||||
|
anchors.rightMargin: suffix.visible ? Appearance.spacing.extraSmall : (trailing.visible ? Appearance.spacing.small : Appearance.padding.larger)
|
||||||
|
anchors.top: parent.top
|
||||||
|
color: DynamicColors.palette.m3onSurface
|
||||||
|
echoMode: root.password ? TextInput.Password : TextInput.Normal
|
||||||
|
enabled: root.enabled
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
height: 56
|
||||||
|
inputMethodHints: root.inputMethodHints
|
||||||
|
maximumLength: root.maxLength > 0 ? root.maxLength : 32767
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
placeholderText: root.focused ? root.placeholder : ""
|
||||||
|
topPadding: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
|
||||||
|
validator: root.validator
|
||||||
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
|
||||||
|
onAccepted: root.accepted()
|
||||||
|
onTextEdited: if (root.isError)
|
||||||
|
root.isError = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: supportRow
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 56 + Appearance.spacing.extraSmall
|
||||||
|
implicitHeight: Math.max(supportText.implicitHeight, counter.implicitHeight)
|
||||||
|
visible: (root.isError && root.errorText.length > 0) || root.supportingText.length > 0 || root.maxLength > 0
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: supportText
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Appearance.padding.larger
|
||||||
|
anchors.right: counter.visible ? counter.left : parent.right
|
||||||
|
anchors.rightMargin: Appearance.spacing.small
|
||||||
|
color: root.supportColor
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
text: root.isError && root.errorText.length > 0 ? root.errorText : root.supportingText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: counter
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Appearance.padding.larger
|
||||||
|
color: root.supportColor
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
opacity: root.enabled ? 1 : root.disabledOpacity
|
||||||
|
text: `${root.text.length}/${root.maxLength}`
|
||||||
|
visible: root.maxLength > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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,55 +6,25 @@ import qs.Effects
|
|||||||
CustomListView {
|
CustomListView {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property real endFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
|
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
|
||||||
property real fadeAmount: 0.1
|
property real fadeAmount: 0.1
|
||||||
readonly property bool horizontal: orientation === ListView.Horizontal
|
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
|
||||||
property real startFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
|
|
||||||
|
|
||||||
function contentSize(): real {
|
|
||||||
return horizontal ? contentWidth : contentHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fadeShouldBeActive(isStart: bool): bool {
|
function fadeShouldBeActive(isStart: bool): bool {
|
||||||
// When content is smaller than flickable size, hide fade when rebound starts.
|
// When content is smaller than flickable size, hide fade when rebound starts
|
||||||
if (contentSize() + marginStart() + marginEnd() < viewportSize() && rebound.running && ((isStart ? overshootStart() > 0 : overshootStart() < 0))) {
|
if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0)))
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (isStart)
|
if (isStart)
|
||||||
return visibleStart() > 0;
|
return visibleArea.yPosition > 0;
|
||||||
|
return visibleArea.yPosition + visibleArea.heightRatio < 1;
|
||||||
return visibleStart() + visibleRatio() < 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function marginEnd(): real {
|
flickableDirection: Flickable.VerticalFlick
|
||||||
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
|
layer.enabled: true
|
||||||
|
orientation: ListView.Vertical
|
||||||
|
|
||||||
Behavior on endFadeOpacity {
|
Behavior on bottomFadeOpacity {
|
||||||
Anim {
|
Anim {
|
||||||
type: Anim.SlowEffects
|
type: Anim.SlowEffects
|
||||||
}
|
}
|
||||||
@@ -70,10 +40,10 @@ CustomListView {
|
|||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
gradient: Gradient {
|
gradient: Gradient {
|
||||||
orientation: root.horizontal ? Gradient.Horizontal : Gradient.Vertical
|
orientation: Gradient.Vertical
|
||||||
|
|
||||||
GradientStop {
|
GradientStop {
|
||||||
color: Qt.rgba(0, 0, 0, root.startFadeOpacity)
|
color: Qt.rgba(0, 0, 0, root.topFadeOpacity)
|
||||||
position: 0
|
position: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,13 +58,13 @@ CustomListView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GradientStop {
|
GradientStop {
|
||||||
color: Qt.rgba(0, 0, 0, root.endFadeOpacity)
|
color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity)
|
||||||
position: 1
|
position: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on startFadeOpacity {
|
Behavior on topFadeOpacity {
|
||||||
Anim {
|
Anim {
|
||||||
type: Anim.SlowEffects
|
type: Anim.SlowEffects
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,10 +40,6 @@ JsonObject {
|
|||||||
id: "tray",
|
id: "tray",
|
||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "network",
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "clock",
|
id: "clock",
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -72,12 +68,10 @@ 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,7 +17,6 @@ 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") || Config.bar.tray.showOnHover) {
|
if (!root.popouts.currentName.startsWith("traymenu")) {
|
||||||
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.offsetScale === 1) {
|
if (root.panels.sidebar.width === 0) {
|
||||||
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 * (1 - root.panels.sidebar.offsetScale);
|
const outOfSidebar = x < root.width - root.panels.sidebar.width;
|
||||||
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,6 +311,17 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-3
@@ -184,14 +184,18 @@ 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.centerIn: parent
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale
|
anchors.top: parent.top
|
||||||
|
// anchors.centerIn: parent
|
||||||
panels: root
|
panels: root
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
visibilities: root.visibilities
|
visibilities: root.visibilities
|
||||||
|
|||||||
+2
-1
@@ -38,6 +38,7 @@ 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 {
|
||||||
@@ -59,7 +60,7 @@ Region {
|
|||||||
}
|
}
|
||||||
|
|
||||||
R {
|
R {
|
||||||
panel: root.panels.settings
|
panel: root.panels.settingsWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
R {
|
R {
|
||||||
|
|||||||
+18
-5
@@ -1,7 +1,6 @@
|
|||||||
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
|
||||||
@@ -26,7 +25,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;
|
||||||
@@ -128,7 +127,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") && (!Config.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
|
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
|
||||||
windows: [root]
|
windows: [root]
|
||||||
|
|
||||||
onCleared: {
|
onCleared: {
|
||||||
@@ -162,6 +161,14 @@ 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
|
||||||
@@ -298,9 +305,15 @@ CustomWindow {
|
|||||||
PanelBg {
|
PanelBg {
|
||||||
id: settingsBg
|
id: settingsBg
|
||||||
|
|
||||||
|
property real extraHeight: 0
|
||||||
|
|
||||||
deformAmount: 0.03
|
deformAmount: 0.03
|
||||||
panel: panels.settings
|
implicitHeight: panels.settings.height * (1 + extraHeight)
|
||||||
|
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 {
|
||||||
@@ -408,7 +421,7 @@ CustomWindow {
|
|||||||
resources.transform: Matrix4x4 {
|
resources.transform: Matrix4x4 {
|
||||||
matrix: resourcesBg.deformMatrix
|
matrix: resourcesBg.deformMatrix
|
||||||
}
|
}
|
||||||
settings.transform: Matrix4x4 {
|
settingsWrapper.transform: Matrix4x4 {
|
||||||
matrix: settingsBg.deformMatrix
|
matrix: settingsBg.deformMatrix
|
||||||
}
|
}
|
||||||
sidebar.transform: Matrix4x4 {
|
sidebar.transform: Matrix4x4 {
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ 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
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,409 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Text {
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
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.lua
|
exec Hyprland -c /etc/zshell-greeter/zshell-hyprland.conf
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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'
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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)
|
|
||||||
+42
-15
@@ -11,13 +11,8 @@ 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 {
|
||||||
@@ -61,6 +56,8 @@ Singleton {
|
|||||||
|
|
||||||
onMonitorsChanged: {
|
onMonitorsChanged: {
|
||||||
ddcMonitors = [];
|
ddcMonitors = [];
|
||||||
|
ddcServiceMon = [];
|
||||||
|
ddcServiceProc.running = true;
|
||||||
ddcProc.running = true;
|
ddcProc.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +92,26 @@ 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"
|
||||||
@@ -166,12 +183,16 @@ Singleton {
|
|||||||
id: monitor
|
id: monitor
|
||||||
|
|
||||||
property real brightness
|
property real brightness
|
||||||
readonly property string busNum: ddcInfo?.busNum ?? ""
|
readonly property string busNum: root.ddcMonitors.find(m => m.connector === modelData.name)?.busNum ?? ""
|
||||||
readonly property var ddcInfo: root.ddcMonitorMap[modelData.name] ?? null
|
readonly property string displayNum: root.ddcServiceMon.find(m => m.name === modelData.model)?.display ?? ""
|
||||||
readonly property Process initProc: Process {
|
readonly property Process initProc: Process {
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (monitor.isAppleDisplay) {
|
if (monitor.isDdcService) {
|
||||||
|
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 {
|
||||||
@@ -182,11 +203,12 @@ 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: ddcInfo !== null
|
readonly property bool isDdc: root.ddcMonitors.some(m => m.connector === modelData.name)
|
||||||
|
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: 400
|
interval: 500
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!isNaN(monitor.queuedBrightness)) {
|
if (!isNaN(monitor.queuedBrightness)) {
|
||||||
@@ -197,7 +219,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initBrightness(): void {
|
function initBrightness(): void {
|
||||||
if (isAppleDisplay)
|
if (isDdcService)
|
||||||
|
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"];
|
||||||
@@ -213,25 +237,28 @@ Singleton {
|
|||||||
if (Math.round(brightness * 100) === rounded)
|
if (Math.round(brightness * 100) === rounded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (isDdc && timer.running) {
|
if ((isDdc || isDdcService) && timer.running) {
|
||||||
queuedBrightness = value;
|
queuedBrightness = value;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
brightness = value;
|
brightness = value;
|
||||||
|
|
||||||
if (isAppleDisplay)
|
if (isDdcService)
|
||||||
|
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)
|
if (isDdc || isDdcService)
|
||||||
timer.restart();
|
timer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: initBrightness()
|
Component.onCompleted: initBrightness()
|
||||||
onBusNumChanged: initBrightness()
|
onBusNumChanged: initBrightness()
|
||||||
|
onDisplayNumChanged: initBrightness()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Networking
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property list<NetworkDevice> devices: Networking.devices.values
|
||||||
|
readonly property list<Network> networks: initialBuildNetworks()
|
||||||
|
property bool scanning: false
|
||||||
|
readonly property list<WifiDevice> wifiDevices: devices.filter(d => wifiDevice(d))
|
||||||
|
readonly property bool wifiEnabled: Networking.wifiEnabled
|
||||||
|
|
||||||
|
// Original code
|
||||||
|
|
||||||
|
readonly property list<NetworkDevice> netDevice: Networking.devices.values
|
||||||
|
readonly property string networkName: getNetworkName()
|
||||||
|
readonly property list<string> nicNames: networkInterfaceCardNames()
|
||||||
|
|
||||||
|
// Useless will prob remove
|
||||||
|
function getConnectedDevices() {
|
||||||
|
let connectedDevices = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < netDevice.length; i++) {
|
||||||
|
if (netDevice[i].connected === true) {
|
||||||
|
connectedDevices.push(netDevice[i].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return connectedDevices;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHOULD retrieve network names of connected devices
|
||||||
|
// Currently only gives connected nic name
|
||||||
|
function getNetworkName() {
|
||||||
|
const devices = netDevice.filter(device => device.connected === true);
|
||||||
|
for (var i = 0; i < devices.length; i++) {
|
||||||
|
return devices[i].name;
|
||||||
|
}
|
||||||
|
return "Failed network name";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searches wired/wireless devices and sets them in a list
|
||||||
|
function networkInterfaceCardNames() {
|
||||||
|
let nicList = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < netDevice.length; ++i) {
|
||||||
|
nicList.push(netDevice[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nicList;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
|
||||||
|
function initialBuildNetworks(): void {
|
||||||
|
const init = [];
|
||||||
|
for (const d of wifiDevices) {
|
||||||
|
d.scannerEnabled = true;
|
||||||
|
init.push(...d.networks.values);
|
||||||
|
d.scannerEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
networks = init;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSecure(security): bool {
|
||||||
|
return !(security === WifiSecurityType.Open);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildNetworks(): void {
|
||||||
|
if (!scanning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const next = [];
|
||||||
|
for (const d of wifiDevices) {
|
||||||
|
next.push(...d.networks.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
networks = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rescanWifi(): void {
|
||||||
|
scanning = true;
|
||||||
|
setScan(true);
|
||||||
|
scanTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setScan(value: bool): void {
|
||||||
|
for (const d of wifiDevices) {
|
||||||
|
d.scannerEnabled = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWifi(value: bool): void {
|
||||||
|
Networking.wifiEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wifiDevice(dev): bool {
|
||||||
|
return dev.type === DeviceType.Wifi;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: scanTimer
|
||||||
|
|
||||||
|
interval: 5000
|
||||||
|
repeat: false
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
root.rebuildNetworks();
|
||||||
|
root.setScan(false);
|
||||||
|
root.scanning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 500
|
||||||
|
repeat: true
|
||||||
|
running: root.scanning
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
root.rebuildNetworks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,25 +9,31 @@ 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: Config.dashboard.performance.showVram ? 70 : 30
|
readonly property int historyLength: 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
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias centerX: notifCenterSpacing.centerX
|
||||||
|
|
||||||
|
JsonAdapter {
|
||||||
|
id: notifCenterSpacing
|
||||||
|
|
||||||
|
property int centerX
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
}
|
||||||
+3
-31
@@ -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.Components
|
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
import qs.Components
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -15,24 +15,7 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -42,12 +25,9 @@ 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: root.active
|
target: active
|
||||||
}
|
}
|
||||||
|
|
||||||
PersistentProperties {
|
PersistentProperties {
|
||||||
@@ -58,10 +38,8 @@ 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: {
|
||||||
@@ -71,10 +49,8 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// qmllint disable unresolved-type
|
|
||||||
CustomShortcut {
|
CustomShortcut {
|
||||||
description: "Previous track"
|
description: "Previous track"
|
||||||
// qmllint enable unresolved-type
|
|
||||||
name: "mediaPrev"
|
name: "mediaPrev"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
@@ -84,10 +60,8 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// qmllint disable unresolved-type
|
|
||||||
CustomShortcut {
|
CustomShortcut {
|
||||||
description: "Next track"
|
description: "Next track"
|
||||||
// qmllint enable unresolved-type
|
|
||||||
name: "mediaNext"
|
name: "mediaNext"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
@@ -97,10 +71,8 @@ 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()
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Text {
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
-107
@@ -1,7 +1,7 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
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: formatTemp(cc?.feelsLikeC)
|
readonly property string feelsLike: `${cc?.feelsLikeC ?? 0}°C`
|
||||||
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), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
|
readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), "h:mm") : "--:--"
|
||||||
readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
|
readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), "h:mm") : "--:--"
|
||||||
readonly property string temp: formatTemp(cc?.tempC)
|
readonly property string temp: `${cc?.tempC ?? 0}°C`
|
||||||
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,48 +29,29 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [lat, lon] = coords.split(",").map(s => s.trim());
|
const [lat, lon] = coords.split(",");
|
||||||
const lang = Qt.locale().name.split("_")[0] || "en";
|
const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=geocodejson`;
|
||||||
|
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;
|
||||||
if (geoCity) {
|
city = geoCity;
|
||||||
city = fixCityName(geoCity);
|
cachedCities.set(coords, geoCity);
|
||||||
cachedCities.set(coords, city);
|
} else {
|
||||||
return;
|
city = "Unknown City";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fallbackToBigDataCloud();
|
});
|
||||||
}, fallbackToBigDataCloud);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchCoordsFromCity(cityName: string): void {
|
function fetchCoordsFromCity(cityName: string): void {
|
||||||
const lang = Qt.locale().name.split("_")[0] || "en";
|
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1&language=en&format=json`;
|
||||||
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 = fixCityName(result.name);
|
city = result.name;
|
||||||
} else {
|
} else {
|
||||||
loc = "";
|
loc = "";
|
||||||
reload();
|
reload();
|
||||||
@@ -91,21 +72,25 @@ 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: json.current.temperature_2m,
|
tempC: Math.round(json.current.temperature_2m),
|
||||||
feelsLikeC: json.current.apparent_temperature,
|
tempF: Math.round(toFahrenheit(json.current.temperature_2m)),
|
||||||
|
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].replace("T", " "),
|
sunrise: json.daily.sunrise[0],
|
||||||
sunset: json.daily.sunset[0].replace("T", " ")
|
sunset: json.daily.sunset[0]
|
||||||
};
|
};
|
||||||
|
|
||||||
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].replace(/-/g, "/"),
|
date: json.daily.time[i],
|
||||||
maxTempC: json.daily.temperature_2m_max[i],
|
maxTempC: Math.round(json.daily.temperature_2m_max[i]),
|
||||||
minTempC: json.daily.temperature_2m_min[i],
|
maxTempF: Math.round(toFahrenheit(json.daily.temperature_2m_max[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])
|
||||||
});
|
});
|
||||||
@@ -114,8 +99,7 @@ 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].replace("T", " "));
|
const time = new Date(json.hourly.time[i]);
|
||||||
|
|
||||||
if (time < now)
|
if (time < now)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -123,7 +107,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]),
|
||||||
precipChance: json.hourly.precipitation_probability[i],
|
tempF: Math.round(toFahrenheit(json.hourly.temperature_2m[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])
|
||||||
});
|
});
|
||||||
@@ -132,59 +116,6 @@ 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",
|
||||||
@@ -223,9 +154,9 @@ Singleton {
|
|||||||
if (!loc || loc.indexOf(",") === -1)
|
if (!loc || loc.indexOf(",") === -1)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
const [lat, lon] = loc.split(",").map(s => s.trim());
|
const [lat, lon] = loc.split(",");
|
||||||
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,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"];
|
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"];
|
||||||
|
|
||||||
return baseUrl + "?" + params.join("&");
|
return baseUrl + "?" + params.join("&");
|
||||||
}
|
}
|
||||||
@@ -258,14 +189,7 @@ Singleton {
|
|||||||
|
|
||||||
onLocChanged: fetchWeatherData()
|
onLocChanged: fetchWeatherData()
|
||||||
|
|
||||||
Connections {
|
// Refresh current location hourly
|
||||||
function onWeatherLocationChanged(): void {
|
|
||||||
root.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: Config.services
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 3600000 // 1 hour
|
interval: 3600000 // 1 hour
|
||||||
repeat: true
|
repeat: true
|
||||||
@@ -276,5 +200,6 @@ Singleton {
|
|||||||
|
|
||||||
ElapsedTimer {
|
ElapsedTimer {
|
||||||
id: timer
|
id: timer
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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,]
|
||||||
|
}
|
||||||
+19
-13
@@ -3,10 +3,12 @@ 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.Network
|
import qs.Modules.SysTray.Widgets
|
||||||
import qs.Modules.Updates
|
import qs.Modules.Updates
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -23,7 +25,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") || Config.bar.tray.showOnHover)
|
if (!popouts.currentName.startsWith("traymenu"))
|
||||||
popouts.hasCurrent = false;
|
popouts.hasCurrent = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -45,7 +47,7 @@ RowLayout {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover)
|
if (!popouts.currentName.startsWith("traymenu"))
|
||||||
popouts.hasCurrent = false;
|
popouts.hasCurrent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +56,19 @@ RowLayout {
|
|||||||
const item = ch.item;
|
const item = ch.item;
|
||||||
const itemWidth = item.implicitWidth;
|
const itemWidth = item.implicitWidth;
|
||||||
|
|
||||||
if (id === "updates") {
|
if (id === "audio" && Config.bar.popouts.audio) {
|
||||||
|
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;
|
||||||
@@ -66,6 +80,7 @@ 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 {
|
||||||
@@ -169,15 +184,6 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "network"
|
|
||||||
|
|
||||||
delegate: WrappedLoader {
|
|
||||||
sourceComponent: NetworkWidget {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "media"
|
roleValue: "media"
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import qs.Config
|
|||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Modules.SysTray
|
import qs.Modules.SysTray
|
||||||
import qs.Modules.SysTray.Widgets
|
import qs.Modules.SysTray.Widgets
|
||||||
import qs.Modules.Network
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import QtQuick
|
|||||||
import qs.Config
|
import qs.Config
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Modules.WSOverview
|
import qs.Modules.WSOverview
|
||||||
import qs.Modules.Network
|
|
||||||
import qs.Modules.SysTray.Popouts
|
import qs.Modules.SysTray.Popouts
|
||||||
import qs.Modules.Updates
|
import qs.Modules.Updates
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-31
@@ -4,66 +4,76 @@ import qs.Components
|
|||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
|
||||||
CustomClippingRect {
|
RowLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var lock
|
required property var lock
|
||||||
|
|
||||||
implicitHeight: layout.implicitHeight
|
spacing: Appearance.spacing.large * 2
|
||||||
implicitWidth: layout.implicitWidth
|
|
||||||
radius: Appearance.rounding.large
|
|
||||||
|
|
||||||
RowLayout {
|
ColumnLayout {
|
||||||
id: layout
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
anchors.fill: parent
|
CustomRect {
|
||||||
spacing: Appearance.spacing.large * 2
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Appearance.spacing.normal
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
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
|
||||||
spacing: Appearance.spacing.normal
|
bottomRightRadius: Appearance.rounding.large
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
topRightRadius: Appearance.rounding.large
|
||||||
|
|
||||||
CustomRect {
|
NotifDock {
|
||||||
Layout.fillHeight: true
|
lock: root.lock
|
||||||
Layout.fillWidth: true
|
|
||||||
bottomRightRadius: Appearance.rounding.large
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
topRightRadius: Appearance.rounding.large
|
|
||||||
|
|
||||||
NotifDock {
|
|
||||||
lock: root.lock
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,71 +149,36 @@ 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
|
||||||
|
|
||||||
Behavior on anchors.horizontalCenterOffset {
|
// MultiEffect {
|
||||||
Anim {
|
// anchors.fill: lockBg
|
||||||
duration: 5000
|
// autoPaddingEnabled: false
|
||||||
}
|
// blur: 1
|
||||||
}
|
// blurEnabled: true
|
||||||
Behavior on anchors.verticalCenterOffset {
|
// blurMax: 64
|
||||||
Anim {
|
// maskEnabled: true
|
||||||
duration: 5000
|
// maskSource: lockBg
|
||||||
}
|
//
|
||||||
}
|
// source: ShaderEffectSource {
|
||||||
|
// sourceItem: background
|
||||||
Timer {
|
// sourceRect: Qt.rect(lockBg.x, lockBg.y, lockBg.width, lockBg, height)
|
||||||
interval: 120000
|
// }
|
||||||
repeat: true
|
// }
|
||||||
running: true
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
lockContent.positionIndex = (lockContent.positionIndex + 1) % lockContent.positions.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
id: lockBg
|
id: lockBg
|
||||||
|
|||||||
+145
-54
@@ -1,110 +1,201 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import qs.Modules
|
||||||
import ZShell.Components
|
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Config
|
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
CustomClippingRect {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var lock
|
required property var lock
|
||||||
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
anchors.fill: parent
|
||||||
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.getArtUrl(Players.active)
|
source: Players.active?.trackArtUrl ?? ""
|
||||||
sourceSize: {
|
sourceSize.height: height
|
||||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
sourceSize.width: width
|
||||||
return Qt.size(width * dpr, height * dpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: mask
|
||||||
|
}
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {
|
Anim {
|
||||||
type: Anim.StandardExtraLarge
|
duration: Appearance.anim.durations.extraLarge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CustomRect {
|
Rectangle {
|
||||||
anchors.fill: parent
|
id: mask
|
||||||
color: DynamicColors.palette.m3surface
|
|
||||||
opacity: 0.7
|
anchors.fill: parent
|
||||||
|
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.left: parent.left
|
anchors.fill: parent
|
||||||
anchors.margins: Appearance.padding.extraLarge
|
anchors.margins: Appearance.padding.large
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
CustomText {
|
||||||
spacing: Appearance.spacing.extraSmall
|
Layout.bottomMargin: Appearance.spacing.larger
|
||||||
|
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.pointSize: Appearance.font.size.medium
|
font.family: Appearance.font.family.mono
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
font.weight: 600
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: (Players.active?.trackTitle ?? qsTr("Nothing playing")) || qsTr("Unknown track")
|
text: Players.active?.trackArtist ?? qsTr("No media")
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomText {
|
CustomText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
animate: true
|
animate: true
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font.pointSize: Appearance.font.size.small
|
font.family: Appearance.font.family.mono
|
||||||
|
font.pointSize: Appearance.font.size.larger
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: (Players.active?.trackArtist ?? qsTr("Try playing some music!")) || qsTr("Unknown artist")
|
text: Players.active?.trackTitle ?? qsTr("No media")
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonRow {
|
RowLayout {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: Appearance.spacing.small
|
Layout.bottomMargin: Appearance.padding.large
|
||||||
spacing: Appearance.spacing.extraSmall
|
Layout.topMargin: Appearance.spacing.large * 1.2
|
||||||
|
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton {
|
PlayerControl {
|
||||||
checked: Players.active?.isPlaying ?? false
|
function onClicked(): void {
|
||||||
enabled: Players.active?.canTogglePlaying
|
if (Players.active?.canTogglePlaying)
|
||||||
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
Players.active.togglePlaying();
|
||||||
implicitWidth: implicitHeight + Appearance.padding.largeIncreased * 2
|
}
|
||||||
isRound: true
|
|
||||||
shapeMorph: true
|
|
||||||
|
|
||||||
onClicked: Players.active?.togglePlaying()
|
active: Players.active?.isPlaying ?? false
|
||||||
|
animate: true
|
||||||
|
icon: active ? "pause" : "play_arrow"
|
||||||
|
level: active ? 2 : 1
|
||||||
|
set_color: "Primary"
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton {
|
PlayerControl {
|
||||||
enabled: Players.active?.canGoNext
|
function onClicked(): void {
|
||||||
|
if (Players.active?.canGoNext)
|
||||||
|
Players.active.next();
|
||||||
|
}
|
||||||
|
|
||||||
icon: "skip_next"
|
icon: "skip_next"
|
||||||
isRound: true
|
}
|
||||||
shapeMorph: true
|
}
|
||||||
type: IconButton.Tonal
|
}
|
||||||
|
|
||||||
onClicked: Players.active?.next()
|
component PlayerControl: CustomRect {
|
||||||
|
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 {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-48
@@ -1,82 +1,81 @@
|
|||||||
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
|
|
||||||
|
|
||||||
CustomRect {
|
GridLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property real fontScale: {
|
anchors.left: parent.left
|
||||||
const diff = width / 391 - 1; // 391 is the width at 1080 height screen
|
anchors.margins: Appearance.padding.large
|
||||||
return 1 + Math.pow(Math.abs(diff), 0.8) * Math.sign(diff);
|
anchors.right: parent.right
|
||||||
}
|
columnSpacing: Appearance.spacing.large
|
||||||
|
columns: 2
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
rowSpacing: Appearance.spacing.large
|
||||||
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
rows: 1
|
||||||
radius: Appearance.rounding.small
|
|
||||||
|
|
||||||
ServiceRef {
|
|
||||||
service: Cpu
|
|
||||||
}
|
|
||||||
|
|
||||||
ServiceRef {
|
ServiceRef {
|
||||||
service: Memory
|
service: Memory
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceRef {
|
ServiceRef {
|
||||||
service: Storage
|
service: Cpu
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
Resource {
|
||||||
id: layout
|
Layout.bottomMargin: Appearance.padding.large
|
||||||
|
Layout.topMargin: Appearance.padding.large
|
||||||
anchors.fill: parent
|
fgColor: DynamicColors.palette.m3primary
|
||||||
anchors.margins: Appearance.padding.large
|
icon: "memory"
|
||||||
spacing: Appearance.spacing.large
|
value: Cpu.percentage
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
component Resource: CircularProgress {
|
Resource {
|
||||||
|
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
|
||||||
implicitSize: width
|
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
||||||
|
implicitHeight: width
|
||||||
|
radius: Appearance.rounding.large
|
||||||
|
|
||||||
Behavior on clampedVal {
|
Behavior on value {
|
||||||
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: Appearance.font.size.extraLarge
|
font.pointSize: (circ.arcRadius * 0.7) || 1
|
||||||
|
font.weight: 600
|
||||||
text: res.icon
|
text: res.icon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+150
-34
@@ -1,23 +1,162 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Modules.Lock.Weather
|
import QtQuick.Layouts
|
||||||
import qs.Config
|
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
CustomRect {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property int rootHeight
|
required property int rootHeight
|
||||||
readonly property bool showForecast: rootHeight >= 700
|
|
||||||
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
anchors.left: parent.left
|
||||||
implicitHeight: {
|
anchors.margins: Appearance.padding.large * 2
|
||||||
const base = brief.implicitHeight + brief.anchors.topMargin;
|
anchors.right: parent.right
|
||||||
if (showForecast)
|
spacing: Appearance.spacing.small
|
||||||
return base + Appearance.spacing.large + forecast.implicitHeight + forecast.anchors.margins;
|
|
||||||
return base + brief.anchors.topMargin;
|
Loader {
|
||||||
|
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
|
||||||
@@ -27,27 +166,4 @@ CustomRect {
|
|||||||
|
|
||||||
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 {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Networking
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import qs.Components
|
|
||||||
import qs.Config
|
|
||||||
import qs.Modules
|
|
||||||
import qs.Helpers as Helpers
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property var wrapper
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: layout
|
|
||||||
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Helpers.Network.devices
|
|
||||||
|
|
||||||
CustomRadioButton {
|
|
||||||
id: network
|
|
||||||
|
|
||||||
required property NetworkDevice modelData
|
|
||||||
|
|
||||||
checked: Helpers.Network.activeDevice?.name === modelData.name
|
|
||||||
text: modelData.description
|
|
||||||
visible: modelData.name !== "lo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import Quickshell
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import qs.Components
|
|
||||||
import qs.Modules
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.top: parent.top
|
|
||||||
implicitWidth: layout.implicitWidth
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: layout
|
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.top: parent.top
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
text: "android_wifi_4_bar"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -133,7 +133,7 @@ CustomRect {
|
|||||||
ParallelAnimation {
|
ParallelAnimation {
|
||||||
Anim {
|
Anim {
|
||||||
duration: Appearance.anim.durations.small
|
duration: Appearance.anim.durations.small
|
||||||
easing.bezierCurve: Appearance.anim.curves.standardAccel
|
easing: 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.bezierCurve: Appearance.anim.curves.standardAccel
|
easing: 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.bezierCurve: Appearance.anim.curves.standardDecel
|
easing: 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.bezierCurve: Appearance.anim.curves.standardDecel
|
easing: 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.bezierCurve: Appearance.anim.curves.emphasizedAccel
|
easing: 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.bezierCurve: Appearance.anim.curves.emphasizedDecel
|
easing: Appearance.anim.curves.emphasizedDecel
|
||||||
from: 0
|
from: 0
|
||||||
to: 1
|
to: 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,13 +102,15 @@ Item {
|
|||||||
to: 1.0
|
to: 1.0
|
||||||
value: root.brightness
|
value: root.brightness
|
||||||
|
|
||||||
onMoved: {
|
onPressedChanged: {
|
||||||
if (Config.osd.allMonBrightness) {
|
if (!pressed) {
|
||||||
for (const mon of Brightness.monitors) {
|
if (Config.osd.allMonBrightness) {
|
||||||
mon.setBrightness(value);
|
for (const mon of Brightness.monitors) {
|
||||||
|
mon.setBrightness(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
root.monitor?.setBrightness(value);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
root.monitor?.setBrightness(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-48
@@ -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,7 +28,9 @@ Scope {
|
|||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onShouldShowChanged(): void {
|
target: root
|
||||||
|
|
||||||
|
onShouldShowChanged: {
|
||||||
if (root.shouldShow) {
|
if (root.shouldShow) {
|
||||||
panelWindow.visible = true;
|
panelWindow.visible = true;
|
||||||
openAnim.start();
|
openAnim.start();
|
||||||
@@ -36,8 +38,6 @@ Scope {
|
|||||||
closeAnim.start();
|
closeAnim.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target: root
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Anim {
|
Anim {
|
||||||
@@ -243,16 +243,17 @@ Scope {
|
|||||||
Layout.preferredWidth: contentRow.implicitWidth
|
Layout.preferredWidth: contentRow.implicitWidth
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
IconTextButton {
|
CustomButton {
|
||||||
id: detailsButton
|
id: detailsButton
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
Layout.alignment: Qt.AlignLeft
|
||||||
icon: "info"
|
Layout.preferredHeight: 40
|
||||||
inactiveColor: DynamicColors.palette.m3surfaceContainer
|
Layout.preferredWidth: 92
|
||||||
inactiveOnColor: DynamicColors.palette.m3onSurface
|
bgColor: DynamicColors.palette.m3surfaceContainer
|
||||||
isRound: true
|
enabled: true
|
||||||
shapeMorph: true
|
radius: Appearance.rounding.full
|
||||||
text: "Details"
|
text: "Details"
|
||||||
|
textColor: DynamicColors.palette.m3onSurface
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
panelWindow.detailsOpen = !panelWindow.detailsOpen;
|
panelWindow.detailsOpen = !panelWindow.detailsOpen;
|
||||||
@@ -265,50 +266,41 @@ Scope {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonRow {
|
CustomButton {
|
||||||
|
id: okButton
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
spacing: Appearance.spacing.normal
|
Layout.preferredHeight: 40
|
||||||
|
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
|
||||||
|
|
||||||
IconTextButton {
|
onClicked: {
|
||||||
id: okButton
|
polkitAgent.flow.submit(passInput.text);
|
||||||
|
passInput.text = "";
|
||||||
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
|
passInput.forceActiveFocus();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IconTextButton {
|
CustomButton {
|
||||||
id: cancelButton
|
id: cancelButton
|
||||||
|
|
||||||
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
|
Layout.alignment: Qt.AlignRight
|
||||||
horizontalPadding: Appearance.padding.large
|
Layout.preferredHeight: 40
|
||||||
icon: "close"
|
Layout.preferredWidth: 76
|
||||||
inactiveColor: DynamicColors.palette.m3surfaceContainer
|
bgColor: DynamicColors.palette.m3surfaceContainer
|
||||||
inactiveOnColor: DynamicColors.palette.m3onSurface
|
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
|
||||||
isRound: true
|
radius: Appearance.rounding.full
|
||||||
isToggle: false
|
text: "Cancel"
|
||||||
shapeMorph: true
|
textColor: DynamicColors.palette.m3onSurface
|
||||||
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,7 +9,6 @@ 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,6 +1,7 @@
|
|||||||
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
|
||||||
@@ -8,6 +9,8 @@ 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
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
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 || memoryCard.active || vramCard.active || networkCard1.active
|
visible: storageCard.active || networkCard.active || memoryCard.active
|
||||||
|
|
||||||
WrappedLoader {
|
WrappedLoader {
|
||||||
id: storageCard
|
id: storageCard
|
||||||
@@ -95,32 +95,15 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WrappedLoader {
|
WrappedLoader {
|
||||||
id: vramCard
|
id: networkCard
|
||||||
|
|
||||||
active: Config.dashboard.performance.showVram
|
active: Config.dashboard.performance.showNetwork
|
||||||
|
|
||||||
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 {
|
||||||
|
|||||||
@@ -32,31 +32,6 @@ 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,6 +267,8 @@ 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);
|
||||||
|
|||||||
@@ -148,200 +148,154 @@ VerticalFadeFlickable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
ListView {
|
||||||
id: resultList
|
id: resultList
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
cacheBuffer: 10000
|
||||||
|
implicitHeight: contentHeight
|
||||||
|
interactive: false
|
||||||
spacing: Appearance.padding.large
|
spacing: Appearance.padding.large
|
||||||
|
|
||||||
add: Transition {
|
delegate: ColumnLayout {
|
||||||
Anim {
|
id: group
|
||||||
from: 0
|
|
||||||
property: "opacity"
|
|
||||||
to: 1
|
|
||||||
type: Anim.DefaultEffects
|
|
||||||
}
|
|
||||||
}
|
|
||||||
move: Transition {
|
|
||||||
Anim {
|
|
||||||
properties: "x,y"
|
|
||||||
}
|
|
||||||
|
|
||||||
Anim {
|
required property int index
|
||||||
property: "opacity"
|
required property var modelData
|
||||||
to: 1
|
|
||||||
type: Anim.DefaultEffects
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
spacing: Appearance.spacing.small
|
||||||
model: ScriptModel {
|
width: resultList.width
|
||||||
objectProp: "pageIdx"
|
|
||||||
values: root.groups
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Appearance.padding.small
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
text: group.modelData.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
text: group.modelData.page
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: group
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.extraSmall / 2
|
||||||
|
|
||||||
required property int index
|
Repeater {
|
||||||
required property var modelData
|
model: group.modelData.entries
|
||||||
|
|
||||||
spacing: Appearance.spacing.small
|
CustomRect {
|
||||||
width: resultList.width
|
id: result
|
||||||
|
|
||||||
RowLayout {
|
required property int index
|
||||||
Layout.fillWidth: true
|
readonly property bool isFirst: index === 0
|
||||||
Layout.leftMargin: Appearance.padding.small
|
readonly property bool isLast: index === group.modelData.entries.length - 1
|
||||||
spacing: Appearance.spacing.small
|
required property var modelData
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
color: DynamicColors.palette.m3primary
|
|
||||||
fill: 1
|
|
||||||
font.pointSize: Appearance.font.size.large
|
|
||||||
text: group.modelData.icon
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: DynamicColors.palette.m3secondary
|
bottomLeftRadius: layer.pressed ? Appearance.rounding.medium : isLast ? Appearance.rounding.large : Appearance.rounding.extraSmall
|
||||||
elide: Text.ElideRight
|
bottomRightRadius: layer.pressed ? Appearance.rounding.medium : isLast ? Appearance.rounding.large : Appearance.rounding.extraSmall
|
||||||
font.pointSize: Appearance.font.size.large
|
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
||||||
text: group.modelData.page
|
implicitHeight: {
|
||||||
}
|
const h = resultLayout.implicitHeight + resultLayout.anchors.margins * 2;
|
||||||
}
|
return h % 2 === 0 ? h : h + 1;
|
||||||
|
|
||||||
Column {
|
|
||||||
id: cardList
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Appearance.spacing.extraSmall / 2
|
|
||||||
|
|
||||||
add: Transition {
|
|
||||||
Anim {
|
|
||||||
from: 0
|
|
||||||
property: "opacity"
|
|
||||||
to: 1
|
|
||||||
type: Anim.DefaultEffects
|
|
||||||
}
|
}
|
||||||
}
|
topLeftRadius: layer.pressed ? Appearance.rounding.medium : isFirst ? Appearance.rounding.large : Appearance.rounding.extraSmall
|
||||||
move: Transition {
|
topRightRadius: layer.pressed ? Appearance.rounding.medium : isFirst ? Appearance.rounding.large : Appearance.rounding.extraSmall
|
||||||
Anim {
|
|
||||||
properties: "x,y"
|
RadiusBehavior on bottomLeftRadius {
|
||||||
|
}
|
||||||
|
RadiusBehavior on bottomRightRadius {
|
||||||
|
}
|
||||||
|
RadiusBehavior on topLeftRadius {
|
||||||
|
}
|
||||||
|
RadiusBehavior on topRightRadius {
|
||||||
}
|
}
|
||||||
|
|
||||||
Anim {
|
ColumnLayout {
|
||||||
property: "opacity"
|
id: resultLayout
|
||||||
to: 1
|
|
||||||
type: Anim.DefaultEffects
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
anchors.fill: parent
|
||||||
model: ScriptModel {
|
anchors.margins: Appearance.padding.large
|
||||||
objectProp: "anchor"
|
anchors.rightMargin: result.modelData.togglePath ? toggle.width + Appearance.padding.large * 2 : Appearance.padding.large
|
||||||
values: group.modelData.entries
|
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.StyledText
|
||||||
|
}
|
||||||
|
|
||||||
|
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.StyledText
|
||||||
|
visible: result.modelData.subtext.length > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomRect {
|
StateLayer {
|
||||||
id: result
|
id: layer
|
||||||
|
|
||||||
required property int index
|
z: 1
|
||||||
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
|
onClicked: {
|
||||||
bottomRightRadius: layer.pressed ? Appearance.rounding.medium : isLast ? Appearance.rounding.large : Appearance.rounding.extraSmall
|
root.sState.jumpToSetting(result.modelData.pageIdx, result.modelData.subPath, result.modelData.anchor);
|
||||||
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 {
|
CustomSwitch {
|
||||||
}
|
id: toggle
|
||||||
RadiusBehavior on bottomRightRadius {
|
|
||||||
}
|
|
||||||
RadiusBehavior on topLeftRadius {
|
|
||||||
}
|
|
||||||
RadiusBehavior on topRightRadius {
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
anchors.right: parent.right
|
||||||
id: resultLayout
|
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
|
||||||
|
|
||||||
anchors.fill: parent
|
onToggled: result.modelData.setToggle(checked)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
model: ScriptModel {
|
||||||
|
objectProp: "pageIdx"
|
||||||
|
values: root.groups
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomText {
|
CustomText {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ 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"
|
settingAnchor: "bar-clock-format"
|
||||||
subtext: qsTr("Change how time is displayed in the widget")
|
subtext: qsTr("Change how time is displayed in the widget")
|
||||||
@@ -57,15 +58,5 @@ 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ PageBase {
|
|||||||
SpinRow {
|
SpinRow {
|
||||||
first: true
|
first: true
|
||||||
from: 12
|
from: 12
|
||||||
|
last: true
|
||||||
settingAnchor: "bar-tray-iconsize"
|
settingAnchor: "bar-tray-iconsize"
|
||||||
stepSize: 1
|
stepSize: 1
|
||||||
text: qsTr("Icon size")
|
text: qsTr("Icon size")
|
||||||
@@ -27,24 +28,5 @@ 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,14 +56,6 @@ PageBase {
|
|||||||
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"
|
settingAnchor: "resources-cpu"
|
||||||
|
|||||||
@@ -10,39 +10,16 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var fzfFinder: null
|
property var fzfFinder: null
|
||||||
readonly property var highlightCache: ({
|
|
||||||
"search": "",
|
|
||||||
"pattern": null
|
|
||||||
})
|
|
||||||
property var inverted: ({})
|
property var inverted: ({})
|
||||||
property var ranking: ({})
|
property var ranking: ({})
|
||||||
|
|
||||||
function highlight(text: string, search: string, colour: color): string {
|
function highlight(text: string, search: string, colour: color): string {
|
||||||
const escaped = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
const escaped = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||||
if (search.length === 0)
|
const tokens = tokenize(search);
|
||||||
|
if (tokens.length === 0)
|
||||||
return escaped;
|
return escaped;
|
||||||
|
const escapedTokens = tokens.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
||||||
const cache = root.highlightCache;
|
const pattern = new RegExp("\\b(" + escapedTokens.join("|") + ")", "gi");
|
||||||
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>`);
|
return escaped.replace(pattern, `<font color="${colour}">$1</font>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ 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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Config
|
||||||
|
import qs.Helpers
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var wrapper
|
||||||
|
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
implicitHeight: networkPopContent.height + networks.implicitHeight + networkPopContent.anchors.margins + networks.anchors.margins * 2
|
||||||
|
implicitWidth: 500 + 8 * 2
|
||||||
|
radius: (20 - Appearance.padding.small) * Appearance.rounding.scale
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: networkPopContent
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||||
|
Layout.rightMargin: Appearance.padding.extraSmall
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
text: qsTr("Network")
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle {
|
||||||
|
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||||
|
checked: Network.wifiEnabled
|
||||||
|
label: qsTr("WiFi enabled")
|
||||||
|
|
||||||
|
toggle.onToggled: Network.setWifi(checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||||
|
Layout.rightMargin: Appearance.padding.extraSmall
|
||||||
|
Layout.topMargin: visible ? Appearance.spacing.small : 0
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
text: qsTr("%1 networks available").arg(Network.networks.length) // qmllint disable missing-property
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: networks
|
||||||
|
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: networkPopContent.bottom
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ScriptModel {
|
||||||
|
values: [...Network.networks]
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: networkItem
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||||
|
Layout.rightMargin: Appearance.padding.extraSmall
|
||||||
|
opacity: 0
|
||||||
|
scale: 0.7
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
opacity = 1;
|
||||||
|
scale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
color: networkItem.modelData.active ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
text: Icons.getNetworkIcon(networkItem.modelData.signalStrength * 100, Network.isSecure(networkItem.modelData.security))
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Appearance.spacing.extraSmall
|
||||||
|
Layout.rightMargin: Appearance.spacing.extraSmall
|
||||||
|
color: networkItem.modelData.active ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: networkItem.modelData.name
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
color: Qt.alpha(DynamicColors.palette.m3primary, networkItem.modelData.active ? 1 : 0)
|
||||||
|
implicitHeight: wirelessConnectIcon.implicitHeight + Appearance.padding.extraSmall
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
// CircularIndicator {
|
||||||
|
// anchors.fill: parent
|
||||||
|
// running: networkItem.loading
|
||||||
|
// }
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
color: networkItem.modelData.active ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
console.log(Network.devices[1].scannerEnabled, Network.devices[2].scannerEnabled, Network.devices[3].scannerEnabled, Network.devices[4].scannerEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: wirelessConnectIcon
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
animate: true
|
||||||
|
color: networkItem.modelData.active ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||||
|
text: networkItem.modelData.active ? "link_off" : "link"
|
||||||
|
|
||||||
|
// opacity: networkItem.loading ? 0 : 1
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||||
|
Layout.topMargin: visible ? Appearance.spacing.small : 0
|
||||||
|
color: DynamicColors.palette.m3primaryContainer
|
||||||
|
implicitHeight: rescanBtn.implicitHeight + Appearance.padding.small
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
color: DynamicColors.palette.m3onPrimaryContainer
|
||||||
|
enabled: !Network.scanning
|
||||||
|
|
||||||
|
onClicked: Network.rescanWifi()
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rescanBtn
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
opacity: Network.scanning ? 0 : 1
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: scanIcon
|
||||||
|
|
||||||
|
Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575)
|
||||||
|
animate: true
|
||||||
|
color: DynamicColors.palette.m3onPrimaryContainer
|
||||||
|
text: "wifi_find"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.topMargin: -Math.round(scanIcon.fontInfo.pointSize * 0.0575)
|
||||||
|
color: DynamicColors.palette.m3onPrimaryContainer
|
||||||
|
text: qsTr("Rescan networks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CircularIndicator {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
bgColor: "transparent"
|
||||||
|
implicitSize: parent.implicitHeight - Appearance.padding.large
|
||||||
|
running: Network.scanning
|
||||||
|
strokeWidth: Appearance.padding.extraSmall / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Toggle: RowLayout {
|
||||||
|
property alias checked: toggle.checked
|
||||||
|
required property string label
|
||||||
|
property alias toggle: toggle
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: Appearance.padding.extraSmall
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: parent.label
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomSwitch {
|
||||||
|
id: toggle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import qs.Components
|
import qs.Components
|
||||||
@@ -13,13 +12,14 @@ import qs.Config
|
|||||||
StackView {
|
StackView {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property int biggestWidth: 0
|
||||||
readonly property int itemHeight: 30
|
readonly property int itemHeight: 30
|
||||||
readonly property int panelRadius: ((itemHeight / 2) + Appearance.padding.small) * Appearance.rounding.scale
|
readonly property int panelRadius: ((itemHeight / 2) + Appearance.padding.small) * Appearance.rounding.scale
|
||||||
required property PopoutState popouts
|
required property PopoutState popouts
|
||||||
property int rootWidth: 0
|
property int rootWidth: 0
|
||||||
required property QsMenuHandle trayItem
|
required property QsMenuHandle trayItem
|
||||||
|
|
||||||
implicitHeight: currentItem.isSubMenu ? currentItem.implicitHeight : currentItem.implicitHeight - currentItem.spacing
|
implicitHeight: currentItem.implicitHeight
|
||||||
implicitWidth: currentItem.implicitWidth
|
implicitWidth: currentItem.implicitWidth
|
||||||
|
|
||||||
initialItem: SubMenu {
|
initialItem: SubMenu {
|
||||||
@@ -46,7 +46,7 @@ StackView {
|
|||||||
duration: 0
|
duration: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
component SubMenu: ColumnLayout {
|
component SubMenu: Column {
|
||||||
id: menu
|
id: menu
|
||||||
|
|
||||||
required property QsMenuHandle handle
|
required property QsMenuHandle handle
|
||||||
@@ -54,8 +54,9 @@ StackView {
|
|||||||
property bool shown
|
property bool shown
|
||||||
|
|
||||||
opacity: shown ? 1 : 0
|
opacity: shown ? 1 : 0
|
||||||
|
padding: 0
|
||||||
scale: shown ? 1 : 0.8
|
scale: shown ? 1 : 0.8
|
||||||
spacing: Appearance.spacing.extraSmall
|
spacing: 4
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {
|
Anim {
|
||||||
@@ -86,27 +87,22 @@ StackView {
|
|||||||
required property int index
|
required property int index
|
||||||
required property QsMenuEntry modelData
|
required property QsMenuEntry modelData
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: modelData.isSeparator ? Appearance.padding.normal : 0
|
|
||||||
Layout.rightMargin: modelData.isSeparator ? Appearance.padding.normal : 0
|
|
||||||
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
|
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
|
||||||
implicitHeight: modelData.isSeparator ? (visible ? 1 : 0) : childrenLoader.item.implicitHeight
|
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
|
||||||
implicitWidth: childrenLoader.item?.implicitWidth ?? 0
|
implicitWidth: root.biggestWidth
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
visible: index !== (menuOpener.children.values.length - 1) ? true : (modelData.isSeparator ? false : true)
|
visible: index !== (menuOpener.children.values.length - 1) ? true : (modelData.isSeparator ? false : true)
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: childrenLoader
|
id: children
|
||||||
|
|
||||||
active: !item.modelData.isSeparator
|
active: !item.modelData.isSeparator
|
||||||
anchors.fill: parent
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: Item {
|
sourceComponent: Item {
|
||||||
property int iconWidth: icon.active ? icon.width + Appearance.spacing.normal + icon.anchors.rightMargin : 0
|
|
||||||
|
|
||||||
implicitHeight: root.itemHeight
|
implicitHeight: root.itemHeight
|
||||||
implicitWidth: label.width + label.anchors.leftMargin * 2 + iconWidth + (expand.item?.width ?? 0)
|
|
||||||
|
|
||||||
StateLayer {
|
StateLayer {
|
||||||
enabled: item.modelData.enabled
|
enabled: item.modelData.enabled
|
||||||
@@ -115,6 +111,8 @@ StackView {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
const entry = item.modelData;
|
const entry = item.modelData;
|
||||||
if (entry.hasChildren) {
|
if (entry.hasChildren) {
|
||||||
|
root.rootWidth = root.biggestWidth;
|
||||||
|
root.biggestWidth = 0;
|
||||||
root.push(subMenuComp.createObject(null, {
|
root.push(subMenuComp.createObject(null, {
|
||||||
handle: entry,
|
handle: entry,
|
||||||
isSubMenu: true
|
isSubMenu: true
|
||||||
@@ -163,7 +161,23 @@ StackView {
|
|||||||
anchors.leftMargin: 10
|
anchors.leftMargin: 10
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
|
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
|
||||||
|
text: labelMetrics.elidedText
|
||||||
|
}
|
||||||
|
|
||||||
|
TextMetrics {
|
||||||
|
id: labelMetrics
|
||||||
|
|
||||||
|
font.family: label.font.family
|
||||||
|
font.pointSize: label.font.pointSize
|
||||||
text: item.modelData.text
|
text: item.modelData.text
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
var biggestWidth = root.biggestWidth;
|
||||||
|
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
|
||||||
|
if (currentWidth > biggestWidth) {
|
||||||
|
root.biggestWidth = currentWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -187,17 +201,17 @@ StackView {
|
|||||||
Loader {
|
Loader {
|
||||||
id: loader
|
id: loader
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumHeight: active ? implicitHeight : 0
|
|
||||||
active: menu.isSubMenu
|
active: menu.isSubMenu
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: Item {
|
sourceComponent: Item {
|
||||||
implicitHeight: 30
|
implicitHeight: 30
|
||||||
|
implicitWidth: back.implicitWidth
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.bottom: parent.bottom
|
||||||
implicitHeight: 30
|
implicitHeight: 30
|
||||||
|
implicitWidth: root.biggestWidth
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -210,6 +224,7 @@ StackView {
|
|||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.pop();
|
root.pop();
|
||||||
|
root.biggestWidth = root.rootWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ Item {
|
|||||||
CustomRect {
|
CustomRect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 3
|
anchors.margins: 3
|
||||||
color: icon.layer.enabled && enabled && root.current ? DynamicColors.palette.m3primary : "transparent"
|
color: icon.layer.enabled && root.current ? DynamicColors.palette.m3primary : "transparent"
|
||||||
enabled: !Config.bar.tray.showOnHover
|
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
StateLayer {
|
StateLayer {
|
||||||
@@ -58,7 +57,7 @@ Item {
|
|||||||
if (mouse.button === Qt.LeftButton) {
|
if (mouse.button === Qt.LeftButton) {
|
||||||
root.item.activate();
|
root.item.activate();
|
||||||
console.log(icon.source + "\n" + root.item.id);
|
console.log(icon.source + "\n" + root.item.id);
|
||||||
} else if (mouse.button === Qt.RightButton && Config.bar.popouts.tray && !Config.bar.tray.showOnHover) {
|
} else if (mouse.button === Qt.RightButton && Config.bar.popouts.tray) {
|
||||||
root.popouts.currentName = `traymenu${root.ind}`;
|
root.popouts.currentName = `traymenu${root.ind}`;
|
||||||
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
|
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
|
||||||
root.popouts.hasCurrent = true;
|
root.popouts.hasCurrent = true;
|
||||||
@@ -77,9 +76,9 @@ Item {
|
|||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
color: root.current && !Config.bar.tray.showOnHover ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||||
implicitSize: Config.bar.tray.trayIconSize * root.dpr
|
implicitSize: Config.bar.tray.trayIconSize * root.dpr
|
||||||
layer.enabled: Config.bar.tray.recolorIcons
|
layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark
|
||||||
scale: 1 / root.dpr
|
scale: 1 / root.dpr
|
||||||
source: root.resolveIcon(root.item.id, root.item.icon)
|
source: root.resolveIcon(root.item.id, root.item.icon)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ RowLayout {
|
|||||||
id: "audio",
|
id: "audio",
|
||||||
item: child
|
item: child
|
||||||
};
|
};
|
||||||
|
if (child.objectName === "networkWidget" && Config.bar.popouts.network)
|
||||||
|
return {
|
||||||
|
id: "network",
|
||||||
|
item: child
|
||||||
|
};
|
||||||
if (child.objectName === "upowerWidget" && Config.bar.popouts.upower)
|
if (child.objectName === "upowerWidget" && Config.bar.popouts.upower)
|
||||||
return {
|
return {
|
||||||
id: "upower",
|
id: "upower",
|
||||||
@@ -61,12 +66,12 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let trayPos = mapToItem(sysTray, localX, localY);
|
let trayPos = mapToItem(sysTray, localX, localY);
|
||||||
if (Config.bar.tray.showOnHover && sysTray.contains(Qt.point(trayPos.x, trayPos.y))) {
|
if (sysTray.contains(Qt.point(trayPos.x, trayPos.y))) {
|
||||||
let trayRowPos = sysTray.mapToItem(sysRow, trayPos.x, trayPos.y);
|
let trayRowPos = sysTray.mapToItem(sysRow, trayPos.x, trayPos.y);
|
||||||
let child = sysRow.childAt(trayRowPos.x, trayRowPos.y);
|
let child = sysRow.childAt(trayRowPos.x, trayRowPos.y);
|
||||||
if (child && child.hasOwnProperty("ind")) {
|
if (child && child.hasOwnProperty("popoutId")) {
|
||||||
return {
|
return {
|
||||||
id: `traymenu${child.ind}`,
|
id: child.popoutId,
|
||||||
item: child
|
item: child
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -146,6 +151,10 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetworkWidget {
|
||||||
|
objectName: "networkWidget"
|
||||||
|
}
|
||||||
|
|
||||||
UPowerWidget {
|
UPowerWidget {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
objectName: "upowerWidget"
|
objectName: "upowerWidget"
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Services.Pipewire
|
||||||
|
import qs.Daemons
|
||||||
|
import qs.Modules
|
||||||
|
import qs.Config
|
||||||
|
import qs.Components
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// property color barColor: DynamicColors.palette.m3primary
|
||||||
|
property color textColor: DynamicColors.palette.m3onSurface
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
animate: true
|
||||||
|
color: root.textColor // Network.connected ? root.textColor : DynamicColors.palette.m3error
|
||||||
|
fill: 1
|
||||||
|
font.pointSize: Appearance.font.size.larger
|
||||||
|
text: "android_wifi_4_bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,10 +12,10 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool completed
|
property bool completed
|
||||||
property real cropHeight: displayData?.height ?? 1.0
|
property real cropHeight: displayData.height ?? 1.0
|
||||||
property real cropWidth: displayData?.width ?? 1.0
|
property real cropWidth: displayData.width ?? 1.0
|
||||||
property real cropX: displayData?.x ?? 0.0
|
property real cropX: displayData.x ?? 0.0
|
||||||
property real cropY: displayData?.y ?? 0.0
|
property real cropY: displayData.y ?? 0.0
|
||||||
property WallpaperImage current
|
property WallpaperImage current
|
||||||
readonly property var displayData: Wallpapers.getCrop(screen.name)
|
readonly property var displayData: Wallpapers.getCrop(screen.name)
|
||||||
required property ShellScreen screen
|
required property ShellScreen screen
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
#include "sensorslib.hpp"
|
#include "sensorslib.hpp"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfile.h>
|
#include <qfile.h>
|
||||||
#include <qobject.h>
|
|
||||||
#include <qregularexpression.h>
|
#include <qregularexpression.h>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
@@ -245,45 +243,6 @@ void Gpu::detectNameOnce() {
|
|||||||
{QStringLiteral("-c"), QString::fromLatin1(kNameDetectScript)});
|
{QStringLiteral("-c"), QString::fromLatin1(kNameDetectScript)});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gpu::readGenericMemory() {
|
|
||||||
const QStringList paths = QDir(QStringLiteral("/sys/class/drm"))
|
|
||||||
.entryList(
|
|
||||||
QStringList() << QStringLiteral("card*"),
|
|
||||||
QDir::Dirs | QDir::NoDotAndDotDot);
|
|
||||||
|
|
||||||
qreal totalMem = 0.0;
|
|
||||||
qreal usedMem = 0.0;
|
|
||||||
for (const QString& card : paths) {
|
|
||||||
QFile total(
|
|
||||||
QStringLiteral("/sys/class/drm/%1/device/mem_info_vram_total")
|
|
||||||
.arg(card));
|
|
||||||
if (!total.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
bool ok = false;
|
|
||||||
const qreal v = total.readAll().trimmed().toDouble(&ok);
|
|
||||||
total.close();
|
|
||||||
if (ok) {
|
|
||||||
totalMem += v;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile used(QStringLiteral("/sys/class/drm/%1/device/mem_info_vram_used")
|
|
||||||
.arg(card));
|
|
||||||
if (!used.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
bool ok1 = false;
|
|
||||||
const qreal v1 = used.readAll().trimmed().toDouble(&ok1);
|
|
||||||
used.close();
|
|
||||||
if (ok1) {
|
|
||||||
usedMem += v1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setMemoryTotal(totalMem);
|
|
||||||
setMemoryUsed(usedMem);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Gpu::readGenericUsage() {
|
void Gpu::readGenericUsage() {
|
||||||
const QStringList paths = QDir(QStringLiteral("/sys/class/drm"))
|
const QStringList paths = QDir(QStringLiteral("/sys/class/drm"))
|
||||||
.entryList(
|
.entryList(
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ class Gpu : public TickingService {
|
|||||||
void readGenericUsage();
|
void readGenericUsage();
|
||||||
void startNvidiaUsage();
|
void startNvidiaUsage();
|
||||||
void readGpuTemperature();
|
void readGpuTemperature();
|
||||||
void readGenericMemory();
|
|
||||||
|
|
||||||
void setUserType(Type value);
|
void setUserType(Type value);
|
||||||
void setAutoType(Type value);
|
void setAutoType(Type value);
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
file(GLOB ZSHELL_CLI_WHEEL "@ZSHELL_CLI_DIST_DIR@/*.whl")
|
|
||||||
if(NOT ZSHELL_CLI_WHEEL)
|
|
||||||
message(FATAL_ERROR "No zshell-cli wheel found in @ZSHELL_CLI_DIST_DIR@")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(_zshell_installer_args "--prefix=@CMAKE_INSTALL_PREFIX@")
|
|
||||||
if(DEFINED ENV{DESTDIR} AND NOT "$ENV{DESTDIR}" STREQUAL "")
|
|
||||||
list(APPEND _zshell_installer_args "--destdir=$ENV{DESTDIR}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
execute_process(
|
|
||||||
COMMAND "@Python3_EXECUTABLE@" -m installer
|
|
||||||
${_zshell_installer_args}
|
|
||||||
${ZSHELL_CLI_WHEEL}
|
|
||||||
RESULT_VARIABLE ZSHELL_CLI_INSTALL_RESULT
|
|
||||||
)
|
|
||||||
if(NOT ZSHELL_CLI_INSTALL_RESULT EQUAL 0)
|
|
||||||
message(FATAL_ERROR "zshell-cli wheel install failed")
|
|
||||||
endif()
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
set(ZSHELL_CLI_BIN_DIR "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/usr/bin")
|
|
||||||
file(MAKE_DIRECTORY "${ZSHELL_CLI_BIN_DIR}")
|
|
||||||
|
|
||||||
file(RELATIVE_PATH ZSHELL_CLI_TARGET
|
|
||||||
"${ZSHELL_CLI_BIN_DIR}"
|
|
||||||
"${CMAKE_INSTALL_PREFIX}/@INSTALL_LIBDIR@/zshell-cli/zshell-cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
file(CREATE_LINK
|
|
||||||
"${ZSHELL_CLI_TARGET}"
|
|
||||||
"${ZSHELL_CLI_BIN_DIR}/zshell-cli" SYMBOLIC
|
|
||||||
)
|
|
||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
cd "$(dirname $0)/../src" || exit
|
||||||
|
|
||||||
|
python3 -m zshell "$@"
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["hatchling >= 1.26"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "zshell"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"typer",
|
||||||
|
"pillow",
|
||||||
|
"jinja2",
|
||||||
|
"materialyoucolor"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
zshell-cli = "zshell:main"
|
||||||
|
|
||||||
|
[tool.hatch.version]
|
||||||
|
source = "vcs"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.sdist]
|
||||||
|
only-include = [
|
||||||
|
"src",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
pythonpath = ["src"]
|
||||||
+33
-51
@@ -1,14 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
|
from typer._completion_shared import install, _get_shell_name
|
||||||
from typer._completion_classes import completion_init
|
from typer._completion_classes import completion_init
|
||||||
from typer._completion_shared import _get_shell_name, install
|
from zshell.subcommands import shell, scheme, screenshot, wallpaper, record
|
||||||
|
|
||||||
from zshell.subcommands import record, scheme, screenshot, shell, wallpaper
|
|
||||||
|
|
||||||
app = typer.Typer(name="zshell-cli", add_completion=False)
|
app = typer.Typer(name="zshell-cli", add_completion=False)
|
||||||
|
|
||||||
@@ -20,56 +18,40 @@ app.add_typer(record.app, name="record")
|
|||||||
|
|
||||||
|
|
||||||
def _completion_installed() -> bool:
|
def _completion_installed() -> bool:
|
||||||
shell = _get_shell_name()
|
shell = _get_shell_name()
|
||||||
match shell:
|
match shell:
|
||||||
case "zsh":
|
case "zsh":
|
||||||
return (Path.home() / ".zfunc" / "_zshell-cli").exists()
|
return (Path.home() / ".zfunc" / "_zshell-cli").exists()
|
||||||
case "bash":
|
case "bash":
|
||||||
return (
|
return (Path.home() / ".bash_completions" / "zshell-cli.sh").exists()
|
||||||
Path.home() / ".bash_completions" / "zshell-cli.sh"
|
case "fish":
|
||||||
).exists()
|
return (Path.home() / ".config" / "fish" / "completions" / "zshell-cli.fish").exists()
|
||||||
case "fish":
|
return False
|
||||||
return (
|
|
||||||
Path.home()
|
|
||||||
/ ".config"
|
|
||||||
/ "fish"
|
|
||||||
/ "completions"
|
|
||||||
/ "zshell-cli.fish"
|
|
||||||
).exists()
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _install_completion() -> None:
|
def _install_completion() -> None:
|
||||||
if _completion_installed():
|
if _completion_installed():
|
||||||
print("zshell-cli: Shell completion already installed.")
|
print("zshell-cli: Shell completion already installed.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
shell = _get_shell_name()
|
shell = _get_shell_name()
|
||||||
if shell is None:
|
if shell is None:
|
||||||
print("zshell-cli: Unable to detect shell type.", file=sys.stderr)
|
print("zshell-cli: Unable to detect shell type.", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
try:
|
try:
|
||||||
_, path = install(prog_name="zshell-cli")
|
_, path = install(prog_name="zshell-cli")
|
||||||
print(f"zshell-cli: Shell completion installed ({shell}: {path})")
|
print(f"zshell-cli: Shell completion installed ({shell}: {path})")
|
||||||
print(
|
print("zshell-cli: Restart your shell or source the file to enable tab-completion.")
|
||||||
"zshell-cli: Restart your shell or source the file to enable tab-completion."
|
except Exception as e:
|
||||||
)
|
print(f"zshell-cli: Failed to install shell completion: {e}", file=sys.stderr)
|
||||||
except Exception as e:
|
raise typer.Exit(code=1)
|
||||||
print(
|
|
||||||
f"zshell-cli: Failed to install shell completion: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
raise typer.Exit(code=1) from None
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
if "--install-autocomplete" in sys.argv:
|
if "--install-autocomplete" in sys.argv:
|
||||||
_install_completion()
|
_install_completion()
|
||||||
return
|
return
|
||||||
if "_ZSHELL_CLI_COMPLETE" in os.environ:
|
if "_ZSHELL_CLI_COMPLETE" in os.environ:
|
||||||
completion_init()
|
completion_init()
|
||||||
if sys.stdout.isatty() and not _completion_installed():
|
if sys.stdout.isatty() and not _completion_installed():
|
||||||
print(
|
print("zshell-cli: Tip: run with --install-autocomplete for tab completion.", file=sys.stderr)
|
||||||
"zshell-cli: Tip: run with --install-autocomplete for tab completion.",
|
app()
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
app()
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user