25 Commits
Author SHA1 Message Date
zach 54d4676ddf Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Successful in 4s
C++ / build (pull_request) Failing after 2m3s
C++ / clang-tidy (pull_request) Failing after 2m2s
JS/TS / fmt (pull_request) Failing after 12s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 29s
Rust / build (pull_request) Successful in 51s
Python / verify (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 33s
Rust / clippy (pull_request) Successful in 57s
2026-09-04 19:44:12 +02:00
zach cd3da1941b fix: truncate context logic
C++ / fmt (pull_request) Successful in 4s
C++ / build (pull_request) Failing after 2m6s
C++ / clang-tidy (pull_request) Failing after 2m5s
JS/TS / fmt (pull_request) Failing after 3m8s
Python / static (pull_request) Successful in 24s
Python / verify (pull_request) Successful in 1m21s
JS/TS / lint (pull_request) Successful in 5m10s
Rust / fmt (pull_request) Successful in 30s
Rust / build (pull_request) Successful in 56s
Rust / clippy (pull_request) Successful in 46s
2026-09-04 02:20:37 +02:00
zach 34dde4bbfb fix: tool call prompt
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 2m4s
C++ / clang-tidy (pull_request) Failing after 2m2s
JS/TS / fmt (pull_request) Failing after 6s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 27s
Rust / build (pull_request) Successful in 47s
Python / verify (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 27s
Rust / clippy (pull_request) Successful in 49s
2026-09-03 23:45:07 +02:00
zach fcb848b6d2 fix: truncate webfetch content to fit context
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 2m18s
C++ / clang-tidy (pull_request) Failing after 2m7s
JS/TS / fmt (pull_request) Failing after 9s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 29s
Rust / build (pull_request) Successful in 50s
Python / verify (pull_request) Successful in 1m38s
Rust / fmt (pull_request) Successful in 26s
Rust / clippy (pull_request) Successful in 47s
2026-09-03 22:46:46 +02:00
zach ae5c1fa148 use blobgroup for window chat interface + slim sidebar
C++ / fmt (pull_request) Successful in 4s
C++ / build (pull_request) Failing after 2m1s
C++ / clang-tidy (pull_request) Failing after 1m54s
JS/TS / fmt (pull_request) Failing after 7s
JS/TS / lint (pull_request) Successful in 7s
Python / static (pull_request) Successful in 29s
Rust / build (pull_request) Successful in 47s
Python / verify (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 29s
Rust / clippy (pull_request) Successful in 51s
2026-09-01 23:14:21 +02:00
zach b254000737 feat: implement findChild* methods
C++ / fmt (pull_request) Canceled after 0s
C++ / build (pull_request) Canceled after 0s
C++ / clang-tidy (pull_request) Canceled after 0s
JS/TS / lint (pull_request) Canceled after 0s
JS/TS / fmt (pull_request) Canceled after 2s
Python / static (pull_request) Canceled after 0s
Python / verify (pull_request) Canceled after 0s
Rust / build (pull_request) Canceled after 0s
Rust / fmt (pull_request) Canceled after 0s
Rust / clippy (pull_request) Canceled after 0s
2026-09-01 15:51:10 +02:00
zach 04fe8b1e43 feat: implement findChild* methods
C++ / fmt (pull_request) Canceled after 0s
C++ / build (pull_request) Canceled after 0s
C++ / clang-tidy (pull_request) Canceled after 0s
JS/TS / fmt (pull_request) Canceled after 0s
JS/TS / lint (pull_request) Canceled after 0s
Python / static (pull_request) Canceled after 0s
Python / verify (pull_request) Canceled after 0s
Rust / build (pull_request) Canceled after 0s
Rust / fmt (pull_request) Canceled after 0s
Rust / clippy (pull_request) Canceled after 0s
2026-09-01 15:49:21 +02:00
zach dfa6b010e6 fix: property link modules
C++ / fmt (pull_request) Canceled after 0s
C++ / build (pull_request) Canceled after 0s
C++ / clang-tidy (pull_request) Canceled after 0s
JS/TS / lint (pull_request) Canceled after 0s
JS/TS / fmt (pull_request) Canceled after 1s
Python / static (pull_request) Canceled after 0s
Python / verify (pull_request) Canceled after 0s
Rust / build (pull_request) Canceled after 0s
Rust / fmt (pull_request) Canceled after 0s
Rust / clippy (pull_request) Canceled after 0s
2026-09-01 15:42:27 +02:00
zach 8a4e9bac31 fix: add gnuinstalldirs include
C++ / fmt (pull_request) Successful in 6s
C++ / build (pull_request) Canceled after 15s
C++ / clang-tidy (pull_request) Canceled after 9s
JS/TS / fmt (pull_request) Canceled after 0s
JS/TS / lint (pull_request) Canceled after 0s
Python / verify (pull_request) Canceled after 0s
Python / static (pull_request) Canceled after 1s
Rust / fmt (pull_request) Canceled after 0s
Rust / clippy (pull_request) Canceled after 0s
Rust / build (pull_request) Canceled after 6s
2026-09-01 15:28:25 +02:00
zach f20fa45b29 fix: cmake install dir
Rust / build (pull_request) Canceled after 0s
Rust / fmt (pull_request) Canceled after 0s
Rust / clippy (pull_request) Canceled after 0s
Python / static (pull_request) Canceled after 0s
Python / verify (pull_request) Canceled after 0s
JS/TS / fmt (pull_request) Canceled after 0s
JS/TS / lint (pull_request) Canceled after 0s
C++ / fmt (pull_request) Canceled after 0s
C++ / build (pull_request) Canceled after 0s
C++ / clang-tidy (pull_request) Canceled after 0s
2026-09-01 15:15:15 +02:00
zach 53a3c76dc0 fix: cmake install dir
C++ / fmt (pull_request) Successful in 7s
JS/TS / fmt (pull_request) Failing after 14s
JS/TS / lint (pull_request) Successful in 18s
Python / static (pull_request) Successful in 57s
C++ / build (pull_request) Failing after 3m50s
C++ / clang-tidy (pull_request) Failing after 3m32s
Python / verify (pull_request) Canceled after 0s
Rust / build (pull_request) Canceled after 0s
Rust / fmt (pull_request) Canceled after 0s
Rust / clippy (pull_request) Canceled after 0s
2026-09-01 15:03:42 +02:00
zach 617e87d64f fix: uncomment settings index cache path
C++ / fmt (pull_request) Successful in 10s
C++ / clang-tidy (pull_request) Failing after 15m10s
C++ / build (pull_request) Failing after 16m4s
JS/TS / fmt (pull_request) Failing after 10s
JS/TS / lint (pull_request) Successful in 11s
Python / static (pull_request) Successful in 53s
Rust / fmt (pull_request) Successful in 1m33s
Rust / build (pull_request) Successful in 3m3s
Rust / clippy (pull_request) Successful in 2m59s
Python / verify (pull_request) Successful in 3m58s
2026-09-01 14:31:44 +02:00
zach ef2fb95fab chore: split cmake into multiple files and fix settings searching regex + add enabled option to llm
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Failing after 17s
JS/TS / lint (pull_request) Successful in 19s
Python / static (pull_request) Successful in 1m11s
Rust / fmt (pull_request) Successful in 1m3s
Rust / build (pull_request) Successful in 2m26s
Rust / clippy (pull_request) Failing after 16m40s
Python / verify (pull_request) Failing after 17m52s
C++ / clang-tidy (pull_request) Failing after 18m0s
C++ / build (pull_request) Failing after 18m2s
2026-09-01 14:30:04 +02:00
zach 2150859847 fix: font family
JS/TS / fmt (pull_request) Successful in 19s
JS/TS / lint (pull_request) Successful in 18s
Rust / fmt (pull_request) Canceled after 0s
Rust / clippy (pull_request) Canceled after 0s
Rust / build (pull_request) Canceled after 5m56s
Python / static (pull_request) Canceled after 6m44s
Python / verify (pull_request) Canceled after 6m40s
C++ / fmt (pull_request) Successful in 8s
C++ / clang-tidy (pull_request) Canceled after 7m8s
C++ / build (pull_request) Failing after 1m24s
2026-08-31 22:54:52 +02:00
AramJonghu c9f7856982 fix(Docker): add required deps to dockerfile
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 11s
Python / static (pull_request) Successful in 45s
Rust / fmt (pull_request) Successful in 2m0s
Rust / build (pull_request) Successful in 2m24s
Rust / clippy (pull_request) Successful in 2m24s
Python / verify (pull_request) Successful in 2m48s
C++ / fmt (pull_request) Successful in 4s
C++ / clang-tidy (pull_request) Canceled after 5m34s
C++ / build (pull_request) Canceled after 0s
2026-08-31 22:46:42 +02:00
zachandAramJonghu 51dc0fc026 chore: format python codeicons 2026-08-31 22:44:57 +02:00
zach cef91caebc chore: format llm c files
C++ / fmt (pull_request) Successful in 16s
C++ / build (pull_request) Failing after 19s
JS/TS / fmt (pull_request) Successful in 10s
C++ / clang-tidy (pull_request) Failing after 29s
JS/TS / lint (pull_request) Successful in 23s
Python / static (pull_request) Failing after 48s
Rust / fmt (pull_request) Successful in 1m15s
Rust / build (pull_request) Successful in 1m46s
Rust / clippy (pull_request) Successful in 1m33s
Python / verify (pull_request) Successful in 2m16s
2026-08-31 19:06:49 +02:00
zach c5a089adaf chore: format llm c files
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 22s
JS/TS / lint (pull_request) Successful in 16s
Python / static (pull_request) Failing after 37s
C++ / clang-tidy (pull_request) Failing after 1m19s
Rust / fmt (pull_request) Successful in 1m24s
Rust / clippy (pull_request) Successful in 1m59s
Rust / build (pull_request) Successful in 2m12s
Python / verify (pull_request) Successful in 2m48s
2026-08-31 19:05:22 +02:00
zach 52feb6006a chore: format llm c files
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
JS/TS / lint (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 16s
C++ / clang-tidy (pull_request) Failing after 43s
Python / static (pull_request) Failing after 57s
Rust / fmt (pull_request) Successful in 1m30s
Rust / build (pull_request) Successful in 2m5s
Rust / clippy (pull_request) Successful in 1m55s
Python / verify (pull_request) Successful in 2m23s
2026-08-31 19:02:26 +02:00
zach dbb51ceadd feat: config object migration path and add new options to settings
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 12s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 15s
C++ / clang-tidy (pull_request) Failing after 33s
Python / static (pull_request) Failing after 36s
Rust / fmt (pull_request) Successful in 1m14s
Rust / build (pull_request) Successful in 1m49s
Rust / clippy (pull_request) Successful in 1m33s
Python / verify (pull_request) Successful in 2m10s
2026-08-31 18:43:41 +02:00
zach 487615f482 fix: shader fade animation linear curve 2026-08-31 14:09:54 +02:00
zach d4e936a7e2 add nightlight hyprland plugin + rename hyprsunsetmanager -> nightlightmanager to control both 2026-08-31 13:53:37 +02:00
zach 703d804746 fab menu and model selector 2026-08-29 18:56:59 +02:00
zach 327270f3fa better support for opening chat in floating window 2026-08-28 14:15:57 +02:00
zach 8a66c3b064 expandable dialogs for color scheme, fonts and scheduling modes 2026-08-27 16:57:02 +02:00
579 changed files with 14378 additions and 47697 deletions
-5
View File
@@ -1,5 +0,0 @@
CompileFlags:
Remove:
# GCC-only flags from the system's default spec that clang/clangd cannot
# parse. They appear in compile_commands.json but are not set by the project.
- -mno-direct-extern-access
-3
View File
@@ -1,3 +0,0 @@
indent: tabs
list_expansion: favour-expansion
definitions: [Plugins/cmake/qml-module.cmake, Plugins/gersemi/qt-add-shaders.cmake]
+1
View File
@@ -38,6 +38,7 @@ body:
attributes:
label: OS
options:
- NixOS
- Arch
- Debian / Ubuntu
- Fedora
-1
View File
@@ -30,7 +30,6 @@ body:
- Refactor / style
- CI / build / tooling
- Documentation
- Other (Describe in description box with header "Issue type")
validations:
required: true
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v4
- name: Install Docker CLI
run: apk add --no-cache docker-cli docker-cli-buildx
run: apk add --no-cache docker-cli
- name: Login to registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.dev --username aramjonghu --password-stdin
+20 -21
View File
@@ -17,47 +17,46 @@ jobs:
run: |
find . \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) \
-not -path './build/*' \
-exec clang-format --dry-run -Werror --style=file {} +
-exec clang-format -i --style=file {} +
git diff --exit-code && echo "clang-format: passed"
build & lint:
build:
runs-on: alpine
container:
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache CMake build
uses: actions/cache@v4
with:
path: build
key: cpp-build-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt') }}
restore-keys: |
cpp-build-
- name: Configure
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
- name: Cache ccache
uses: actions/cache@v4
- name: Build
run: ninja -C build
clang-tidy:
runs-on: alpine
container:
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
path: ~/.ccache
key: ccache-${{ runner.os }}-${{ github.sha }}
restore-keys: |
ccache-${{ runner.os }}-
fetch-depth: 0
- name: Configure
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
- name: Build
run: ninja -C build
env:
CMAKE_CXX_COMPILER_LAUNCHER: ccache
- name: clang-tidy
run: |
sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json
set -o pipefail
run-clang-tidy -p build -j "$(nproc)" \
-source-filter="^$(pwd)/Plugins/" \
-header-filter=".*Plugins/.*" \
-removed-arg=-mno-direct-extern-access
run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \
grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error\|warnings generated\.\|^Use -header-filter'
+15 -1
View File
@@ -4,7 +4,7 @@ on:
pull_request:
jobs:
fmt & lint:
fmt:
runs-on: alpine
container: node:26-alpine
@@ -24,6 +24,20 @@ jobs:
else
echo "No JS/TS files found"
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
run: |
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
+3 -3
View File
@@ -7,7 +7,7 @@ on:
pull_request:
jobs:
fmt & lint:
static:
runs-on: debian
container: node:26-trixie-slim
@@ -33,7 +33,7 @@ jobs:
. .venv/bin/activate
ruff check .
test & nuitka build:
verify:
if: always()
runs-on: debian
container: node:26-trixie-slim
@@ -64,4 +64,4 @@ jobs:
- name: Nuitka module check
run: |
. .venv/bin/activate
nuitka --module --include-package=zshell cli/src/zshell/
nuitka --module --include-package=zshell cli/src/zshell/
+121 -49
View File
@@ -4,77 +4,149 @@ on:
pull_request:
jobs:
clippy & fmt:
build:
runs-on: alpine
container: rust:alpine
container: node:26-alpine
env:
CARGO_HOME: ${{ github.workspace }}/.cargo
steps:
- name: Install node for actions
run: |
apk add --no-cache nodejs git
- name: Install rust components
run: |
rustup component add clippy rustfmt
cargo clippy --version
rustfmt --version
- 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 }}-cargo-${{ hashFiles('**/Cargo.lock') }}
key: rust-build-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
rust-${{ runner.os }}-cargo-
rust-${{ runner.os }}-build-${{ env.cache-name }}-
rust-${{ runner.os }}-build-
rust-
- name: Clippy (with compile check)
- name: Install tools
run: |
manifests=$(find . \( -name .cargo -o -name .git -o -name target \) -prune -o -name Cargo.toml -print)
if [ -z "$manifests" ]; then
if [ -n "$(find . \( -name .cargo -o -name target \) -prune -o -name '*.rs' -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
fi
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"
exit 0
fi
status=0
for manifest in $manifests; do
if (cd "$(dirname "$manifest")" && cargo clippy --all-targets --locked -- -D warnings); then
echo "$manifest: Clippy passed"
else
echo "$manifest: Clippy failed"
status=1
fi
done
exit $status
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: |
manifests=$(find . \( -name .cargo -o -name .git -o -name target \) -prune -o -name Cargo.toml -print)
if [ -z "$manifests" ]; then
if [ -n "$(find . \( -name .cargo -o -name target \) -prune -o -name '*.rs' -print -quit)" ]; then
echo "Rust files found but no Cargo.toml"
exit 1
fi
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"
exit 0
fi
status=0
for manifest in $manifests; do
if (cd "$(dirname "$manifest")" && cargo fmt --check); then
echo "$manifest: formatting OK"
else
echo "$manifest: needs formatting"
status=1
fi
done
exit $status
+10 -28
View File
@@ -1,38 +1,20 @@
**/__pycache__/
./result/
.pyre/
.cache/
.venv/
.qmlls.ini
build/
compile_commands.json
testpython
pkg/
*.tar.*
uv.lock
.qtcreator/
dist/
**/target/
**/test-plugins/
**/Charts/
network-dev/
**/zshell.dist/
# Qml
.qtcreator/
.qmlls.ini
# CMake compile artifacts
.cache/
compile_commands.json
build/
# Cargo home (CI sets CARGO_HOME inside the workspace)
.cargo/
# Python
uv.lock
testpython
.venv/
**/__pycache__/
# Nuitka compile
**/zshell.pyi
**/zshell.cpython-*
**/zshell.build/
# Agents stuff
.opencode/
run-agent.sh
AGENTS.md
**/zshell.dist/
-1
View File
@@ -1,4 +1,3 @@
.venv/
scripts/fzf.js
scripts/fuzzysort.js
scripts/settings-indexer.js
+2 -12
View File
@@ -50,7 +50,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(QT_NO_PRIVATE_MODULE_WARNING ON)
set(ENABLE_MODULES "plugin;shell;cli;m3shapes" CACHE STRING "Modules to build/install")
set(INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}/ZShell" CACHE STRING "Library install dir")
@@ -62,12 +61,10 @@ set(CMAKE_INSTALL_MESSAGE NEVER)
add_compile_options(
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wnull-dereference -Wdouble-promotion
-Wformat=2 -Wfloat-equal
-Wold-style-cast -Wnull-dereference -Wdouble-promotion
-Wformat=2 -Wfloat-equal -Woverloaded-virtual
-Wsign-conversion -Wredundant-decls -Wswitch
-Wunreachable-code
$<$<COMPILE_LANGUAGE:CXX>:-Wold-style-cast>
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
)
@@ -191,11 +188,4 @@ if("m3shapes" IN_LIST ENABLE_MODULES)
if(TARGET m3shapesplugin)
set_target_properties(m3shapesplugin PROPERTIES INSTALL_RPATH "$ORIGIN")
endif()
# Silence warnings that come from m3shapes files we do not manage.
foreach(m3shapes_target m3shapes_core m3shapes_morph m3shapes_shapes m3shapes m3shapesplugin)
if(TARGET ${m3shapes_target})
target_compile_options(${m3shapes_target} PRIVATE -Wno-shadow -Wno-float-equal)
endif()
endforeach()
endif()
+44 -23
View File
@@ -80,29 +80,49 @@ Slider {
property bool moving
anchors.centerIn: parent
anchors.verticalCenterOffset: 1
text: moving ? Math.round(root.value * 100) : root.icon
color: Colors.palette.m3inverseOnSurface
font: moving ? Tokens.font.body.small : Tokens.font.icon.medium
function update(): void {
animate = !moving;
binding.when = moving;
font.pointSize = moving ? Tokens.font.size.small : Tokens.font.size.larger;
font.family = moving ? Config.appearance.font.family.sans : Config.appearance.font.family.material;
}
Behavior on moving {
SequentialAnimation {
Anim {
target: icon
property: "scale"
to: 0.3
duration: Tokens.anim.durations.small / 2
easing: Tokens.anim.standardAccel
}
PropertyAction {}
Anim {
target: icon
property: "scale"
to: 1
duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardDecel
}
anchors.centerIn: parent
color: Colors.palette.m3inverseOnSurface
text: root.icon
onMovingChanged: anim.restart()
Binding {
id: binding
property: "text"
target: icon
value: Math.round(root.value * root.multiplier)
when: false
}
SequentialAnimation {
id: anim
Anim {
duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardAccel
property: "scale"
target: icon
to: 0
}
ScriptAction {
script: icon.update()
}
Anim {
duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardDecel
property: "scale"
target: icon
to: 1
}
}
}
@@ -110,7 +130,7 @@ Slider {
}
Behavior on value {
Anim {
type: Anim.StandardLarge
duration: Tokens.anim.durations.large
}
}
@@ -118,6 +138,7 @@ Slider {
onValueChanged: {
if (!initialized) {
initialized = true;
oldValue = value;
return;
}
+6 -2
View File
@@ -20,7 +20,11 @@ CustomRect {
property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1)
property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38)
property bool fillWidth
property font font: Tokens.font.body.small
property font font: ({
family: Config.appearance.font.family.sans,
pointSize: Tokens.font.size.larger,
bold: false
})
property real horizontalPadding: padding
readonly property alias hovered: stateLayer.containsMouse
required implicitHeight
@@ -51,7 +55,7 @@ CustomRect {
if (internalChecked)
return checkedRadius;
if (isRound)
return (height ?? implicitHeight) / 2 * Math.min(1, Tokens.rounding.scale);
return (height || implicitHeight) / 2 * Math.min(1, Tokens.rounding.scale);
return defaultRadius;
}
+1 -1
View File
@@ -20,7 +20,7 @@ BusyIndicator {
property int animState
property color bgColor: Colors.palette.m3secondaryContainer
property color fgColor: Colors.palette.m3primary
property real implicitSize: Tokens.font.body.medium.pointSize * 3
property real implicitSize: Tokens.font.size.normal * 3
property real internalStrokeWidth: strokeWidth
readonly property alias progress: manager.progress
property real strokeWidth: Tokens.padding.extraSmall * 0.8
+14 -46
View File
@@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Shapes
import ZShell
import ZShell.Components
import ZShell.Config
import qs.Services
@@ -10,50 +9,21 @@ import qs.Services
Item {
id: root
// Ring geometry, quantized in the item's own device pixel space and never
// against its position: the parent layout places the item on the device
// grid, so these values cannot change when the item moves (no jitter), and
// the ring stays crisp wherever it lands.
// Device-pixel center of the ring: an even stroke width wants a pixel
// boundary, an odd one a pixel center. Floored, matching
// WavyLineRenderer::snapCenterDev, so the wave and this background arc
// always land on the same center even when the box's device size is odd.
readonly property real centerDev: Math.floor(
sizeDev / 2 - strokeParity / 2
) + strokeParity / 2
readonly property real centerX: centerDev / dpr
readonly property real centerY: centerDev / dpr
// Whole device pixels, and one pixel of slack on the outer edge: the ring's
// antialias feather needs somewhere to fade, otherwise it is cut flat at
// the top/left/right/bottom of the item rect.
readonly property real arcRadius: Math.max(
1,
Math.floor(
(sizeDev - Math.round(padding * dpr) - strokeDev * (1 + waveAmplitude * 2) - 2) / 2
)
) / dpr
readonly property real arcRadius: (size - padding - strokeWidth * (1 + waveAmplitude * 2)) / 2
property color bgColor: Colors.palette.m3secondaryContainer
property real clampedVal: Math.max(1 / 360, Math.min(1, isNaN(value) ? 0 : value))
readonly property real dotAngleRad: (startAngle + sweepAngle - gapAngle * (sweepAngle < 360 ? 0 : 1)) * Math.PI / 180
readonly property real dpr: ZUtils.dprFor(root)
property color fgColor: Colors.palette.m3primary
readonly property real gapAngle: ((spacing + strokeLogical) / (arcRadius || 1)) * (180 / Math.PI)
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
property alias hasEndIndicator: dot.active
property real implicitSize
property int padding: 0
// The item's own size in whole device pixels. Never adjusted upwards: the
// texture is exactly this wide, so ring values derived from it cannot spill
// past the item's bounds.
readonly property real size: sizeDev / dpr
readonly property real sizeDev: Math.round(Math.min(width, height) * dpr)
readonly property real size: Math.min(width, height)
property int spacing: Tokens.spacing.small
readonly property real strokeDev: Math.max(1, Math.round(strokeWidth * dpr))
readonly property real strokeLogical: strokeDev / dpr
readonly property real strokeParity: strokeDev % 2
property int startAngle: -90
property int strokeWidth: Tokens.padding.extraSmall
property int sweepAngle: 360
readonly property real thickness: strokeLogical * (1 + waveAmplitude) * 2
readonly property real thickness: strokeWidth * (1 + waveAmplitude) * 2
property real value
property alias waveAmplitude: wave.amplitudeMultiplier
property alias waveDuration: waveProgAnim.duration
@@ -73,17 +43,18 @@ Item {
capStyle: ShapePath.RoundCap
fillColor: "transparent"
strokeColor: root.bgColor
strokeWidth: Math.max(1, Math.round(Math.min(1, remainingArc.sweepAngle) * root.strokeDev)) / root.dpr
strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.strokeWidth
Behavior on strokeColor {
CAnim {}
CAnim {
}
}
PathAngleArc {
id: remainingArc
centerX: root.centerX
centerY: root.centerY
centerX: root.size / 2
centerY: root.size / 2
radiusX: root.arcRadius
radiusY: root.arcRadius
startAngle: root.startAngle + root.clampedVal * root.sweepAngle + root.gapAngle
@@ -97,12 +68,11 @@ Item {
amplitudeMultiplier: root.wavy ? 0.5 : 0
anchors.fill: parent
anchors.margins: -Math.round(lineWidth * amplitudeMultiplier * root.dpr) / root.dpr
anchors.margins: -lineWidth * amplitudeMultiplier
color: root.fgColor
frequency: 8
fullAngle: root.sweepAngle
lineWidth: root.strokeWidth
objectName: "wave"
pathType: WavyLine.Arc
radius: root.arcRadius
startAngle: root.startAngle
@@ -114,7 +84,8 @@ Item {
}
}
Behavior on color {
CAnim {}
CAnim {
}
}
Anim on waveProgress {
id: waveProgAnim
@@ -132,11 +103,8 @@ Item {
Loader {
id: dot
// Rounded in local device pixels: the dot's own size is a whole even
// device count, so an integral local position puts its center on the grid
// whatever the ring's angle, and moving the gauge moves the dot with it.
x: Math.round((root.centerX + root.arcRadius * Math.cos(root.dotAngleRad) - width / 2) * root.dpr) / root.dpr
y: Math.round((root.centerY + root.arcRadius * Math.sin(root.dotAngleRad) - height / 2) * root.dpr) / root.dpr
x: root.size / 2 + root.arcRadius * Math.cos(root.dotAngleRad) - width / 2
y: root.size / 2 + root.arcRadius * Math.sin(root.dotAngleRad) - height / 2
sourceComponent: CustomRect {
color: root.fgColor
-42
View File
@@ -1,42 +0,0 @@
import QtQuick
import Quickshell
import ZShell.Components
Item {
id: root
property real radius
property real topLeftRadius: radius
property real topRightRadius: radius
property real bottomRightRadius: radius
property real bottomLeftRadius: radius
property color color: "transparent"
default property alias contentData: contentItem.data
Item {
id: contentItem
anchors.fill: parent
}
ShaderEffectSource {
id: src
hideSource: true
sourceItem: contentItem
}
RoundedMask {
anchors.fill: parent
source: src
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomRightRadius: root.bottomRightRadius
bottomLeftRadius: root.bottomLeftRadius
color: root.color
}
Behavior on color {
CAnim {}
}
}
+7 -6
View File
@@ -46,10 +46,10 @@ Item {
}
function syncFromPenColor() {
if (!drawing.screenState)
if (!drawing.drawingState)
return;
const c = drawing.screenState.penColor;
const c = drawing.drawingState.penColor;
if (c.hsvSaturation > 0) {
currentHue = c.hsvHue;
@@ -86,7 +86,7 @@ Item {
currentHue = relative / arcSweep;
lastChromaticHue = currentHue;
drawing.screenState.penColor = Qt.hsva(currentHue, drawing.screenState.penColor.hsvSaturation, drawing.screenState.penColor.hsvValue, drawing.screenState.penColor.a);
drawing.drawingState.penColor = Qt.hsva(currentHue, drawing.drawingState.penColor.hsvSaturation, drawing.drawingState.penColor.hsvValue, drawing.drawingState.penColor.a);
}
implicitHeight: 180
@@ -112,7 +112,7 @@ Item {
root.syncFromPenColor();
}
target: root.drawing.screenState
target: root.drawing.drawingState
}
Canvas {
@@ -152,7 +152,7 @@ Item {
CustomRect {
anchors.centerIn: parent
color: root.drawing?.screenState.penColor
color: root.drawing?.drawingState.penColor
implicitHeight: implicitWidth
implicitWidth: canvas.height - root.handleSize - Tokens.padding.extraLarge * 2
radius: Tokens.rounding.full
@@ -160,7 +160,8 @@ Item {
Behavior on color {
enabled: false
CAnim {}
CAnim {
}
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ MultiEffect {
colorization: 1
Behavior on colorizationColor {
CAnim {}
CAnim {
}
}
}
+2 -1
View File
@@ -7,6 +7,7 @@ ClippingRectangle {
color: "transparent"
Behavior on color {
CAnim {}
CAnim {
}
}
}
+2 -1
View File
@@ -7,6 +7,7 @@ ClippingWrapperRectangle {
color: "transparent"
Behavior on color {
CAnim {}
CAnim {
}
}
}
+2 -3
View File
@@ -1,12 +1,12 @@
import QtQuick
import qs.Services
import qs.Helpers
Flickable {
id: root
property bool doneFakeFlick
interactive: !ShellState.forActive().isDrawing
interactive: !Visibilities.getForActive().isDrawing
maximumFlickVelocity: 3000
rebound: Transition {
@@ -30,7 +30,6 @@ Flickable {
onTriggered: {
root.doneFakeFlick = false;
root.returnToBounds();
}
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
import QtQuick
import qs.Services
import qs.Helpers
ListView {
id: root
property bool doneFakeFlick
interactive: !ShellState.forActive()?.isDrawing
interactive: !Visibilities.getForActive()?.isDrawing
maximumFlickVelocity: 3000
rebound: Transition {
+5 -5
View File
@@ -96,12 +96,9 @@ ProgressBar {
anchors.right: parent.right
implicitWidth: parent.width - wave.implicitWidth - Tokens.spacing.extraSmall
anchors.top: parent.top
anchors.bottom: parent.bottom
}
Line {
id: dot
property real implicitSize
anchors.right: parent.right
@@ -127,7 +124,8 @@ ProgressBar {
anchors.left: parent.left
Behavior on implicitWidth {
Anim {}
Anim {
}
}
Component.onCompleted: implicitWidth = Qt.binding(() => parent.width * root.visualPosition)
@@ -199,6 +197,7 @@ ProgressBar {
component Line: CustomRect {
property point bounds
anchors.verticalCenter: parent.verticalCenter
color: root.bgColor
implicitHeight: parent.height
implicitWidth: bounds.y - bounds.x
@@ -223,7 +222,8 @@ ProgressBar {
}
}
Behavior on color {
CAnim {}
CAnim {
}
}
Anim on waveProgress {
duration: root.waveDuration
+4 -3
View File
@@ -6,7 +6,7 @@ import qs.Services
RadioButton {
id: root
font: Tokens.font.body.medium
font.pointSize: Tokens.font.size.normal
implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight)
implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin
@@ -14,7 +14,7 @@ RadioButton {
anchors.left: outerCircle.right
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
font: root.font
font.pointSize: root.font.pointSize
text: root.text
}
indicator: Rectangle {
@@ -29,7 +29,8 @@ RadioButton {
radius: Tokens.rounding.full
Behavior on border.color {
CAnim {}
CAnim {
}
}
StateLayer {
+4 -8
View File
@@ -2,14 +2,13 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Templates
import qs.Services
import qs.Helpers
import ZShell.Config
ScrollBar {
id: root
required property Flickable flickable
readonly property bool isHorizontal: flickable && flickable.ScrollBar.horizontal === root
readonly property real axisSize: isHorizontal ? flickable.width : flickable.height
readonly property real axisContentSize: isHorizontal ? flickable.contentWidth : flickable.contentHeight
@@ -21,13 +20,10 @@ ScrollBar {
readonly property real nonAnimY: root.axisContentPos / root.axisContentSize
readonly property real rawTravel: Math.max(0, 1 - root.nonAnimHeight)
readonly property bool reversed: isHorizontal ? (flickable instanceof ListView && flickable.layoutDirection === Qt.RightToLeft) : (flickable instanceof ListView && flickable.verticalLayoutDirection === ListView.BottomToTop)
property bool shouldBeActive
readonly property real travelScale: root.rawTravel > 0 ? root.effectiveTravel / root.rawTravel : 0
property bool shouldBeActive
property bool inverted
enabled: !ShellState.forActive().isDrawing
enabled: !Visibilities.getForActive().isDrawing
implicitWidth: size === 1 ? 0 : isHorizontal ? 0 : Tokens.padding.extraSmall * 2
implicitHeight: size === 1 ? 0 : isHorizontal ? Tokens.padding.extraSmall * 2 : 0
@@ -123,7 +119,7 @@ ScrollBar {
}
var delta = event.angleDelta.y > 0 ? -0.1 : 0.1;
var newPos = Math.max(0, Math.min(1 - root.size, root.position + (root.inverted ? -delta : delta)));
var newPos = Math.max(0, Math.min(1 - root.size, root.position + delta));
root.position = newPos;
}
+13 -15
View File
@@ -2,10 +2,10 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Templates
import ZShell
import ZShell.Components
import ZShell.Config
import ZShell
import qs.Components
import ZShell.Config
import qs.Services
Slider {
@@ -28,7 +28,7 @@ Slider {
signal interaction(v: real)
implicitHeight: ZUtils.snap(12, root)
implicitHeight: 12
implicitWidth: 200
contentItem: Item {
@@ -100,19 +100,15 @@ Slider {
anchors.bottom: parent.bottom
anchors.top: parent.top
color: root.insetColor
fontStyle: {
if (root.height < 96)
return Tokens.font.icon.pixelSize(24).build();
else
return Tokens.font.icon.pixelSize(32).build();
}
font.pixelSize: Tokens.font.size.smaller * 2
text: root.insetIcon
verticalAlignment: Text.AlignVCenter
visible: !root.wavy && root.insetIcon !== ""
x: Tokens.spacing.extraSmall + dockT * (handle.x + Tokens.spacing.small)
Behavior on dockT {
Anim {}
Anim {
}
}
}
@@ -123,7 +119,7 @@ Slider {
bottomRightRadius: Tokens.rounding.extraSmall / 2
color: root.fgColor
implicitHeight: root.height
implicitWidth: ZUtils.snap(root.filledWidth, root)
implicitWidth: root.filledWidth
radius: root.radius
topRightRadius: Tokens.rounding.extraSmall / 2
}
@@ -137,12 +133,13 @@ Slider {
frequency: root.waveFrequency
fullLength: root.width - handle.implicitWidth - handle.anchors.leftMargin
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
implicitWidth: ZUtils.snap(root.filledWidth, root)
lineWidth: ZUtils.snap(root.height * 0.7, root)
implicitWidth: root.filledWidth
lineWidth: root.height * 0.7
startX: x
Behavior on color {
CAnim {}
CAnim {
}
}
Anim on waveProgress {
duration: root.waveDuration
@@ -159,7 +156,8 @@ Slider {
Behavior on filledWidth {
id: widthBehavior
Anim {}
Anim {
}
}
Component.onCompleted: {
+10 -5
View File
@@ -105,16 +105,20 @@ Switch {
width: height
Behavior on end1 {
PropAnim {}
PropAnim {
}
}
Behavior on end2 {
PropAnim {}
PropAnim {
}
}
Behavior on start1 {
PropAnim {}
PropAnim {
}
}
Behavior on start2 {
PropAnim {}
PropAnim {
}
}
ShapePath {
@@ -130,7 +134,8 @@ Switch {
strokeWidth: 16 * 0.15
Behavior on strokeColor {
CAnim {}
CAnim {
}
}
PathLine {
+7 -4
View File
@@ -14,13 +14,15 @@ Text {
property real animateTo: 1
color: Colors.palette.m3onSurface
font: Tokens.font.body.small
font.family: Config.appearance.font.family.sans
font.pointSize: Tokens.font.size.normal
linkColor: Colors.palette.m3onPrimaryFixedVariant
renderType: Text.QtRendering
renderType: Text.NativeRendering
textFormat: Text.PlainText
Behavior on color {
CAnim {}
CAnim {
}
}
Behavior on text {
enabled: root.animate
@@ -32,7 +34,8 @@ Text {
type: Anim.FastEffects
}
PropertyAction {}
PropertyAction {
}
TAnim {
target: root
+22 -26
View File
@@ -15,33 +15,28 @@ TextFieldBase {
readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius)
readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText
readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
readonly property real smallFontScale: smallFontSize / font.pointSize
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.spacing.extraSmall : 0
readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
property int horizontalPadding: Tokens.padding.large
property int verticalPadding: Tokens.padding.large
property int radius: Tokens.rounding.small
property string supportingText
property string trailingIcon
property int smallFontSize: Tokens.font.label.small.pointSize
property int type: CustomTextField.Outlined
property var validate // Regex or function
property bool emptyIsValid: true
property string errorText
readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
readonly property int horizontalPadding: Tokens.padding.large
property bool isError
property string leadingIcon
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
property int radius: Tokens.rounding.small
readonly property real smallFontScale: smallFontSize / font.pointSize
property int smallFontSize: Tokens.font.size.small
property string supportingText
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.spacing.extraSmall : 0
property string trailingIcon
readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
property int type: CustomTextField.Outlined
readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
property var validate // Regex or function
bottomPadding: verticalPadding + supportingTextOffset - filledOffset
bottomPadding: Tokens.padding.large + supportingTextOffset - filledOffset
leftPadding: horizontalPadding + leadingOffset
rightPadding: horizontalPadding + trailingOffset
topPadding: verticalPadding + filledOffset
topPadding: Tokens.padding.large + filledOffset
background: Loader {
anchors.bottomMargin: root.supportingTextOffset
@@ -151,7 +146,6 @@ TextFieldBase {
sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant
text: root.leadingIcon
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
}
}
@@ -166,7 +160,6 @@ TextFieldBase {
sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant
text: root.trailingIcon
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
}
}
}
@@ -181,7 +174,7 @@ TextFieldBase {
sourceComponent: CustomText {
color: root.isError ? Colors.palette.m3error : Colors.palette.m3onSurfaceVariant
font: Tokens.font.label.small
font.pointSize: Tokens.font.size.small
text: root.effectiveSupportingText
}
}
@@ -219,10 +212,12 @@ TextFieldBase {
}
}
Behavior on strokeColor {
CAnim {}
CAnim {
}
}
Behavior on strokeWidth {
Anim {}
Anim {
}
}
PathLine {
@@ -297,7 +292,8 @@ TextFieldBase {
implicitHeight: root.activeFocus ? 2 : 1
Behavior on implicitHeight {
Anim {}
Anim {
}
}
}
}
+2 -1
View File
@@ -65,7 +65,8 @@ Image {
value: root.source
}
PropertyAction {}
PropertyAction {
}
ScriptAction {
script: root.maybeStartInAnim()
-42
View File
@@ -1,42 +0,0 @@
import QtQuick
import Quickshell
ShaderEffect {
id: root
property Item target: parent
property real glowSize: 40
property real innerGlow: 0
property real lineWidth: 2
property real intensity: 1.0
property real speed: 1.0
property int period: 6000
property bool running: true
property color color1: "#4285F4"
property color color2: "#9B72CB"
property color color3: "#D96570"
property color color4: "#F2A54A"
readonly property size itemSize: Qt.size(width, height)
readonly property size rectSize: target ? Qt.size(target.width, target.height) : Qt.size(0, 0)
readonly property real radius: (target as Rectangle).radius ?? 0
property real phase: 0
anchors.fill: target
anchors.margins: -glowSize
z: -1
enabled: false
fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/geminiglow.frag.qsb`)
FrameAnimation {
running: root.running && root.visible
onTriggered: {
const dt = frameTime * 1000;
root.phase = (root.phase + dt * root.speed / root.period) % 1.0;
}
}
}
+39 -41
View File
@@ -2,57 +2,55 @@ import QtQuick
import ZShell.Config
import qs.Services
Item {
CustomClippingRect {
id: track
required property var scrollBar
required property var mouseArea
ClipRect {
radius: Tokens.rounding.full
anchors.left: parent.left
anchors.right: parent.right
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
implicitHeight: handle.implicitHeight
radius: Tokens.rounding.full
CustomRect {
id: handle
anchors.bottom: parent.bottom
implicitHeight: handle.implicitHeight
CustomRect {
id: handle
anchors.bottom: parent.bottom
color: Colors.palette.m3secondary
implicitWidth: track.scrollBar.width * track.scrollBar.effectiveSize
implicitHeight: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
opacity: {
if (!track.scrollBar.enabled)
return 0;
if (track.scrollBar.size === 1)
return 0;
if (track.mouseArea.pressed)
return 1;
if (track.mouseArea.containsMouse)
return 0.8;
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
return 0.6;
color: Colors.palette.m3secondary
implicitWidth: track.scrollBar.width * track.scrollBar.effectiveSize
implicitHeight: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
opacity: {
if (!track.scrollBar.enabled)
return 0;
}
radius: Tokens.rounding.full
x: track.scrollBar.reversed ? track.scrollBar.width * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.width * track.scrollBar.nonAnimY * track.scrollBar.travelScale
if (track.scrollBar.size === 1)
return 0;
if (track.mouseArea.pressed)
return 1;
if (track.mouseArea.containsMouse)
return 0.8;
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
return 0.6;
return 0;
}
radius: Tokens.rounding.full
x: track.scrollBar.reversed ? track.scrollBar.width * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.width * track.scrollBar.nonAnimY * track.scrollBar.travelScale
Behavior on implicitHeight {
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
Behavior on implicitHeight {
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
MouseArea {
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
MouseArea {
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
}
}
+3 -5
View File
@@ -1,5 +1,4 @@
import QtQuick
import ZShell
import ZShell.Config
import qs.Services
@@ -9,16 +8,15 @@ ButtonBase {
property alias icon: label.text
readonly property alias label: label
font: Tokens.font.icon.medium
activeColor: type === IconButton.Filled ? Colors.palette.m3primary : Colors.palette.m3secondary
activeOnColor: type === IconButton.Filled ? Colors.palette.m3onPrimary : type === IconButton.Tonal ? Colors.palette.m3onSecondary : Colors.palette.m3primary
implicitHeight: {
const h = label.implicitHeight + padding * 2;
if (h % 2 !== 0)
return h + 1;
return ZUtils.snap(h, root);
return h;
}
implicitWidth: ZUtils.snap(implicitHeight, root)
implicitWidth: implicitHeight
inactiveColor: {
if (!isToggle && type === IconButton.Filled)
return Colors.palette.m3primary;
@@ -38,7 +36,7 @@ ButtonBase {
anchors.verticalCenterOffset: 1
color: root.onColor
fill: !root.isToggle || root.internalChecked ? 1 : 0
fontStyle: root.font
font.pointSize: root.font.pointSize
Behavior on fill {
Anim {
+3 -4
View File
@@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import ZShell.Config
import qs.Services
@@ -35,11 +34,10 @@ ButtonBase {
}
verticalPadding: Tokens.padding.extraSmall
SnapLayout {
RowLayout {
id: row
anchors.centerIn: parent
spacing: Tokens.spacing.small
MaterialIcon {
@@ -48,9 +46,10 @@ ButtonBase {
Layout.alignment: Qt.AlignVCenter
color: root.onColor
fill: root.internalChecked ? 1 : 0
fontStyle: {
font: {
const f = Qt.font(root.font);
f.pointSize = Math.round(root.font.pointSize * 1.2);
f.family = "Material Symbols Rounded";
return f;
}
-23
View File
@@ -1,23 +0,0 @@
import QtQuick
import qs.Services
Flickable {
id: root
interactive: !ShellState.forActive().isDrawing
rebound: Transition {
Anim {
properties: "x,y"
type: Anim.FastEffects
}
}
readonly property KineticWheelScroller _scroller: KineticWheelScroller {
id: wheelScroller
anchors.fill: parent
parent: root
z: 12
flickable: root
}
}
-653
View File
@@ -1,653 +0,0 @@
import QtQuick
// Taken from hyprfm: https://github.com/soyeb-jim285/hyprfm/blob/main/src/qml/views/KineticWheelScroller.qml
// Adapted to fit this project where needed.
// Deliberately not a MouseArea: a MouseArea claims the arrow cursor for its
// whole area, which silently killed every cursorShape in the views this
// overlays (column resize grips, pointer hands). A WheelHandler on a plain
// Item takes wheel events without touching the cursor.
Item {
id: root
required property var flickable
property real wheelStep: 56
property real mouseWheelMultiplier: 1.0
property real touchpadMultiplier: 1.35
property real minVelocity: 480
property real maxVelocity: 5200
property real kineticGain: 1.35
property real smoothing: 0.72
property real edgeEpsilon: 1.0
// Browser-style overscroll: larger visible rubber-band, softer
// resistance, and a springy settle on release.
property real overshootLimit: 140
property real overshootResistance: 0.38
property real maxOvershootInput: 520
// Bounce magnitude per unit of impact velocity. Keep this deliberately
// small: slow edge hits should settle, not fire a visibly faster bounce.
property real kineticBounceGain: 0.018
property real maxKineticBounceInput: 90
// Minimum impact velocity that actually produces a visible bounce.
// Very slow landings skip bounce entirely — matches what browsers do
// when a fling decays to near-zero before reaching the edge.
property real minBounceVelocity: 260
property real velocity: 0
property double lastWheelTimestamp: 0
property bool kineticCandidate: false
property real overshootInput: 0
property real overshootReleaseVelocity: 0
property real pendingKineticBounceVelocity: 0
// Set by runKineticGlide when the glide will clamp against an edge.
// It is the glide velocity at the instant of impact, which drives the
// elastic bounce amount and duration — browser-style: slow impact
// makes a small gentle bounce; fast impact makes a big springy one.
property real pendingImpactVelocity: 0
// Browser-style exponential-feeling glide replaces the linear
// flickDeceleration decay. Tail uses OutQuart — soft landing.
property real kineticTau: 0.42
property real mouseGlideTime: 0.09 // decay time constant (s); higher = longer coast
property real mouseAccelWindow: 140 // ms between notches that counts as "continuous"
property real mouseAccelStep: 0.15 // extra impulse multiplier per consecutive notch
property real mouseMaxAccel: 2.5
property real mouseSnap: 0.2 // fraction of each notch applied instantly (0 = fully glided, 1 = no smoothing)
property real mouseVelocity: 0
property int mouseDir: 0
property int mouseStreak: 0
property double mouseLastTime: 0
// Escape hatch if auto-detection misjudges a device
property bool forceMouseWheel: false
readonly property real visualOvershoot: overshootInput < 0 ? rubberDistance(-overshootInput) : -rubberDistance(overshootInput)
signal scrollStarted
WheelHandler {
id: wheelHandler
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
// `blocking` decides whether the event continues to the Flickable
// underneath; handleWheel() flips it per event (the old MouseArea
// code toggled wheel.accepted for the same purpose).
onWheel: wheel => root.handleWheel(wheel)
}
function isMouseWheel(wheel) {
if (forceMouseWheel)
return true;
var ay = wheel.angleDelta.y;
if (ay === 0)
return false;
return Math.abs(ay) % 120 === 0;
}
function minimumContentY() {
if (!flickable)
return 0;
return flickable.originY;
}
function maximumContentY() {
if (!flickable)
return 0;
return Math.max(minimumContentY(), minimumContentY() + flickable.contentHeight - flickable.height);
}
function boundedContentY(value) {
return Math.max(minimumContentY(), Math.min(maximumContentY(), value));
}
function rubberDistance(distance) {
if (distance <= 0)
return 0;
var scaled = (distance * overshootResistance) / overshootLimit;
return overshootLimit * (1 - 1 / (1 + scaled));
}
function setOvershootInput(value) {
overshootInput = Math.max(-maxOvershootInput, Math.min(maxOvershootInput, value));
}
function hasOvershoot() {
return Math.abs(overshootInput) > edgeEpsilon;
}
function absorbOvershoot(delta) {
if (!hasOvershoot())
return delta;
var sameDirection = (overshootInput < 0 && delta < 0) || (overshootInput > 0 && delta > 0);
if (sameDirection) {
setOvershootInput(overshootInput + delta);
return 0;
}
var combined = overshootInput + delta;
if ((overshootInput < 0 && combined < 0) || (overshootInput > 0 && combined > 0)) {
setOvershootInput(combined);
return 0;
}
setOvershootInput(0);
return combined;
}
function deltaFor(wheel) {
var rawDelta;
if (isMouseWheel(wheel))
rawDelta = (wheel.angleDelta.y / 120.0) * wheelStep * mouseWheelMultiplier;
else
rawDelta = wheel.pixelDelta.y !== 0 ? wheel.pixelDelta.y * touchpadMultiplier : (wheel.angleDelta.y / 120.0) * wheelStep * mouseWheelMultiplier;
return wheel.inverted ? rawDelta : -rawDelta;
}
function shouldUseKinetic(wheel, gap) {
return wheel.pixelDelta.y !== 0 || (wheel.phase !== Qt.NoScrollPhase && wheel.angleDelta.y === 0);
}
function scrollBy(delta) {
if (!flickable || Math.abs(delta) < 0.01)
return;
kineticBounceSequence.stop();
bounceBackAnimation.stop();
var remainingDelta = absorbOvershoot(delta);
if (Math.abs(remainingDelta) < 0.01)
return;
var minY = minimumContentY();
var maxY = maximumContentY();
var next = flickable.contentY + remainingDelta;
if (next < minY) {
flickable.contentY = minY;
setOvershootInput(overshootInput + next - minY);
return;
}
if (next > maxY) {
flickable.contentY = maxY;
setOvershootInput(overshootInput + next - maxY);
return;
}
flickable.contentY = next;
}
function remainingDistanceFor(velocityValue) {
if (!flickable)
return 0;
if (velocityValue > 0)
return Math.max(0, maximumContentY() - flickable.contentY);
if (velocityValue < 0)
return Math.max(0, flickable.contentY - minimumContentY());
return 0;
}
function clampedContentVelocity(velocityValue) {
if (!flickable || velocityValue === 0)
return 0;
var remaining = remainingDistanceFor(velocityValue);
if (remaining <= edgeEpsilon)
return 0;
var maxAllowed = Math.sqrt(2 * flickable.flickDeceleration * remaining);
var magnitude = Math.min(Math.abs(velocityValue), maxAllowed);
return velocityValue < 0 ? -magnitude : magnitude;
}
function resetState() {
velocity = 0;
lastWheelTimestamp = 0;
kineticCandidate = false;
pendingKineticBounceVelocity = 0;
pendingImpactVelocity = 0;
overshootReleaseVelocity = 0;
finishTimer.stop();
kineticAnim.stop();
kineticBounceSequence.stop();
bounceBackAnimation.stop();
stopMouseGlide();
}
function bounceBack() {
if (!hasOvershoot()) {
setOvershootInput(0);
overshootReleaseVelocity = 0;
return;
}
var mag = Math.min(1.0, Math.abs(overshootInput) / maxOvershootInput);
var speed = Math.abs(overshootReleaseVelocity);
var duration = speed >= minBounceVelocity ? Math.round(Math.max(280, Math.min(620, Math.abs(overshootInput) / speed * 1600))) : Math.round(380 + mag * 260);
kineticBounceSequence.stop();
bounceBackAnimation.stop();
bounceBackAnimation.duration = duration;
bounceBackAnimation.from = overshootInput;
bounceBackAnimation.to = 0;
overshootReleaseVelocity = 0;
bounceBackAnimation.start();
}
function triggerKineticBounce(contentVelocity) {
if (!flickable || contentVelocity === 0)
return;
var atTop = flickable.contentY <= minimumContentY() + edgeEpsilon;
var atBottom = flickable.contentY >= maximumContentY() - edgeEpsilon;
if ((contentVelocity < 0 && !atTop) || (contentVelocity > 0 && !atBottom))
return;
var speed = Math.abs(contentVelocity);
var effectiveVelocity = contentVelocity;
if (speed < minBounceVelocity)
effectiveVelocity = (contentVelocity < 0 ? -1 : 1) * Math.max(90, speed * 0.6);
var target = Math.max(-maxKineticBounceInput, Math.min(maxKineticBounceInput, effectiveVelocity * kineticBounceGain));
if (Math.abs(target) < 2.5)
target = contentVelocity < 0 ? -2.5 : 2.5;
var mag = Math.min(1.0, Math.abs(target) / maxKineticBounceInput);
var inDur = Math.round(150 + mag * 120);
var outDur = Math.round(320 + mag * 300);
kineticBounceSequence.stop();
bounceBackAnimation.stop();
overshootReleaseVelocity = 0;
overshootInput = 0;
kineticBounceIn.from = 0;
kineticBounceIn.to = target;
kineticBounceIn.duration = inDur;
kineticBounceOut.from = target;
kineticBounceOut.to = 0;
kineticBounceOut.duration = outDur;
kineticBounceSequence.start();
}
function stopAndSettle() {
kineticAnim.stop();
if (flickable && flickable.flicking)
flickable.cancelFlick();
resetState();
bounceBack();
}
function runKineticGlide(contentVelocity) {
// contentVelocity > 0 means the content should scroll downward
// (contentY increases), matching deltaFor()'s sign convention.
if (!flickable || contentVelocity === 0) {
pendingImpactVelocity = 0;
return false;
}
var current = flickable.contentY;
var unclampedTarget = current + contentVelocity * kineticTau;
var target = boundedContentY(unclampedTarget);
var distUnclamped = Math.abs(unclampedTarget - current);
var distActual = Math.abs(target - current);
if (distActual < 4) {
pendingImpactVelocity = 0;
return false;
}
// If the glide is clipped by an edge, compute the velocity at the
// moment of impact using the OutQuart velocity profile. That is
// the velocity the bounce should feed on — gentle impacts get
// gentle bounces, hard impacts get big springy ones.
var hitsEdge = distActual + 0.5 < distUnclamped;
if (hitsEdge && distUnclamped > 0) {
var f = distActual / distUnclamped; // fraction covered
// OutQuart: y = 1 - (1-x)^4, so velocity fraction at distance
// fraction f equals (1-f)^(3/4) (times the constant peak).
var velFraction = Math.pow(Math.max(0, 1 - f), 0.75);
pendingImpactVelocity = contentVelocity * velFraction;
} else {
pendingImpactVelocity = 0;
}
kineticAnim.stop();
kineticAnim.from = current;
kineticAnim.to = target;
// Duration from distance and velocity; clamped so short flicks
// stay snappy and long flicks don't run forever.
kineticAnim.duration = Math.round(Math.min(1400, Math.max(180, distActual / Math.abs(contentVelocity) * 1800)));
kineticAnim.start();
return true;
}
function atEdgeFor(contentVelocity) {
if (!flickable)
return false;
if (contentVelocity > 0)
return flickable.contentY >= maximumContentY() - edgeEpsilon;
if (contentVelocity < 0)
return flickable.contentY <= minimumContentY() + edgeEpsilon;
return false;
}
function finishScroll() {
if (!flickable)
return;
finishTimer.stop();
if (hasOvershoot()) {
velocity = 0;
lastWheelTimestamp = 0;
kineticCandidate = false;
pendingKineticBounceVelocity = 0;
pendingImpactVelocity = 0;
kineticAnim.stop();
bounceBack();
return;
}
var rawContentVelocity = Math.max(-maxVelocity, Math.min(maxVelocity, velocity * kineticGain));
var contentVelocity = clampedContentVelocity(rawContentVelocity);
var useKinetic = kineticCandidate && Math.abs(rawContentVelocity) >= minVelocity;
velocity = 0;
lastWheelTimestamp = 0;
kineticCandidate = false;
if (useKinetic) {
if (atEdgeFor(rawContentVelocity)) {
// Already at the edge — fling bounces immediately.
pendingKineticBounceVelocity = 0;
triggerKineticBounce(rawContentVelocity);
} else if (Math.abs(contentVelocity) >= minVelocity && runKineticGlide(contentVelocity)) {
// Glide started — bounce fires on kineticAnim.onFinished
// if it lands at an edge.
pendingKineticBounceVelocity = contentVelocity;
} else {
pendingKineticBounceVelocity = 0;
flickable.returnToBounds();
}
} else {
pendingKineticBounceVelocity = 0;
flickable.returnToBounds();
}
}
function handleWheel(wheel) {
wheelHandler.blocking = true;
if (!enabled || !visible || !flickable || !flickable.visible || !flickable.interactive) {
wheelHandler.blocking = false;
return;
}
if (wheel.modifiers !== Qt.NoModifier) {
wheelHandler.blocking = false;
return;
}
if (maximumContentY() <= minimumContentY()) {
wheelHandler.blocking = false;
return;
}
var delta = deltaFor(wheel);
if (delta === 0) {
wheelHandler.blocking = false;
return;
}
// Cancel any in-flight glide — user input always wins, position
// stays where it is, then new delta is applied from there.
if (kineticAnim.running) {
pendingKineticBounceVelocity = 0;
pendingImpactVelocity = 0;
kineticAnim.stop();
}
kineticBounceSequence.stop();
bounceBackAnimation.stop();
if (flickable.flicking) {
pendingKineticBounceVelocity = 0;
pendingImpactVelocity = 0;
flickable.cancelFlick();
}
var now = Date.now();
var isTouchpad = !isMouseWheel(wheel) && (wheel.pixelDelta.y !== 0 || (wheel.phase !== Qt.NoScrollPhase && wheel.angleDelta.y === 0));
if (!isTouchpad) {
velocity = 0;
lastWheelTimestamp = 0;
kineticCandidate = false;
scrollStarted();
handleMouseDelta(delta, now);
return;
}
// Touchpad path (unchanged below), but cancel any mouse glide first
stopMouseGlide();
var gap = lastWheelTimestamp > 0 ? now - lastWheelTimestamp : 16;
var dt = Math.max(8, Math.min(24, gap));
var instantVelocity = (delta / dt) * 1000;
kineticCandidate = kineticCandidate || shouldUseKinetic(wheel, gap);
scrollStarted();
scrollBy(delta);
if (hasOvershoot()) {
overshootReleaseVelocity = Math.max(-maxVelocity, Math.min(maxVelocity, instantVelocity));
velocity = 0;
kineticCandidate = false;
lastWheelTimestamp = now;
finishTimer.restart();
return;
}
velocity = lastWheelTimestamp <= 0 ? instantVelocity : velocity * (1.0 - smoothing) + instantVelocity * smoothing;
lastWheelTimestamp = now;
finishTimer.restart();
if (wheel.phase === Qt.ScrollEnd)
finishScroll();
}
function stopMouseGlide() {
mouseGlide.stop();
mouseVelocity = 0;
mouseDir = 0;
mouseStreak = 0;
mouseLastTime = 0;
}
function handleMouseDelta(delta, now) {
var dir = delta > 0 ? 1 : -1;
// Edge or active overshoot: existing rubber-band path
if (hasOvershoot() || atEdgeFor(dir)) {
stopMouseGlide();
scrollBy(delta);
overshootReleaseVelocity = 0;
finishTimer.restart();
return;
}
var continuous = dir === mouseDir && (now - mouseLastTime) <= mouseAccelWindow;
if (!continuous)
mouseVelocity = 0; // new gesture or reversal: start clean
mouseStreak = continuous ? mouseStreak + 1 : 0;
// Compute the gap BEFORE overwriting mouseLastTime
var gap = mouseLastTime > 0 ? Math.max(12, Math.min(mouseAccelWindow, now - mouseLastTime)) : 0;
mouseDir = dir;
mouseLastTime = now;
var accel = Math.min(mouseMaxAccel, 1 + mouseStreak * mouseAccelStep);
// --- velocity update (this is the part that changed) ---
// Part of the notch lands immediately
var instant = delta * mouseSnap;
flickable.contentY = boundedContentY(flickable.contentY + instant);
// The rest becomes glide velocity, stacked onto the current velocity
var glide = delta * (1 - mouseSnap) * accel;
var v = mouseVelocity + glide / mouseGlideTime;
// While notches keep arriving, don't fall below the rate they imply
if (continuous && gap > 0) {
var rate = glide / (gap / 1000);
if (Math.abs(rate) > Math.abs(v))
v = rate;
}
mouseVelocity = Math.max(-maxVelocity, Math.min(maxVelocity, v));
// --- end velocity update ---
if (!mouseGlide.running)
mouseGlide.start();
}
function stepMouseGlide(dt) {
if (!flickable || flickable.dragging) {
stopMouseGlide();
return;
}
dt = Math.min(dt, 0.05);
var decay = Math.exp(-dt / mouseGlideTime);
// Exact integral of v0 * e^(-t/tau) over dt, so it's frame-rate independent
var step = mouseVelocity * mouseGlideTime * (1 - decay);
var next = flickable.contentY + step;
var minY = minimumContentY();
var maxY = maximumContentY();
if (next <= minY || next >= maxY) {
var impact = mouseVelocity;
flickable.contentY = next <= minY ? minY : maxY;
mouseGlide.stop();
mouseVelocity = 0;
mouseDir = 0;
mouseStreak = 0;
triggerKineticBounce(impact); // bounce scales with the real impact speed
return;
}
flickable.contentY = next;
mouseVelocity *= decay;
if (Math.abs(mouseVelocity) < 40) // was 8
stopMouseGlide();
}
FrameAnimation {
id: mouseGlide
onTriggered: root.stepMouseGlide(frameTime)
}
Timer {
id: finishTimer
interval: 45
repeat: false
onTriggered: root.finishScroll()
}
NumberAnimation {
id: kineticAnim
target: root.flickable
property: "contentY"
easing.type: Easing.OutQuart
onFinished: {
// Prefer the impact-time velocity (computed in runKineticGlide)
// so the bounce matches the residual energy at the moment the
// glide hit the edge — slow landings → soft bounce, fast
// landings → big springy bounce.
var bv = Math.abs(root.pendingImpactVelocity) > root.edgeEpsilon ? root.pendingImpactVelocity : root.pendingKineticBounceVelocity;
root.pendingImpactVelocity = 0;
root.pendingKineticBounceVelocity = 0;
if (Math.abs(bv) > root.edgeEpsilon)
root.triggerKineticBounce(bv);
}
}
NumberAnimation {
id: bounceBackAnimation
target: root
property: "overshootInput"
easing.type: Easing.OutCubic
}
SequentialAnimation {
id: kineticBounceSequence
NumberAnimation {
id: kineticBounceIn
target: root
property: "overshootInput"
easing.type: Easing.OutCubic
}
NumberAnimation {
id: kineticBounceOut
target: root
property: "overshootInput"
easing.type: Easing.OutCubic
}
}
Translate {
id: overshootTranslate
y: root.visualOvershoot
}
Connections {
target: root.flickable
function onInteractiveChanged() {
if (!root.flickable.interactive)
root.stopAndSettle();
}
function onContentHeightChanged() {
if (!root.flickable.moving) {
root.flickable.contentY = root.boundedContentY(root.flickable.contentY);
root.bounceBack();
}
}
function onHeightChanged() {
if (!root.flickable.moving) {
root.flickable.contentY = root.boundedContentY(root.flickable.contentY);
root.bounceBack();
}
}
function onVisibleChanged() {
if (!root.flickable.visible)
root.stopAndSettle();
}
function onMovementEnded() {
if (Math.abs(root.pendingKineticBounceVelocity) > root.edgeEpsilon) {
var bounceVelocity = root.pendingKineticBounceVelocity;
root.pendingKineticBounceVelocity = 0;
root.triggerKineticBounce(bounceVelocity);
}
}
}
Component.onCompleted: {
if (!flickable)
return;
flickable.flickDeceleration = 2800;
flickable.maximumFlickVelocity = Math.max(flickable.maximumFlickVelocity, maxVelocity);
flickable.contentItem.transform = [overshootTranslate];
}
}
-506
View File
@@ -1,506 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtCanvas2D
import ZShell
Item {
id: root
required property list<var> values
property int xSteps: 24
property real yMin: NaN
property real yMax: NaN
property real yPaddingTop: 0
property real yPaddingBottom: 0
property int verticalLineStep: 0
property int horizontalLineStep: 0
property bool firstXLine: true
property bool lastXLine: false
property int minHorizontalLines: 0
property var xLabel: null
property var yLabel: null
property real smoothing: 0
property bool curveSmoothing: true
property real markerX: -1
property real markerGap: 2 // px gap between line and marker dot edge, each side
property color lineColor: "white"
property color gridColor: "white"
property color markerColor: "white"
property color labelColor: "white"
property real gridAlpha: 0.12
property real labelAlpha: 0.6
property real fillAlpha: 0.15
property real lineWidth: 2
property real markerDotRadius: 4
property real labelFontSize: 8
Canvas2D {
id: canvas
anchors.fill: parent
alphaBlending: true
fillColor: "transparent"
onWidthChanged: requestPaint()
onHeightChanged: requestPaint()
function snapV(v) {
return ZUtils.snap(v, canvas);
}
// A value is plottable only if it is a real number. Everything else -
// null, undefined, NaN, an unparsable string from the API - is a hole, so
// the series splits there. Non-finite coordinates must never reach the
// path: one NaN lineTo makes the canvas ignore the rest of the curve, and
// the fill then runs from whatever y it last had straight down.
function numeric(v) {
if (typeof v === "number")
return Number.isFinite(v) ? v : null;
if (typeof v === "string") {
const n = Number(v);
return Number.isFinite(n) ? n : null;
}
return null;
}
function strokeCurve(ctx, pts) {
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
const dpr = ZUtils.dprFor(canvas);
for (let i = 0; i < pts.length - 1; i++) {
const p1 = pts[i];
const p2 = pts[i + 1];
const [c1x, c1y, c2x, c2y] = segmentControls(pts, i);
const firstCol = Math.ceil(p1.x * dpr) + 1;
const lastCol = Math.floor(p2.x * dpr) - 1;
for (let col = firstCol; col <= lastCol; ++col) {
const x = col / dpr;
ctx.lineTo(x, bezierY(p1, c1x, c1y, c2x, c2y, p2, x));
}
ctx.lineTo(p2.x, p2.y);
}
}
function bezierY(p1, c1x, c1y, c2x, c2y, p2, targetX) {
let tlo = 0;
let thi = 1;
for (let it = 0; it < 16; it++) {
const tm = (tlo + thi) / 2;
const u = 1 - tm;
const x = u * u * u * p1.x + 3 * u * u * tm * c1x + 3 * u * tm * tm * c2x + tm * tm * tm * p2.x;
if (x < targetX)
tlo = tm;
else
thi = tm;
}
const t = (tlo + thi) / 2;
const u = 1 - t;
return u * u * u * p1.y + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * p2.y;
}
function segmentControls(pts, i) {
const p0 = pts[Math.max(i - 1, 0)];
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = pts[Math.min(i + 2, pts.length - 1)];
if (root.curveSmoothing && pts.length > 2) {
const k = 1 / 6;
let c1x = p1.x + (p2.x - p0.x) * k;
let c1y = p1.y + (p2.y - p0.y) * k;
let c2x = p2.x - (p3.x - p1.x) * k;
let c2y = p2.y - (p3.y - p1.y) * k;
const minY = Math.min(p1.y, p2.y);
const maxY = Math.max(p1.y, p2.y);
c1y = Math.max(minY, Math.min(maxY, c1y));
c2y = Math.max(minY, Math.min(maxY, c2y));
return [c1x, c1y, c2x, c2y];
}
const cx = (p1.x + p2.x) * 0.5;
return [cx, p1.y, cx, p2.y];
}
function pointAtX(pts, targetX) {
let j = pts.length - 2;
for (let i = 0; i < pts.length - 1; i++) {
if (targetX <= pts[i + 1].x) {
j = i;
break;
}
}
const p1 = pts[j];
const p2 = pts[j + 1];
const [c1x, c1y, c2x, c2y] = segmentControls(pts, j);
let tlo = 0;
let thi = 1;
for (let it = 0; it < 24; it++) {
const tm = (tlo + thi) / 2;
const u = 1 - tm;
const x = u * u * u * p1.x + 3 * u * u * tm * c1x + 3 * u * tm * tm * c2x + tm * tm * tm * p2.x;
if (x < targetX)
tlo = tm;
else
thi = tm;
}
const t = (tlo + thi) / 2;
const u = 1 - t;
const y = u * u * u * p1.y + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * p2.y;
return {
x: targetX,
y
};
}
function cutPoint(pts, targetX) {
return pointAtX(pts, targetX);
}
function cutLeft(pts, targetX) {
if (targetX <= pts[0].x)
return [];
if (targetX >= pts[pts.length - 1].x)
return pts.slice();
let j = pts.length - 2;
for (let i = 0; i < pts.length - 1; i++) {
if (targetX <= pts[i + 1].x) {
j = i;
break;
}
}
const left = pts.slice(0, j + 1);
if (targetX > pts[j].x)
left.push(cutPoint(pts, targetX));
return left;
}
function cutRight(pts, targetX) {
if (targetX >= pts[pts.length - 1].x)
return [];
if (targetX <= pts[0].x)
return pts.slice();
let j = pts.length - 2;
for (let i = 0; i < pts.length - 1; i++) {
if (targetX <= pts[i + 1].x) {
j = i;
break;
}
}
const right = pts.slice(j + 1);
if (targetX < pts[j + 1].x)
right.unshift(pointAtX(pts, targetX));
return right;
}
onPaint: {
const ctx = getContext("2d");
if (!ctx)
return;
ctx.reset();
ctx.clearRect(0, 0, width, height);
const W = width;
const H = height;
if (W < 2 || H < 2)
return;
const padLeft = root.labelFontSize * 5;
const padRight = 0;
const padTop = 0;
const padBottom = root.labelFontSize + 14;
const plot = {
x: padLeft,
y: padTop,
w: W - padLeft - padRight,
h: H - padTop - padBottom
};
if (plot.w < 2 || plot.h < 2)
return;
let lo = root.yMin;
let hi = root.yMax;
const loAuto = Number.isNaN(lo);
const hiAuto = Number.isNaN(hi);
if (loAuto || hiAuto) {
let dmin = Infinity;
let dmax = -Infinity;
for (const value of root.values) {
const v = numeric(value);
if (v == null)
continue;
if (v < dmin)
dmin = v;
if (v > dmax)
dmax = v;
}
if (dmin === Infinity) {
dmin = 0;
dmax = 1;
}
const step = root.horizontalLineStep;
const minSteps = root.minHorizontalLines > 1 ? root.minHorizontalLines - 1 : 0;
if (step > 0) {
if (hiAuto) {
const baseLo = loAuto ? Math.floor(dmin / step) * step : lo;
const span = Math.max(Math.ceil((dmax - baseLo) / step), minSteps, 1);
lo = baseLo;
hi = baseLo + span * step;
} else {
const span = Math.max(Math.ceil((hi - dmin) / step), minSteps, 1);
lo = hi - span * step;
}
} else {
if (loAuto)
lo = dmin;
if (hiAuto)
hi = dmax;
if (hi <= lo)
hi = lo + 1;
}
}
const scaleBottom = lo - root.yPaddingBottom;
const scaleTop = hi + root.yPaddingTop;
const range = scaleTop - scaleBottom;
if (range <= 0 || root.xSteps < 1)
return;
let series = root.values.map(v => numeric(v));
if (root.smoothing > 0 && series.length > 0) {
const radius = Math.round(root.smoothing);
const raw = series;
series = raw.map((v, i) => {
if (v == null)
return null;
let sum = 0;
let n = 0;
for (let j = -radius; j <= radius; j++) {
const u = raw[i + j];
if (u != null) {
sum += u;
n++;
}
}
return n > 0 ? sum / n : null;
});
}
const xAt = i => plot.x + (i / root.xSteps) * plot.w;
const yAt = v => plot.y + (1 - (v - scaleBottom) / range) * plot.h;
const dpr = ZUtils.dprFor(canvas);
const devW = w => Math.max(1, Math.round(w * dpr));
const snapLine = (v, w) => {
const dw = devW(w);
return (Math.round(v * dpr - dw / 2) + dw / 2) / dpr;
};
const gridW = devW(1) / dpr;
ctx.strokeStyle = Qt.alpha(root.gridColor, root.gridAlpha);
ctx.lineWidth = gridW;
if (root.verticalLineStep > 0) {
for (let i = 0; i <= root.xSteps; i += root.verticalLineStep) {
if (i === 0 && !root.firstXLine)
continue;
if (i === root.xSteps && !root.lastXLine)
continue;
const x = snapLine(xAt(i), 1);
ctx.beginPath();
ctx.moveTo(x, plot.y);
ctx.lineTo(x, H);
ctx.stroke();
}
}
const vStart = lo - (root.yPaddingBottom > 0 ? Math.floor(root.yPaddingBottom / root.horizontalLineStep) * root.horizontalLineStep : 0);
if (root.horizontalLineStep > 0) {
for (let v = vStart; v <= hi + 0.001; v += root.horizontalLineStep) {
const y = snapLine(yAt(v), 1);
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(plot.x + plot.w, y);
ctx.stroke();
}
}
ctx.fillStyle = Qt.alpha(root.labelColor, root.labelAlpha);
ctx.font = root.labelFontSize + "pt 'sans-serif'";
ctx.textAlign = "left";
ctx.textBaseline = "top";
if (root.verticalLineStep > 0 && root.xLabel) {
for (let i = 0; i <= root.xSteps; i += root.verticalLineStep) {
if (i === 0 && !root.firstXLine)
continue;
if (i === root.xSteps && !root.lastXLine)
continue;
const text = root.xLabel(i);
if (text)
ctx.fillText(text, xAt(i) + 4, plot.y + plot.h + 8);
}
}
ctx.textBaseline = "bottom";
if (root.horizontalLineStep > 0 && root.yLabel) {
for (let v = vStart; v <= hi + 0.001; v += root.horizontalLineStep) {
const text = root.yLabel(v);
if (text)
ctx.fillText(text, 2, yAt(v) - 2);
}
}
const segments = [];
let current = null;
for (let i = 0; i < series.length; i++) {
if (series[i] == null) {
if (current)
segments.push(current);
current = null;
continue;
}
const pt = {
x: snapV(xAt(i)),
y: yAt(series[i])
};
if (!current)
current = [];
current.push(pt);
}
if (current)
segments.push(current);
ctx.lineCap = "round";
ctx.lineJoin = "round";
const hasMarker = root.markerX >= 0 && root.markerX <= root.xSteps;
const markerPx = hasMarker ? xAt(root.markerX) : 0;
let gapPx = 0;
if (hasMarker) {
let slope = 0;
const seg = segments.find(s => s[0].x <= markerPx && markerPx <= s[s.length - 1].x);
if (seg && seg.length > 2 && markerPx - 1 >= seg[0].x && markerPx + 1 <= seg[seg.length - 1].x)
slope = (pointAtX(seg, markerPx + 1).y - pointAtX(seg, markerPx - 1).y) / 2;
gapPx = (root.markerDotRadius + root.markerGap) / Math.sqrt(1 + slope * slope);
}
for (const seg of segments) {
if (seg.length < 2)
continue;
ctx.fillStyle = Qt.alpha(root.lineColor, root.fillAlpha);
strokeCurve(ctx, seg);
ctx.lineTo(seg[seg.length - 1].x, plot.y + plot.h);
ctx.lineTo(seg[0].x, plot.y + plot.h);
ctx.closePath();
ctx.fill();
const pieces = hasMarker ? [cutLeft(seg, markerPx - gapPx), cutRight(seg, markerPx + gapPx)] : [seg];
for (const pts of pieces) {
if (pts.length < 2)
continue;
ctx.strokeStyle = root.lineColor;
ctx.lineWidth = ZUtils.snap(root.lineWidth, root);
strokeCurve(ctx, pts);
ctx.stroke();
}
}
if (root.markerX >= 0 && root.markerX <= root.xSteps) {
const x = markerPx;
ctx.strokeStyle = Qt.alpha(root.markerColor, 0.6);
ctx.lineWidth = gridW;
const lineX = snapLine(x, 1);
let dashY = plot.y;
let dashOn = true;
while (dashY < H) {
const dashEnd = Math.min(dashY + 4, H);
if (dashOn) {
ctx.beginPath();
ctx.moveTo(lineX, snapV(dashY));
ctx.lineTo(lineX, snapV(dashEnd));
ctx.stroke();
}
dashY = dashEnd;
dashOn = !dashOn;
}
const i0 = Math.floor(root.markerX);
const v0 = series[i0];
const v1 = series[i0 + 1];
let y = null;
if (v0 != null && v1 != null)
y = yAt(v0 + (v1 - v0) * (root.markerX - i0));
else if (v0 != null)
y = yAt(v0);
if (y != null) {
ctx.fillStyle = root.markerColor;
ctx.beginPath();
ctx.circle(snapV(x), y, root.markerDotRadius);
ctx.fill();
}
}
}
}
onValuesChanged: canvas.requestPaint()
onXStepsChanged: canvas.requestPaint()
onYMinChanged: canvas.requestPaint()
onYMaxChanged: canvas.requestPaint()
onYPaddingTopChanged: canvas.requestPaint()
onYPaddingBottomChanged: canvas.requestPaint()
onVerticalLineStepChanged: canvas.requestPaint()
onHorizontalLineStepChanged: canvas.requestPaint()
onFirstXLineChanged: canvas.requestPaint()
onLastXLineChanged: canvas.requestPaint()
onMinHorizontalLinesChanged: canvas.requestPaint()
onXLabelChanged: canvas.requestPaint()
onYLabelChanged: canvas.requestPaint()
onSmoothingChanged: canvas.requestPaint()
onCurveSmoothingChanged: canvas.requestPaint()
onMarkerXChanged: canvas.requestPaint()
onMarkerGapChanged: canvas.requestPaint()
onLineColorChanged: canvas.requestPaint()
onGridColorChanged: canvas.requestPaint()
onMarkerColorChanged: canvas.requestPaint()
onLabelColorChanged: canvas.requestPaint()
onGridAlphaChanged: canvas.requestPaint()
onLabelAlphaChanged: canvas.requestPaint()
onFillAlphaChanged: canvas.requestPaint()
onLineWidthChanged: canvas.requestPaint()
onMarkerDotRadiusChanged: canvas.requestPaint()
onLabelFontSizeChanged: canvas.requestPaint()
}
+2 -1
View File
@@ -64,7 +64,8 @@ MaterialShape {
to: 360
}
Behavior on color {
CAnim {}
CAnim {
}
}
ElapsedTimer {
+4 -2
View File
@@ -47,10 +47,12 @@ Item {
implicitWidth: vertical ? elideText.implicitHeight : elideText.implicitWidth
Behavior on leftFadeStrength {
Anim {}
Anim {
}
}
Behavior on rightFadeStrength {
Anim {}
Anim {
}
}
onHeightChanged: resetMarquee()
+17 -4
View File
@@ -1,14 +1,27 @@
import QtQuick
import ZShell.Config
import qs.Services
CustomText {
id: root
readonly property list<int> allowed: [20, 24, 40, 48]
property real fill: 0
property int grade: Colors.light ? 0 : -25
property font fontStyle: Tokens.font.icon.small
readonly property int opsz: clampOpsz(optical)
property int optical: fontInfo.pixelSize
renderType: Text.NativeRendering
font: Tokens.font.icon.size(fontStyle.pointSize).weight(fontStyle.weight).vaxes(fontStyle.variableAxes).fill(fill.toFixed(1)).grade(grade).build()
function clampOpsz(value): int {
return allowed.reduce((closest, current) => {
return Math.abs(current - value) < Math.abs(closest - value) ? current : closest;
});
}
font.family: "Material Symbols Rounded"
font.pointSize: Tokens.font.size.larger
font.variableAxes: ({
FILL: fill,
GRAD: grade,
opsz: opsz,
wght: fontInfo.weight
})
}
+19 -22
View File
@@ -4,7 +4,6 @@ import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Config
import ZShell.Components
import qs.Drawers
import qs.Services
@@ -35,7 +34,7 @@ MouseArea {
cursorShape: expanded ? Qt.ArrowCursor : undefined
enabled: expanded
hoverEnabled: expanded
layer.enabled: false
layer.enabled: opacity < 1
opacity: expanded ? 1 : 0
parent: {
const win = QsWindow.window;
@@ -102,7 +101,8 @@ MouseArea {
yScale: root.expanded ? 1 : 0.1
Behavior on yScale {
Anim {}
Anim {
}
}
}
@@ -111,14 +111,12 @@ MouseArea {
color: Colors.palette.m3surfaceContainerLow
radius: parent.radius
SnapLayout {
ColumnLayout {
id: column
anchors.fill: parent
anchors.margins: Tokens.padding.extraSmall
spacing: Tokens.spacing.extraSmall
orientation: Qt.Vertical
Repeater {
id: repeater
@@ -126,7 +124,7 @@ MouseArea {
model: root.items
CustomRect {
id: menuItem
id: item
readonly property bool active: modelData === root?.active
required property int index
@@ -139,52 +137,51 @@ MouseArea {
radius: menu.radius - column.anchors.margins
Behavior on radius {
Anim {}
Anim {
}
}
StateLayer {
color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
enabled: root.expanded
onClicked: {
root.itemSelected(menuItem.modelData);
root.active = menuItem.modelData;
menuItem.modelData.clicked();
root.itemSelected(item.modelData);
root.active = item.modelData;
item.modelData.clicked();
root.expanded = false;
}
}
SnapLayout {
RowLayout {
id: menuOptionRow
anchors.fill: parent
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
text: menuItem.modelData?.icon ?? ""
visible: text !== ""
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
text: item.modelData?.icon ?? ""
}
CustomText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
text: menuItem.modelData?.text ?? ""
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
text: item.modelData?.text ?? ""
}
Loader {
Layout.alignment: Qt.AlignVCenter
active: menuItem.modelData?.trailingIcon.length > 0
active: item.modelData?.trailingIcon.length > 0
asynchronous: true
visible: active
sourceComponent: MaterialIcon {
color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
text: menuItem.modelData.trailingIcon
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
text: item.modelData.trailingIcon
}
}
}
-120
View File
@@ -1,120 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Services.Notifications
import Quickshell.Widgets
import ZShell.Components
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Services
ButtonRow {
id: root
required property NotificationRow notif
required property color buttonColour
readonly property color fillColour: notif.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondary : buttonColour
readonly property color buttonTextColour: notif.urgency === NotificationUrgency.Critical ? Colors.palette.m3onSecondary : Colors.palette.m3onSurfaceVariant
function invoke(action: var): void {
action.invoke();
if (!notif.resident)
notif.dismiss();
}
spacing: Tokens.spacing.extraSmall
IconButton {
isRound: true
shapeMorph: true
fillWidth: root.notif.actions.length === 0
inactiveColor: root.fillColour
inactiveOnColor: root.buttonTextColour
icon: "close"
padding: Tokens.padding.extraSmall
onClicked: root.notif.dismiss()
}
Repeater {
model: root.notif.actions
delegate: root.notif.hasActionIcons ? iconAction : textAction
}
IconButton {
isRound: true
shapeMorph: true
fillWidth: root.notif.actions.length === 0
inactiveColor: root.fillColour
inactiveOnColor: root.buttonTextColour
icon: copyTimer.running ? "inventory" : "content_copy"
padding: Tokens.padding.extraSmall
onClicked: {
Quickshell.clipboardText = root.notif.body;
copyTimer.restart();
}
label.animate: true
Timer {
id: copyTimer
interval: 3000
}
}
Component {
id: textAction
TextButton {
required property var modelData
isRound: true
shapeMorph: true
fillWidth: true
inactiveColor: root.fillColour
inactiveOnColor: root.buttonTextColour
text: modelData.text
onClicked: root.invoke(modelData)
label.horizontalAlignment: Text.AlignHCenter
label.anchors.left: left
label.anchors.right: right
label.anchors.verticalCenter: verticalCenter
label.anchors.centerIn: undefined
label.anchors.margins: Tokens.padding.medium
label.elide: Text.ElideRight
}
}
Component {
id: iconAction
ButtonBase {
id: button
required property var modelData
isRound: true
shapeMorph: true
fillWidth: true
padding: Tokens.padding.extraSmall
inactiveColor: root.fillColour
inactiveOnColor: root.buttonTextColour
implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + padding * 2
onClicked: root.invoke(modelData)
IconImage {
id: icon
anchors.centerIn: parent
implicitSize: Math.round(Tokens.font.icon.medium.pointSize * 1.3)
asynchronous: true
source: Quickshell.iconPath(button.modelData.identifier)
}
}
}
}
-201
View File
@@ -1,201 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Shapes
import Quickshell
import ZShell
import M3Shapes
import ZShell.Config
import qs.Components
import qs.Helpers
import qs.Services
import Quickshell.Services.Notifications
Item {
id: root
required property var owner
required property string image
required property string icon
required property int urgency
required property string summary
required property color lowFillColour
property real progress: 0
property string shownImage
property string shownIcon
readonly property color fillColour: urgency === NotificationUrgency.Critical ? Colors.palette.m3primary : urgency === NotificationUrgency.Low ? lowFillColour : Colors.palette.m3secondaryContainer
readonly property color iconColour: urgency === NotificationUrgency.Critical ? Colors.palette.m3onPrimary : urgency === NotificationUrgency.Low ? Colors.palette.m3onSurfaceVariant : Colors.palette.m3onSecondaryContainer
implicitWidth: Config.notifs.sizes.image
implicitHeight: Config.notifs.sizes.image
// Keeping the previously shown url avoids replaying the fade when a row
// swaps between its raw source and the cached blob.
onImageChanged: {
if (image && !owner.isSameImage(shownImage))
shownImage = image;
}
onIconChanged: {
if (icon && !owner.isSameAppIcon(shownIcon))
shownIcon = icon;
}
Component.onCompleted: {
shownImage = image;
shownIcon = icon;
}
Component {
id: imageComp
FadeImage {
source: Qt.resolvedUrl(root.shownImage)
width: Config.notifs.sizes.image
height: Config.notifs.sizes.image
fadeInLargeAnim: Anim.DefaultEffects
}
}
Component {
id: appIconComp
FadeIcon {
size: Math.round(Config.notifs.sizes.image * 0.6)
}
}
Component {
id: materialIconComp
MaterialIcon {
animate: true
text: Icons.getNotifIcon(root.summary, root.urgency)
color: root.iconColour
fontStyle: Tokens.font.icon.medium
}
}
MaterialShape {
id: bgShape
anchors.fill: parent
shape: contentLoader.sourceComponent === materialIconComp && root.urgency === NotificationUrgency.Critical ? MaterialShape.SoftBurst : MaterialShape.Circle
color: Qt.alpha(root.fillColour, 1)
opacity: root.fillColour.a
Behavior on color {
CAnim {}
}
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
Item {
anchors.fill: parent
Loader {
id: contentLoader
anchors.centerIn: parent
anchors.verticalCenterOffset: sourceComponent === materialIconComp ? 1 : 0
asynchronous: true
sourceComponent: root.image ? imageComp : root.icon ? appIconComp : materialIconComp
}
}
Loader {
asynchronous: true
anchors.right: parent.right
anchors.bottom: parent.bottom
active: opacity > 0
opacity: root.icon && root.image ? 1 : 0
sourceComponent: CustomRect {
implicitWidth: Config.notifs.sizes.badge
implicitHeight: Config.notifs.sizes.badge
color: root.fillColour
radius: Tokens.rounding.full
FadeIcon {
anchors.centerIn: parent
size: Math.round(Config.notifs.sizes.badge * 0.6)
}
}
Behavior on opacity {
Anim {
type: root.icon && root.image ? Anim.DefaultEffects : Anim.FastEffects
}
}
}
Loader {
asynchronous: true
anchors.fill: parent
active: root.progress > 0
sourceComponent: Shape {
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: progressShape
property real sweep: root.progress * 360
property real spacing: root.urgency === NotificationUrgency.Critical ? Tokens.spacing.extraSmall : 0
capStyle: ShapePath.RoundCap
fillColor: "transparent"
strokeWidth: 2
strokeColor: Colors.palette.m3primary
PathPolyline {
function pointAt(angle: real): point {
const dist = bgShape.distanceAtAngle(angle) + progressShape.strokeWidth / 2 + progressShape.spacing;
const rad = angle * Math.PI / 180;
return Qt.point(root.width / 2 + dist * Math.sin(rad), root.height / 2 - dist * Math.cos(rad));
}
path: {
bgShape.morphProgress; // mapToItem-free dependency: re-run when the shape morphs
const points = [];
for (let angle = 0; angle < progressShape.sweep; angle += 3)
points.push(pointAt(angle));
points.push(pointAt(progressShape.sweep));
return points;
}
}
Behavior on sweep {
Anim {
easing: Tokens.anim.emphasizedDecel
}
}
Behavior on spacing {
Anim {}
}
Behavior on strokeColor {
CAnim {}
}
}
}
}
component FadeIcon: FadeImage {
id: fadeIcon
required property int size
width: size
height: size
source: Quickshell.iconPath(root.shownIcon)
fillMode: Image.PreserveAspectFit
fadeInLargeAnim: Anim.DefaultEffects
}
}
-124
View File
@@ -1,124 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Notifications
import ZShell.Components
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Services
CustomRect {
id: root
required property NotificationGroup group
required property Flickable container
required property bool expanded
property bool expandable: true
property int baseSurface: 0
property int baseLayer: baseSurface
readonly property list<NotificationRow> notifs: [...group.activeNotifs]
readonly property bool single: notifs.length === 1
readonly property alias rowPressed: notifList.rowPressed
readonly property list<color> surfaces: [Colors.palette.m3surfaceContainer, Colors.palette.m3surfaceContainerHigh, Colors.palette.m3surfaceContainerHighest]
readonly property int nonAnimHeight: {
const headerHeight = header.implicitHeight + (expanded ? Tokens.spacing.extraSmall : 0);
const columnHeight = headerHeight + notifList.layoutHeight;
return Math.round(Math.max(Config.notifs.sizes.image, columnHeight) + Tokens.padding.medium * 2);
}
signal requestExpand(expand: bool)
signal swiped(notif: NotificationRow)
signal linkActivated(notif: NotificationRow)
function surface(offset: int, layer: int): color {
return Colors.layer(surfaces[Math.min(baseSurface + offset, surfaces.length - 1)], baseLayer + layer);
}
Component.onCompleted: group.lock(this)
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: nonAnimHeight
clip: true
radius: Tokens.rounding.large
color: group.urgency === NotificationUrgency.Critical && (single || !expanded) ? Colors.palette.m3secondaryContainer : surface(0, 1)
Behavior on implicitHeight {
Anim {}
}
SnapLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Tokens.padding.medium
objectName: "notif-content"
orientation: Qt.Horizontal
spacing: Tokens.spacing.medium
NotifAvatar {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: root.expanded && !root.single ? header.implicitHeight + Tokens.spacing.extraSmall + Tokens.padding.extraSmall : 0
owner: root.expanded ? root.notifs[0] ?? root.group : root.group
image: root.expanded ? root.notifs[0]?.image ?? "" : root.group.image
icon: root.expanded ? root.notifs[0]?.appIcon ?? "" : root.group.appIcon
urgency: root.expanded ? root.notifs[0]?.urgency ?? NotificationUrgency.Normal : root.group.urgency
lowFillColour: root.surface(2, 2)
summary: root.notifs[0]?.summary ?? ""
progress: (root.notifs[0]?.progress ?? -1) / 100
Behavior on Layout.topMargin {
Anim {}
}
}
Column {
Layout.fillWidth: true
spacing: root.expanded ? Tokens.spacing.extraSmall : 0
Behavior on spacing {
Anim {}
}
NotifGroupHeader {
id: header
anchors.left: parent.left
anchors.right: parent.right
appName: root.group.appName
single: root.single
timeStr: root.notifs[0]?.timeStr ?? ""
count: root.notifs.length
urgency: root.group.urgency
pillColour: root.surface(1, 2)
expandable: root.expandable
expanded: root.expanded
onToggle: root.requestExpand(!root.expanded)
}
NotifGroupList {
id: notifList
notifs: root.notifs
expanded: root.expanded
single: root.single
headerHeight: header.implicitHeight
headerTitleWidth: header.titleWidth
expandable: root.expandable
container: root.container
itemColour: root.surface(1, 2)
avatarColour: root.surface(2, 2)
buttonColour: root.surface(2, 3)
onRequestExpand: expand => root.requestExpand(expand)
onSwiped: notif => root.swiped(notif)
onLinkActivated: notif => root.linkActivated(notif)
}
}
}
}
-156
View File
@@ -1,156 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Services.Notifications
import ZShell.Components
import ZShell.Config
import qs.Components
import qs.Services
Item {
id: root
required property string appName
required property bool single
required property string timeStr
required property int count
required property int urgency
required property color pillColour
required property bool expandable
required property bool expanded
readonly property alias titleWidth: appName.width
readonly property color expandBtnTextColour: urgency === NotificationUrgency.Critical && !single ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
signal toggle
implicitHeight: Math.max(appName.implicitHeight, time.implicitHeight, pill.implicitHeight)
objectName: "notif-header"
CustomText {
id: appName
anchors.left: parent.left
anchors.right: time.left
anchors.rightMargin: Tokens.spacing.small
anchors.verticalCenter: parent.verticalCenter
opacity: root.single && !root.expanded ? 0 : 1
text: root.appName
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.body.small
elide: Text.ElideRight
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
CustomText {
id: time
anchors.right: pill.left
anchors.rightMargin: Tokens.spacing.small
anchors.verticalCenter: parent.verticalCenter
animate: true
text: root.timeStr
color: {
const col = root.urgency === NotificationUrgency.Critical && (root.single || !root.expanded) ? Colors.palette.m3secondary : Colors.palette.m3outline;
return root.expanded && !root.single ? Qt.alpha(col, 0) : col;
}
font: Tokens.font.body.small
}
CustomRect {
id: pill
property real horizontalPadding: root.single ? implicitHeight - expandIcon.implicitWidth : Tokens.padding.large
property real countSpan: root.single ? 0 : groupCount.implicitWidth + Tokens.spacing.extraSmall
property real iconInset: root.single ? 0 : Tokens.padding.extraSmall / 2
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
enabled: root.expandable
// StateLayer inherits this through its parent.enabled binding, so a
// non-expandable group gets no hover, ripple, or cursor.
implicitWidth: horizontalPadding + countSpan + (root.expandable ? expandIcon.implicitWidth : 0)
implicitHeight: groupCount.implicitHeight + Tokens.padding.extraSmall
color: {
const c = root.urgency === NotificationUrgency.Critical ? Colors.palette.m3primary : root.pillColour;
return root.single ? Qt.alpha(c, 0) : c;
}
radius: Tokens.rounding.full
StateLayer {
color: root.expandBtnTextColour
onClicked: root.toggle()
}
Item {
anchors.left: parent.left
anchors.leftMargin: pill.horizontalPadding / 2 + pill.iconInset
anchors.verticalCenter: parent.verticalCenter
objectName: "notif-expand"
implicitWidth: groupCount.implicitWidth
implicitHeight: groupCount.implicitHeight
opacity: root.single ? 0 : 1
CustomText {
id: groupCount
animate: true
text: root.count
color: root.expandBtnTextColour
font: Tokens.font.body.small
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
MaterialIcon {
id: expandIcon
anchors.right: parent.right
anchors.rightMargin: pill.horizontalPadding / 2 - pill.iconInset
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: root.expanded && !root.single ? -Tokens.padding.extraSmall / 2 : 0
visible: root.expandable
text: "expand_more"
color: root.expandBtnTextColour
rotation: root.expanded ? 180 : 0
Behavior on rotation {
Anim {}
}
Behavior on anchors.verticalCenterOffset {
Anim {}
}
}
Behavior on horizontalPadding {
Anim {}
}
Behavior on countSpan {
Anim {}
}
Behavior on iconInset {
Anim {}
}
}
}
-152
View File
@@ -1,152 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import ZShell.Components
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Services
LazyListView {
id: root
required property list<NotificationRow> notifs
required property bool expanded
required property bool expandable
required property bool single
required property Flickable container
property real headerHeight
property real headerTitleWidth
required property color itemColour
required property color avatarColour
required property color buttonColour
readonly property bool rowPressed: pressedRows > 0
property int pressedRows
signal requestExpand(expand: bool)
signal swiped(notif: NotificationRow)
signal linkActivated(notif: NotificationRow)
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: contentHeight
spacing: Math.round(Tokens.spacing.extraSmall)
asynchronous: true
readyDelay: 1
cacheBuffer: 400
removeDuration: Tokens.anim.durations.normal
useCustomViewport: true
viewport: {
tWatcher.transform; // mapToItem is not reactive so use this to trigger updates
return Qt.rect(0, container.contentY - mapToItem(container.contentItem, 0, 0).y, width, container.height);
}
model: ScriptModel {
// Spread, not `as Array`: `notifs` is a list<NotificationRow> property,
// and reading it yields a list reference that ScriptModel cannot assign.
values: root.expanded ? [...root.notifs] : [...root.notifs].slice(0, Config.notifs.groupPreviewNum);
}
delegate: Component {
NotifSwipeArea {
id: notif
required property int index
required property NotificationRow modelData
Component.onCompleted: modelData.ref(this)
Component.onDestruction: {
if (pressed)
root.pressedRows--;
}
LazyListView.preferredHeight: LazyListView.removing ? 0 : notifInner.nonAnimHeight
LazyListView.visibleHeight: LazyListView.removing ? 0 : notifInner.implicitHeight
implicitHeight: notifInner.implicitHeight
opacity: LazyListView.removing || LazyListView.adding ? 0 : 1
scale: LazyListView.removing || LazyListView.adding ? 0.7 : 1
expanded: root.expanded
expandable: root.expandable
dragExpand: false
hoveringLink: !!notifInner.body?.hoveredLink
enabled: root.expandable && root.expanded && !LazyListView.removing
acceptedButtons: root.single ? Qt.NoButton : Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressedChanged: root.pressedRows += pressed ? 1 : -1
onRequestExpand: expand => root.requestExpand(expand)
onSwiped: root.swiped(modelData)
onDismissed: modelData.dismiss()
Connections {
function onClosedChanged(): void {
if (notif.modelData.closed)
closeAnim.start();
}
target: notif.modelData
}
ParallelAnimation {
id: closeAnim
Anim {
type: Anim.DefaultEffects
target: notif
property: "opacity"
to: 0
}
Anim {
target: notif
property: "x"
to: notif.x >= 0 ? notif.width : -notif.width
}
}
NotifItem {
id: notifInner
anchors.fill: parent
modelData: notif.modelData
expanded: root.expanded
single: root.single
ready: notif.LazyListView.ready
headerHeight: root.headerHeight
headerTitleWidth: root.headerTitleWidth
showAvatar: notif.index > 0
expandedColour: root.itemColour
avatarColour: root.avatarColour
buttonColour: root.buttonColour
onLinkActivated: root.linkActivated(notif.modelData)
}
Behavior on y {
enabled: notif.LazyListView.ready
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on scale {
Anim {}
}
}
}
TransformWatcher {
id: tWatcher
a: root.container.contentItem
b: root
}
}
-287
View File
@@ -1,287 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.Notifications
import ZShell.Components
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Services
CustomRect {
id: root
required property NotificationRow modelData
required property bool expanded
required property color expandedColour
required property color avatarColour
required property color buttonColour
property bool showAvatar: true
property bool single: false
property bool ready: true
property real headerHeight
property real headerTitleWidth
readonly property bool critical: modelData.urgency === NotificationUrgency.Critical
readonly property bool low: modelData.urgency === NotificationUrgency.Low
readonly property CustomText body: (expandedContent.item as ExpandedBody)?.body ?? null
readonly property real padding: single ? 0 : Tokens.padding.medium
readonly property real headerSummaryOffset: -(headerHeight + summaryHeightMetrics.height) / 2
readonly property real inset: expanded ? padding : 0
readonly property real summaryOffset: single && !expanded ? headerSummaryOffset : inset
readonly property real nonAnimHeight: {
if (!expanded)
return summaryHeightMetrics.height;
const contentHeight = summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + padding * 2;
return single ? contentHeight : Math.max(contentHeight, Config.notifs.sizes.image + Tokens.padding.extraSmall * 2);
}
signal linkActivated
implicitHeight: nonAnimHeight
radius: Tokens.rounding.medium
color: {
const c = root.critical ? Colors.palette.m3secondaryContainer : root.expandedColour;
return expanded && !single ? c : Qt.alpha(c, 0);
}
TextMetrics {
id: summaryHeightMetrics
font: dummySummary.font
text: " " // Use this height to prevent weird characters from changing the line height
}
TextMetrics {
id: summaryMetrics
font: dummySummary.font
text: root.modelData.summary
elide: Text.ElideRight
elideWidth: root.single ? root.headerTitleWidth : root.width
}
WrappedLoader {
id: avatar
shouldBeActive: root.expanded && root.showAvatar
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: Tokens.padding.extraSmall
anchors.leftMargin: -(Config.notifs.sizes.image + Tokens.spacing.medium)
sourceComponent: NotifAvatar {
owner: root.modelData
image: root.modelData.image
icon: root.modelData.appIcon
urgency: root.modelData.urgency
lowFillColour: root.avatarColour
summary: root.modelData.summary
progress: root.modelData.progress / 100
}
}
WrappedLoader {
id: compactSummary
shouldBeActive: !root.expanded
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: root.inset
anchors.topMargin: root.summaryOffset
sourceComponent: CustomText {
animate: root.ready
text: summaryMetrics.elidedText
color: root.critical ? Colors.palette.m3onSecondaryContainer : root.low ? Colors.palette.m3onSurfaceVariant : Colors.palette.m3onSurface
}
Behavior on anchors.leftMargin {
Anim {}
}
Behavior on anchors.topMargin {
Anim {}
}
}
CustomText {
id: dummySummary
anchors.top: parent.top
anchors.left: parent.left
visible: false
text: root.modelData.summary
}
WrappedLoader {
id: compactBody
shouldBeActive: !root.expanded
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: root.inset + (root.single ? 0 : dummySummary.width + Tokens.spacing.small)
anchors.topMargin: root.single && root.expanded ? summary.implicitHeight + expandedContent.anchors.topMargin : root.inset
sourceComponent: CustomText {
text: root.modelData.body.replace(/\n/g, " ")
color: root.critical ? Colors.palette.m3secondary : root.low ? Colors.palette.m3outline : Colors.palette.m3onSurfaceVariant
elide: Text.ElideRight
}
Behavior on anchors.leftMargin {
Anim {}
}
Behavior on anchors.topMargin {
Anim {}
}
}
WrappedLoader {
id: summary
shouldBeActive: root.expanded
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: root.inset
anchors.topMargin: root.summaryOffset
width: root.width - root.padding * 2 - timeStr.implicitWidth - Tokens.spacing.small
sourceComponent: CustomText {
text: root.modelData.summary
color: root.critical ? Colors.palette.m3onSecondaryContainer : root.low ? Colors.palette.m3onSurfaceVariant : Colors.palette.m3onSurface
wrapMode: Text.WordWrap
}
Behavior on anchors.leftMargin {
Anim {}
}
Behavior on anchors.topMargin {
Anim {}
}
}
WrappedLoader {
id: timeStr
shouldBeActive: root.expanded && !root.single
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: root.inset
sourceComponent: CustomText {
animate: true
text: root.modelData.timeStr
color: root.critical ? Colors.palette.m3secondary : Colors.palette.m3outline
font: Tokens.font.body.small
}
Behavior on anchors.margins {
Anim {}
}
}
WrappedLoader {
id: expandedContent
shouldBeActive: root.expanded
anchors.top: summary.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: root.inset
anchors.topMargin: Tokens.spacing.extraSmall
sourceComponent: ExpandedBody {}
Behavior on anchors.margins {
Anim {}
}
}
Behavior on implicitHeight {
Anim {}
}
component ExpandedBody: SnapLayout {
readonly property alias body: bodyText
orientation: Qt.Vertical
spacing: Tokens.spacing.medium
CustomText {
id: bodyText
Layout.fillWidth: true
textFormat: Text.MarkdownText
text: root.modelData.body.replace(/(.)\n(?!\n)/g, "$1\n\n") || qsTr("No body here! :/")
color: root.critical ? Colors.palette.m3secondary : root.low ? Colors.palette.m3outline : Colors.palette.m3onSurfaceVariant
wrapMode: Text.WordWrap
onLinkActivated: link => {
Qt.openUrlExternally(link);
root.linkActivated();
}
}
NotifActions {
Layout.fillWidth: true
notif: root.modelData
buttonColour: root.buttonColour
}
}
component WrappedLoader: Loader {
id: comp
required property bool shouldBeActive
active: false
opacity: 0
// Makes the loader load on the same frame shouldBeActive becomes true, which ensures size is set
states: State {
name: "active"
when: comp.shouldBeActive
PropertyChanges {
comp.opacity: 1
comp.active: true
}
}
transitions: [
Transition {
from: ""
to: "active"
SequentialAnimation {
PropertyAction {
property: "active"
}
Anim {
type: Anim.DefaultEffects
property: "opacity"
}
}
},
Transition {
from: "active"
to: ""
SequentialAnimation {
Anim {
type: Anim.DefaultEffects
property: "opacity"
}
PropertyAction {
property: "active"
}
}
}
]
}
}
-70
View File
@@ -1,70 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import ZShell.Config
import qs.Components
MouseArea {
id: root
required property bool expanded
property bool expandable: true
property bool dragExpand: expandable
property bool hoveringLink
property int startY
property real lastX
property real lastMoveTime
property real velocity
signal swiped
signal dismissed
signal requestExpand(expand: bool)
hoverEnabled: true
cursorShape: hoveringLink ? Qt.PointingHandCursor : pressedButtons === Qt.LeftButton ? Qt.ClosedHandCursor : undefined
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
preventStealing: dragExpand
drag.target: pressedButtons === Qt.LeftButton ? this : null
drag.axis: Drag.XAxis
onPressed: event => {
startY = event.y;
lastX = x;
lastMoveTime = Date.now();
velocity = 0;
if (event.button === Qt.RightButton && expandable)
requestExpand(!expanded);
else if (event.button === Qt.MiddleButton)
dismissed();
}
onPositionChanged: event => {
if (pressedButtons !== Qt.LeftButton)
return;
const now = Date.now();
const dt = now - lastMoveTime;
if (dt > 0) {
velocity = (velocity + (x - lastX) / dt * 1000) / 2;
lastX = x;
lastMoveTime = now;
}
if (dragExpand) {
const diffY = event.y - startY;
if (Math.abs(diffY) > Config.notifs.expandThreshold)
requestExpand(diffY > 0);
}
}
onReleased: {
const flung = Date.now() - lastMoveTime < 100 && Math.sign(velocity) === Math.sign(x) && Math.abs(velocity) > Config.notifs.clearVelocity;
if (!flung && Math.abs(x) < width * Config.notifs.clearThreshold)
x = 0;
else
swiped();
}
Behavior on x {
Anim {}
}
}
-40
View File
@@ -1,40 +0,0 @@
import QtQuick
import Quickshell
PersistentProperties {
required property ShellScreen modelData
reloadableId: "screenState"
// Drawer visibilities
property bool bar
property bool clipboard
property bool dashboard
property bool dock
property bool isDrawing
property bool launcher
property bool osd
property bool resources
property bool settings
property bool sidebar
// Dashboard state
property int dashboardTab: 0
property date dashboardDate: new Date()
// Sidebar state
property int sidebarTab: 0
property list<string> expandedNotifs: []
// Utilities state
property string recordingConfirmDelete
property bool recordingListExpanded
property string recordingMode
// Drawing state
property color penColor: "white"
property int penWidth: 4
// Screenshot state
property bool screenshot
}
+1 -1
View File
@@ -62,7 +62,7 @@ TextFieldBase {
anchors.leftMargin: Tokens.padding.large
anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
font.pointSize: Tokens.font.size.large
text: "search"
}
+7 -5
View File
@@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import ZShell.Config
import qs.Services
@@ -54,7 +53,7 @@ Row {
onClicked: root.active?.clicked()
}
SnapLayout {
RowLayout {
id: textRow
anchors.centerIn: parent
@@ -103,7 +102,8 @@ Row {
topLeftRadius: rad
Behavior on rad {
Anim {}
Anim {
}
}
StateLayer {
@@ -127,10 +127,12 @@ Row {
text: "expand_more"
Behavior on anchors.horizontalCenterOffset {
Anim {}
Anim {
}
}
Behavior on rotation {
Anim {}
Anim {
}
}
}
}
+4 -3
View File
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Shapes
import ZShell
import ZShell.Config
import qs.Helpers
import qs.Services
MouseArea {
@@ -51,7 +52,7 @@ MouseArea {
anchors.fill: parent
cursorShape: !enabled ? undefined : Qt.PointingHandCursor
enabled: parent.enabled && !ShellState.forActive()?.isDrawing
enabled: parent.enabled && !Visibilities.getForActive()?.isDrawing
hoverEnabled: true
Behavior on stateOpacity {
@@ -98,13 +99,13 @@ MouseArea {
id: base
anchors.fill: parent
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
color: Colors.palette.m3onSurface
opacity: root.stateOpacity
// Pick up radius from parent if it has one (parent can be anything with radius props)
// qmllint disable missing-property
radius: root.parent?.radius ?? 0
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
// qmllint enable missing-property
+1 -1
View File
@@ -8,7 +8,7 @@ TextArea {
color: Colors.palette.m3onSurface
cursorVisible: !readOnly
font: Tokens.font.body.small
font.pointSize: Tokens.font.size.small
implicitHeight: contentHeight + topPadding + bottomPadding
implicitWidth: contentWidth + leftPadding + rightPadding
placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
+8 -4
View File
@@ -11,18 +11,22 @@ TextEdit {
color: Colors.palette.m3onSurface
cursorVisible: !readOnly
font: Tokens.font.body.small
font.pointSize: Tokens.font.size.small
font.family: Config.appearance.font.family.sans
renderType: TextField.NativeRendering
selectedTextColor: color
selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4)
verticalAlignment: TextInput.AlignVCenter
Behavior on color {
CAnim {}
CAnim {
}
}
cursorDelegate: Item {
}
cursorDelegate: Item {}
Behavior on selectionColor {
CAnim {}
CAnim {
}
}
CustomRect {
+8 -5
View File
@@ -8,21 +8,24 @@ TextField {
color: Colors.palette.m3onSurface
cursorVisible: !readOnly
font: Tokens.font.body.small
font.pointSize: Tokens.font.size.small
implicitHeight: contentHeight + topPadding + bottomPadding
implicitWidth: contentWidth + leftPadding + rightPadding
placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
renderType: TextField.QtRendering
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
selectedTextColor: color
selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4)
verticalAlignment: TextInput.AlignVCenter
Behavior on color {
CAnim {}
CAnim {
}
}
cursorDelegate: Item {
}
cursorDelegate: Item {}
Behavior on selectionColor {
CAnim {}
CAnim {
}
}
CustomRect {
+6 -8
View File
@@ -1,5 +1,4 @@
import ZShell
import ZShell.Components
import QtQuick
import QtQuick.Layouts
import qs.Components
@@ -37,7 +36,8 @@ CustomRect {
radius: Tokens.rounding.medium
Behavior on border.color {
CAnim {}
CAnim {
}
}
Elevation {
@@ -48,7 +48,7 @@ CustomRect {
z: -1
}
SnapLayout {
RowLayout {
id: layout
anchors.fill: parent
@@ -84,16 +84,14 @@ CustomRect {
return Colors.palette.m3onError;
return Colors.palette.m3onSurfaceVariant;
}
fontStyle: Tokens.font.icon.builders.large.scale(1.2).build()
font.pointSize: Math.round(Tokens.font.size.large * 1.2)
text: root.modelData.icon
}
}
SnapLayout {
ColumnLayout {
Layout.fillWidth: true
spacing: 0
orientation: Qt.Vertical
CustomText {
id: title
@@ -109,7 +107,7 @@ CustomRect {
return Colors.palette.m3onSurface;
}
elide: Text.ElideRight
font: Tokens.font.title.small
font.pointSize: Tokens.font.size.normal
text: root.modelData.title
}
+6 -3
View File
@@ -44,7 +44,8 @@ Item {
onValuesChanged: root.flagChanged()
}
ToastWrapper {}
ToastWrapper {
}
}
component ToastWrapper: MouseArea {
@@ -85,10 +86,12 @@ Item {
}
}
Behavior on opacity {
Anim {}
Anim {
}
}
Behavior on scale {
Anim {}
Anim {
}
}
Component.onCompleted: modelData.lock(this)
-3
View File
@@ -1,7 +1,6 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Effects
CustomFlickable {
@@ -10,7 +9,6 @@ CustomFlickable {
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property real fadeAmount: 0.1
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
readonly property real dpr: Math.ceil((QsWindow.window as QsWindow)?.devicePixelRatio ?? 1)
function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts
@@ -30,7 +28,6 @@ CustomFlickable {
type: Anim.SlowEffects
}
}
layer.textureSize: Qt.size(width * dpr, height * dpr)
layer.effect: Mask {
maskSource: mask
+1 -4
View File
@@ -7,7 +7,6 @@ CustomListView {
id: root
property real endFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property bool fadeEnabled: true
property real fadeAmount: 0.1
property real fadeThreshold: 0.0
readonly property bool horizontal: orientation === ListView.Horizontal
@@ -18,9 +17,7 @@ CustomListView {
}
function fadeShouldBeActive(isStart: bool): bool {
if (!fadeEnabled)
return false;
// When content is smaller than flickable size, hide fade when rebound starts.
if (contentSize() + marginStart() + marginEnd() < viewportSize() && rebound.running && ((isStart ? overshootStart() > 0 : overshootStart() < 0))) {
return false;
}
+39 -41
View File
@@ -2,57 +2,55 @@ import QtQuick
import ZShell.Config
import qs.Services
Item {
CustomClippingRect {
id: track
required property var scrollBar
required property var mouseArea
ClipRect {
anchors.bottom: parent.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.top: parent.top
implicitWidth: handle.implicitWidth
radius: Tokens.rounding.full
CustomRect {
id: handle
anchors.right: parent.right
anchors.top: parent.top
radius: Tokens.rounding.full
implicitWidth: handle.implicitWidth
CustomRect {
id: handle
anchors.right: parent.right
color: Colors.palette.m3secondary
implicitHeight: track.scrollBar.height * track.scrollBar.effectiveSize
implicitWidth: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
opacity: {
if (!track.scrollBar.enabled)
return 0;
if (track.scrollBar.size === 1)
return 0;
if (track.mouseArea.pressed)
return 1;
if (track.mouseArea.containsMouse)
return 0.8;
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
return 0.6;
color: Colors.palette.m3secondary
implicitHeight: track.scrollBar.height * track.scrollBar.effectiveSize
implicitWidth: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
opacity: {
if (!track.scrollBar.enabled)
return 0;
}
radius: Tokens.rounding.full
y: track.scrollBar.reversed ? track.scrollBar.height * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.height * track.scrollBar.nonAnimY * track.scrollBar.travelScale
if (track.scrollBar.size === 1)
return 0;
if (track.mouseArea.pressed)
return 1;
if (track.mouseArea.containsMouse)
return 0.8;
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
return 0.6;
return 0;
}
radius: Tokens.rounding.full
y: track.scrollBar.reversed ? track.scrollBar.height * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.height * track.scrollBar.nonAnimY * track.scrollBar.travelScale
Behavior on implicitWidth {
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
Behavior on implicitWidth {
Anim {}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
MouseArea {
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
MouseArea {
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
}
}
+2 -1
View File
@@ -20,7 +20,8 @@ Shape {
strokeWidth: 0
Behavior on fillColor {
CAnim {}
CAnim {
}
}
PathSvg {
+1
View File
@@ -141,6 +141,7 @@ Singleton {
BeatTracker {
id: beatTracker
}
PwObjectTracker {
+2 -1
View File
@@ -295,7 +295,8 @@ Singleton {
Component {
id: apComp
AccessPoint {}
AccessPoint {
}
}
Process {
+5 -2
View File
@@ -1081,13 +1081,15 @@ Singleton {
Component {
id: commandProc
CommandProcess {}
CommandProcess {
}
}
Component {
id: apComp
AccessPoint {}
AccessPoint {
}
}
Timer {
@@ -1308,6 +1310,7 @@ Singleton {
}
stdout: StdioCollector {
id: stdoutCollector
}
onExited: code => {
+261 -18
View File
@@ -4,11 +4,13 @@ pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import Quickshell.Services.Notifications
import ZShell.Services
import Quickshell.Hyprland
import QtQuick
import ZShell
import qs.Components
import qs.Modules
import qs.Helpers
import qs.Paths
import qs.Services
import ZShell.Config
Singleton {
@@ -16,14 +18,14 @@ Singleton {
readonly property var appCooldownMap: new Map()
property alias dnd: props.dnd
readonly property alias groups: store.groups
property alias list: store.rows
readonly property alias popupGroups: store.popupGroups
readonly property list<NotificationRow> notClosed: store.rows.filter(n => !n.closed)
readonly property list<NotificationRow> popups: store.rows.filter(n => n.popup)
property list<Notif> list: []
property bool loaded
readonly property list<Notif> notClosed: list.filter(n => !n.closed)
readonly property list<Notif> popups: list.filter(n => n.popup)
property alias server: server
function shouldThrottle(appName: string): bool {
if (props.dnd || ShellState.anySidebarOpen())
if (props.dnd || [...Visibilities.screens.values()].some(v => v.sidebar))
return false;
const key = (appName || "unknown").trim().toLowerCase();
@@ -43,6 +45,33 @@ Singleton {
return true;
}
onListChanged: {
if (loaded) {
saveTimer.restart();
}
}
Timer {
id: saveTimer
interval: 1000
onTriggered: storage.setText(JSON.stringify(root.notClosed.map(n => ({
time: n.time,
id: n.id,
summary: n.summary,
body: n.body,
appIcon: n.appIcon,
appName: n.appName,
image: n.image,
expireTimeout: n.expireTimeout,
urgency: n.urgency,
resident: n.resident,
hasActionIcons: n.hasActionIcons,
actions: n.actions
}))))
}
PersistentProperties {
id: props
@@ -51,12 +80,6 @@ Singleton {
reloadableId: "notifs"
}
NotificationStore {
id: store
path: `${Paths.state}/notifs.sqlite`
}
NotificationServer {
id: server
@@ -65,12 +88,40 @@ Singleton {
bodyImagesSupported: true
bodyMarkupSupported: true
imageSupported: true
keepOnReload: true
keepOnReload: false
persistenceSupported: true
onNotification: notif => {
notif.tracked = true;
store.insert(notif, root.shouldThrottle(notif.appName));
const is_popup = root.shouldThrottle(notif.appName);
const comp = notifComp.createObject(root, {
popup: is_popup,
notification: notif
});
root.list = [comp, ...root.list];
}
}
FileView {
id: storage
path: `${Paths.state}/notifs.json`
printErrors: false
onLoadFailed: err => {
if (err === FileViewError.FileNotFound) {
root.loaded = true;
Qt.callLater(() => setText("[]"));
}
}
onLoaded: {
const data = JSON.parse(text());
for (const notif of data)
root.list.push(notifComp.createObject(root, notif));
root.list.sort((a, b) => b.time - a.time);
root.loaded = true;
}
}
@@ -78,12 +129,16 @@ Singleton {
description: "Clear all notifications"
name: "clearnotifs"
onPressed: store.clear()
onPressed: {
for (const notif of root.list.slice())
notif.close();
}
}
IpcHandler {
function clear(): void {
store.clear();
for (const notif of root.list.slice())
notif.close();
}
function disableDnd(): void {
@@ -104,4 +159,192 @@ Singleton {
target: "notifs"
}
Component {
id: notifComp
Notif {
}
}
component Notif: QtObject {
id: notif
property list<var> actions
property string appIcon
property string appName
property string body
property string cachedImageSource: ""
property bool cachingImage: false
property bool closed
readonly property Connections conn: Connections {
function onActionsChanged(): void {
notif.actions = notif.notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
function onAppIconChanged(): void {
notif.appIcon = notif.notification.appIcon;
}
function onAppNameChanged(): void {
notif.appName = notif.notification.appName;
}
function onBodyChanged(): void {
notif.body = notif.notification.body;
}
function onClosed(): void {
notif.close();
}
function onExpireTimeoutChanged(): void {
notif.expireTimeout = notif.notification.expireTimeout;
}
function onHasActionIconsChanged(): void {
notif.hasActionIcons = notif.notification.hasActionIcons;
}
function onHintsChanged(): void {
notif.hints = notif.notification.hints;
}
function onImageChanged(): void {
notif.imageSource = notif.notification.image || "";
notif.image = notif.imageSource;
notif.cacheImageIfNeeded();
}
function onResidentChanged(): void {
notif.resident = notif.notification.resident;
}
function onSummaryChanged(): void {
notif.summary = notif.notification.summary;
}
function onUrgencyChanged(): void {
notif.urgency = notif.notification.urgency;
}
target: notif.notification
}
property real expireTimeout: 5
property bool hasActionIcons
property var hints
property string image
property string imageSource
property var locks: new Set()
property string notifId
property Notification notification
property bool popup
property bool resident
property string summary
property date time: new Date()
readonly property string timeStr: {
const diff = Time.date.getTime() - time.getTime();
const m = Math.floor(diff / 60000);
if (m < 1)
return qsTr("now");
const h = Math.floor(m / 60);
const d = Math.floor(h / 24);
if (d > 0)
return `${d}d`;
if (h > 0)
return `${h}h`;
return `${m}m`;
}
readonly property Timer timer: Timer {
property bool paused: false
property int remainingTime: totalTime
property int totalTime: Config.notifs.defaultExpireTimeout
interval: 50
repeat: true
running: !paused
onTriggered: {
remainingTime -= interval;
if (remainingTime <= 0) {
remainingTime = 0;
notif.popup = false;
stop();
}
}
}
property int urgency: NotificationUrgency.Normal
function cacheImageIfNeeded(): void {
const source = imageSource;
if (!source || cachingImage)
return;
if (cachedImageSource === source)
return;
cachingImage = true;
ZShellIo.cacheImage(Qt.resolvedUrl(source), Paths.notifimagecache, (path, url) => {
cachedImageSource = source;
image = path;
cachingImage = false;
}, () => {
cachingImage = false;
});
}
function close(): void {
closed = true;
if (locks.size === 0 && root.list.includes(this)) {
root.list = root.list.filter(n => n !== this);
notification?.dismiss();
destroy();
}
}
function lock(item: Item): void {
locks.add(item);
}
function unlock(item: Item): void {
locks.delete(item);
if (closed)
close();
}
Component.onCompleted: {
if (!notification)
return;
notifId = notification.id;
summary = notification.summary;
body = notification.body;
appIcon = notification.appIcon;
appName = notification.appName;
imageSource = notification.image || "";
image = imageSource;
hints = notification.hints;
expireTimeout = notification.expireTimeout;
urgency = notification.urgency;
resident = notification.resident;
hasActionIcons = notification.hasActionIcons;
actions = notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
cacheImageIfNeeded();
}
}
}
+9 -11
View File
@@ -3,30 +3,28 @@ pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import ZShell.Internal
import qs.Components
import qs.Services
Item {
id: root
readonly property alias content: contentLoader.item
readonly property PersistentProperties drawingState: PersistentProperties {
property color penColor: "white"
property int penWidth: 4
required property ScreenState screenState
property rect holeRect
property real holeRadius: 0
reloadableId: "drawingState"
}
required property PersistentProperties visibilities
Loader {
id: contentLoader
active: root.screenState.isDrawing
active: root.visibilities.isDrawing
anchors.fill: parent
sourceComponent: StrokeCanvas {
anchors.fill: parent
hole: root.holeRect
holeRadius: root.holeRadius
penColor: root.screenState.penColor
penWidth: root.screenState.penWidth
penColor: root.drawingState.penColor
penWidth: root.drawingState.penWidth
}
}
}
-95
View File
@@ -1,95 +0,0 @@
import Quickshell
import QtQuick
import ZShell.Config
import qs.Components
CustomMouseArea {
id: root
required property real borderThickness
required property Drawing drawing
required property Panels panels
required property ScreenState screenState
required property EdgeGeometry geometry
function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
}
function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelY = panel.y + geometry.insetTop(borderThickness);
return y >= panelY && y <= panelY + panel.height;
}
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.BlankCursor
enabled: z > 0
hoverEnabled: true
focus: root.screenState.isDrawing
z: 2
Binding {
target: root.drawing.content
property: "hoverPoint"
value: Qt.point(root.mouseX, root.mouseY)
}
Keys.onPressed: e => {
const mod = e.modifiers;
const key = e.key;
if ((key == Qt.Key_Z) && (mod & Qt.ControlModifier) && (mod & Qt.ShiftModifier)) {
root.drawing.content.redo();
e.accepted = true;
} else if ((key == Qt.Key_Z) && (mod & Qt.ControlModifier)) {
root.drawing.content.undoLastGroup();
e.accepted = true;
}
}
onPositionChanged: event => {
const x = event.x;
const y = event.y;
if (!drawing.content.hoverVisible)
drawing.content.showHover();
if (event.buttons & Qt.LeftButton) {
drawing.content.appendPoint(x, y);
return;
}
if (!(event.buttons & Qt.LeftButton) && inLeftPanel(panels.drawing, x, y)) {
z = -2;
panels.drawing.expand();
drawing.content.hideHover();
}
}
onPressed: event => {
const x = event.x;
const y = event.y;
panels.drawing.collapse();
if (event.buttons & Qt.LeftButton) {
panels.drawing.collapse();
drawing.content.beginStroke(x, y);
return;
}
if (event.button & Qt.RightButton) {
drawing.content.clear();
return;
}
}
onReleased: event => {
const x = event.x;
const y = event.y;
if (event.button & Qt.LeftButton) {
drawing.content.endStroke();
drawing.content.showHover();
}
}
}
+9 -1
View File
@@ -43,6 +43,13 @@ Scope {
hasBar: root.geometry.barOnBottom
}
Timer {
interval: 5000
running: true
onTriggered: console.log("top height:", top.exclusiveZone, "left width:", left.exclusiveZone, "right width:", right.exclusiveZone, "bottom height:", bottom.exclusiveZone)
}
component ExclusionZone: CustomWindow {
property bool hasBar
@@ -52,6 +59,7 @@ Scope {
name: "Bar-Exclusion"
screen: root.screen
mask: Region {}
mask: Region {
}
}
}
+199 -146
View File
@@ -1,33 +1,25 @@
import Quickshell
import QtQuick
import ZShell.Config
import ZShell.Internal
import qs.Components
import ZShell.Config
import qs.Helpers
import qs.Modules.Bar.Popouts
CustomMouseArea {
Item {
id: root
required property Drawing drawing
required property DrawingInput input
required property Item bar
required property real borderThickness
property bool dashboardShortcutActive
required property Drawing drawing
required property EdgeGeometry geometry
property bool osdShortcutActive
required property Panels panels
required property Wrapper popouts
required property ShellScreen screen
required property ScreenState screenState
property bool dashboardShortcutActive
property point dragStart
property bool osdShortcutActive
property bool setInitialPoint: false
property bool singleGestureTriggered: false
property bool utilitiesShortcutActive
required property PersistentProperties visibilities
function inBottomPanel(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
@@ -64,116 +56,110 @@ CustomMouseArea {
return x >= panelX && x <= panelX + panel.width;
}
acceptedButtons: bar.fullscreen ? Qt.NoButton : Qt.AllButtons
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: {
if (!containsMouse) {
if (!osdShortcutActive) {
screenState.osd = false;
panels.osd.hovered = false;
DragHandler {
id: singleHandler
cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
dragThreshold: 0
enabled: !root.visibilities.isDrawing
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything
maximumPointCount: 1
minimumPointCount: 1
target: null
onActiveChanged: {
if (!active)
root.singleGestureTriggered = false;
}
onCentroidChanged: {
const x = centroid.position.x;
const y = centroid.position.y;
const dragX = x - centroid.pressPosition.x;
const dragY = y - centroid.pressPosition.y;
if (centroid.pressPosition.y >= root.screen.height - Config.bar.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold)
root.visibilities.launcher = true;
if (root.singleGestureTriggered)
return;
if (root.geometry.insetTop(root.borderThickness) > centroid.pressPosition.y) {
if (dragY > 20) {
root.visibilities.settings = true;
root.singleGestureTriggered = true;
} else if (dragY < -20) {
root.visibilities.settings = false;
root.singleGestureTriggered = true;
}
}
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
popouts.hasCurrent = false;
if (centroid.pressPosition.y > root.screen.height - root.geometry.insetBottom(root.borderThickness) && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50)
root.visibilities.clipboard = true;
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.geometry.insetTop(root.borderThickness) && centroid.pressPosition.x > root.screen.width / 5 && !root.visibilities.launcher)
if (dragY < -10) {
root.visibilities.dock = true;
root.singleGestureTriggered = true;
}
if (centroid.pressPosition.x > root.screen.width - Config.bar.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) {
root.visibilities.sidebar = true;
root.singleGestureTriggered = true;
}
if (Config.bar.autoHide || bar.fullscreen)
bar.isHovered = false;
if (centroid.pressPosition.x >= root.screen.width - Config.bar.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
root.singleGestureTriggered = true;
}
if (screenState.dashboard && Config.dashboard.showOnHover)
screenState.dashboard = false;
if (centroid.pressPosition.x <= Config.bar.border && dragX > 20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
root.singleGestureTriggered = true;
}
}
}
onPositionChanged: event => {
const x = event.x;
const y = event.y;
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
if (screenState.isDrawing && !inLeftPanel(panels.drawing, x, y)) {
input.z = 2;
panels.drawing.collapse();
}
PointHandler {
id: drawingHandler
if (root.bar.fullscreen && !root.bar.isHovered)
unhideTimer.start();
property bool setInitialPoint: false
if (pressed && dragStart.y >= screen.height - Config.bar.border && dragStart.x > screen.width / 5 && dragY < -Config.launcher.dragThreshold)
screenState.launcher = true;
acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: root.visibilities.isDrawing && (!root.inLeftPanel(root.panels.drawing, hoverHandler.point.position.x, hoverHandler.point.position.y) || !root.panels.drawing.expanded)
const entryAt = bar.entryAt(geometry.axisPos(x, y));
const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboardWrapper, x, y) && (!entryAt || entryAt === "activeWindow");
if (!dashboardShortcutActive) {
screenState.dashboard = showDashboard;
} else if (showDashboard) {
dashboardShortcutActive = false;
}
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && withinPanelWidth(panels.dashboardWrapper, x, y)) {
if (dragY > Config.dashboard.dragThreshold)
screenState.dashboard = true;
else if (dragY < -Config.dashboard.dragThreshold)
screenState.dashboard = false;
}
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && !inTopPanel(panels.dashboardWrapper, dragStart.x, dragStart.y)) {
if (dragY > 20) {
screenState.settings = true;
} else if (dragY < -20) {
screenState.settings = false;
onActiveChanged: {
if (!active) {
setInitialPoint = false;
root.drawing.content.endStroke();
} else {
root.panels.drawing.collapse();
}
}
onPointChanged: {
if (!active)
return;
const x = point.position.x;
const y = point.position.y;
const origX = point.pressPosition.x;
const origY = point.pressPosition.y;
if (pressed && dragStart.y > screen.height - geometry.insetBottom(borderThickness) && dragStart.x < screen.width / 5 && dragY < -50)
screenState.clipboard = true;
if (pressed && !Config.dock.hoverToReveal && dragStart.y > screen.height - geometry.insetTop(borderThickness) && dragStart.x > screen.width / 5 && !screenState.launcher)
if (dragY < -10)
screenState.dock = true;
if (pressed && dragStart.x > screen.width - Config.bar.border && dragStart.y < (screen.height / 2) && dragX < -Config.sidebar.dragThreshold)
screenState.sidebar = true;
if (pressed && dragStart.x >= screen.width - Config.bar.border && dragStart.y > (screen.height / 2) && dragX < -20)
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
if (pressed && dragStart.x <= Config.bar.border && dragX > 20)
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
if (!screenState.bar && Config.bar.autoHide && geometry.barContains(x, y, true))
bar.isHovered = true;
if (panels.sidebar.offsetScale === 1) {
const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) {
osdShortcutActive = false;
panels.osd.hovered = true;
if (point.pressedButtons & Qt.RightButton) {
root.drawing.content.clear();
return;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width * (1 - panels.sidebar.offsetScale);
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (x === 0 && y === 0 && origX === 0 && origY === 0)
return;
if (!osdShortcutActive) {
screenState.osd = showOsd;
panels.osd.hovered = showOsd;
} else if (showOsd) {
osdShortcutActive = false;
panels.osd.hovered = true;
if (!setInitialPoint) {
setInitialPoint = true;
root.drawing.content.beginStroke(origX, origY);
return;
}
root.drawing.content.appendPoint(x, y);
}
if (Config.dock.enable && Config.dock.hoverToReveal && !screenState.dock && !screenState.launcher && inBottomPanel(panels.dock, x, y))
screenState.dock = true;
if (geometry.barContains(x, y))
bar.checkPopout(geometry.axisPos(x, y));
}
onPressed: event => {
dragStart = Qt.point(event.x, event.y);
}
Timer {
@@ -184,39 +170,105 @@ CustomMouseArea {
running: false
onTriggered: {
if (root.containsMouse)
if (hoverHandler.hovered)
root.bar.isHovered = true;
}
}
HoverHandler {
id: hoverHandler
cursorShape: root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, point.position.x, point.position.y) ? Qt.BlankCursor : undefined
onHoveredChanged: {
if (!hovered) {
if (!root.osdShortcutActive) {
root.visibilities.osd = false;
root.panels.osd.hovered = false;
}
if (!root.popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
root.popouts.hasCurrent = false;
}
if (Config.bar.autoHide || root.bar.fullscreen)
root.bar.isHovered = false;
}
}
onPointChanged: {
const x = point.position.x;
const y = point.position.y;
if (root.visibilities.isDrawing) {
if (root.inLeftPanel(root.panels.drawing, x, y) && !(drawingHandler.point.pressedButtons & Qt.LeftButton)) {
root.panels.drawing.expand();
root.drawing.content.hideHover();
return;
}
root.drawing.content.showHover(x, y);
return;
}
if (!root.visibilities.bar && Config.bar.autoHide && root.geometry.barContains(x, y, true))
root.bar.isHovered = true;
if (root.bar.fullscreen && !root.bar.isHovered)
unhideTimer.start();
if (root.panels.sidebar.offsetScale === 1) {
const showOsd = root.inRightPanel(root.panels.osdWrapper, x, y);
if (showOsd) {
root.osdShortcutActive = false;
root.panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < root.width - root.panels.sidebar.width * (1 - root.panels.sidebar.offsetScale);
const showOsd = outOfSidebar && root.inRightPanel(root.panels.osdWrapper, x, y);
if (!root.osdShortcutActive) {
root.visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
root.osdShortcutActive = false;
root.panels.osd.hovered = true;
}
}
if (Config.dock.enable && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y))
root.visibilities.dock = true;
if (root.geometry.barContains(x, y))
root.bar.checkPopout(root.geometry.axisPos(x, y));
}
}
Connections {
function onDashboardChanged(): void {
if (root.screenState.dashboard) {
const inDashboardArea = root.inTopPanel(root.panels.dashboardWrapper, root.mouseX, root.mouseY);
function onDashboardChanged() {
if (root.visibilities.dashboard) {
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
if (!inDashboardArea) {
root.dashboardShortcutActive = true;
}
if (root.panels.launcher.x + root.panels.launcher.width > root.panels.dashboardWrapper.x)
root.visibilities.launcher = false;
root.screenState.launcher = false;
root.screenState.settings = false;
root.screenState.sidebar = false;
root.screenState.resources = false;
root.visibilities.settings = false;
root.visibilities.sidebar = false;
root.popouts.hasCurrent = false;
} else {
root.dashboardShortcutActive = false;
}
}
function onIsDrawingChanged(): void {
if (root.screenState.isDrawing) {
root.drawing.content.showHover();
} else {
root.drawing.content.clear();
}
function onIsDrawingChanged() {
if (!root.visibilities.isDrawing)
root.drawing.clear();
}
function onLauncherChanged(): void {
if (!root.screenState.launcher) {
function onLauncherChanged() {
if (!root.visibilities.launcher) {
root.dashboardShortcutActive = false;
root.osdShortcutActive = false;
root.utilitiesShortcutActive = false;
@@ -224,20 +276,23 @@ CustomMouseArea {
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) {
root.screenState.osd = false;
root.visibilities.osd = false;
root.panels.osd.hovered = false;
}
}
if (root.screenState.launcher) {
root.screenState.dashboard = false;
root.screenState.dock = false;
root.screenState.settings = false;
if (root.visibilities.launcher) {
if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) {
root.visibilities.dashboard = false;
}
root.visibilities.dock = false;
root.visibilities.settings = false;
}
}
function onOsdChanged(): void {
if (root.screenState.osd) {
function onOsdChanged() {
if (root.visibilities.osd) {
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) {
root.osdShortcutActive = true;
@@ -247,35 +302,33 @@ CustomMouseArea {
}
}
function onResourcesChanged(): void {
if (root.screenState.resources) {
if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
root.popouts.hasCurrent = false;
root.screenState.settings = false;
root.screenState.dashboard = false;
function onResourcesChanged() {
if (root.visibilities.resources && (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))) {
root.popouts.hasCurrent = false;
}
if (root.visibilities.resources)
root.visibilities.settings = false;
}
function onSettingsChanged(): void {
if (root.screenState.settings) {
root.screenState.resources = false;
root.screenState.dashboard = false;
root.screenState.sidebar = false;
function onSettingsChanged() {
if (root.visibilities.settings) {
root.visibilities.resources = false;
root.visibilities.dashboard = false;
root.visibilities.sidebar = false;
root.panels.popouts.hasCurrent = false;
root.screenState.launcher = false;
root.visibilities.launcher = false;
}
}
function onSidebarChanged(): void {
if (root.screenState.sidebar) {
if (root.panels.dashboardWrapper.x + root.panels.dashboardWrapper.width > root.panels.sidebar.x)
root.screenState.dashboard = false;
root.screenState.settings = false;
function onSidebarChanged() {
if (root.visibilities.sidebar) {
root.visibilities.dashboard = false;
root.visibilities.settings = false;
root.popouts.hasCurrent = false;
}
}
target: root.screenState
target: root.visibilities
}
}
+35 -44
View File
@@ -1,12 +1,10 @@
import Quickshell
import QtQuick
import ZShell.Config
import ZShell
import qs.Components
import qs.Modules.Bar.Popouts as Popouts
import qs.Modules.Notifications as Notifications
import qs.Modules.Sidebar as Sidebar
import qs.Modules.Sidebar.Utils as Utils
import qs.Modules.Notifications.Sidebar as Sidebar
import qs.Modules.Notifications.Sidebar.Utils as Utils
import qs.Modules.Dashboard as Dashboard
import qs.Modules.Osd as Osd
import qs.Components.Toast as Toasts
@@ -16,15 +14,20 @@ import qs.Modules.Settings as Settings
import qs.Modules.Drawing as Drawing
import qs.Modules.Dock as Dock
import qs.Modules.Clipboard as Clipboard
import ZShell.Config
Item {
id: root
required property Item bar
required property real borderThickness
readonly property alias clipboard: clipboard
readonly property alias dashboard: dashboard
readonly property alias dashboardWrapper: dashboardWrapper
readonly property alias dock: dock
readonly property alias drawing: drawing
required property var drawingItem
required property EdgeGeometry geometry
readonly property alias launcher: launcher
readonly property alias notifications: notifications
readonly property alias osd: osd
@@ -33,18 +36,13 @@ Item {
readonly property alias popoutsWrapper: popouts
readonly property alias resources: resources
readonly property alias resourcesWrapper: resourcesWrapper
required property ShellScreen screen
readonly property alias settings: settings
readonly property alias settingsWrapper: settingsWrapper
readonly property alias sidebar: sidebar
readonly property alias toasts: toasts
readonly property alias utilities: utilities
required property Item bar
required property real borderThickness
required property var drawingItem
required property EdgeGeometry geometry
required property ShellScreen screen
required property ScreenState screenState
required property PersistentProperties visibilities
anchors.bottomMargin: geometry.insetBottom(borderThickness)
anchors.fill: parent
@@ -66,7 +64,7 @@ Item {
id: resources
position: root.geometry.position
screenState: root.screenState
visibilities: root.visibilities
}
}
@@ -74,11 +72,8 @@ Item {
id: osdWrapper
anchors.right: parent.right
anchors.rightMargin: (sidebar.width - 5) * (1 - sidebar.offsetScale)
y: ZUtils.snap((parent.height - implicitHeight) / 2, root)
// anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: sidebar.width * (1 - sidebar.offsetScale)
anchors.verticalCenter: parent.verticalCenter
clip: sidebar.visible
implicitHeight: osd.implicitHeight
implicitWidth: osd.implicitWidth * (1 - osd.offsetScale)
@@ -87,10 +82,10 @@ Item {
id: osd
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
screen: root.screen
screenState: root.screenState
sidebarOrSessionVisible: sidebar.visible
visibilities: root.visibilities
}
}
@@ -101,7 +96,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
drawing: root.drawingItem
screen: root.screen
screenState: root.screenState
visibilities: root.visibilities
}
Popouts.ClipWrapper {
@@ -126,20 +121,18 @@ Item {
anchors.right: parent.right
anchors.top: parent.top
panels: root
screenState: root.screenState
sidebarPanel: sidebar
topPadding: Math.max(root.anchors.topMargin, root.anchors.bottomMargin)
visibilities: root.visibilities
}
Launcher.Wrapper {
id: launcher
x: ZUtils.snap((parent.width - implicitWidth) / 2, root)
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
panels: root
screen: root.screen
screenState: root.screenState
visibilities: root.visibilities
}
Utils.Wrapper {
@@ -147,8 +140,8 @@ Item {
anchors.bottom: parent.bottom
anchors.right: parent.right
screenState: root.screenState
sidebar: sidebar
visibilities: root.visibilities
}
Item {
@@ -156,13 +149,13 @@ Item {
property real offsetScale: dashboard.shouldBeActive ? 0 : 1
anchors.top: parent.top
x: ZUtils.snap((parent.width - implicitWidth) / 2, dashboardWrapper)
clip: offsetScale > 0
implicitHeight: dashboard.implicitHeight * (1 - offsetScale)
implicitWidth: dashboard.implicitWidth
anchors.bottom: root.geometry.barOnLeft || root.geometry.barOnBottom ? parent.bottom : undefined
anchors.left: root.geometry.barOnLeft ? parent.left : undefined
anchors.right: root.geometry.barOnLeft ? undefined : parent.right
anchors.top: root.geometry.barOnBottom || root.geometry.barOnLeft ? undefined : parent.top
clip: true
implicitHeight: root.geometry.horizontal ? dashboard.implicitHeight * (1 - offsetScale) : dashboard.implicitHeight
implicitWidth: root.geometry.horizontal ? dashboard.implicitWidth : dashboard.implicitWidth * (1 - offsetScale)
Behavior on offsetScale {
Anim {
@@ -175,7 +168,8 @@ Item {
id: dashboard
offsetScale: dashboardWrapper.offsetScale
screenState: root.screenState
position: root.geometry.position
visibilities: root.visibilities
}
}
@@ -184,9 +178,9 @@ Item {
anchors.bottom: utilities.top
anchors.right: parent.right
anchors.top: parent.top
anchors.top: notifications.bottom
panels: root
screenState: root.screenState
visibilities: root.visibilities
}
Item {
@@ -198,14 +192,11 @@ Item {
Settings.Wrapper {
id: settings
x: ZUtils.snap((parent.width - implicitWidth) / 2, settings)
y: ZUtils.snap((parent.height - implicitHeight) / 2 - (implicitHeight + 5 + (parent.height - implicitHeight) / 2) * offsetScale, settings)
// anchors.centerIn: parent
// anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale
anchors.centerIn: parent
anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale
panels: root
screen: root.screen
screenState: root.screenState
visibilities: root.visibilities
}
}
@@ -216,7 +207,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
panels: root
screen: root.screen
screenState: root.screenState
visibilities: root.visibilities
}
Clipboard.Wrapper {
@@ -225,6 +216,6 @@ Item {
anchors.bottom: parent.bottom
anchors.left: parent.left
screen: root.screen
screenState: root.screenState
visibilities: root.visibilities
}
}
+96 -111
View File
@@ -2,14 +2,16 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import ZShell.Blobs
import ZShell.Config
import qs.Daemons
import qs.Components
import qs.Modules.Bar
import ZShell.Config
import qs.Helpers
import qs.Drawers
import qs.Services
@@ -48,16 +50,16 @@ CustomWindow {
readonly property alias menuRegion: regions.menuPopoutRegion
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
property var root: Quickshell.shellDir
readonly property ScreenState screenState: ShellState.forScreen(screen)
readonly property real sdfBorderOffset: 2 * fsTransitionProg
readonly property real shadowOpacity: 0.5 * (1 - (bar.isHovered ? 0 : fsTransitionProg))
readonly property real shadowOpacity: 0.7 * (1 - (bar.isHovered ? 0 : fsTransitionProg))
property color surfaceColor: Colors.tPalette.m3surface
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: screenState.screenshot ? WlrKeyboardFocus.Exclusive : screenState.launcher || screenState.settings || screenState.sidebar || screenState.isDrawing ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.settings || visibilities.sidebar ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
color: "transparent"
mask: screenState.isDrawing || screenState.screenshot ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions)
contentItem.focus: true
mask: visibilities.isDrawing ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions)
name: "Bar"
Behavior on fsTransitionProg {
@@ -69,26 +71,26 @@ CustomWindow {
contentItem.Keys.onEscapePressed: {
if (Config.bar.autoHide)
screenState.bar = false;
screenState.launcher = false;
screenState.sidebar = false;
screenState.dashboard = false;
screenState.osd = false;
screenState.settings = false;
screenState.resources = false;
screenState.dock = false;
screenState.clipboard = false;
visibilities.bar = false;
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
visibilities.dock = false;
visibilities.clipboard = false;
panels.popouts.hasCurrent = false;
}
onHasFullscreenChanged: {
screenState.launcher = false;
screenState.sidebar = false;
screenState.dashboard = false;
screenState.osd = false;
screenState.settings = false;
screenState.resources = false;
screenState.clipboard = false;
screenState.dock = false;
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
visibilities.clipboard = false;
visibilities.dock = false;
panels.popouts.hasCurrent = false;
}
@@ -140,41 +142,44 @@ CustomWindow {
HyprlandFocusGrab {
id: focusGrab
active: {
const s = root.screenState;
const c = Config;
if ((s.launcher && c.launcher.enabled) || (s.sidebar && c.sidebar.enabled) || (s.resources && c.dashboard.performance.enabled) || (s.clipboard && c.clipboard.enabled))
return true;
if (s.dashboard && !c.dashboard.showOnHover && c.dashboard.enabled)
return true;
if (!c.dock.hoverToReveal && s.dock && c.dock.enable)
return true;
if (panels.popouts.currentName.startsWith("traymenu") && (panels.popouts.current as StackView)?.depth > 1)
return true;
if (s.settings)
return true;
return false;
}
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!Config.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
windows: [root]
onCleared: {
root.screenState.launcher = false;
root.screenState.sidebar = false;
root.screenState.dashboard = false;
root.screenState.osd = false;
root.screenState.settings = false;
root.screenState.resources = false;
root.screenState.clipboard = false;
root.screenState.dock = false;
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
visibilities.clipboard = false;
visibilities.dock = false;
panels.popouts.hasCurrent = false;
}
}
PersistentProperties {
id: visibilities
property bool bar
property bool clipboard
property bool dashboard
property bool dock
property bool isDrawing
property bool launcher
property bool notif: NotifServer.popups.length > 0
property bool osd
property bool resources
property bool settings
property bool sidebar
Component.onCompleted: Visibilities.load(root.screen, this)
}
Binding {
property: "bar"
target: root.screenState
value: root.screenState.sidebar || root.screenState.dashboard || root.screenState.osd || (!Config.bar.hideWhenNotif && NotifServer.popups.length > 0) || root.screenState.resources || root.screenState.settings || bar.isHovered
target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.bar.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.bar.autoHide
}
@@ -182,19 +187,23 @@ CustomWindow {
id: surface
anchors.fill: parent
layer.enabled: true
opacity: root.surfaceColor.a
BlobGroup2 {
layer.effect: MultiEffect {
blurMax: 32
shadowColor: Qt.alpha(Colors.palette.m3shadow, Math.max(0, root.shadowOpacity))
shadowEnabled: true
}
BlobGroup {
id: blobGroup
anchors.fill: parent
color: root.surfaceColor
shadowColor: Qt.alpha(Colors.palette.m3shadow, Math.max(0, root.shadowOpacity))
shadowRadius: 10
smoothing: Config.bar.smoothing
}
BlobInvertedRect2 {
BlobInvertedRect {
anchors.fill: parent
anchors.margins: -50
borderBottom: geometry.insetBottom(root.borderThickness) - anchors.margins - root.sdfBorderOffset
@@ -244,8 +253,8 @@ CustomWindow {
PanelBg {
id: osdBg
atRestOverride: panels.osd.offsetScale < 0.99
deformAmount: 0.1
implicitHeight: panels.osd.height
implicitWidth: panels.osd.width
panel: panels.osdWrapper
radius: 20
@@ -255,9 +264,8 @@ CustomWindow {
PanelBg {
id: notifsBg
atRestOverride: panel.implicitHeight > 0
panel: panels.notifications
radius: Tokens.rounding.large + Tokens.padding.small
radius: Tokens.rounding.large
}
PanelBg {
@@ -290,22 +298,19 @@ CustomWindow {
PanelBg {
id: resourcesBg
property real extraExtent: 0.2
atRestOverride: panels.resources.offsetScale < 0.99
deformAmount: 0.05
implicitHeight: panels.resources.height * (1 + extraExtent)
implicitWidth: panels.resources.width * (1 + extraExtent)
implicitHeight: panels.resources.height
implicitWidth: panels.resources.width
panel: panels.resourcesWrapper
radius: Tokens.rounding.largeIncreased + Tokens.padding.small
x: panels.resourcesWrapper.x + panels.resources.x + geometry.insetLeft(root.borderThickness) - panels.resources.width * extraExtent
y: panels.resourcesWrapper.y + panels.resources.y + geometry.insetTop(root.borderThickness) - panels.resources.height * extraExtent
x: panels.resourcesWrapper.x + panels.resources.x + geometry.insetLeft(root.borderThickness)
y: panels.resourcesWrapper.y + panels.resources.y + geometry.insetTop(root.borderThickness)
}
PanelBg {
id: settingsBg
deformAmount: 0.01
deformAmount: 0.03
panel: panels.settings
radius: Tokens.rounding.largeIncreased + Tokens.padding.small
}
@@ -329,42 +334,43 @@ CustomWindow {
PanelBg {
id: clipboardBg
property real extraExtent: 0.2
deformAmount: 0.03
implicitHeight: panels.clipboard.height * (1 + extraExtent)
implicitWidth: panels.clipboard.width * (1 + extraExtent)
panel: panels.clipboard
radius: 29
x: panels.clipboard.x + geometry.insetLeft(root.borderThickness) - panels.clipboard.width * extraExtent
y: panels.clipboard.y + geometry.insetTop(root.borderThickness)
}
}
Drawing {
id: drawing
readonly property int extraWidth: drawingBg.radius * 2
anchors.fill: parent
holeRect: Qt.rect(root.borderThickness + panels.drawing.x - extraWidth, panels.drawing.y + bar.implicitHeight, panels.drawing.implicitWidth + extraWidth, panels.drawing.implicitHeight)
holeRadius: drawingBg.radius
screenState: root.screenState
layer.enabled: true
visibilities: visibilities
z: 2
layer.effect: MultiEffect {
maskEnabled: true
maskInverted: true
maskSource: maskSource
}
}
LazyLoader {
id: lazyLoader
Item {
id: maskSource
activeAsync: root.screenState.isDrawing
anchors.fill: parent
layer.enabled: true
visible: false
component: DrawingInput {
borderThickness: root.borderLayoutThickness
drawing: drawing
panels: panels
screenState: root.screenState
geometry: geometry
parent: root.contentItem
CustomRect {
readonly property int extraWidth: radius
color: "white"
implicitHeight: panels.drawing.height
implicitWidth: panels.drawing.width + extraWidth
radius: drawingBg.radius
x: -extraWidth + root.borderThickness + panels.drawing.x
y: panels.drawing.y + bar.implicitHeight
}
}
@@ -380,9 +386,7 @@ CustomWindow {
panels: panels
popouts: panels.popouts
screen: root.screen
screenState: root.screenState
input: lazyLoader.item
z: 1
visibilities: visibilities
Panels {
id: panels
@@ -392,7 +396,7 @@ CustomWindow {
drawingItem: drawing
geometry: geometry
screen: root.screen
screenState: root.screenState
visibilities: visibilities
clipboard.transform: Matrix4x4 {
matrix: clipboardBg.deformMatrix
@@ -435,41 +439,22 @@ CustomWindow {
anchors.bottom: geometry.barOnBottom ? parent.bottom : undefined
anchors.left: parent.left
anchors.top: geometry.barOnBottom ? undefined : parent.top
enabled: !screenState.isDrawing
enabled: !visibilities.isDrawing
fullscreen: root.hasFullscreen
height: geometry.horizontal ? implicitHeight : parent.height
popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper
position: geometry.position
screen: root.screen
screenState: root.screenState
visibilities: visibilities
width: geometry.horizontal ? parent.width : implicitWidth
}
}
LazyLoader {
id: loader
property bool closing: !root.screenState.screenshot
onClosingChanged: if (!closing)
activeAsync = true
Picker {
loader: loader
parent: root.contentItem
screen: root.screen
screenState: root.screenState
z: 4
}
}
component PanelBg: BlobRect2 {
component PanelBg: BlobRect {
property real deformAmount: 0.15
required property Item panel
atRestOverride: panel?.offsetScale < 0.99 ?? false
damping: 28.3
deformScale: (deformAmount * Config.appearance.deform.scale) / 10000
group: blobGroup
implicitHeight: panel.height
+31 -18
View File
@@ -3,12 +3,11 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import ZShell.Components
import qs.Components
import qs.Helpers
import qs.Services
SnapLayout {
ColumnLayout {
id: root
readonly property real centerScale: Math.min(1, screenHeight / 1440)
@@ -19,32 +18,36 @@ SnapLayout {
Layout.fillHeight: true
Layout.fillWidth: false
Layout.preferredWidth: centerWidth
orientation: Qt.Vertical
spacing: Tokens.spacing.largeIncreased * 2
SnapLayout {
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Tokens.spacing.small
CustomText {
Layout.alignment: Qt.AlignVCenter
color: Colors.palette.m3secondary
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
font.bold: true
font.family: Config.appearance.font.family.clock
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
text: Time.hourStr
}
CustomText {
Layout.alignment: Qt.AlignVCenter
color: Colors.palette.m3primary
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
font.bold: true
font.family: Config.appearance.font.family.clock
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
text: ":"
}
CustomText {
Layout.alignment: Qt.AlignVCenter
color: Colors.palette.m3secondary
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
font.bold: true
font.family: Config.appearance.font.family.clock
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
text: Time.minuteStr
}
}
@@ -53,11 +56,13 @@ SnapLayout {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: -Tokens.padding.large * 2
color: Colors.palette.m3tertiary
font: Tokens.font.title.builders.medium.weight(Font.DemiBold).build()
font.bold: true
font.family: Config.appearance.font.family.mono
font.pointSize: Math.floor(Tokens.font.size.extraLarge * root.centerScale)
text: Time.format("dddd, d MMMM yyyy")
}
ClipRect {
CustomClippingRect {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Tokens.spacing.largeIncreased * 2
color: Colors.tPalette.m3surfaceContainer
@@ -68,7 +73,7 @@ SnapLayout {
MaterialIcon {
anchors.centerIn: parent
color: Colors.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.size(root.centerWidth / 4).build()
font.pointSize: Math.floor(root.centerWidth / 4)
text: "person"
}
@@ -83,7 +88,9 @@ SnapLayout {
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.body.large
font.family: Config.appearance.font.family.mono
font.pointSize: Tokens.font.size.normal
font.weight: 600
text: root.greeter.username
visible: text.length > 0
}
@@ -119,7 +126,7 @@ SnapLayout {
}
}
SnapLayout {
RowLayout {
id: input
anchors.fill: parent
@@ -148,7 +155,8 @@ SnapLayout {
}
Behavior on opacity {
Anim {}
Anim {
}
}
}
@@ -183,6 +191,7 @@ SnapLayout {
anchors.centerIn: parent
color: root.greeter.buffer && !root.greeter.launching ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
font.weight: 500
text: root.greeter.launching ? "hourglass_top" : "arrow_forward"
}
}
@@ -195,7 +204,8 @@ SnapLayout {
implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight)
Behavior on implicitHeight {
Anim {}
Anim {
}
}
CustomText {
@@ -235,10 +245,12 @@ SnapLayout {
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
Behavior on opacity {
Anim {}
Anim {
}
}
Behavior on scale {
Anim {}
Anim {
}
}
onMsgChanged: {
@@ -280,7 +292,8 @@ SnapLayout {
anchors.left: parent.left
anchors.right: parent.right
color: isError ? Colors.palette.m3error : Colors.palette.m3primary
font: Tokens.font.mono.medium
font.family: Config.appearance.font.family.mono
font.pointSize: Tokens.font.size.small
horizontalAlignment: Qt.AlignHCenter
opacity: 0
scale: 0.7
+1 -1
View File
@@ -20,7 +20,7 @@ BusyIndicator {
property int animState
property color bgColor: Colors.palette.m3secondaryContainer
property color fgColor: Colors.palette.m3primary
property real implicitSize: Tokens.font.body.medium.pointSize * 3
property real implicitSize: Tokens.font.size.normal * 3
property real internalStrokeWidth: strokeWidth
readonly property alias progress: manager.progress
property real strokeWidth: Tokens.padding.extraSmall * 0.8
+4 -2
View File
@@ -46,7 +46,8 @@ Item {
strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.strokeWidth
Behavior on strokeColor {
CAnim {}
CAnim {
}
}
PathAngleArc {
@@ -83,7 +84,8 @@ Item {
}
}
Behavior on color {
CAnim {}
CAnim {
}
}
Anim on waveProgress {
id: waveProgAnim
-41
View File
@@ -1,41 +0,0 @@
import QtQuick
import ZShell.Components
Item {
id: root
property real radius
property real topLeftRadius: radius
property real topRightRadius: radius
property real bottomRightRadius: radius
property real bottomLeftRadius: radius
property color color: "transparent"
default property alias contentData: contentItem.data
Item {
id: contentItem
anchors.fill: parent
}
ShaderEffectSource {
id: source
hideSource: true
sourceItem: contentItem
}
RoundedMask {
anchors.fill: parent
source: source
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomRightRadius: root.bottomRightRadius
bottomLeftRadius: root.bottomLeftRadius
color: root.color
}
Behavior on color {
CAnim {}
}
}
+2 -1
View File
@@ -7,6 +7,7 @@ ClippingRectangle {
color: "transparent"
Behavior on color {
CAnim {}
CAnim {
}
}
}
-23
View File
@@ -1,36 +1,13 @@
import QtQuick
import qs.Services
Flickable {
id: root
property bool doneFakeFlick
interactive: !ShellState.forActive().isDrawing
maximumFlickVelocity: 3000
rebound: Transition {
onRunningChanged: {
if (!running && !root.doneFakeFlick) {
root.doneFakeFlick = true;
root.flick(1, 1);
root.flick(-1, -1);
Qt.callLater(() => root.cancelFlick());
}
}
Anim {
properties: "x,y"
}
}
Timer {
interval: 10
running: root.doneFakeFlick
onTriggered: {
root.doneFakeFlick = false;
root.returnToBounds();
}
}
}
-23
View File
@@ -1,36 +1,13 @@
import QtQuick
import qs.Services
ListView {
id: root
property bool doneFakeFlick
interactive: !ShellState.forActive()?.isDrawing
maximumFlickVelocity: 3000
rebound: Transition {
// onRunningChanged: {
// if (!running && !root.doneFakeFlick) {
// root.doneFakeFlick = true;
// root.flick(1, 1);
// root.flick(-1, -1);
// Qt.callLater(() => root.cancelFlick());
// }
// }
Anim {
properties: "x,y"
type: Anim.DefaultEffects
}
}
// Timer {
// interval: 10
// running: root.doneFakeFlick
//
// onTriggered: {
// root.doneFakeFlick = false;
// }
// }
}
-1
View File
@@ -4,7 +4,6 @@ MouseArea {
property int scrollAccumulatedY: 0
function onWheel(event: WheelEvent): void {
event.accepted = false;
}
onWheel: event => {
+2 -1
View File
@@ -6,6 +6,7 @@ Rectangle {
color: "transparent"
Behavior on color {
CAnim {}
CAnim {
}
}
}
+11 -11
View File
@@ -14,36 +14,36 @@ Text {
property real animateTo: 1
color: Colors.palette.m3onSurface
font: Tokens.font.body.small
font.family: Config.appearance.font.family.sans
font.pointSize: Tokens.font.size.normal
linkColor: Colors.palette.m3onPrimaryFixedVariant
renderType: Text.QtRendering
renderType: Text.NativeRendering
textFormat: Text.PlainText
Behavior on color {
CAnim {}
CAnim {
}
}
Behavior on text {
enabled: root.animate
SequentialAnimation {
TAnim {
target: root
Anim {
to: root.animateFrom
type: Anim.FastEffects
}
PropertyAction {}
PropertyAction {
}
TAnim {
target: root
Anim {
to: root.animateTo
type: Anim.DefaultEffects
}
}
}
component TAnim: Anim {
component Anim: NumberAnimation {
duration: root.animateDuration / 2
easing.type: Easing.BezierSpline
properties: root.animateProp.split(",").length > 1 ? root.animateProp : ""
property: root.animateProp.split(",").length === 1 ? root.animateProp : ""
target: root
+52 -281
View File
@@ -1,305 +1,76 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Shapes
import QtQuick.Controls
import ZShell.Config
import qs.Services
TextFieldBase {
TextField {
id: root
enum TextFieldType {
Outlined,
Filled
}
background: null
color: Colors.palette.m3onSurface
cursorVisible: !readOnly
font.family: Config.appearance.font.family.sans
font.pointSize: Tokens.font.size.smaller
placeholderTextColor: Colors.palette.m3outline
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius)
readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText
readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
readonly property real smallFontScale: smallFontSize / font.pointSize
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.spacing.extraSmall : 0
readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
property int horizontalPadding: Tokens.padding.large
property int verticalPadding: Tokens.padding.large
property int radius: Tokens.rounding.small
property string supportingText
property string trailingIcon
property int smallFontSize: Tokens.font.label.small.pointSize
property int type: CustomTextField.Outlined
property var validate // Regex or function
property bool emptyIsValid: true
property string errorText
property bool isError
property string leadingIcon
bottomPadding: verticalPadding + supportingTextOffset - filledOffset
leftPadding: horizontalPadding + leadingOffset
rightPadding: horizontalPadding + trailingOffset
topPadding: verticalPadding + filledOffset
background: Loader {
anchors.bottomMargin: root.supportingTextOffset
anchors.fill: parent
sourceComponent: root.type === CustomTextField.Filled ? filledComp : outlineComp
StateLayer {
id: stateLayer
cursorShape: Qt.IBeamCursor
enabled: !root.activeFocus
manualPressOverride: tapHandler.pressed
radius: root.type === CustomTextField.Outlined ? root.clampedRadius : 0
topLeftRadius: root.clampedRadius
topRightRadius: root.clampedRadius
onClicked: root.focus = true
Behavior on color {
CAnim {
}
}
cursorDelegate: CustomRect {
id: cursor
onEditingFinished: {
if (!valid)
isError = true;
}
onPressed: {
if (stateLayer.enabled)
stateLayer.press(stateLayer.mouseX, stateLayer.mouseY);
}
onTextEdited: {
if (isError)
isError = false;
}
property bool disableBlink
Item {
id: contentWrapper
color: Colors.palette.m3primary
implicitWidth: 2
radius: Tokens.rounding.medium
anchors.bottomMargin: root.supportingTextOffset
anchors.fill: parent
CustomText {
id: placeholder
anchors.left: parent.left
anchors.leftMargin: root.leftPadding
anchors.topMargin: Tokens.padding.extraSmall
anchors.verticalCenter: parent.verticalCenter
color: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : root.text ? Colors.palette.m3outline : root.placeholderTextColor)
font.family: root.font.family
font.pointSize: root.font.pointSize
font.variableAxes: root.font.variableAxes
font.weight: root.font.weight
renderType: Text.QtRendering
text: root.placeholderText
states: [
State {
name: "smallOutlined"
when: root.type === CustomTextField.Outlined && (root.activeFocus || root.text)
PropertyChanges {
placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding
placeholder.scale: root.smallFontScale
}
AnchorChanges {
anchors.verticalCenter: contentWrapper.top
target: placeholder
}
},
State {
name: "smallFilled"
when: root.type === CustomTextField.Filled && (root.activeFocus || root.text)
PropertyChanges {
placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding + root.leadingOffset
placeholder.scale: root.smallFontScale
}
AnchorChanges {
anchors.top: contentWrapper.top
anchors.verticalCenter: undefined
target: placeholder
}
}
]
transitions: Transition {
Anim {
properties: "scale,leftMargin"
type: Anim.DefaultEffects
}
AnchorAnim {
duration: Tokens.anim.durations.expressiveDefaultEffects
easing: Tokens.anim.expressiveDefaultEffects
}
Behavior on opacity {
Anim {
duration: Tokens.anim.durations.small
}
}
Loader {
id: leadingIconLoader
active: root.leadingIcon
anchors.left: parent.left
anchors.leftMargin: Tokens.padding.small
anchors.verticalCenter: parent.verticalCenter
sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant
text: root.leadingIcon
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
}
}
Loader {
id: trailingIconLoader
active: root.trailingIcon
anchors.right: parent.right
anchors.rightMargin: Tokens.padding.small
anchors.verticalCenter: parent.verticalCenter
sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant
text: root.trailingIcon
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
}
}
}
Loader {
id: supportingTextLoader
active: root.effectiveSupportingText
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: root.horizontalPadding
sourceComponent: CustomText {
color: root.isError ? Colors.palette.m3error : Colors.palette.m3onSurfaceVariant
font: Tokens.font.label.small
text: root.effectiveSupportingText
}
}
TapHandler {
id: tapHandler
}
Component {
id: outlineComp
Shape {
id: bg
asynchronous: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: path
readonly property real inset: strokeWidth / 2
readonly property real outlineGap: placeholder.width * root.smallFontScale + Tokens.spacing.small * 2
property real outlineGapScale: root.activeFocus || root.text ? 1 : 0
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: path.inset + root.horizontalPadding - root.clampedRadius + path.outlineGap * (1 - path.outlineGapScale) / 2 + path.outlineGap * path.outlineGapScale
startY: path.inset
strokeColor: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : Colors.palette.m3outline)
strokeWidth: root.activeFocus ? 2 : 1
Behavior on outlineGapScale {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on strokeColor {
CAnim {}
}
Behavior on strokeWidth {
Anim {}
}
PathLine {
x: bg.width - path.inset - root.clampedRadius
y: path.inset
}
PathArc {
radiusX: root.clampedRadius
radiusY: root.clampedRadius
x: bg.width - path.inset
y: path.inset + root.clampedRadius
}
PathLine {
x: bg.width - path.inset
y: bg.height - path.inset - root.clampedRadius
}
PathArc {
radiusX: root.clampedRadius
radiusY: root.clampedRadius
x: bg.width - path.inset - root.clampedRadius
y: bg.height - path.inset
}
PathLine {
x: path.inset + root.clampedRadius
y: bg.height - path.inset
}
PathArc {
radiusX: root.clampedRadius
radiusY: root.clampedRadius
x: path.inset
y: bg.height - path.inset - root.clampedRadius
}
PathLine {
x: path.inset
y: path.inset + root.clampedRadius
}
PathArc {
radiusX: root.clampedRadius
radiusY: root.clampedRadius
x: path.inset + root.clampedRadius
y: path.inset
}
PathLine {
x: path.inset + root.horizontalPadding - root.clampedRadius / 2 + path.outlineGap * (1 - path.outlineGapScale) / 2
y: path.inset
Connections {
function onCursorPositionChanged(): void {
if (root.activeFocus && root.cursorVisible) {
cursor.opacity = 1;
cursor.disableBlink = true;
enableBlink.restart();
}
}
target: root
}
Timer {
id: enableBlink
interval: 100
onTriggered: cursor.disableBlink = false
}
Timer {
interval: 500
repeat: true
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
triggeredOnStart: true
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
}
Binding {
cursor.opacity: 0
when: !root.activeFocus || !root.cursorVisible
}
}
Component {
id: filledComp
CustomRect {
color: root.activeFocus ? Colors.tPalette.m3surfaceContainerHighest : Colors.tPalette.m3surfaceContainerHigh
topLeftRadius: root.clampedRadius
topRightRadius: root.clampedRadius
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : Colors.palette.m3outline)
implicitHeight: root.activeFocus ? 2 : 1
Behavior on implicitHeight {
Anim {}
}
}
Behavior on placeholderTextColor {
CAnim {
}
}
}
+2 -3
View File
@@ -1,16 +1,15 @@
import QtQuick
import QtQuick.Controls
import ZShell.Config
import qs.Services
TextInput {
renderType: Text.NativeRendering
selectedTextColor: Colors.palette.m3onSecondaryContainer
selectionColor: Colors.tPalette.colSecondaryContainer
selectionColor: Colors.tPalette.m3onSecondaryContainer
font {
family: Config.appearance?.font.family.sans ?? "sans-serif"
hintingPreference: Font.PreferFullHinting
pixelSize: Config.appearance?.font.size.normal ?? 15
pixelSize: Tokens?.font.size.normal ?? 15
}
}
-78
View File
@@ -1,78 +0,0 @@
import QtQuick
import Quickshell
import ZShell.Config
Image {
id: root
property int fadeInAnim: Anim.DefaultEffects
property int fadeInLargeAnim: Anim.StandardLarge
property int fadeOutAnim: Anim.FastEffects
property bool fadingOut
property bool hadPrevious
property bool preventInit
function maybeStartInAnim(): void {
if (!preventInit && !opacityInAnim.running && status === Image.Ready) {
opacityInAnim.type = hadPrevious ? fadeInAnim : fadeInLargeAnim;
opacityInAnim.start();
}
}
asynchronous: true
fillMode: Image.PreserveAspectCrop
opacity: 0
retainWhileLoading: true
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
Anim on opacity {
id: opacityInAnim
running: false
to: 1
}
Behavior on source {
SequentialAnimation {
ScriptAction {
script: opacityInAnim.stop()
}
PropertyAction {
property: "fadingOut"
target: root
value: true
}
Anim {
property: "opacity"
target: root
to: 0
type: root.fadeOutAnim
}
PropertyAction {
property: "fadingOut"
target: root
value: false
}
PropertyAction {
property: "hadPrevious"
target: root
value: root.source
}
PropertyAction {}
ScriptAction {
script: root.maybeStartInAnim()
}
}
}
onPreventInitChanged: maybeStartInAnim()
onStatusChanged: maybeStartInAnim()
}
-114
View File
@@ -1,114 +0,0 @@
import QtQuick
import Quickshell
import M3Shapes
import ZShell.Config
import qs.Services
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: Colors.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();
}
}
}
+9 -7
View File
@@ -1,14 +1,16 @@
import QtQuick
import ZShell.Config
import qs.Services
CustomText {
id: root
property real fill: 0
property real fill
property int grade: Colors.light ? 0 : -25
property font fontStyle: Tokens.font.icon.small
renderType: Text.NativeRendering
font: Tokens.font.icon.size(fontStyle.pointSize).weight(fontStyle.weight).vaxes(fontStyle.variableAxes).fill(fill.toFixed(1)).grade(grade).build()
font.family: "Material Symbols Rounded"
font.pointSize: Tokens.font.size.larger
font.variableAxes: ({
FILL: fill.toFixed(1),
GRAD: grade,
opsz: fontInfo.pixelSize,
wght: fontInfo.weight
})
}
+8 -6
View File
@@ -1,6 +1,8 @@
import QtQuick
import QtQuick.Shapes
import ZShell
import ZShell.Components
import qs.Helpers
import ZShell.Config
import qs.Services
@@ -11,6 +13,7 @@ MouseArea {
property alias bottomRightRadius: base.bottomRightRadius
property real circleRadius
property alias color: base.color
property bool disabled
readonly property real endRadius: {
const d1 = distSq(0, 0);
const d2 = distSq(width, 0);
@@ -19,7 +22,6 @@ MouseArea {
return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
}
property real endRadiusAtPress
property bool manualHoverOverride
property bool manualPressOverride
property real pressX: width / 2
property real pressY: height / 2
@@ -27,7 +29,7 @@ MouseArea {
readonly property alias rect: base
property bool shapeMorph
property bool showHoverBackground: true
property real stateOpacity: containsMouse || manualHoverOverride ? 0.08 : 0
property real stateOpacity: containsMouse ? 0.08 : 0
property alias topLeftRadius: base.topLeftRadius
property alias topRightRadius: base.topRightRadius
@@ -50,8 +52,8 @@ MouseArea {
}
anchors.fill: parent
cursorShape: !enabled ? undefined : Qt.PointingHandCursor
enabled: parent.enabled
cursorShape: disabled ? undefined : Qt.PointingHandCursor
enabled: !disabled
hoverEnabled: true
Behavior on stateOpacity {
@@ -98,13 +100,13 @@ MouseArea {
id: base
anchors.fill: parent
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
color: Colors.palette.m3onSurface
opacity: root.stateOpacity
// Pick up radius from parent if it has one (parent can be anything with radius props)
// qmllint disable missing-property
radius: root.parent?.radius ?? 0
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
// qmllint enable missing-property
+41 -19
View File
@@ -1,12 +1,11 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import ZShell.Components
import qs.Components
import qs.Helpers
import ZShell.Config
import qs.Services
SnapLayout {
RowLayout {
id: root
required property var greeter
@@ -14,16 +13,34 @@ SnapLayout {
spacing: Tokens.spacing.largeIncreased * 2
CustomRect {
Layout.fillHeight: true
ColumnLayout {
Layout.fillWidth: true
topLeftRadius: Tokens.rounding.largeIncreased
bottomLeftRadius: Tokens.rounding.largeIncreased
color: Colors.tPalette.m3surfaceContainer
radius: Tokens.rounding.small
spacing: Tokens.spacing.medium
UserDock {
greeter: root.greeter
CustomRect {
Layout.fillWidth: true
color: Colors.tPalette.m3surfaceContainer
implicitHeight: weather.implicitHeight
radius: Tokens.rounding.small
topLeftRadius: Tokens.rounding.largeIncreased
WeatherInfo {
id: weather
rootHeight: root.height
}
}
CustomClippingRect {
Layout.fillHeight: true
Layout.fillWidth: true
bottomLeftRadius: Tokens.rounding.largeIncreased
color: Colors.tPalette.m3surfaceContainer
radius: Tokens.rounding.small
UserDock {
greeter: root.greeter
}
}
}
@@ -32,16 +49,21 @@ SnapLayout {
screenHeight: root.screenHeight
}
CustomRect {
Layout.fillHeight: true
ColumnLayout {
Layout.fillWidth: true
bottomRightRadius: Tokens.rounding.largeIncreased
color: Colors.tPalette.m3surfaceContainer
radius: Tokens.rounding.small
topRightRadius: Tokens.rounding.largeIncreased
spacing: Tokens.spacing.medium
SessionDock {
greeter: root.greeter
CustomRect {
Layout.fillHeight: true
Layout.fillWidth: true
bottomRightRadius: Tokens.rounding.largeIncreased
color: Colors.tPalette.m3surfaceContainer
radius: Tokens.rounding.small
topRightRadius: Tokens.rounding.largeIncreased
SessionDock {
greeter: root.greeter
}
}
}
}
+2 -1
View File
@@ -134,7 +134,8 @@ Scope {
id: lockIcon
anchors.centerIn: parent
fontStyle: Tokens.font.icon.builders.extraLarge.size(Tokens.font.icon.extraLarge.pointSize * 3).build()
font.bold: true
font.pointSize: Tokens.font.size.extraLarge * 4
text: "lock"
}
+1
View File
@@ -200,5 +200,6 @@ Singleton {
ElapsedTimer {
id: timer
}
}
+15 -9
View File
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import ZShell.Config
import qs.Components
import qs.Services
@@ -40,7 +39,8 @@ Item {
anchors.centerIn: parent
animate: true
color: root.greeter.launching ? Colors.palette.m3secondary : Colors.palette.m3outline
font: Tokens.font.mono.medium
font.family: Config.appearance.font.family.mono
font.pointSize: Tokens.font.size.normal
opacity: root.buffer ? 0 : 1
text: {
if (root.greeter.launching)
@@ -51,7 +51,8 @@ Item {
}
Behavior on opacity {
Anim {}
Anim {
}
}
}
@@ -63,13 +64,15 @@ Item {
anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onSurface
elide: Text.ElideLeft
font: Tokens.font.mono.medium
font.family: Config.appearance.font.family.mono
font.pointSize: Tokens.font.size.normal
horizontalAlignment: Qt.AlignHCenter
opacity: root.greeter.echoResponse && root.buffer ? 1 : 0
text: root.buffer
Behavior on opacity {
Anim {}
Anim {
}
}
}
@@ -86,7 +89,7 @@ Item {
anchors.centerIn: parent
anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0
implicitHeight: Tokens.font.body.medium
implicitHeight: Tokens.font.size.normal
implicitWidth: fullWidth
interactive: false
opacity: root.greeter.echoResponse ? 0 : 1
@@ -104,7 +107,8 @@ Item {
scale: 0
Behavior on opacity {
Anim {}
Anim {
}
}
Behavior on scale {
Anim {
@@ -152,13 +156,15 @@ Item {
Behavior on implicitWidth {
id: imWidthBehavior
Anim {}
Anim {
}
}
model: ScriptModel {
values: root.buffer.split("")
}
Behavior on opacity {
Anim {}
Anim {
}
}
}
}
+21 -21
View File
@@ -3,32 +3,32 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import ZShell.Components
import qs.Components
import qs.Services
SnapLayout {
ColumnLayout {
id: root
required property var greeter
anchors.fill: parent
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.large
orientation: Qt.Vertical
spacing: Tokens.spacing.small
CustomText {
Layout.fillWidth: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
font.family: Config.appearance.font.family.mono
font.weight: 500
text: root.greeter.sessions.length > 0 ? qsTr("%1 session%2").arg(root.greeter.sessions.length).arg(root.greeter.sessions.length === 1 ? "" : "s") : qsTr("Sessions")
}
Item {
CustomClippingRect {
Layout.fillHeight: true
Layout.fillWidth: true
color: "transparent"
radius: Tokens.rounding.small
Loader {
active: opacity > 0
@@ -40,21 +40,22 @@ SnapLayout {
duration: Tokens.anim.durations.extraLarge
}
}
sourceComponent: SnapLayout {
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.largeIncreased
orientation: Qt.Vertical
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3outlineVariant
fontStyle: Tokens.font.icon.large
font.pointSize: Tokens.font.size.extraLarge * 2
text: "desktop_windows"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3outlineVariant
font: Tokens.font.headline.builders.small.width(90).build()
font.family: Config.appearance.font.family.mono
font.pointSize: Tokens.font.size.large
font.weight: 500
text: qsTr("No Sessions Found")
}
}
@@ -67,7 +68,6 @@ SnapLayout {
clip: true
currentIndex: root.greeter.sessionIndex
highlightFollowsCurrentItem: false
boundsBehavior: ListView.StopAtBounds
model: root.greeter.sessions
spacing: Tokens.spacing.small
@@ -80,7 +80,7 @@ SnapLayout {
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: row.implicitHeight + Tokens.padding.small * 2
radius: Tokens.rounding.large
radius: Tokens.rounding.medium - Tokens.padding.small
StateLayer {
onClicked: {
@@ -88,7 +88,7 @@ SnapLayout {
}
}
SnapLayout {
RowLayout {
id: row
anchors.fill: parent
@@ -97,21 +97,20 @@ SnapLayout {
MaterialIcon {
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.extraLarge
font.pointSize: Tokens.font.size.extraLarge
text: modelData.kind === "x11" ? "tv" : "desktop_windows"
}
SnapLayout {
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small / 2
orientation: Qt.Vertical
CustomText {
Layout.fillWidth: true
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
elide: Text.ElideRight
font: Tokens.font.body.builders.medium.weight(Font.Bold).build()
font.pointSize: Tokens.font.size.normal
font.weight: 600
text: modelData.name
}
@@ -119,7 +118,8 @@ SnapLayout {
Layout.fillWidth: true
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimaryFixedVariant : Colors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font: Tokens.font.mono.small
font.family: Config.appearance.font.family.mono
font.pointSize: Tokens.font.size.small
text: modelData.kind
}
}
@@ -129,7 +129,7 @@ SnapLayout {
color: Colors.palette.m3primary
implicitHeight: sessions.currentItem?.implicitHeight ?? 0
implicitWidth: sessions.width
radius: Tokens.rounding.large
radius: Tokens.rounding.medium - Tokens.padding.small
y: sessions.currentItem?.y ?? 0
Behavior on y {
+20 -21
View File
@@ -3,33 +3,33 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import ZShell.Components
import qs.Components
import qs.Helpers
import qs.Services
SnapLayout {
ColumnLayout {
id: root
required property var greeter
anchors.fill: parent
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.large
orientation: Qt.Vertical
spacing: Tokens.spacing.small
CustomText {
Layout.fillWidth: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
font.family: Config.appearance.font.family.mono
font.weight: 500
text: root.greeter.users.length > 0 ? qsTr("%1 user%2").arg(root.greeter.users.length).arg(root.greeter.users.length === 1 ? "" : "s") : qsTr("Users")
}
Item {
CustomClippingRect {
Layout.fillHeight: true
Layout.fillWidth: true
color: "transparent"
radius: Tokens.rounding.small
Loader {
active: opacity > 0
@@ -41,22 +41,23 @@ SnapLayout {
duration: Tokens.anim.durations.extraLarge
}
}
sourceComponent: SnapLayout {
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.largeIncreased
orientation: Qt.Vertical
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3outlineVariant
fill: 1
fontStyle: Tokens.font.icon.large
font.pointSize: Tokens.font.size.extraLarge * 2
text: "account_circle"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3outlineVariant
font: Tokens.font.headline.builders.small.width(90).build()
font.family: Config.appearance.font.family.mono
font.pointSize: Tokens.font.size.large
font.weight: 500
text: qsTr("No Users Found")
}
}
@@ -69,7 +70,6 @@ SnapLayout {
clip: true
currentIndex: root.greeter.selectedUser ? root.greeter.users.indexOf(root.greeter.selectedUser) : -1
highlightFollowsCurrentItem: false
boundsBehavior: ListView.StopAtBounds
model: root.greeter.users
spacing: Tokens.spacing.small
@@ -82,7 +82,7 @@ SnapLayout {
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: row.implicitHeight + Tokens.padding.small * 2
radius: Tokens.rounding.large
radius: Tokens.rounding.medium - Tokens.padding.small
StateLayer {
onClicked: {
@@ -90,14 +90,14 @@ SnapLayout {
}
}
SnapLayout {
RowLayout {
id: row
anchors.fill: parent
anchors.margins: Tokens.padding.small
spacing: Tokens.spacing.medium
ClipRect {
CustomClippingRect {
Layout.preferredHeight: 50
Layout.preferredWidth: 50
radius: Tokens.rounding.full
@@ -106,7 +106,7 @@ SnapLayout {
anchors.centerIn: parent
color: user.index === users.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
fill: 1
fontStyle: Tokens.font.icon.extraLarge
font.pointSize: Tokens.font.size.extraLarge
text: modelData.kind === "x11" ? "tv" : "account_circle"
}
@@ -116,17 +116,16 @@ SnapLayout {
}
}
SnapLayout {
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small / 2
orientation: Qt.Vertical
CustomText {
Layout.fillWidth: true
color: user.index === users.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
elide: Text.ElideRight
font: Tokens.font.body.builders.medium.weight(Font.Bold).build()
font.pointSize: Tokens.font.size.normal
font.weight: 600
text: modelData.username
}
}
@@ -136,7 +135,7 @@ SnapLayout {
color: Colors.palette.m3primary
implicitHeight: users.currentItem?.implicitHeight ?? 0
implicitWidth: users.width
radius: Tokens.rounding.large
radius: Tokens.rounding.medium - Tokens.padding.small
y: users.currentItem?.y ?? 0
Behavior on y {
+170
View File
@@ -0,0 +1,170 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Helpers
import qs.Services
ColumnLayout {
id: root
required property int rootHeight
anchors.left: parent.left
anchors.margins: Tokens.padding.large * 2
anchors.right: parent.right
spacing: Tokens.spacing.small
Loader {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: -Tokens.padding.large
Layout.topMargin: Tokens.padding.large * 2
active: root.rootHeight > 610
visible: active
sourceComponent: CustomText {
color: Colors.palette.m3primary
font.pointSize: Tokens.font.size.extraLarge
font.weight: 500
text: qsTr("Weather")
}
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.largeIncreased
MaterialIcon {
animate: true
color: Colors.palette.m3secondary
font.pointSize: Tokens.font.size.extraLarge * 2.5
text: Weather.icon
}
ColumnLayout {
spacing: Tokens.spacing.small
CustomText {
Layout.fillWidth: true
animate: true
color: Colors.palette.m3secondary
elide: Text.ElideRight
font.pointSize: Tokens.font.size.large
font.weight: 500
text: Weather.description
}
CustomText {
Layout.fillWidth: true
animate: true
color: Colors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Tokens.font.size.normal
text: qsTr("Humidity: %1%").arg(Weather.humidity)
}
}
Loader {
Layout.rightMargin: Tokens.padding.small
active: root.width > 400
visible: active
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.small
CustomText {
Layout.fillWidth: true
animate: true
color: Colors.palette.m3primary
elide: Text.ElideLeft
font.pointSize: Tokens.font.size.extraLarge
font.weight: 500
horizontalAlignment: Text.AlignRight
text: Weather.temp
}
CustomText {
Layout.fillWidth: true
animate: true
color: Colors.palette.m3outline
elide: Text.ElideLeft
font.pointSize: Tokens.font.size.smaller
horizontalAlignment: Text.AlignRight
text: qsTr("Feels like: %1").arg(Weather.feelsLike)
}
}
}
}
Loader {
id: forecastLoader
Layout.bottomMargin: Tokens.padding.large * 2
Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.small
active: root.rootHeight > 820
visible: active
sourceComponent: RowLayout {
spacing: Tokens.spacing.largeIncreased
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: Tokens.spacing.small
CustomText {
Layout.fillWidth: true
color: Colors.palette.m3outline
font.pointSize: Tokens.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: Tokens.font.size.extraLarge * 1.5
font.weight: 500
text: forecastHour.modelData?.icon ?? "cloud_alert"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3secondary
font.pointSize: Tokens.font.size.larger
text: Config.services.useFahrenheit ? `${forecastHour.modelData?.tempF ?? 0}°F` : `${forecastHour.modelData?.tempC ?? 0}°C`
}
}
}
}
}
Timer {
interval: 900000 // 15 minutes
repeat: true
running: true
triggeredOnStart: true
onTriggered: Weather.reload()
}
}
+1
View File
@@ -16,6 +16,7 @@ ShellRoot {
GreeterState {
id: greeter
}
GreeterSurface {
+88
View File
@@ -0,0 +1,88 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import Quickshell.Hyprland
import ZShell
import qs.Components
Scope {
LazyLoader {
id: root
property bool closing
property bool freeze
Variants {
model: Quickshell.screens
PanelWindow {
id: win
required property ShellScreen modelData
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: root.closing ? WlrKeyboardFocus.None : WlrKeyboardFocus.Exclusive
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "areapicker"
color: "transparent"
mask: root.closing ? empty : null
screen: modelData
anchors {
bottom: true
left: true
right: true
top: true
}
Region {
id: empty
}
Picker {
loader: root
screen: win.modelData
}
}
}
}
IpcHandler {
function open(): void {
root.freeze = false;
root.closing = false;
root.activeAsync = true;
}
function openFreeze(): void {
root.freeze = true;
root.closing = false;
root.activeAsync = true;
}
target: "picker"
}
CustomShortcut {
name: "screenshot"
onPressed: {
root.freeze = false;
root.closing = false;
root.activeAsync = true;
}
}
CustomShortcut {
name: "screenshotFreeze"
onPressed: {
root.freeze = true;
root.closing = false;
root.activeAsync = true;
}
}
}
+2 -1
View File
@@ -69,7 +69,8 @@ Singleton {
model: Quickshell.screens
Monitor {}
Monitor {
}
}
Process {
@@ -23,7 +23,7 @@ Singleton {
function toggleNightLight(): void {
service.manualToggle = true;
service.toggle(true);
service.toggle();
}
NightlightManager {
+2 -1
View File
@@ -33,7 +33,8 @@ Singleton {
implicitHeight: 0
implicitWidth: 0
mask: Region {}
mask: Region {
}
}
}

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