Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51ec6a7939
|
||
|
|
dd64d6947d
|
||
|
|
9d6cd909a5
|
||
|
|
516ec9dd54
|
||
|
|
b279c5d664
|
||
|
|
82c307de11
|
||
|
|
388482c620
|
||
|
|
ab4a662013
|
||
|
|
fda39fc4c0
|
||
|
|
9e97569467
|
||
|
|
630b05ff2c
|
||
|
|
b86f865396
|
||
|
|
34585a639b
|
||
|
|
163370354a
|
||
|
|
012d2777c2
|
||
|
|
9c0b74ff66 | ||
|
|
24825250bb
|
||
|
|
1364b5ed1a
|
||
|
|
ef4d849b4b
|
||
|
|
e12a047da2
|
||
|
|
5db2718b04
|
||
|
|
e50a28b877
|
||
|
|
0e23b18f72
|
||
|
|
90184bff58 | ||
|
|
abdde2450d | ||
|
|
fd05b3d4c4 | ||
|
|
9d8bde1413 | ||
|
|
118b8f482a | ||
|
|
b58b4fdc9c | ||
|
|
a56afd7fac | ||
|
|
b0cb43f75c | ||
|
|
e655ec5bc7 | ||
|
|
dc0f222ace | ||
|
|
de19fd6ac4 | ||
|
|
9edbcbd610 | ||
|
|
cd44d27b74 | ||
|
|
0d0faecb1c | ||
|
|
200a6a5d54 | ||
|
|
2d2231745c |
@@ -0,0 +1,3 @@
|
||||
|
||||
#omz revision: d2b7f557cceaddb16ba89bbf8200d8a8df35c4d8
|
||||
#omz fpath: /home/zach/.oh-my-zsh/plugins/colored-man-pages /home/zach/.oh-my-zsh/plugins/universalarchive /home/zach/.oh-my-zsh/plugins/tldr /home/zach/.oh-my-zsh/plugins/extract /home/zach/.oh-my-zsh/plugins/copypath /home/zach/.oh-my-zsh/plugins/cp /home/zach/.oh-my-zsh/plugins/conda-env /home/zach/.oh-my-zsh/plugins/command-not-found /home/zach/.oh-my-zsh/plugins/dirhistory /home/zach/.oh-my-zsh/plugins/copybuffer /home/zach/.oh-my-zsh/plugins/copyfile /home/zach/.oh-my-zsh/custom/plugins/fast-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-autosuggestions /home/zach/.oh-my-zsh/plugins/archlinux /home/zach/.oh-my-zsh/plugins/web-search /home/zach/.oh-my-zsh/plugins/sudo /home/zach/.oh-my-zsh/plugins/git /home/zach/.oh-my-zsh/functions /home/zach/.oh-my-zsh/completions /home/zach/.oh-my-zsh/custom/functions /home/zach/.oh-my-zsh/custom/completions /home/zach/tools/ripgrep/complete /home/linuxbrew/.linuxbrew/share/zsh/site-functions /home/zach/.oh-my-zsh/plugins/colored-man-pages /home/zach/.oh-my-zsh/plugins/universalarchive /home/zach/.oh-my-zsh/plugins/tldr /home/zach/.oh-my-zsh/plugins/extract /home/zach/.oh-my-zsh/plugins/copypath /home/zach/.oh-my-zsh/plugins/cp /home/zach/.oh-my-zsh/plugins/conda-env /home/zach/.oh-my-zsh/plugins/command-not-found /home/zach/.oh-my-zsh/plugins/dirhistory /home/zach/.oh-my-zsh/plugins/copybuffer /home/zach/.oh-my-zsh/plugins/copyfile /home/zach/.oh-my-zsh/custom/plugins/fast-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-autosuggestions /home/zach/.oh-my-zsh/plugins/archlinux /home/zach/.oh-my-zsh/plugins/web-search /home/zach/.oh-my-zsh/plugins/sudo /home/zach/.oh-my-zsh/plugins/git /home/zach/.oh-my-zsh/functions /home/zach/.oh-my-zsh/completions /home/zach/.oh-my-zsh/custom/functions /home/zach/.oh-my-zsh/custom/completions /home/zach/.oh-my-zsh/cache/completions /home/zach/tools/ripgrep/complete /home/linuxbrew/.linuxbrew/share/zsh/site-functions /usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/functions/Calendar /usr/share/zsh/functions/Chpwd /usr/share/zsh/functions/Completion /usr/share/zsh/functions/Completion/Base /usr/share/zsh/functions/Completion/Linux /usr/share/zsh/functions/Completion/Unix /usr/share/zsh/functions/Completion/X /usr/share/zsh/functions/Completion/Zsh /usr/share/zsh/functions/Exceptions /usr/share/zsh/functions/MIME /usr/share/zsh/functions/Math /usr/share/zsh/functions/Misc /usr/share/zsh/functions/Newuser /usr/share/zsh/functions/Prompts /usr/share/zsh/functions/TCP /usr/share/zsh/functions/VCS_Info /usr/share/zsh/functions/VCS_Info/Backends /usr/share/zsh/functions/Zftp /usr/share/zsh/functions/Zle /home/zach/.zfunc
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: apk add --no-cache docker-cli
|
||||
run: apk add --no-cache docker-cli docker-cli-buildx
|
||||
|
||||
- name: Login to registry
|
||||
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.dev --username aramjonghu --password-stdin
|
||||
|
||||
+22
-21
@@ -17,46 +17,47 @@ jobs:
|
||||
run: |
|
||||
find . \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) \
|
||||
-not -path './build/*' \
|
||||
-exec clang-format -i --style=file {} +
|
||||
git diff --exit-code && echo "clang-format: passed"
|
||||
-exec clang-format --dry-run -Werror --style=file {} +
|
||||
|
||||
build:
|
||||
build & lint:
|
||||
runs-on: alpine
|
||||
container:
|
||||
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure
|
||||
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
run: ninja -C build
|
||||
|
||||
clang-tidy:
|
||||
runs-on: alpine
|
||||
container:
|
||||
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Cache CMake build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
path: build
|
||||
key: cpp-build-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt') }}
|
||||
restore-keys: |
|
||||
cpp-build-
|
||||
|
||||
- name: Cache ccache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: ccache-${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-${{ runner.os }}-
|
||||
|
||||
- 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) -header-filter="Plugins/.*" 2>&1 | \
|
||||
grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error\|warnings generated\.\|^Use -header-filter'
|
||||
run-clang-tidy -p build -j "$(nproc)" \
|
||||
-source-filter="^$(pwd)/Plugins/" \
|
||||
-header-filter=".*Plugins/.*" \
|
||||
-removed-arg=-mno-direct-extern-access
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
fmt & lint:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
|
||||
@@ -24,20 +24,6 @@ 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
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
static:
|
||||
fmt & lint:
|
||||
runs-on: debian
|
||||
container: node:26-trixie-slim
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
. .venv/bin/activate
|
||||
ruff check .
|
||||
|
||||
verify:
|
||||
test & nuitka build:
|
||||
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/
|
||||
|
||||
+49
-121
@@ -4,149 +4,77 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
clippy & fmt:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
container: rust: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 }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
key: rust-build-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
rust-${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
rust-${{ runner.os }}-build-
|
||||
rust-
|
||||
rust-${{ runner.os }}-cargo-
|
||||
|
||||
- name: Install tools
|
||||
- name: Clippy (with compile check)
|
||||
run: |
|
||||
apk add --no-cache \
|
||||
git \
|
||||
cargo \
|
||||
rust
|
||||
|
||||
- name: Cargo check
|
||||
run: |
|
||||
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
||||
for manifest in $(find . -name "Cargo.toml"); do
|
||||
cargo check --manifest-path "$manifest" && \
|
||||
echo "$manifest: check passed" || \
|
||||
{ echo "$manifest: check failed"; exit 1; }
|
||||
done
|
||||
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
|
||||
echo "Rust files found but no Cargo.toml"
|
||||
exit 1
|
||||
else
|
||||
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
|
||||
echo "No Rust project found"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
fmt:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
env:
|
||||
CARGO_HOME: ${{ github.workspace }}/.cargo
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache cargo packages
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-cargo-packages
|
||||
with:
|
||||
path: |
|
||||
.cargo/registry
|
||||
.cargo/git
|
||||
target
|
||||
key: rust-fmt-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
rust-${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
rust-${{ runner.os }}-build-
|
||||
rust-
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
apk add --no-cache \
|
||||
git \
|
||||
cargo \
|
||||
rust \
|
||||
rustfmt
|
||||
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
|
||||
|
||||
- name: Format check
|
||||
run: |
|
||||
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
||||
status=0
|
||||
for manifest in $(find . -name "Cargo.toml"); do
|
||||
cargo fmt --manifest-path "$manifest" --check && \
|
||||
echo "$manifest: formatting OK" || \
|
||||
{ echo "$manifest: needs formatting"; status=1; }
|
||||
done
|
||||
exit $status
|
||||
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
|
||||
echo "Rust files found but no Cargo.toml"
|
||||
exit 1
|
||||
else
|
||||
echo "No Rust project found"
|
||||
fi
|
||||
|
||||
clippy:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
env:
|
||||
CARGO_HOME: ${{ github.workspace }}/.cargo
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache cargo packages
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-cargo-packages
|
||||
with:
|
||||
path: |
|
||||
.cargo/registry
|
||||
.cargo/git
|
||||
target
|
||||
key: rust-clippy-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
rust-${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
rust-${{ runner.os }}-build-
|
||||
rust-
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
apk add --no-cache \
|
||||
git \
|
||||
cargo \
|
||||
rust \
|
||||
rust-clippy
|
||||
|
||||
- name: Clippy
|
||||
run: |
|
||||
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
||||
status=0
|
||||
for manifest in $(find . -name "Cargo.toml"); do
|
||||
cargo clippy --manifest-path "$manifest" --all-targets --all-features -- -D warnings && \
|
||||
echo "$manifest: Clippy passed" || \
|
||||
{ echo "$manifest: Clippy failed"; status=1; }
|
||||
done
|
||||
exit $status
|
||||
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
|
||||
echo "Rust files found but no Cargo.toml"
|
||||
exit 1
|
||||
else
|
||||
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
|
||||
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
|
||||
|
||||
+25
-10
@@ -1,23 +1,38 @@
|
||||
**/__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.build/
|
||||
**/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
|
||||
|
||||
@@ -50,6 +50,7 @@ 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")
|
||||
|
||||
@@ -46,8 +46,7 @@ Item {
|
||||
strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.strokeWidth
|
||||
|
||||
Behavior on strokeColor {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
PathAngleArc {
|
||||
@@ -84,8 +83,7 @@ Item {
|
||||
}
|
||||
}
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
Anim on waveProgress {
|
||||
id: waveProgAnim
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import ZShell.Components
|
||||
|
||||
Item {
|
||||
@@ -19,7 +20,7 @@ Item {
|
||||
}
|
||||
|
||||
ShaderEffectSource {
|
||||
id: source
|
||||
id: src
|
||||
|
||||
hideSource: true
|
||||
sourceItem: contentItem
|
||||
@@ -27,7 +28,7 @@ Item {
|
||||
|
||||
RoundedMask {
|
||||
anchors.fill: parent
|
||||
source: source
|
||||
source: src
|
||||
topLeftRadius: root.topLeftRadius
|
||||
topRightRadius: root.topRightRadius
|
||||
bottomRightRadius: root.bottomRightRadius
|
||||
|
||||
@@ -160,8 +160,7 @@ Item {
|
||||
Behavior on color {
|
||||
enabled: false
|
||||
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ MultiEffect {
|
||||
colorization: 1
|
||||
|
||||
Behavior on colorizationColor {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ ClippingRectangle {
|
||||
color: "transparent"
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ ClippingWrapperRectangle {
|
||||
color: "transparent"
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +124,7 @@ ProgressBar {
|
||||
anchors.left: parent.left
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: implicitWidth = Qt.binding(() => parent.width * root.visualPosition)
|
||||
@@ -222,8 +221,7 @@ ProgressBar {
|
||||
}
|
||||
}
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
Anim on waveProgress {
|
||||
duration: root.waveDuration
|
||||
|
||||
@@ -105,20 +105,16 @@ Switch {
|
||||
width: height
|
||||
|
||||
Behavior on end1 {
|
||||
PropAnim {
|
||||
}
|
||||
PropAnim {}
|
||||
}
|
||||
Behavior on end2 {
|
||||
PropAnim {
|
||||
}
|
||||
PropAnim {}
|
||||
}
|
||||
Behavior on start1 {
|
||||
PropAnim {
|
||||
}
|
||||
PropAnim {}
|
||||
}
|
||||
Behavior on start2 {
|
||||
PropAnim {
|
||||
}
|
||||
PropAnim {}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
@@ -134,8 +130,7 @@ Switch {
|
||||
strokeWidth: 16 * 0.15
|
||||
|
||||
Behavior on strokeColor {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
PathLine {
|
||||
|
||||
@@ -20,8 +20,7 @@ Text {
|
||||
textFormat: Text.PlainText
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
Behavior on text {
|
||||
enabled: root.animate
|
||||
@@ -33,8 +32,7 @@ Text {
|
||||
type: Anim.FastEffects
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
PropertyAction {}
|
||||
|
||||
TAnim {
|
||||
target: root
|
||||
|
||||
@@ -65,8 +65,7 @@ Image {
|
||||
value: root.source
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
PropertyAction {}
|
||||
|
||||
ScriptAction {
|
||||
script: root.maybeStartInAnim()
|
||||
|
||||
@@ -138,7 +138,10 @@ Item {
|
||||
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 };
|
||||
return {
|
||||
x: targetX,
|
||||
y
|
||||
};
|
||||
}
|
||||
|
||||
// Part of the curve left/right of a cut x, including the cut point
|
||||
|
||||
@@ -64,8 +64,7 @@ MaterialShape {
|
||||
to: 360
|
||||
}
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
ElapsedTimer {
|
||||
|
||||
@@ -47,12 +47,10 @@ Item {
|
||||
implicitWidth: vertical ? elideText.implicitHeight : elideText.implicitWidth
|
||||
|
||||
Behavior on leftFadeStrength {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on rightFadeStrength {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onHeightChanged: resetMarquee()
|
||||
|
||||
+3
-5
@@ -34,7 +34,7 @@ MouseArea {
|
||||
cursorShape: expanded ? Qt.ArrowCursor : undefined
|
||||
enabled: expanded
|
||||
hoverEnabled: expanded
|
||||
layer.enabled: opacity < 1
|
||||
layer.enabled: false
|
||||
opacity: expanded ? 1 : 0
|
||||
parent: {
|
||||
const win = QsWindow.window;
|
||||
@@ -101,8 +101,7 @@ MouseArea {
|
||||
yScale: root.expanded ? 1 : 0.1
|
||||
|
||||
Behavior on yScale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +136,7 @@ MouseArea {
|
||||
radius: menu.radius - column.anchors.margins
|
||||
|
||||
Behavior on radius {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
|
||||
@@ -102,8 +102,7 @@ Row {
|
||||
topLeftRadius: rad
|
||||
|
||||
Behavior on rad {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
@@ -127,12 +126,10 @@ Row {
|
||||
text: "expand_more"
|
||||
|
||||
Behavior on anchors.horizontalCenterOffset {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on rotation {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,7 @@ Item {
|
||||
onValuesChanged: root.flagChanged()
|
||||
}
|
||||
|
||||
ToastWrapper {
|
||||
}
|
||||
ToastWrapper {}
|
||||
}
|
||||
|
||||
component ToastWrapper: MouseArea {
|
||||
@@ -86,12 +85,10 @@ Item {
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: modelData.lock(this)
|
||||
|
||||
@@ -20,8 +20,7 @@ Shape {
|
||||
strokeWidth: 0
|
||||
|
||||
Behavior on fillColor {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
PathSvg {
|
||||
|
||||
@@ -141,7 +141,6 @@ Singleton {
|
||||
|
||||
BeatTracker {
|
||||
id: beatTracker
|
||||
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
|
||||
+1
-2
@@ -295,8 +295,7 @@ Singleton {
|
||||
Component {
|
||||
id: apComp
|
||||
|
||||
AccessPoint {
|
||||
}
|
||||
AccessPoint {}
|
||||
}
|
||||
|
||||
Process {
|
||||
|
||||
+2
-5
@@ -1081,15 +1081,13 @@ Singleton {
|
||||
Component {
|
||||
id: commandProc
|
||||
|
||||
CommandProcess {
|
||||
}
|
||||
CommandProcess {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: apComp
|
||||
|
||||
AccessPoint {
|
||||
}
|
||||
AccessPoint {}
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -1310,7 +1308,6 @@ Singleton {
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
id: stdoutCollector
|
||||
|
||||
}
|
||||
|
||||
onExited: code => {
|
||||
|
||||
@@ -164,8 +164,7 @@ Singleton {
|
||||
Component {
|
||||
id: notifComp
|
||||
|
||||
Notif {
|
||||
}
|
||||
Notif {}
|
||||
}
|
||||
|
||||
component Notif: QtObject {
|
||||
|
||||
@@ -46,8 +46,7 @@ Item {
|
||||
strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.strokeWidth
|
||||
|
||||
Behavior on strokeColor {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
PathAngleArc {
|
||||
@@ -84,8 +83,7 @@ Item {
|
||||
}
|
||||
}
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
Anim on waveProgress {
|
||||
id: waveProgAnim
|
||||
|
||||
@@ -7,7 +7,6 @@ ClippingRectangle {
|
||||
color: "transparent"
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ Text {
|
||||
textFormat: Text.PlainText
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
Behavior on text {
|
||||
enabled: root.animate
|
||||
@@ -33,8 +32,7 @@ Text {
|
||||
type: Anim.FastEffects
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
PropertyAction {}
|
||||
|
||||
TAnim {
|
||||
target: root
|
||||
|
||||
@@ -65,8 +65,7 @@ Image {
|
||||
value: root.source
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
PropertyAction {}
|
||||
|
||||
ScriptAction {
|
||||
script: root.maybeStartInAnim()
|
||||
|
||||
@@ -64,8 +64,7 @@ MaterialShape {
|
||||
to: 360
|
||||
}
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
ElapsedTimer {
|
||||
|
||||
@@ -200,6 +200,5 @@ Singleton {
|
||||
|
||||
ElapsedTimer {
|
||||
id: timer
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ ShellRoot {
|
||||
|
||||
GreeterState {
|
||||
id: greeter
|
||||
|
||||
}
|
||||
|
||||
GreeterSurface {
|
||||
|
||||
@@ -69,8 +69,7 @@ Singleton {
|
||||
|
||||
model: Quickshell.screens
|
||||
|
||||
Monitor {
|
||||
}
|
||||
Monitor {}
|
||||
}
|
||||
|
||||
Process {
|
||||
|
||||
@@ -33,8 +33,7 @@ Singleton {
|
||||
implicitHeight: 0
|
||||
implicitWidth: 0
|
||||
|
||||
mask: Region {
|
||||
}
|
||||
mask: Region {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+84
-47
@@ -5,7 +5,7 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import ZShell.Models
|
||||
import ZShell.Internal
|
||||
import ZShell.Wallpaper
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
@@ -16,18 +16,17 @@ Searcher {
|
||||
id: root
|
||||
|
||||
property string actualCurrent: WallpaperPath.currentWallpaperPath
|
||||
property alias crops: adapter.monitorCrops
|
||||
readonly property string current: showPreview ? previewPath : actualCurrent
|
||||
readonly property string currentThumbnail: thumbnailFor(current)
|
||||
readonly property bool currentIsVideo: isVideoPath(current)
|
||||
property alias monitorCrops: monitorCrops
|
||||
property bool pendingPreviewClear
|
||||
property bool previewColorLock
|
||||
property string previewPath
|
||||
property bool recentlyChanged
|
||||
property bool showPreview: false
|
||||
property int cropVersion: 0
|
||||
|
||||
function getCategoryFor(w: FileSystemEntry): string {
|
||||
function getCategoryFor(w: WallpaperEntry): string {
|
||||
let category = w.parentDir.slice(Paths.wallsdir.length + 1);
|
||||
if (category.includes("/"))
|
||||
category = category.slice(0, category.indexOf("/"));
|
||||
@@ -46,19 +45,19 @@ Searcher {
|
||||
}
|
||||
|
||||
function thumbnailFor(path: string): string {
|
||||
if (!isVideoPath(path))
|
||||
return path;
|
||||
if (!path)
|
||||
return "";
|
||||
|
||||
for (const entry of root.list) {
|
||||
if (entry.path === path && entry.thumbnailPath)
|
||||
return entry.thumbnailPath;
|
||||
if (entry.path === path)
|
||||
return entry.thumbnailPath || path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function getCrop(screen: string): var {
|
||||
return root.crops[screen];
|
||||
return wallpapers.crop(root.current, screen);
|
||||
}
|
||||
|
||||
function preview(path: string): void {
|
||||
@@ -75,19 +74,18 @@ Searcher {
|
||||
else if (zoom > 5.0)
|
||||
zoom = 5.0;
|
||||
|
||||
root.crops[screen] = {
|
||||
wallpapers.setCrop(root.current, screen, {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
zoom: zoom
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function setWallpaper(path: string): void {
|
||||
actualCurrent = path;
|
||||
WallpaperPath.currentWallpaperPath = path;
|
||||
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 1.0));
|
||||
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
|
||||
if (Config.colors.schemeGen)
|
||||
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.colors.mode}`]);
|
||||
@@ -121,38 +119,13 @@ Searcher {
|
||||
target: "wallpaper"
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: monitorCrops
|
||||
|
||||
path: `${Paths.state}/wallpaper-crops.json`
|
||||
watchChanges: true
|
||||
|
||||
onAdapterUpdated: cropWriteDelay.restart()
|
||||
onFileChanged: reload()
|
||||
|
||||
JsonAdapter {
|
||||
id: adapter
|
||||
|
||||
property var monitorCrops: ({})
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: cropWriteDelay
|
||||
|
||||
interval: 100
|
||||
repeat: false
|
||||
running: false
|
||||
|
||||
onTriggered: monitorCrops.writeAdapter()
|
||||
}
|
||||
|
||||
FileSystemModel {
|
||||
WallpaperDb {
|
||||
id: wallpapers
|
||||
|
||||
filter: FileSystemModel.Media
|
||||
dbPath: `${Paths.state}/wallpapers.sqlite`
|
||||
cacheSize: wallpaper.largestBufferSize
|
||||
requireHardwareCodec: true
|
||||
path: Config.general.wallpaperPath
|
||||
recursive: true
|
||||
}
|
||||
|
||||
Process {
|
||||
@@ -171,36 +144,100 @@ Searcher {
|
||||
WallpaperService {
|
||||
id: wallpaper
|
||||
|
||||
dbPath: wallpapers.dbPath
|
||||
path: root.current
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: wallpapers
|
||||
|
||||
function onCacheStored(path: string): void {
|
||||
if (path === root.current)
|
||||
wallpaper.invalidateCache();
|
||||
}
|
||||
|
||||
function onCropsChanged(path: string): void {
|
||||
if (path === root.current)
|
||||
root.cropVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
id: screenVariants
|
||||
model: Quickshell.screens
|
||||
|
||||
WallpaperObject {
|
||||
readonly property var crops: root.getCrop(modelData.name)
|
||||
id: obj
|
||||
required property var modelData
|
||||
|
||||
cropHeight: crops?.height ?? 1.0
|
||||
cropWidth: crops?.width ?? 1.0
|
||||
cropX: crops?.x ?? 0.0
|
||||
cropY: crops?.y ?? 0.0
|
||||
// Crop animation only for edits, never for wallpaper swaps.
|
||||
property bool animateCrop: false
|
||||
|
||||
screen: modelData.name
|
||||
service: wallpaper
|
||||
zoom: crops?.zoom ?? 1.0
|
||||
|
||||
// Apply the wallpaper's own crop. Driven off the service's pathChanged
|
||||
// (which fires after applyPath has snapshotted the outgoing frame with
|
||||
// the outgoing crop) and off crop edits, never as a reactive binding on
|
||||
// the wallpaper path — so a swap applies the incoming crop atomically
|
||||
// after the base is captured.
|
||||
function seedCrop(): void {
|
||||
// Load the current wallpaper's crop without pushing: the service
|
||||
// already read it from the database. This only seeds the values so
|
||||
// a later edit animates from the right starting point.
|
||||
obj.syncCrop(root.getCrop(modelData.name));
|
||||
}
|
||||
|
||||
// Apply an edited crop: assign via the setters so the Behaviors animate
|
||||
// the numbers and push each intermediate value to the service.
|
||||
function editCrop(): void {
|
||||
const c = root.getCrop(modelData.name);
|
||||
obj.cropX = c?.x ?? 0.0;
|
||||
obj.cropY = c?.y ?? 0.0;
|
||||
obj.cropWidth = c?.width ?? 1.0;
|
||||
obj.cropHeight = c?.height ?? 1.0;
|
||||
obj.zoom = c?.zoom ?? 1.0;
|
||||
}
|
||||
|
||||
Component.onCompleted: seedCrop()
|
||||
onScreenChanged: seedCrop()
|
||||
|
||||
Behavior on cropHeight {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
Behavior on cropWidth {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
Behavior on cropX {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
Behavior on cropY {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: wallpaper
|
||||
function onPathChanged(): void {
|
||||
for (const obj of screenVariants.instances) {
|
||||
obj.animateCrop = false;
|
||||
obj.seedCrop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onCropVersionChanged(): void {
|
||||
for (const obj of screenVariants.instances) {
|
||||
obj.animateCrop = true;
|
||||
obj.editCrop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -210,8 +210,7 @@ GridLayout {
|
||||
roleValue: "network"
|
||||
|
||||
delegate: EntryWrapper {
|
||||
NetworkWidget {
|
||||
}
|
||||
NetworkWidget {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,9 @@ WidgetBase {
|
||||
}
|
||||
required property GridLayout loader
|
||||
required property Wrapper popouts
|
||||
readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.medium * 2 : verticalColumn.implicitHeight + Tokens.padding.medium * 2
|
||||
readonly property int roundedWidth: Math.round(timeText.implicitWidth)
|
||||
readonly property int evenWidth: roundedWidth % 2 === 0 ? roundedWidth : roundedWidth + 1
|
||||
readonly property real size: horizontal ? evenWidth + Tokens.padding.medium * 2 : verticalColumn.implicitHeight + Tokens.padding.medium * 2
|
||||
required property ScreenState screenState
|
||||
|
||||
implicitHeight: horizontal ? baseSize : size
|
||||
|
||||
@@ -24,7 +24,7 @@ Row {
|
||||
color: Battery.getColors(root.percentage, root.devState).bg
|
||||
height: root.batHeight
|
||||
radius: root.radius
|
||||
width: root.batWidth
|
||||
width: root.batWidth - 1
|
||||
|
||||
CustomText {
|
||||
color: Battery.getColors(root.percentage, root.devState).fg
|
||||
|
||||
@@ -13,7 +13,7 @@ WidgetBase {
|
||||
readonly property int textWidth: Math.min(metrics.width, 200)
|
||||
|
||||
implicitHeight: horizontal ? baseSize : layout.implicitHeight + Tokens.padding.small * 2
|
||||
implicitWidth: horizontal ? layout.implicitWidth + Tokens.padding.small * 2 : baseSize
|
||||
implicitWidth: horizontal ? (layout.implicitWidth % 2 === 0 ? layout.implicitWidth : layout.implicitWidth + 1) + Tokens.padding.small * 2 : baseSize
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
Behavior on implicitHeight {
|
||||
|
||||
@@ -12,7 +12,6 @@ GridLayout {
|
||||
property real animatedPercentage: 0
|
||||
readonly property real arcStartAngle: 0.75 * Math.PI
|
||||
readonly property real arcSweep: 1.5 * Math.PI
|
||||
readonly property real dpr: (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1
|
||||
required property bool horizontal
|
||||
property string icon
|
||||
required property color iconColor
|
||||
@@ -23,17 +22,12 @@ GridLayout {
|
||||
property bool warning: percentage * 100 >= warningThreshold
|
||||
property int warningThreshold: 80
|
||||
|
||||
function snap(v) {
|
||||
return Math.round(v * dpr) / dpr;
|
||||
}
|
||||
|
||||
columnSpacing: Tokens.spacing.medium
|
||||
columns: horizontal ? -1 : 1
|
||||
percentage: 0
|
||||
|
||||
Behavior on animatedPercentage {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: animatedPercentage = percentage
|
||||
@@ -59,7 +53,7 @@ GridLayout {
|
||||
Layout.preferredHeight: root.horizontal ? icon.implicitHeight - Tokens.padding.extraSmall : 4
|
||||
Layout.preferredWidth: root.horizontal ? 4 : icon.implicitWidth
|
||||
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
|
||||
radius: Math.min(width, height) / 2
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
Item {
|
||||
id: clipper
|
||||
@@ -69,18 +63,20 @@ GridLayout {
|
||||
anchors.left: root.horizontal ? undefined : parent.left
|
||||
anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter
|
||||
clip: true
|
||||
height: root.horizontal ? root.snap(root.percentage * track.height) : track.height * 2
|
||||
width: root.horizontal ? track.width * 2 : root.snap(root.percentage * track.width)
|
||||
implicitHeight: root.horizontal ? root.percentage * track.height : track.height * 2
|
||||
implicitWidth: root.horizontal ? track.width * 2 : root.percentage * track.width
|
||||
|
||||
CustomRect {
|
||||
anchors.bottom: root.horizontal ? parent.bottom : undefined
|
||||
anchors.horizontalCenter: root.horizontal ? parent.horizontalCenter : undefined
|
||||
anchors.left: root.horizontal ? undefined : parent.left
|
||||
anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter
|
||||
|
||||
color: root.mainColor
|
||||
height: track.height
|
||||
radius: track.radius
|
||||
width: track.width
|
||||
|
||||
implicitHeight: track.height
|
||||
implicitWidth: track.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ WidgetBase {
|
||||
anchors.horizontalCenterOffset: root.horizontal ? 0 : 1
|
||||
anchors.verticalCenterOffset: root.horizontal ? 0 : -3
|
||||
columnSpacing: Tokens.spacing.small
|
||||
rowSpacing: 0
|
||||
columns: root.horizontal ? -1 : 1
|
||||
|
||||
ServiceRef {
|
||||
@@ -51,6 +52,7 @@ WidgetBase {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillHeight: root.horizontal
|
||||
Layout.fillWidth: !root.horizontal
|
||||
|
||||
horizontal: root.horizontal
|
||||
icon: "memory"
|
||||
iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
|
||||
@@ -31,7 +31,7 @@ WidgetBase {
|
||||
return -1;
|
||||
}
|
||||
readonly property real size: horizontal ? grid.implicitWidth + grid.anchors.leftMargin + grid.anchors.rightMargin : grid.implicitHeight + grid.anchors.bottomMargin + grid.anchors.topMargin
|
||||
readonly property int spacing: Tokens.spacing.medium / 2
|
||||
readonly property int spacing: Tokens.spacing.extraSmall
|
||||
|
||||
// Entries that can shrink to nothing, spacing included
|
||||
function collapsed(entry: var): bool {
|
||||
@@ -51,14 +51,12 @@ WidgetBase {
|
||||
Behavior on implicitHeight {
|
||||
enabled: !root.horizontal
|
||||
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
enabled: root.horizontal
|
||||
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
@@ -70,6 +68,8 @@ WidgetBase {
|
||||
anchors.rightMargin: root.horizontal ? Tokens.padding.extraSmall : 0
|
||||
anchors.topMargin: root.horizontal ? 0 : Tokens.padding.extraSmall
|
||||
columns: root.horizontal ? -1 : 1
|
||||
columnSpacing: root.spacing
|
||||
rowSpacing: root.spacing
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
@@ -111,8 +111,7 @@ WidgetBase {
|
||||
delegate: EntryWrapper {
|
||||
name: "upower"
|
||||
|
||||
UPowerWidget {
|
||||
}
|
||||
UPowerWidget {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,8 +134,8 @@ WidgetBase {
|
||||
Layout.rightMargin: root.horizontal ? Math.round(rightGap) : 0
|
||||
Layout.topMargin: root.horizontal ? 0 : Math.round(leftGap)
|
||||
children: item
|
||||
implicitHeight: item?.implicitHeight ?? 0
|
||||
implicitWidth: item?.implicitWidth ?? 0
|
||||
implicitHeight: item?.height ?? item?.implicitHeight ?? 0
|
||||
implicitWidth: item?.width ?? item?.implicitWidth ?? 0
|
||||
|
||||
Behavior on leftGap {
|
||||
Anim {
|
||||
|
||||
@@ -6,7 +6,7 @@ import qs.Components
|
||||
import qs.Services
|
||||
|
||||
MaterialIcon {
|
||||
id: speaker
|
||||
id: root
|
||||
|
||||
required property bool horizontal
|
||||
|
||||
@@ -15,6 +15,8 @@ MaterialIcon {
|
||||
fill: 1
|
||||
fontStyle: Tokens.font.icon.small
|
||||
text: Audio.muted ? "volume_off" : "volume_up"
|
||||
width: Math.round(implicitWidth)
|
||||
height: Math.round(implicitHeight)
|
||||
|
||||
Behavior on Layout.maximumWidth {
|
||||
Anim {}
|
||||
|
||||
@@ -6,7 +6,7 @@ import qs.Components
|
||||
import qs.Services
|
||||
|
||||
MaterialIcon {
|
||||
id: mic
|
||||
id: root
|
||||
|
||||
required property bool horizontal
|
||||
|
||||
@@ -15,6 +15,8 @@ MaterialIcon {
|
||||
fill: 1
|
||||
fontStyle: Tokens.font.icon.small
|
||||
text: Audio.sourceMuted ? "mic_off" : "mic"
|
||||
width: Math.round(implicitWidth)
|
||||
height: Math.round(implicitHeight)
|
||||
|
||||
Behavior on Layout.maximumWidth {
|
||||
Anim {}
|
||||
|
||||
@@ -38,6 +38,7 @@ WidgetBase {
|
||||
anchors.topMargin: root.horizontal ? 0 : Tokens.padding.extraSmall
|
||||
columns: root.horizontal ? -1 : 1
|
||||
rowSpacing: -2
|
||||
columnSpacing: Tokens.spacing.extraSmall
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
@@ -12,13 +12,14 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real activeWorkspaceMargin: Math.ceil(Tokens.padding.medium / 4)
|
||||
property real activeWorkspaceMargin: Tokens.padding.extraSmall / 2
|
||||
readonly property int spacing: Tokens.spacing.extraSmall / 2
|
||||
readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1
|
||||
required property bool horizontal
|
||||
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
|
||||
required property ShellScreen screen
|
||||
readonly property real shortSize: Math.max(Config.bar.height, Tokens.sizes.bar.innerSize)
|
||||
readonly property real size: (workspaceButtonWidth * workspacesShown) + activeWorkspaceMargin * 2
|
||||
readonly property real size: (workspaceButtonWidth * workspacesShown) + (spacing * Math.max(0, workspacesShown - 1)) + activeWorkspaceMargin * 2
|
||||
property int workspaceButtonWidth: (horizontal ? bgRect.implicitHeight : bgRect.implicitWidth) - root.activeWorkspaceMargin * 2
|
||||
property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown
|
||||
readonly property list<var> workspaces: Hyprland.workspaces.values.filter(w => w.monitor === root.monitor)
|
||||
@@ -59,15 +60,17 @@ Item {
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.activeWorkspaceMargin
|
||||
columnSpacing: 0
|
||||
columnSpacing: root.spacing
|
||||
columns: root.horizontal ? root.workspacesShown : 1
|
||||
rowSpacing: 0
|
||||
rowSpacing: root.spacing
|
||||
rows: root.horizontal ? 1 : root.workspacesShown
|
||||
layer.enabled: true
|
||||
layer.mipmap: true
|
||||
|
||||
Repeater {
|
||||
model: root.workspaces
|
||||
|
||||
Button {
|
||||
CustomRect {
|
||||
id: button
|
||||
|
||||
required property int index
|
||||
@@ -75,27 +78,19 @@ Item {
|
||||
|
||||
implicitHeight: indicator.indicatorThickness
|
||||
implicitWidth: indicator.indicatorThickness
|
||||
width: root.workspaceButtonWidth
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
background: Item {
|
||||
id: workspaceButtonBackground
|
||||
|
||||
implicitHeight: root.workspaceButtonWidth
|
||||
implicitWidth: root.workspaceButtonWidth
|
||||
|
||||
CustomText {
|
||||
anchors.centerIn: parent
|
||||
color: Colors.palette.m3onSecondaryContainer
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: button.modelData?.name ?? ""
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
CustomText {
|
||||
anchors.centerIn: parent
|
||||
color: Colors.palette.m3onSecondaryContainer
|
||||
text: button.modelData?.name ?? ""
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
const ws = button.modelData.name;
|
||||
Hyprland.dispatch(Hyprland.usingLua ? `hl.dsp.focus({ workspace= "${ws}"})` : `workspace ${ws}`);
|
||||
StateLayer {
|
||||
onPressed: {
|
||||
const ws = button.modelData.name;
|
||||
Hyprland.dispatch(Hyprland.usingLua ? `hl.dsp.focus({ workspace= "${ws}"})` : `workspace ${ws}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,8 +99,10 @@ Item {
|
||||
CustomRect {
|
||||
id: indicator
|
||||
|
||||
property real indicatorLength: (Math.abs(idxPair.idx1 - idxPair.idx2) + 1) * root.workspaceButtonWidth
|
||||
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin
|
||||
readonly property real step: root.workspaceButtonWidth + root.spacing
|
||||
|
||||
property real indicatorLength: (Math.abs(idxPair.idx1 - idxPair.idx2) + 1) * root.workspaceButtonWidth + Math.abs(idxPair.idx1 - idxPair.idx2) * root.spacing
|
||||
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * step + root.activeWorkspaceMargin
|
||||
property real indicatorThickness: root.workspaceButtonWidth
|
||||
|
||||
anchors.horizontalCenter: root.horizontal ? undefined : parent.horizontalCenter
|
||||
@@ -129,9 +126,10 @@ Item {
|
||||
implicitHeight: grid.height
|
||||
implicitWidth: grid.width
|
||||
source: grid
|
||||
autoPaddingEnabled: false
|
||||
sourceColor: Colors.palette.m3onSurface
|
||||
x: root.horizontal ? -indicator.x + 3 : 0
|
||||
y: root.horizontal ? 0 : -indicator.y + 3
|
||||
x: root.horizontal ? -indicator.x + root.activeWorkspaceMargin : 0
|
||||
y: root.horizontal ? 0 : -indicator.y + root.activeWorkspaceMargin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,7 @@ Item {
|
||||
}
|
||||
|
||||
Behavior on offsetScale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on x {
|
||||
enabled: !root.horizontal || root.offsetScale < 1
|
||||
|
||||
@@ -26,20 +26,15 @@ StackView {
|
||||
initialItem: SubMenu {
|
||||
handle: root.trayItem
|
||||
}
|
||||
popEnter: NoAnim {
|
||||
}
|
||||
popExit: NoAnim {
|
||||
}
|
||||
pushEnter: NoAnim {
|
||||
}
|
||||
pushExit: NoAnim {
|
||||
}
|
||||
popEnter: NoAnim {}
|
||||
popExit: NoAnim {}
|
||||
pushEnter: NoAnim {}
|
||||
pushExit: NoAnim {}
|
||||
|
||||
Component {
|
||||
id: subMenuComp
|
||||
|
||||
SubMenu {
|
||||
}
|
||||
SubMenu {}
|
||||
}
|
||||
|
||||
component NoAnim: Transition {
|
||||
@@ -59,12 +54,10 @@ StackView {
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: shown = true
|
||||
|
||||
@@ -54,7 +54,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomRect {
|
||||
id: preview
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
@@ -64,6 +64,7 @@ Item {
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
implicitWidth: ClipHistory.previewIsImage ? Math.max(Math.min(imagePreview.sourceSize.width + Tokens.padding.large * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) : Math.max(Math.min(textPreviewColumn.width + textPreviewColumn.anchors.margins * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth)
|
||||
radius: 25
|
||||
clip: true
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
@@ -228,11 +229,12 @@ Item {
|
||||
anchors.fill: parent
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
ClipRect {
|
||||
Item {
|
||||
id: textRect
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
|
||||
Item {
|
||||
id: textWrapper
|
||||
|
||||
@@ -307,12 +307,10 @@ CustomMouseArea {
|
||||
}
|
||||
}
|
||||
Behavior on x {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on y {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onTodayItemChanged: {
|
||||
|
||||
@@ -77,8 +77,7 @@ Item {
|
||||
implicitSize: height
|
||||
|
||||
Behavior on clampedVal {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
|
||||
@@ -33,8 +33,7 @@ Item {
|
||||
Repeater {
|
||||
id: shapes
|
||||
|
||||
DriftingShape {
|
||||
}
|
||||
DriftingShape {}
|
||||
}
|
||||
|
||||
FrameAnimation {
|
||||
@@ -80,8 +79,7 @@ Item {
|
||||
shape: root.shapePool[Math.floor(Math.random() * root.shapePool.length)]
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
|
||||
@@ -18,8 +18,7 @@ Item {
|
||||
layer.enabled: true
|
||||
|
||||
Behavior on fallbackColor {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
layer.effect: MultiEffect {
|
||||
blurMax: 1
|
||||
|
||||
@@ -54,14 +54,12 @@ Item {
|
||||
Behavior on x {
|
||||
enabled: !mouseArea.drag.active && !root.isSnapping && !root.iconsRoot.selectedIcons.includes(root.filePath)
|
||||
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on y {
|
||||
enabled: !mouseArea.drag.active && !root.isSnapping && !root.iconsRoot.selectedIcons.includes(root.filePath)
|
||||
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -85,8 +83,7 @@ Item {
|
||||
y: (root.iconsRoot.selectedIcons.includes(root.filePath) && root.iconsRoot.dragLeader !== "" && root.iconsRoot.dragLeader !== root.filePath) ? root.iconsRoot.groupDragY : 0
|
||||
}
|
||||
transitions: Transition {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onXChanged: {
|
||||
@@ -208,8 +205,7 @@ Item {
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,8 +264,7 @@ Item {
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,7 @@ CustomRect {
|
||||
Loader {
|
||||
active: isSeparator
|
||||
|
||||
sourceComponent: DockSeparator {
|
||||
}
|
||||
sourceComponent: DockSeparator {}
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
|
||||
Executable → Regular
+1
-2
@@ -41,8 +41,7 @@ CarouselView {
|
||||
tileHeight: focalWidth / 16 * 9
|
||||
|
||||
delegate: Component {
|
||||
WallpaperTile {
|
||||
}
|
||||
WallpaperTile {}
|
||||
}
|
||||
|
||||
Component.onDestruction: Wallpapers.stopPreview()
|
||||
|
||||
@@ -55,15 +55,15 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
CachingImage {
|
||||
Image {
|
||||
id: img
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
cache: true
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
height: root.tileHeight
|
||||
path: root.modelData ? root.modelData.thumbnailPath : ""
|
||||
source: root.modelData ? root.modelData.thumbnailPath : ""
|
||||
smooth: root.isCurrent
|
||||
sourceSize.height: root.tileHeight
|
||||
sourceSize.width: root.tileHeight * (16 / 9)
|
||||
|
||||
@@ -22,8 +22,7 @@ Searcher {
|
||||
|
||||
model: Config.launcher.actions.values.filter(a => (a.enabled ?? true) && (Config.launcher.enableDangerousActions || !(a.dangerous ?? false)))
|
||||
|
||||
Action {
|
||||
}
|
||||
Action {}
|
||||
}
|
||||
|
||||
component Action: QtObject {
|
||||
|
||||
@@ -5,14 +5,13 @@ import qs.Helpers
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
ClipRect {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
radius: Tokens.rounding.largeIncreased
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
@@ -50,22 +49,10 @@ ClipRect {
|
||||
lock: root.lock
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
NotifDock {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
topRightRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
NotifDock {
|
||||
lock: root.lock
|
||||
}
|
||||
}
|
||||
lock: root.lock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,9 +221,9 @@ WlSessionLockSurface {
|
||||
|
||||
anchors.fill: parent
|
||||
color: Colors.palette.m3surface
|
||||
layer.enabled: true
|
||||
opacity: Colors.transparency.enabled ? Colors.transparency.base : 1
|
||||
radius: lockContent.radius
|
||||
layer.enabled: true
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
blurMax: 15
|
||||
|
||||
+31
-26
@@ -7,39 +7,44 @@ import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
ClipRect {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
FadeImage {
|
||||
id: image
|
||||
|
||||
ClipRect {
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
layer.enabled: true
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
source: Players.getArtUrl(Players.active)
|
||||
sourceSize: {
|
||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||
return Qt.size(width * dpr, height * dpr);
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.StandardExtraLarge
|
||||
}
|
||||
}
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
FadeImage {
|
||||
id: image
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
color: Colors.palette.m3surface
|
||||
opacity: 0.7
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
source: Players.getArtUrl(Players.active)
|
||||
sourceSize: {
|
||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||
return Qt.size(width * dpr, height * dpr);
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.StandardExtraLarge
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
color: Colors.palette.m3surface
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +82,7 @@ ClipRect {
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
IconButton {
|
||||
enabled: Players.active?.canGoPrevious
|
||||
enabled: Players.active?.canGoPrevious ?? false
|
||||
icon: "skip_previous"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
@@ -88,7 +93,7 @@ ClipRect {
|
||||
|
||||
IconButton {
|
||||
checked: Players.active?.isPlaying ?? false
|
||||
enabled: Players.active?.canTogglePlaying
|
||||
enabled: Players.active?.canTogglePlaying ?? false
|
||||
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
||||
implicitWidth: implicitHeight + Tokens.padding.largeIncreased * 2
|
||||
isRound: true
|
||||
@@ -98,7 +103,7 @@ ClipRect {
|
||||
}
|
||||
|
||||
IconButton {
|
||||
enabled: Players.active?.canGoNext
|
||||
enabled: Players.active?.canGoNext ?? false
|
||||
icon: "skip_next"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
|
||||
+94
-104
@@ -11,126 +11,116 @@ import ZShell.Config
|
||||
import qs.Daemons
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.small
|
||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
topRightRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
text: NotifServer.list.length > 0 ? qsTr("%1 notification%2").arg(NotifServer.list.length).arg(NotifServer.list.length === 1 ? "" : "s") : qsTr("Notifications")
|
||||
}
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
ClipRect {
|
||||
id: clipRect
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
opacity: NotifServer.list.length > 0 ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.extraLarge
|
||||
}
|
||||
}
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
Image {
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
layer.enabled: true
|
||||
source: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/dino.png`)
|
||||
sourceSize.width: clipRect.width * 0.8
|
||||
|
||||
layer.effect: Coloriser {
|
||||
brightness: 1
|
||||
colorizationColor: Colors.palette.m3outlineVariant
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
text: qsTr("No Notifications")
|
||||
}
|
||||
}
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
text: NotifServer.list.length > 0 ? qsTr("%1 notification%2").arg(NotifServer.list.length).arg(NotifServer.list.length === 1 ? "" : "s") : qsTr("Notifications")
|
||||
}
|
||||
|
||||
CustomListView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
spacing: Tokens.spacing.small
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
opacity: NotifServer.list.length > 0 ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.extraLarge
|
||||
}
|
||||
}
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
from: 0
|
||||
property: "scale"
|
||||
to: 1
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
text: qsTr("No Notifications")
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate: NotifGroup {}
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const list = NotifServer.notClosed.map(n => [n.appName, null]);
|
||||
return [...new Map(list).keys()];
|
||||
}
|
||||
}
|
||||
move: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
CustomListView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
add: Transition {
|
||||
Anim {
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
property: "scale"
|
||||
to: 0.6
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
from: 0
|
||||
property: "scale"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
delegate: NotifGroup {}
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const list = NotifServer.notClosed.map(n => [n.appName, null]);
|
||||
return [...new Map(list).keys()];
|
||||
}
|
||||
}
|
||||
move: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
|
||||
Anim {
|
||||
property: "scale"
|
||||
to: 0.6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
animate: true
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.title.large
|
||||
font: Tokens.font.body.large
|
||||
text: Weather.description
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ ColumnLayout {
|
||||
|
||||
animate: true
|
||||
color: Colors.palette.m3primary
|
||||
font: Tokens.font.body.large
|
||||
font: Tokens.font.title.large
|
||||
text: Weather.temp
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ ColumnLayout {
|
||||
animate: true
|
||||
color: Colors.palette.m3secondary
|
||||
text: Weather.icon
|
||||
fontStyle: Tokens.font.icon.large
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
animate: true
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.normal
|
||||
font: Tokens.font.body.medium
|
||||
text: {
|
||||
const today = Weather.forecast[0];
|
||||
return qsTr("High %1 • Low %2").arg(Weather.formatTemp(today?.maxTempC)).arg(Weather.formatTemp(today?.minTempC));
|
||||
|
||||
@@ -36,7 +36,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
VerticalFadeListView {
|
||||
CustomListView {
|
||||
id: layout
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
@@ -45,13 +45,20 @@ CustomRect {
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.right: parent.right
|
||||
implicitHeight: contentItem.childrenRect.height
|
||||
model: Weather.hourlyForecast
|
||||
currentIndex: Weather.currentHourIndex
|
||||
// highlightFollowsCurrentItem: true
|
||||
// preferredHighlightEnd: currentItem.implicitWidth
|
||||
// preferredHighlightBegin: 0
|
||||
// highlightRangeMode: ListView.ApplyRange
|
||||
clip: true
|
||||
model: Weather.hourlyForecast.slice(Weather.currentHourIndex - 1)
|
||||
orientation: VerticalFadeListView.Horizontal
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: hour
|
||||
|
||||
readonly property bool current: ListView.view.currentItem === this
|
||||
readonly property var cond: modelData
|
||||
required property int index
|
||||
required property var modelData
|
||||
@@ -60,7 +67,7 @@ CustomRect {
|
||||
|
||||
MaterialShape {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Qt.alpha(Colors.palette.m3primary, hour.index === Weather.currentHourIndex ? 1 : 0)
|
||||
color: Qt.alpha(Colors.palette.m3primary, hour.current ? 1 : 0)
|
||||
implicitSize: temp.implicitHeight + Tokens.padding.small * 2
|
||||
shape: MaterialShape.Cookie4Sided
|
||||
|
||||
@@ -72,7 +79,7 @@ CustomRect {
|
||||
id: temp
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: hour.index === Weather.currentHourIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
color: hour.current ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
font: Tokens.font.body.medium
|
||||
text: Weather.formatTemp(hour.cond.tempC).slice(0, -1) // Remove C/F
|
||||
}
|
||||
@@ -96,7 +103,7 @@ CustomRect {
|
||||
Layout.topMargin: Tokens.spacing.extraSmall
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.small
|
||||
text: hour.index === 0 ? qsTr("Now") : Qt.formatDateTime(new Date(hour.cond.timestamp.replace("T", " ")), Config.services.useTwelveHourClock ? "ha" : "hh:00")
|
||||
text: hour.current ? qsTr("Now") : Qt.formatDateTime(new Date(hour.cond.timestamp.replace("T", " ")), Config.services.useTwelveHourClock ? "ha" : "hh:00")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ CustomRect {
|
||||
return base + brief.anchors.topMargin;
|
||||
}
|
||||
radius: Tokens.rounding.small
|
||||
topLeftRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
Timer {
|
||||
interval: 900000 // 15 minutes
|
||||
@@ -48,7 +49,6 @@ CustomRect {
|
||||
anchors.right: parent.right
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Forecast {
|
||||
}
|
||||
sourceComponent: Forecast {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,8 +131,7 @@ Item {
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ ClipRect {
|
||||
radius: Tokens.rounding.largeIncreased
|
||||
|
||||
Behavior on animPerc {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Contents {
|
||||
|
||||
@@ -51,8 +51,7 @@ CustomRect {
|
||||
value: Memory.percentage
|
||||
|
||||
Behavior on clampedVal {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
@@ -81,8 +81,7 @@ Item {
|
||||
|
||||
active: Config.dashboard.performance.showStorage
|
||||
|
||||
sourceComponent: StorageCard {
|
||||
}
|
||||
sourceComponent: StorageCard {}
|
||||
}
|
||||
|
||||
WrappedLoader {
|
||||
@@ -90,8 +89,7 @@ Item {
|
||||
|
||||
active: Config.dashboard.performance.showMemory
|
||||
|
||||
sourceComponent: MemoryCard {
|
||||
}
|
||||
sourceComponent: MemoryCard {}
|
||||
}
|
||||
|
||||
WrappedLoader {
|
||||
@@ -99,8 +97,7 @@ Item {
|
||||
|
||||
active: Config.dashboard.performance.showVram
|
||||
|
||||
sourceComponent: VramCard {
|
||||
}
|
||||
sourceComponent: VramCard {}
|
||||
}
|
||||
|
||||
WrappedLoader {
|
||||
@@ -108,8 +105,7 @@ Item {
|
||||
|
||||
active: Config.dashboard.performance.showNetwork && !vramCard.active
|
||||
|
||||
sourceComponent: NetworkCard {
|
||||
}
|
||||
sourceComponent: NetworkCard {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +114,7 @@ Item {
|
||||
|
||||
active: Config.dashboard.performance.showNetwork && vramCard.active
|
||||
|
||||
sourceComponent: NetworkCard {
|
||||
}
|
||||
sourceComponent: NetworkCard {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +122,7 @@ Item {
|
||||
Layout.fillWidth: false
|
||||
active: Battery.isLaptop && Config.dashboard.performance.showBattery
|
||||
|
||||
sourceComponent: BatteryTank {
|
||||
}
|
||||
sourceComponent: BatteryTank {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ CustomRect {
|
||||
topRightRadius: first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
|
||||
@@ -14,10 +14,13 @@ Item {
|
||||
id: wrapper
|
||||
|
||||
readonly property bool isVideo: Wallpapers.currentIsVideo
|
||||
readonly property string currentWallpaper: Wallpapers.current
|
||||
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
|
||||
readonly property real maxZoom: 5.0
|
||||
readonly property bool changesMade: {
|
||||
cropRevision;
|
||||
Wallpapers.cropVersion;
|
||||
wrapper.currentWallpaper;
|
||||
|
||||
const r = cropRectLoader.item;
|
||||
if (!r || !currentScreen || scaledImg.status !== Image.Ready)
|
||||
@@ -115,6 +118,9 @@ Item {
|
||||
refreshScreens();
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
onCurrentWallpaperChanged: {
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
onSelectedScreenIndexChanged: {
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ Item {
|
||||
id: container
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: opacity < 1
|
||||
objectName: "PageContainer"
|
||||
|
||||
Component.onCompleted: root.loadPage(root.sState.currentPageIdx)
|
||||
|
||||
@@ -51,7 +51,7 @@ PageBase {
|
||||
|
||||
WallItem {
|
||||
id: wallItem
|
||||
required property FileSystemEntry modelData
|
||||
required property WallpaperEntry modelData
|
||||
|
||||
enabled: modelData
|
||||
opacity: modelData ? 1 : 0
|
||||
|
||||
@@ -13,6 +13,42 @@ import qs.Services
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
readonly property list<MenuItem> wallTransitions: [
|
||||
MenuItem {
|
||||
text: "Bowtie"
|
||||
},
|
||||
MenuItem {
|
||||
text: "Fade"
|
||||
},
|
||||
MenuItem {
|
||||
text: "LinearBlur"
|
||||
},
|
||||
MenuItem {
|
||||
text: "WindowSlices"
|
||||
},
|
||||
MenuItem {
|
||||
text: "WipeRight"
|
||||
},
|
||||
MenuItem {
|
||||
text: "WipeDown"
|
||||
},
|
||||
MenuItem {
|
||||
text: "TilesWave"
|
||||
},
|
||||
MenuItem {
|
||||
text: "SplitSlideInHorizontal"
|
||||
},
|
||||
MenuItem {
|
||||
text: "Hexagonalize"
|
||||
},
|
||||
MenuItem {
|
||||
text: "DoomMelt"
|
||||
},
|
||||
MenuItem {
|
||||
text: "Random"
|
||||
}
|
||||
]
|
||||
|
||||
title: qsTr("Wallpaper & style")
|
||||
|
||||
ColumnLayout {
|
||||
@@ -73,6 +109,18 @@ PageBase {
|
||||
onToggled: Config.background.enabled = checked
|
||||
}
|
||||
|
||||
SelectRow {
|
||||
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
||||
active: root.wallTransitions[Config.background.wallTransition]
|
||||
menuItems: root.wallTransitions
|
||||
menuOnTop: true
|
||||
settingAnchor: "style-wallpaper-transition"
|
||||
subtext: qsTr("Currently selected: %1").arg(active.text)
|
||||
text: qsTr("Wallpaper transition")
|
||||
|
||||
onSelected: item => Config.background.wallTransition = root.wallTransitions.indexOf(item)
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
||||
checked: Colors.transparency.enabled
|
||||
|
||||
@@ -337,7 +337,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
Item {
|
||||
id: expandedTool
|
||||
|
||||
anchors.top: collapsedRow.bottom
|
||||
@@ -348,8 +348,16 @@ Item {
|
||||
anchors.topMargin: Tokens.spacing.medium
|
||||
|
||||
implicitHeight: fullToolCall.implicitHeight + fullToolCall.anchors.margins * 2
|
||||
radius: Tokens.rounding.medium
|
||||
color: Colors.palette.m3surfaceContainerLow
|
||||
clip: true
|
||||
|
||||
CustomRect {
|
||||
id: bg
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
radius: Tokens.rounding.medium
|
||||
color: Colors.palette.m3surfaceContainerLow
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: fullToolCall
|
||||
|
||||
@@ -59,7 +59,6 @@ Item {
|
||||
id: container
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: opacity < 1
|
||||
objectName: "ConversationContainer"
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -125,7 +125,7 @@ Item {
|
||||
current: root.current
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
Item {
|
||||
id: expandedRect
|
||||
|
||||
anchors.left: parent.left
|
||||
@@ -133,11 +133,19 @@ Item {
|
||||
anchors.top: layout.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: Tokens.spacing.small
|
||||
color: Colors.palette.m3surfaceContainerLow
|
||||
implicitHeight: expandedContent.contentHeight + expandedContent.anchors.margins * 2
|
||||
opacity: root.expanded ? 1 : 0
|
||||
radius: Tokens.rounding.medium
|
||||
visible: opacity > 0
|
||||
clip: true
|
||||
|
||||
CustomRect {
|
||||
id: bg
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
radius: Tokens.rounding.medium
|
||||
color: Colors.palette.m3surfaceContainerLow
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
|
||||
@@ -26,8 +26,7 @@ CustomRect {
|
||||
state: expanded ? "expanded" : ""
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
states: State {
|
||||
name: "expanded"
|
||||
@@ -116,8 +115,7 @@ CustomRect {
|
||||
anchors.topMargin: Tokens.spacing.extraSmall
|
||||
shouldBeActive: root.expanded
|
||||
|
||||
sourceComponent: ExpandedBody {
|
||||
}
|
||||
sourceComponent: ExpandedBody {}
|
||||
}
|
||||
|
||||
component ExpandedBody: ColumnLayout {
|
||||
|
||||
@@ -58,18 +58,15 @@ LazyListView {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on x {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on y {
|
||||
enabled: notif.LazyListView.ready
|
||||
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onPositionChanged: event => {
|
||||
|
||||
@@ -52,8 +52,7 @@ CustomRect {
|
||||
radius: Tokens.rounding.large
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
@@ -155,8 +154,7 @@ CustomRect {
|
||||
spacing: root.expanded ? Math.round(Tokens.spacing.extraSmall) : 0
|
||||
|
||||
Behavior on spacing {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -214,12 +212,10 @@ CustomRect {
|
||||
text: "expand_more"
|
||||
|
||||
Behavior on Layout.topMargin {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on rotation {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,18 +59,15 @@ LazyListView {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on x {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on y {
|
||||
enabled: notif.LazyListView.ready
|
||||
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: modelData?.lock(this)
|
||||
|
||||
@@ -17,8 +17,7 @@ ShapePath {
|
||||
strokeWidth: -1
|
||||
|
||||
Behavior on fillColor {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
PathLine {
|
||||
|
||||
@@ -21,6 +21,13 @@ pkg_check_modules(EGL REQUIRED IMPORTED_TARGET egl)
|
||||
pkg_check_modules(WAYLAND_EGL REQUIRED IMPORTED_TARGET wayland-egl)
|
||||
pkg_check_modules(GL REQUIRED IMPORTED_TARGET gl)
|
||||
pkg_check_modules(MPV REQUIRED IMPORTED_TARGET mpv)
|
||||
|
||||
pkg_check_modules(AVCODEC REQUIRED IMPORTED_TARGET libavcodec)
|
||||
pkg_check_modules(AVFORMAT REQUIRED IMPORTED_TARGET libavformat)
|
||||
pkg_check_modules(AVUTIL REQUIRED IMPORTED_TARGET libavutil)
|
||||
pkg_check_modules(LIBVA QUIET IMPORTED_TARGET libva)
|
||||
|
||||
find_package(CUDAToolkit QUIET)
|
||||
pkg_check_modules(Qalculate IMPORTED_TARGET libqalculate REQUIRED)
|
||||
pkg_check_modules(Pipewire IMPORTED_TARGET libpipewire-0.3 REQUIRED)
|
||||
pkg_check_modules(Aubio IMPORTED_TARGET aubio REQUIRED)
|
||||
|
||||
@@ -218,7 +218,7 @@ void BlobInvertedRect::outerCornerRadii(float out[4]) const {
|
||||
const auto base = std::min(static_cast<float>(m_outerRadius), maxR);
|
||||
out[0] = std::min(
|
||||
m_outerTopRightRadius >= 0 ? static_cast<float>(m_outerTopRightRadius)
|
||||
: base,
|
||||
: base,
|
||||
maxR);
|
||||
out[1] = std::min(
|
||||
m_outerBottomRightRadius >= 0
|
||||
|
||||
@@ -60,11 +60,15 @@ class BlobInvertedRect : public BlobShape {
|
||||
void setOuterRadius(qreal v);
|
||||
|
||||
// Per-corner outer radii. -1 falls back to outerRadius (mirrors BlobRect).
|
||||
[[nodiscard]] qreal outerTopLeftRadius() const { return m_outerTopLeftRadius; }
|
||||
[[nodiscard]] qreal outerTopLeftRadius() const {
|
||||
return m_outerTopLeftRadius;
|
||||
}
|
||||
|
||||
void setOuterTopLeftRadius(qreal v);
|
||||
|
||||
[[nodiscard]] qreal outerTopRightRadius() const { return m_outerTopRightRadius; }
|
||||
[[nodiscard]] qreal outerTopRightRadius() const {
|
||||
return m_outerTopRightRadius;
|
||||
}
|
||||
|
||||
void setOuterTopRightRadius(qreal v);
|
||||
|
||||
|
||||
@@ -240,8 +240,7 @@ void BlobShape::updatePolish() {
|
||||
m_cachedInvertedRadius = 0;
|
||||
memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter));
|
||||
memset(m_cachedInvertedInner, 0, sizeof(m_cachedInvertedInner));
|
||||
memset(
|
||||
m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
||||
memset(m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
||||
|
||||
auto* inv = m_group->invertedRect();
|
||||
if (inv) {
|
||||
|
||||
@@ -37,6 +37,7 @@ list(APPEND CMAKE_AUTOMOC_MACRO_NAMES
|
||||
add_subdirectory(Settings)
|
||||
add_subdirectory(Models)
|
||||
add_subdirectory(Internal)
|
||||
add_subdirectory(Wallpaper)
|
||||
add_subdirectory(Services)
|
||||
add_subdirectory(Components)
|
||||
add_subdirectory(Blobs)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
#include "common.hpp"
|
||||
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace ZShell::config {
|
||||
|
||||
class Background : public settings::ObjectNode {
|
||||
@@ -9,6 +12,8 @@ class Background : public settings::ObjectNode {
|
||||
|
||||
CONFIG_PROPERTY(bool, enabled, true)
|
||||
CONFIG_PROPERTY(int, wallFadeDuration, 300)
|
||||
CONFIG_ENUM_PROPERTY(
|
||||
WallpaperTransition, wallTransition, WallpaperTransition::Fade)
|
||||
};
|
||||
|
||||
} // namespace ZShell::config
|
||||
|
||||
@@ -23,6 +23,19 @@ ENUM(NotifsFullscreen, On, Off)
|
||||
ENUM(TemperatureUnit, Auto, Celsius, Fahrenheit, Kelvin)
|
||||
ENUM(DataUnit, Binary, Decimal)
|
||||
ENUM(ClockFormat, Auto, TwelveHour, TwentyFourHour)
|
||||
ENUM(
|
||||
WallpaperTransition,
|
||||
Bowtie,
|
||||
Fade,
|
||||
LinearBlur,
|
||||
WindowSlices,
|
||||
WipeRight,
|
||||
WipeDown,
|
||||
TilesWave,
|
||||
SplitSlideInHorizontal,
|
||||
Hexagonalize,
|
||||
DoomMelt,
|
||||
Random)
|
||||
|
||||
#undef ENUM
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
#include <hyprland/src/plugins/PluginAPI.hpp>
|
||||
#include <hyprland/src/render/OpenGL.hpp>
|
||||
#include <hyprland/src/render/Renderer.hpp>
|
||||
#include <hyprland/src/render/Context.hpp>
|
||||
#undef private
|
||||
|
||||
#include <hyprland/src/managers/EventManager.hpp>
|
||||
#include <hyprland/src/ipc/s2/S2.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
@@ -27,6 +28,7 @@ extern "C" {
|
||||
|
||||
using ZShell::services::kelvinToGain;
|
||||
|
||||
using Render::CRenderContext;
|
||||
using Render::IFramebuffer;
|
||||
using Render::GL::CHyprOpenGLImpl;
|
||||
using Render::GL::g_pHyprOpenGL;
|
||||
@@ -149,25 +151,14 @@ static bool gainIsNeutral(const std::array<float, 3>& gain) {
|
||||
}
|
||||
|
||||
static CFunctionHook* g_pBeginHook = nullptr;
|
||||
using origBegin = void (*)(
|
||||
using origBegin = bool (*)(
|
||||
CHyprOpenGLImpl*,
|
||||
CRenderContext&,
|
||||
PHLMONITOR,
|
||||
const CRegion&,
|
||||
SP<IFramebuffer>,
|
||||
std::optional<CRegion>);
|
||||
|
||||
static CFunctionHook* g_pSaveMirrorHook = nullptr;
|
||||
using origSaveMirror = bool (*)(CHyprOpenGLImpl*, const CBox&);
|
||||
|
||||
static bool hkSaveBufferForMirror(CHyprOpenGLImpl* thisptr, const CBox& box) {
|
||||
const bool saved = thisptr->m_applyFinalShader;
|
||||
thisptr->m_applyFinalShader = false;
|
||||
const bool result = (*reinterpret_cast<origSaveMirror>(
|
||||
g_pSaveMirrorHook->m_original))(thisptr, box);
|
||||
thisptr->m_applyFinalShader = saved;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void installShader() {
|
||||
auto* gl = g_pHyprOpenGL.get();
|
||||
if (!gl) return;
|
||||
@@ -198,34 +189,33 @@ static void setUniformGain(const std::array<float, 3>& gain) {
|
||||
shader->setUniformFloat3(SHADER_TINT, gain[0], gain[1], gain[2]);
|
||||
}
|
||||
|
||||
static void hkBegin(
|
||||
static bool hkBegin(
|
||||
CHyprOpenGLImpl* thisptr,
|
||||
CRenderContext& ctx,
|
||||
PHLMONITOR mon,
|
||||
const CRegion& damage,
|
||||
SP<IFramebuffer> fb,
|
||||
std::optional<CRegion> finalDamage) {
|
||||
(*reinterpret_cast<origBegin>(
|
||||
g_pBeginHook->m_original))(thisptr, mon, damage, fb, finalDamage);
|
||||
const bool ok = (*reinterpret_cast<origBegin>(
|
||||
g_pBeginHook->m_original))(thisptr, ctx, mon, damage, fb, finalDamage);
|
||||
if (!ok) return false;
|
||||
|
||||
if (fb) {
|
||||
auto* renderer = g_pHyprRenderer.get();
|
||||
if (renderer) renderer->m_renderData.blockScreenShader = true;
|
||||
}
|
||||
if (fb) ctx.m_data.blockScreenShader = true;
|
||||
|
||||
if (!g_active) return;
|
||||
if (!g_active) return true;
|
||||
|
||||
auto* gl = g_pHyprOpenGL.get();
|
||||
if (!gl) return;
|
||||
if (!gl) return true;
|
||||
|
||||
if (gl->m_finalScreenShader.get() && gl->m_finalScreenShader->program() < 1)
|
||||
installShader();
|
||||
if (!gl->m_finalScreenShader.get() ||
|
||||
gl->m_finalScreenShader->program() < 1)
|
||||
return;
|
||||
return true;
|
||||
|
||||
if (g_transition.active) {
|
||||
auto* monPtr = mon.get();
|
||||
if (!monPtr) return;
|
||||
if (!monPtr) return true;
|
||||
const float elapsed =
|
||||
std::chrono::duration<float>(
|
||||
std::chrono::steady_clock::now() - g_transition.start)
|
||||
@@ -265,6 +255,7 @@ static void hkBegin(
|
||||
} else if (g_uniformMode) {
|
||||
setUniformGain(g_currentGain);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr int KELVIN_MIN = 2000;
|
||||
@@ -333,9 +324,10 @@ static int luaNightlightState(lua_State* L) {
|
||||
}
|
||||
|
||||
static void postStateEvent(bool enabled, bool animating) {
|
||||
if (!g_pEventManager) return;
|
||||
g_pEventManager->postEvent(
|
||||
SHyprIPCEvent{
|
||||
auto& sock = IPC::Socket2::sock();
|
||||
if (!sock) return;
|
||||
sock->postEvent(
|
||||
IPC::Socket2::SEvent{
|
||||
.event = "zshell-nightlight",
|
||||
.data = std::format(
|
||||
"{},{},{},{}",
|
||||
@@ -377,31 +369,6 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
||||
throw std::runtime_error("[zshell-nightlight] version mismatch");
|
||||
}
|
||||
|
||||
static const auto SAVEMIRROR_METHODS =
|
||||
HyprlandAPI::findFunctionsByName(PHANDLE, "saveBufferForMirror");
|
||||
const SFunctionMatch* saveMirrorTarget = findExactMethod(
|
||||
SAVEMIRROR_METHODS,
|
||||
manglePrefix("CHyprOpenGLImpl", "saveBufferForMirror"));
|
||||
|
||||
if (saveMirrorTarget) {
|
||||
g_pSaveMirrorHook = HyprlandAPI::createFunctionHook(
|
||||
PHANDLE,
|
||||
saveMirrorTarget->address,
|
||||
reinterpret_cast<void*>(&hkSaveBufferForMirror));
|
||||
if (g_pSaveMirrorHook && g_pSaveMirrorHook->hook())
|
||||
dlog(
|
||||
"saveBufferForMirror hook installed: " +
|
||||
saveMirrorTarget->signature);
|
||||
}
|
||||
|
||||
if (!g_pSaveMirrorHook)
|
||||
HyprlandAPI::addNotification(
|
||||
PHANDLE,
|
||||
"[zshell-nightlight] WARNING: couldn't hook saveBufferForMirror — "
|
||||
"captures may still show the nightlight",
|
||||
CHyprColor{1.f, 0.6f, 0.2f, 1.f},
|
||||
8000);
|
||||
|
||||
static const auto METHODS =
|
||||
HyprlandAPI::findFunctionsByName(PHANDLE, "begin");
|
||||
const SFunctionMatch* target =
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
wl_proto(wlp-layer-shell wlr-layer-shell-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}/protocols")
|
||||
wl_proto(wlp-viewporter viewporter "${WAYLAND_PROTOCOLS}/stable/viewporter")
|
||||
wl_proto(wlp-fractional-scale fractional-scale-v1 "${WAYLAND_PROTOCOLS}/staging/fractional-scale")
|
||||
wl_proto(wlp-xdg-shell xdg-shell "${WAYLAND_PROTOCOLS}/stable/xdg-shell")
|
||||
|
||||
qml_module(ZShell-internal
|
||||
URI ZShell.Internal
|
||||
INCLUDE_PREFIX internal
|
||||
@@ -13,10 +8,6 @@ qml_module(ZShell-internal
|
||||
circularindicatormanager.hpp circularindicatormanager.cpp
|
||||
circularbuffer.hpp circularbuffer.cpp
|
||||
arcgauge.hpp arcgauge.cpp
|
||||
wallpaperimage.hpp wallpaperimage.cpp
|
||||
wallpaperservice.hpp wallpaperservice.cpp
|
||||
wallpapersurface.hpp wallpapersurface.cpp
|
||||
wallpaperobject.hpp wallpaperobject.cpp
|
||||
lidwatcher.hpp lidwatcher.cpp
|
||||
visualizerbars.hpp visualizerbars.cpp
|
||||
linearindicatormanager.hpp linearindicatormanager.cpp
|
||||
@@ -27,17 +18,14 @@ qml_module(ZShell-internal
|
||||
Qt::Gui
|
||||
Qt::Quick
|
||||
Qt::Concurrent
|
||||
Qt::Sql
|
||||
Qt::Core
|
||||
Qt::Network
|
||||
Qt::DBus
|
||||
Qt::CanvasPainter
|
||||
Qt::GuiPrivate
|
||||
wlp-layer-shell
|
||||
wlp-xdg-shell
|
||||
wlp-viewporter
|
||||
wlp-fractional-scale
|
||||
PkgConfig::EGL
|
||||
PkgConfig::WAYLAND_EGL
|
||||
PkgConfig::GL
|
||||
PkgConfig::MPV
|
||||
ZShell-config
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,207 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QElapsedTimer>
|
||||
#include <QRectF>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVariantMap>
|
||||
#include <QtQml/qqml.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <atomic>
|
||||
|
||||
#include <qwayland-wlr-layer-shell-unstable-v1.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
#include "wallpapersurface.hpp"
|
||||
|
||||
struct mpv_handle;
|
||||
struct mpv_render_context;
|
||||
|
||||
class QProcess;
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
class WallpaperSurface;
|
||||
|
||||
class WallpaperService : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
||||
Q_PROPERTY(QVariantMap crops READ crops WRITE setCrops NOTIFY cropsChanged)
|
||||
|
||||
public:
|
||||
explicit WallpaperService(QObject* parent = nullptr);
|
||||
~WallpaperService() override;
|
||||
|
||||
[[nodiscard]] QString path() const;
|
||||
void setPath(const QString& path);
|
||||
|
||||
void addScreen(const QString& screen);
|
||||
void removeScreen(const QString& screen);
|
||||
|
||||
[[nodiscard]] const QVariantMap& crops() const;
|
||||
void setCrops(const QVariantMap& crops);
|
||||
|
||||
Q_INVOKABLE void redraw();
|
||||
|
||||
Q_INVOKABLE void setCrop(const QString& screen, const QVariantMap& crop);
|
||||
|
||||
signals:
|
||||
void pathChanged();
|
||||
void cropsChanged();
|
||||
|
||||
private:
|
||||
friend class WallpaperSurface;
|
||||
|
||||
static bool isVideoPath(const QString& path);
|
||||
|
||||
static bool isHardwareCodec(const QString& codec);
|
||||
|
||||
[[nodiscard]] QString codecName(const QString& path) const;
|
||||
[[nodiscard]] QString cacheDir() const;
|
||||
[[nodiscard]] QString transcodeTarget(const QString& path) const;
|
||||
[[nodiscard]] bool needsTranscode(const QString& path) const;
|
||||
void transcodeVideo(const QString& src, const QString& dst);
|
||||
|
||||
void applyPath(const QString& path);
|
||||
|
||||
void startVideo(const QString& path);
|
||||
void stopVideo();
|
||||
void teardownMpv();
|
||||
|
||||
void initMpvRender();
|
||||
void renderMpvToSurface(const QSize& size);
|
||||
void pumpMpvEvents();
|
||||
|
||||
static void* getMpvProcAddress(void* ctx, const char* name);
|
||||
static void mpvUpdate(void* ctx);
|
||||
|
||||
void bindGlobals();
|
||||
static void registryGlobal(
|
||||
void* data,
|
||||
struct wl_registry* registry,
|
||||
uint32_t name,
|
||||
const char* interface,
|
||||
uint32_t version);
|
||||
|
||||
void storeGlobal(const char* interface, void* proxy);
|
||||
|
||||
[[nodiscard]] bool setupGl();
|
||||
[[nodiscard]] uint32_t createProgram();
|
||||
|
||||
void uploadTexture(uint32_t& texture, const QImage& image);
|
||||
void uploadImage();
|
||||
void createBlackTexture(uint32_t& texture);
|
||||
|
||||
void drawTexture(
|
||||
uint32_t texture,
|
||||
const QRectF& rect,
|
||||
float sizeW,
|
||||
float sizeH,
|
||||
float alpha);
|
||||
|
||||
bool decode(const QString& path);
|
||||
|
||||
bool ensureDecodeSize();
|
||||
|
||||
[[nodiscard]] QSize largestBufferSize();
|
||||
|
||||
void queue(WallpaperSurface* surface);
|
||||
void queueAll();
|
||||
|
||||
void startDrawing();
|
||||
void loop();
|
||||
|
||||
void present();
|
||||
|
||||
void drawSurface(WallpaperSurface* surface);
|
||||
|
||||
void wake();
|
||||
|
||||
[[nodiscard]] wl_event_queue* eventQueue() const { return m_queue; }
|
||||
|
||||
[[nodiscard]] QRectF sourceRect(
|
||||
const QVariantMap& crops,
|
||||
const QString& screen,
|
||||
const QSize& size,
|
||||
const QSize& image) const;
|
||||
|
||||
void start();
|
||||
|
||||
void addSurfaces();
|
||||
void addSurface(const QString& screen);
|
||||
|
||||
QString m_path;
|
||||
QStringList m_screens;
|
||||
QVariantMap m_crops;
|
||||
|
||||
QImage m_image;
|
||||
|
||||
bool m_isVideo = false;
|
||||
bool m_videoReload = false;
|
||||
bool m_previousVideo = false;
|
||||
mpv_handle* m_mpv = nullptr;
|
||||
mpv_render_context* m_mpvRender = nullptr;
|
||||
uint32_t m_blackTex = 0;
|
||||
std::atomic<bool> m_mpvFrameReady{false};
|
||||
std::atomic<bool> m_videoReady{false};
|
||||
std::atomic<bool> m_videoLoaded{false};
|
||||
|
||||
QProcess* m_transcode = nullptr;
|
||||
|
||||
QSize m_sourceSize;
|
||||
QSize m_decodeSize;
|
||||
|
||||
wl_display* m_display = nullptr;
|
||||
wl_registry* m_registry = nullptr;
|
||||
wl_compositor* m_compositor = nullptr;
|
||||
QtWayland::zwlr_layer_shell_v1* m_layerShell = nullptr;
|
||||
QtWayland::wp_viewporter* m_viewporter = nullptr;
|
||||
QtWayland::wp_fractional_scale_manager_v1* m_scaleManager = nullptr;
|
||||
|
||||
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
||||
EGLConfig m_config = nullptr;
|
||||
EGLContext m_context = EGL_NO_CONTEXT;
|
||||
uint32_t m_texture = 0;
|
||||
uint32_t m_program = 0;
|
||||
int m_sizeUniform = -1;
|
||||
int m_cropUniform = -1;
|
||||
int m_alphaUniform = -1;
|
||||
|
||||
QImage m_previousImage;
|
||||
QVariantMap m_previousCrops;
|
||||
uint32_t m_previousTexture = 0;
|
||||
bool m_fading = false;
|
||||
bool m_fadeClockStarted = false;
|
||||
bool m_firstFadeDone = false;
|
||||
QElapsedTimer m_fadeTimer;
|
||||
|
||||
QList<WallpaperSurface*> m_surfaces;
|
||||
|
||||
std::thread m_thread;
|
||||
mutable std::recursive_mutex m_mutex;
|
||||
wl_event_queue* m_queue = nullptr;
|
||||
|
||||
int m_wake[2] = {-1, -1};
|
||||
|
||||
bool m_stop = false;
|
||||
bool m_drawing = false;
|
||||
|
||||
bool started = false;
|
||||
|
||||
bool m_forceDecode = false;
|
||||
bool m_imageChanged = false;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
@@ -2,7 +2,12 @@ qml_module(ZShell-models
|
||||
URI ZShell.Models
|
||||
SOURCES
|
||||
filesystemmodel.hpp filesystemmodel.cpp
|
||||
wallpaperdb.hpp wallpaperdb.cpp
|
||||
wallpaperutil.hpp
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
Qt::Quick
|
||||
Qt::Qml
|
||||
Qt::Concurrent
|
||||
Qt::Sql
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <qtconcurrentrun.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
|
||||
namespace ZShell::models {
|
||||
|
||||
@@ -39,136 +40,43 @@ bool hasVideoExtension(const QString& path) {
|
||||
return kVideoExtensions.contains(QFileInfo(path).suffix().toLower());
|
||||
}
|
||||
|
||||
QString videoThumbnailPath(const QString& path) {
|
||||
const QFileInfo info(path);
|
||||
const QStringList kHardwareCodecs = {
|
||||
"av1",
|
||||
"hevc",
|
||||
"h264",
|
||||
"vp9",
|
||||
"vp8",
|
||||
"vc1",
|
||||
"mpeg2",
|
||||
"mpeg2video",
|
||||
"mpeg4",
|
||||
"mjpeg",
|
||||
};
|
||||
|
||||
QString key = QStringLiteral("%1_%2_%3")
|
||||
.arg(info.baseName())
|
||||
.arg(info.size())
|
||||
.arg(info.lastModified().toMSecsSinceEpoch() / 1000);
|
||||
key.replace(QRegularExpression("[^A-Za-z0-9._-]"), "_");
|
||||
|
||||
return QDir::homePath() + "/.cache/zshell/imagecache/video-previews/" +
|
||||
key + "_preview.jpg";
|
||||
}
|
||||
|
||||
bool generateVideoThumbnail(const QString& src, const QString& cachePath) {
|
||||
if (QFile::exists(cachePath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString scale = QStringLiteral(
|
||||
"scale=1920:1080:force_original_aspect_ratio=decrease,"
|
||||
"scale=trunc(iw/2)*2:trunc(ih/2)*2");
|
||||
|
||||
auto runProcess = [](const QString& program, const QStringList& args) {
|
||||
QProcess process;
|
||||
process.start(program, args);
|
||||
process.waitForFinished(15000);
|
||||
return qMakePair(process.exitCode(), process.readAllStandardOutput());
|
||||
};
|
||||
|
||||
double duration = 0.0;
|
||||
const auto probe = runProcess(
|
||||
bool probeHardwareCodec(const QString& path) {
|
||||
QProcess process;
|
||||
process.start(
|
||||
"ffprobe",
|
||||
{
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"v:0",
|
||||
"-show_entries",
|
||||
"format=duration",
|
||||
"stream=codec_name",
|
||||
"-of",
|
||||
"default=nw=1:nk=1",
|
||||
src,
|
||||
path,
|
||||
});
|
||||
if (probe.first == 0) {
|
||||
duration = QString::fromUtf8(probe.second).trimmed().toDouble();
|
||||
}
|
||||
process.waitForFinished(5000);
|
||||
|
||||
const int samples = duration > 0 ? std::min(10, std::max(4, int(duration)))
|
||||
: 8;
|
||||
|
||||
QTemporaryDir tmpDir;
|
||||
if (!tmpDir.isValid()) {
|
||||
if (process.exitCode() != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bestIndex = -1;
|
||||
double bestChroma = -1.0;
|
||||
double bestLuma = -1.0;
|
||||
|
||||
for (int i = 0; i < samples; ++i) {
|
||||
const double frac = duration > 0 ? (i + 0.5) / samples : 0.0;
|
||||
const QString seek = duration > 0
|
||||
? QString::number(frac * duration, 'f', 3)
|
||||
: QStringLiteral("0");
|
||||
|
||||
const QString framePath =
|
||||
tmpDir.filePath(QStringLiteral("frame_%1.jpg").arg(i));
|
||||
|
||||
runProcess(
|
||||
"ffmpeg",
|
||||
{
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"error",
|
||||
"-ss",
|
||||
seek,
|
||||
"-i",
|
||||
src,
|
||||
"-frames:v",
|
||||
"1",
|
||||
"-vf",
|
||||
scale,
|
||||
"-pix_fmt",
|
||||
"yuvj420p",
|
||||
"-q:v",
|
||||
"2",
|
||||
"-y",
|
||||
framePath,
|
||||
});
|
||||
|
||||
QImage frame(framePath);
|
||||
if (frame.isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
frame = frame.convertToFormat(QImage::Format_RGB888);
|
||||
|
||||
double chroma = 0.0;
|
||||
double luma = 0.0;
|
||||
int count = 0;
|
||||
for (int y = 0; y < frame.height(); ++y) {
|
||||
const auto* line = frame.constScanLine(y);
|
||||
for (int x = 0; x < frame.width(); ++x) {
|
||||
const int r = line[x * 3];
|
||||
const int g = line[x * 3 + 1];
|
||||
const int b = line[x * 3 + 2];
|
||||
chroma += std::max({r, g, b}) - std::min({r, g, b});
|
||||
luma += r + g + b;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
const int n = std::max(1, count);
|
||||
chroma /= n;
|
||||
luma /= 3.0 * n;
|
||||
|
||||
if (chroma > bestChroma + 0.5 ||
|
||||
(chroma > bestChroma - 0.5 && luma > bestLuma)) {
|
||||
bestChroma = chroma;
|
||||
bestLuma = luma;
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir().mkpath(QFileInfo(cachePath).absolutePath());
|
||||
return QFile::copy(
|
||||
tmpDir.filePath(QStringLiteral("frame_%1.jpg").arg(bestIndex)),
|
||||
cachePath);
|
||||
const QString codec =
|
||||
QString::fromUtf8(process.readAllStandardOutput()).trimmed().toLower();
|
||||
return kHardwareCodecs.contains(codec);
|
||||
}
|
||||
|
||||
FileSystemEntry::FileSystemEntry(
|
||||
@@ -177,7 +85,6 @@ FileSystemEntry::FileSystemEntry(
|
||||
, m_fileInfo(path)
|
||||
, m_path(path)
|
||||
, m_relativePath(relativePath)
|
||||
, m_thumbnailPath(path)
|
||||
, m_isImageInitialised(false)
|
||||
, m_mimeTypeInitialised(false) {}
|
||||
|
||||
@@ -227,16 +134,7 @@ bool FileSystemEntry::isVideo() const {
|
||||
}
|
||||
|
||||
QString FileSystemEntry::thumbnailPath() const {
|
||||
return m_thumbnailPath;
|
||||
}
|
||||
|
||||
void FileSystemEntry::setThumbnailPath(const QString& path) {
|
||||
if (m_thumbnailPath == path) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_thumbnailPath = path;
|
||||
emit thumbnailPathChanged();
|
||||
return m_path;
|
||||
}
|
||||
|
||||
QString FileSystemEntry::mimeType() const {
|
||||
@@ -404,6 +302,46 @@ void FileSystemModel::setNameFilters(const QStringList& nameFilters) {
|
||||
update();
|
||||
}
|
||||
|
||||
bool FileSystemModel::requireHardwareCodec() const {
|
||||
return m_requireHardwareCodec;
|
||||
}
|
||||
|
||||
void FileSystemModel::setRequireHardwareCodec(bool requireHardwareCodec) {
|
||||
if (m_requireHardwareCodec == requireHardwareCodec) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_requireHardwareCodec = requireHardwareCodec;
|
||||
emit requireHardwareCodecChanged();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
bool FileSystemModel::isHardwareVideo(const QString& path) {
|
||||
const QFileInfo info(path);
|
||||
const QString key =
|
||||
path + '|' + QByteArray::number(info.size()) + '|' +
|
||||
QByteArray::number(info.lastModified().toMSecsSinceEpoch() / 1000);
|
||||
|
||||
{
|
||||
std::lock_guard<QMutex> lock(m_codecMutex);
|
||||
|
||||
const auto it = m_codecCache.constFind(key);
|
||||
if (it != m_codecCache.constEnd()) {
|
||||
return it.value();
|
||||
}
|
||||
}
|
||||
|
||||
const bool hardware = probeHardwareCodec(path);
|
||||
|
||||
{
|
||||
std::lock_guard<QMutex> lock(m_codecMutex);
|
||||
m_codecCache.insert(key, hardware);
|
||||
}
|
||||
|
||||
return hardware;
|
||||
}
|
||||
|
||||
QQmlListProperty<FileSystemEntry> FileSystemModel::entries() {
|
||||
return QQmlListProperty<FileSystemEntry>(this, &m_entries);
|
||||
}
|
||||
@@ -434,14 +372,12 @@ void FileSystemModel::watchDirIfRecursive(const QString& path) {
|
||||
const auto paths = watcher->result();
|
||||
if (currentDir == m_dir && showHidden == m_showHidden &&
|
||||
!paths.isEmpty()) {
|
||||
// Ignore if dir or showHidden has changed
|
||||
m_watcher.addPaths(paths);
|
||||
}
|
||||
watcher->deleteLater();
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
void FileSystemModel::update() {
|
||||
@@ -488,6 +424,7 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||
const auto showHidden = m_showHidden;
|
||||
const auto filter = m_filter;
|
||||
const auto nameFilters = m_nameFilters;
|
||||
const auto requireHardware = m_requireHardwareCodec;
|
||||
|
||||
QSet<QString> oldPaths;
|
||||
for (const auto& entry : std::as_const(m_entries)) {
|
||||
@@ -495,7 +432,7 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||
}
|
||||
|
||||
const auto future = QtConcurrent::run(
|
||||
[=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
|
||||
[=, this](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
|
||||
const auto flags = recursive ? QDirIterator::Subdirectories
|
||||
: QDirIterator::NoIteratorFlags;
|
||||
|
||||
@@ -553,18 +490,15 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||
|
||||
QString path = iter->next();
|
||||
|
||||
if (filter == Images) {
|
||||
if (hasVideoExtension(path)) {
|
||||
if (requireHardware && !isHardwareVideo(path)) {
|
||||
continue;
|
||||
}
|
||||
} else if (filter == Images || filter == Media) {
|
||||
QImageReader reader(path);
|
||||
if (!reader.canRead()) {
|
||||
continue;
|
||||
}
|
||||
} else if (filter == Media) {
|
||||
if (!hasVideoExtension(path)) {
|
||||
QImageReader reader(path);
|
||||
if (!reader.canRead()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newPaths.insert(path);
|
||||
@@ -605,31 +539,6 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||
});
|
||||
|
||||
watcher->setFuture(future);
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
void FileSystemModel::startThumbnailGeneration(FileSystemEntry* entry) {
|
||||
const QPointer<FileSystemEntry> guard(entry);
|
||||
const QString source = entry->path();
|
||||
const QString cachePath = videoThumbnailPath(source);
|
||||
|
||||
const auto future = QtConcurrent::run([source, cachePath]() {
|
||||
return generateVideoThumbnail(source, cachePath);
|
||||
});
|
||||
|
||||
const auto watcher = new QFutureWatcher<bool>(this);
|
||||
connect(
|
||||
watcher,
|
||||
&QFutureWatcher<bool>::finished,
|
||||
this,
|
||||
[guard, cachePath, watcher]() {
|
||||
if (guard && watcher->result()) {
|
||||
guard->setThumbnailPath(cachePath);
|
||||
}
|
||||
watcher->deleteLater();
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
void FileSystemModel::applyChanges(
|
||||
@@ -642,7 +551,6 @@ void FileSystemModel::applyChanges(
|
||||
}
|
||||
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>());
|
||||
|
||||
// Batch remove old entries
|
||||
int start = -1;
|
||||
int end = -1;
|
||||
for (int idx : std::as_const(removedIndices)) {
|
||||
@@ -670,14 +578,10 @@ void FileSystemModel::applyChanges(
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
// Create new entries
|
||||
QList<FileSystemEntry*> newEntries;
|
||||
for (const auto& path : addedPaths) {
|
||||
auto* entry =
|
||||
new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
|
||||
if (entry->isVideo()) {
|
||||
startThumbnailGeneration(entry);
|
||||
}
|
||||
newEntries << entry;
|
||||
}
|
||||
std::sort(
|
||||
@@ -687,7 +591,6 @@ void FileSystemModel::applyChanges(
|
||||
return compareEntries(a, b);
|
||||
});
|
||||
|
||||
// Batch insert new entries
|
||||
int insertStart = -1;
|
||||
QList<FileSystemEntry*> batchItems;
|
||||
for (const auto& entry : std::as_const(newEntries)) {
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include <qdir.h>
|
||||
#include <qfilesystemwatcher.h>
|
||||
#include <qfuture.h>
|
||||
#include <qhash.h>
|
||||
#include <qimagereader.h>
|
||||
#include <qmutex.h>
|
||||
#include <qmimedatabase.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
@@ -29,8 +31,7 @@ class FileSystemEntry : public QObject {
|
||||
Q_PROPERTY(bool isDir READ isDir CONSTANT)
|
||||
Q_PROPERTY(bool isImage READ isImage CONSTANT)
|
||||
Q_PROPERTY(bool isVideo READ isVideo CONSTANT)
|
||||
Q_PROPERTY(
|
||||
QString thumbnailPath READ thumbnailPath NOTIFY thumbnailPathChanged)
|
||||
Q_PROPERTY(QString thumbnailPath READ thumbnailPath CONSTANT)
|
||||
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
|
||||
|
||||
public:
|
||||
@@ -52,20 +53,16 @@ class FileSystemEntry : public QObject {
|
||||
[[nodiscard]] QString thumbnailPath() const;
|
||||
[[nodiscard]] QString mimeType() const;
|
||||
|
||||
void setThumbnailPath(const QString& path);
|
||||
|
||||
void updateRelativePath(const QDir& dir);
|
||||
|
||||
signals:
|
||||
void relativePathChanged();
|
||||
void thumbnailPathChanged();
|
||||
|
||||
private:
|
||||
const QFileInfo m_fileInfo;
|
||||
|
||||
const QString m_path;
|
||||
QString m_relativePath;
|
||||
QString m_thumbnailPath;
|
||||
|
||||
mutable bool m_isImage;
|
||||
mutable bool m_isImageInitialised;
|
||||
@@ -94,6 +91,9 @@ class FileSystemModel : public QAbstractListModel {
|
||||
Q_PROPERTY(
|
||||
QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY
|
||||
nameFiltersChanged)
|
||||
Q_PROPERTY(
|
||||
bool requireHardwareCodec READ requireHardwareCodec WRITE
|
||||
setRequireHardwareCodec NOTIFY requireHardwareCodecChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries
|
||||
@@ -132,6 +132,9 @@ class FileSystemModel : public QAbstractListModel {
|
||||
[[nodiscard]] QStringList nameFilters() const;
|
||||
void setNameFilters(const QStringList& nameFilters);
|
||||
|
||||
[[nodiscard]] bool requireHardwareCodec() const;
|
||||
void setRequireHardwareCodec(bool requireHardwareCodec);
|
||||
|
||||
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
|
||||
|
||||
signals:
|
||||
@@ -142,6 +145,7 @@ class FileSystemModel : public QAbstractListModel {
|
||||
void sortReverseChanged();
|
||||
void filterChanged();
|
||||
void nameFiltersChanged();
|
||||
void requireHardwareCodecChanged();
|
||||
void entriesChanged();
|
||||
|
||||
private:
|
||||
@@ -157,6 +161,10 @@ class FileSystemModel : public QAbstractListModel {
|
||||
bool m_sortReverse;
|
||||
Filter m_filter;
|
||||
QStringList m_nameFilters;
|
||||
bool m_requireHardwareCodec = false;
|
||||
|
||||
QHash<QString, bool> m_codecCache;
|
||||
QMutex m_codecMutex;
|
||||
|
||||
void watchDirIfRecursive(const QString& path);
|
||||
void update();
|
||||
@@ -165,7 +173,7 @@ class FileSystemModel : public QAbstractListModel {
|
||||
void updateEntriesForDir(const QString& dir);
|
||||
void applyChanges(
|
||||
const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
||||
void startThumbnailGeneration(FileSystemEntry* entry);
|
||||
[[nodiscard]] bool isHardwareVideo(const QString& path);
|
||||
[[nodiscard]] bool compareEntries(
|
||||
const FileSystemEntry* a, const FileSystemEntry* b) const;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user