mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Compare commits
283 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a56b2d6a9f | ||
|
|
342f980bad | ||
|
|
dbc1bdeb3b | ||
|
|
1cb10e879e | ||
|
|
d90dd5288b | ||
|
|
e55a517dae | ||
|
|
378def1fa3 | ||
|
|
ea7676c98e | ||
|
|
b9b15568b4 | ||
|
|
701d8cbd8a | ||
|
|
bd525763de | ||
|
|
479868718e | ||
|
|
951136bc4c | ||
|
|
8ab25ef8e4 | ||
|
|
a11cd9b0df | ||
|
|
1e72733e81 | ||
|
|
967b7d05de | ||
|
|
90bc890190 | ||
|
|
647c358b72 | ||
|
|
2a89885437 | ||
|
|
47cc43185d | ||
|
|
aa6e09ed3e | ||
|
|
c7bc3d6f3b | ||
|
|
0b68bf7c07 | ||
|
|
3274ef5e3e | ||
|
|
f93a00c8d4 | ||
|
|
92bcb83b16 | ||
|
|
4e0c813db7 | ||
|
|
d4509c80b7 | ||
|
|
c9313df3a4 | ||
|
|
9b6fb29d46 | ||
|
|
50ce5cf257 | ||
|
|
b19e5b3b40 | ||
|
|
c07ba3f737 | ||
|
|
eff5f60264 | ||
|
|
355b2e16b4 | ||
|
|
c389101a10 | ||
|
|
4aa0b3d0fc | ||
|
|
322f1415f6 | ||
|
|
0d57691e38 | ||
|
|
507b516f89 | ||
|
|
7bf73ab14d | ||
|
|
9a305355c2 | ||
|
|
6ac382a25f | ||
|
|
e02b255442 | ||
|
|
d978740d66 | ||
|
|
62b7492e9f | ||
|
|
c2f42f3f69 | ||
|
|
2c4a40e778 | ||
|
|
635fcad416 | ||
|
|
2c92f830d1 | ||
|
|
4924f3e55a | ||
|
|
53306165e1 | ||
|
|
1ebcdaaf62 | ||
|
|
078ef203b6 | ||
|
|
59669d8b7f | ||
|
|
d38b98459a | ||
|
|
f54e53b8a0 | ||
|
|
851d47213c | ||
|
|
ad778b5d81 | ||
|
|
0cb081a6d0 | ||
|
|
daf3525e80 | ||
|
|
b35198c710 | ||
|
|
1feb77aadb | ||
|
|
d6b690ae2f | ||
|
|
b1ae246c86 | ||
|
|
4ceb5f13e5 | ||
|
|
64960e4dcd | ||
|
|
e1c180a13f | ||
|
|
86a0fd409a | ||
|
|
5a32398446 | ||
|
|
bcb22ec265 | ||
|
|
8719dcf98f | ||
|
|
9b4b2f75c1 | ||
|
|
8acde3a347 | ||
|
|
7c1e247ef8 | ||
|
|
f4cd27d316 | ||
|
|
205c43181b | ||
|
|
05a72abf41 | ||
|
|
14262ba510 | ||
|
|
d847b1e09c | ||
|
|
0086e42a86 | ||
|
|
7474d5a7bf | ||
|
|
5696a36115 | ||
|
|
3cdc1a9c81 | ||
|
|
b095fb9005 | ||
|
|
ce6c16214c | ||
|
|
b6f7f2734e | ||
|
|
4db55e4d77 | ||
|
|
b21f6e80b3 | ||
|
|
a804fb849e | ||
|
|
4ca91cd9f7 | ||
|
|
16e1b587b4 | ||
|
|
5e2756d200 | ||
|
|
ce9ab22ae1 | ||
|
|
72ad35e1f9 | ||
|
|
c0d110cde0 | ||
|
|
b9d5deb2ae | ||
|
|
d4b13ef46b | ||
|
|
748d9e342e | ||
|
|
f49312fc0e | ||
|
|
e0d8bbb243 | ||
|
|
153f2a49f8 | ||
|
|
8b272dc2fd | ||
|
|
87a919bbde | ||
|
|
d3017e98c5 | ||
|
|
5758d7274e | ||
|
|
0e215d69cb | ||
|
|
cbaaa32ce8 | ||
|
|
5c81646397 | ||
|
|
30ca1fb14f | ||
|
|
9fab49984a | ||
|
|
696fa6e4f8 | ||
|
|
921393e84e | ||
|
|
13e894e910 | ||
|
|
7c7e8aaef3 | ||
|
|
7ea3bd9df9 | ||
|
|
7bf7d0afae | ||
|
|
0d329baaca | ||
|
|
941a87b59c | ||
|
|
a9e8ac46d8 | ||
|
|
0d3a294118 | ||
|
|
7a27537632 | ||
|
|
287e778ddb | ||
|
|
ce44edb419 | ||
|
|
9dcd8af7a3 | ||
|
|
76dfcd0ccb | ||
|
|
13a188635d | ||
|
|
cd18fd5aed | ||
|
|
b277bd8014 | ||
|
|
daa0d368ab | ||
|
|
2cc7777e16 | ||
|
|
d276e31f7b | ||
|
|
7f35ba7e21 | ||
|
|
edd54dda84 | ||
|
|
a50a97314d | ||
|
|
4bc05e7083 | ||
|
|
09a45b49a6 | ||
|
|
1c0b71436e | ||
|
|
24f5e9a7e6 | ||
|
|
59d123a4a1 | ||
|
|
ed2afa03f9 | ||
|
|
3c531dc2ec | ||
|
|
83564bd03f | ||
|
|
7146d0d92d | ||
|
|
e842d6761a | ||
|
|
17b405e9dc | ||
|
|
f281513a41 | ||
|
|
63b876479f | ||
|
|
38b833c886 | ||
|
|
d75ea18e9f | ||
|
|
f311b20ef7 | ||
|
|
78f7237422 | ||
|
|
726af3393b | ||
|
|
c772331554 | ||
|
|
80d257b94f | ||
|
|
e2db034959 | ||
|
|
c4e88e5c05 | ||
|
|
e47e7667c6 | ||
|
|
8bb2a64663 | ||
|
|
e056e08fc1 | ||
|
|
342cd55bc0 | ||
|
|
23ef19e683 | ||
|
|
437fd29e96 | ||
|
|
aa7a07fd99 | ||
|
|
5217006dec | ||
|
|
ab4f6baae6 | ||
|
|
1976ea4d49 | ||
|
|
697fc4d2b7 | ||
|
|
38c1f7bbcb | ||
|
|
8cbfaab807 | ||
|
|
f4a4151632 | ||
|
|
5f810fe741 | ||
|
|
adaa0caab8 | ||
|
|
54ef14e765 | ||
|
|
d1383b5d1b | ||
|
|
caa703af99 | ||
|
|
90aab9f4db | ||
|
|
3439030145 | ||
|
|
058c7408d1 | ||
|
|
a4f7fd58f6 | ||
|
|
6f3024c90d | ||
|
|
5f95fa5e79 | ||
|
|
f9cb0506e9 | ||
|
|
2429401d0e | ||
|
|
9ff0d7405f | ||
|
|
5bb5cd296d | ||
|
|
273662e03e | ||
|
|
9c1a89d786 | ||
|
|
524d7ee5c0 | ||
|
|
51d2bc9aae | ||
|
|
0c8a7ff332 | ||
|
|
309b8d9efe | ||
|
|
c2f32b7bdc | ||
|
|
563bc7b359 | ||
|
|
94ca5a5bef | ||
|
|
4464589c0f | ||
|
|
748d4fe2ac | ||
|
|
50b28dc8ca | ||
|
|
118980a9fb | ||
|
|
8aff381676 | ||
|
|
6d0fba1905 | ||
|
|
7e885d3cee | ||
|
|
811daf74ff | ||
|
|
ee755b8bd6 | ||
|
|
1019eb925a | ||
|
|
061bb50b88 | ||
|
|
eb7e665c86 | ||
|
|
80301d1aab | ||
|
|
ea56fb5840 | ||
|
|
692b45c4f0 | ||
|
|
8d53a8826e | ||
|
|
64ea115303 | ||
|
|
b5e29cf50c | ||
|
|
381df1e949 | ||
|
|
13a81eda6f | ||
|
|
a48c39642a | ||
|
|
3be3e622bc | ||
|
|
07fe2ca407 | ||
|
|
9b96dae744 | ||
|
|
0e3d3d1a40 | ||
|
|
cb3274fb0c | ||
|
|
a3ada5b2bb | ||
|
|
3e167a2c52 | ||
|
|
38b3ad2b31 | ||
|
|
56e5cd13b7 | ||
|
|
d63c0fc6f0 | ||
|
|
6814b140fc | ||
|
|
5f7e478118 | ||
|
|
7317024da5 | ||
|
|
9b9fbabc3f | ||
|
|
3c5a23799f | ||
|
|
3bfdc6163c | ||
|
|
cf2f74a38d | ||
|
|
2a7f52c67e | ||
|
|
50fde1e308 | ||
|
|
46fd0ae413 | ||
|
|
65e32dc429 | ||
|
|
413675dfc1 | ||
|
|
a17343f40e | ||
|
|
5d023804c1 | ||
|
|
fa07a846b9 | ||
|
|
5df46b605e | ||
|
|
77cf371a21 | ||
|
|
89802dd040 | ||
|
|
59b95e9dd6 | ||
|
|
b836db5252 | ||
|
|
4dc4b15925 | ||
|
|
5c3062e699 | ||
|
|
3a7777c643 | ||
|
|
71543c35d6 | ||
|
|
f4cf66dc01 | ||
|
|
7870dff0fd | ||
|
|
9fc9c1ed19 | ||
|
|
4d0151350f | ||
|
|
dff10c8d13 | ||
|
|
362bcb9294 | ||
|
|
351b4f8a94 | ||
|
|
90955eb0a1 | ||
|
|
62669747ad | ||
|
|
466d00c666 | ||
|
|
63845ff875 | ||
|
|
d013748a51 | ||
|
|
474af3bc07 | ||
|
|
8e09e155fa | ||
|
|
7f9f4f96b9 | ||
|
|
cd488a8623 | ||
|
|
080b7a28b1 | ||
|
|
6949ed0ebd | ||
|
|
8465fa45bb | ||
|
|
40835ffc89 | ||
|
|
01a42ff330 | ||
|
|
ba49654a64 | ||
|
|
bc6577fe18 | ||
|
|
4ca3f0da67 | ||
|
|
7f2086488b | ||
|
|
3014fd8095 | ||
|
|
27885c8ac3 | ||
|
|
d6be0509ac | ||
|
|
1c85f5e857 | ||
|
|
abe5515aca | ||
|
|
6fba975490 | ||
|
|
2de6798f45 |
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -26,6 +26,14 @@ assignees: ""
|
||||
- [ ] Hyprland
|
||||
- [ ] Other (specify)
|
||||
|
||||
## Distribution
|
||||
|
||||
<!-- Arch, Fedora, Debian, etc. -->
|
||||
|
||||
## dms version
|
||||
|
||||
<!-- Output of dms version command -->
|
||||
|
||||
## Description
|
||||
|
||||
<!-- Brief description of the issue -->
|
||||
@@ -45,6 +53,14 @@ assignees: ""
|
||||
## Error Messages/Logs
|
||||
|
||||
<!-- Please include any error messages, stack traces, or relevant logs -->
|
||||
<!-- you can get a log file with the following steps:
|
||||
dms kill
|
||||
mkdir ~/dms_logs
|
||||
nohup dms run > ~/dms_logs/dms-$(date +%s).txt 2>&1 &
|
||||
|
||||
Then trigger your issue, and share the contents of ~/dms_logs/dms-<timestamp>.txt
|
||||
|
||||
-->
|
||||
|
||||
```
|
||||
Paste error messages or logs here
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/support_request.md
vendored
8
.github/ISSUE_TEMPLATE/support_request.md
vendored
@@ -12,6 +12,14 @@ assignees: ""
|
||||
- [ ] Hyprland
|
||||
- [ ] other
|
||||
|
||||
## Distribution
|
||||
|
||||
<!-- Arch, Fedora, Debian, etc. -->
|
||||
|
||||
## dms version
|
||||
|
||||
<!-- Output of dms version command -->
|
||||
|
||||
## Description
|
||||
|
||||
<!-- Brief description of the support needed -->
|
||||
|
||||
289
.github/workflows/copr-release.yml
vendored
Normal file
289
.github/workflows/copr-release.yml
vendored
Normal file
@@ -0,0 +1,289 @@
|
||||
name: DMS Copr Stable Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
build-and-upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Determine version
|
||||
id: version
|
||||
run: |
|
||||
if [ -n "${{ github.event.inputs.version }}" ]; then
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
echo "Using manual version: $VERSION"
|
||||
elif [ "${{ github.event_name }}" = "release" ]; then
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
VERSION="${VERSION#v}"
|
||||
echo "Using release version: $VERSION"
|
||||
elif [ "${{ github.event_name }}" = "push" ] && [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||
VERSION="${{ github.ref_name }}"
|
||||
VERSION="${VERSION#v}"
|
||||
echo "Using tag version: $VERSION"
|
||||
else
|
||||
# Fallback to latest release
|
||||
VERSION=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | jq -r '.tag_name' | sed 's/^v//')
|
||||
echo "Using latest release version: $VERSION"
|
||||
fi
|
||||
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "✅ Building DMS stable version: $VERSION"
|
||||
|
||||
- name: Setup build environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y rpm wget curl jq gzip
|
||||
mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
echo "✅ RPM build environment ready"
|
||||
|
||||
- name: Download release assets
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
cd ~/rpmbuild/SOURCES
|
||||
|
||||
echo "📦 Downloading DMS QML source for v${VERSION}..."
|
||||
|
||||
# Download DMS QML source
|
||||
wget "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-qml.tar.gz" || {
|
||||
echo "❌ Failed to download dms-qml.tar.gz for v${VERSION}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "✅ Source downloaded"
|
||||
echo "Note: dms-cli and dgop binaries will be downloaded during build based on target architecture"
|
||||
ls -lh
|
||||
|
||||
- name: Generate stable spec file
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
||||
|
||||
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
|
||||
# Spec for DMS stable releases - Generated by GitHub Actions
|
||||
|
||||
%global debug_package %{nil}
|
||||
%global version VERSION_PLACEHOLDER
|
||||
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
Name: dms
|
||||
Version: %{version}
|
||||
Release: 1%{?dist}
|
||||
Summary: %{pkg_summary}
|
||||
|
||||
License: GPL-3.0-only
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
Source0: dms-qml.tar.gz
|
||||
|
||||
BuildRequires: gzip
|
||||
BuildRequires: wget
|
||||
|
||||
Requires: (quickshell or quickshell-git)
|
||||
Requires: dms-cli
|
||||
Requires: dgop
|
||||
Requires: fira-code-fonts
|
||||
Requires: material-symbols-fonts
|
||||
Requires: rsms-inter-fonts
|
||||
|
||||
Recommends: brightnessctl
|
||||
Recommends: cava
|
||||
Recommends: cliphist
|
||||
Recommends: hyprpicker
|
||||
Recommends: matugen
|
||||
Recommends: wl-clipboard
|
||||
Recommends: NetworkManager
|
||||
Recommends: qt6-qtmultimedia
|
||||
Suggests: qt6ct
|
||||
|
||||
%description
|
||||
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
and optimized for the niri and hyprland compositors. Features notifications,
|
||||
app launcher, wallpaper customization, and fully customizable with plugins.
|
||||
|
||||
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||
process monitoring, notification center, clipboard history, dock, control center,
|
||||
lock screen, and comprehensive plugin system.
|
||||
|
||||
%package -n dms-cli
|
||||
Summary: DankMaterialShell CLI tool
|
||||
License: GPL-3.0-only
|
||||
URL: https://github.com/AvengeMedia/danklinux
|
||||
|
||||
%description -n dms-cli
|
||||
Command-line interface for DankMaterialShell configuration and management.
|
||||
Provides native DBus bindings, NetworkManager integration, and system utilities.
|
||||
|
||||
%package -n dgop
|
||||
Summary: Stateless CPU/GPU monitor for DankMaterialShell
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/dgop
|
||||
Provides: dgop
|
||||
|
||||
%description -n dgop
|
||||
DGOP is a stateless system monitoring tool that provides CPU, GPU, memory, and
|
||||
network statistics. Designed for integration with DankMaterialShell but can be
|
||||
used standalone. This package always includes the latest stable dgop release.
|
||||
|
||||
%prep
|
||||
%setup -q -c -n dms-qml
|
||||
|
||||
# Download architecture-specific binaries during build
|
||||
# This ensures the correct architecture is used for each build target
|
||||
case "%{_arch}" in
|
||||
x86_64)
|
||||
ARCH_SUFFIX="amd64"
|
||||
;;
|
||||
aarch64)
|
||||
ARCH_SUFFIX="arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: %{_arch}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Download dms-cli for target architecture
|
||||
wget -O %{_builddir}/dms-cli.gz "https://github.com/AvengeMedia/danklinux/releases/latest/download/dms-distropkg-${ARCH_SUFFIX}.gz" || {
|
||||
echo "Failed to download dms-cli for architecture %{_arch}"
|
||||
exit 1
|
||||
}
|
||||
gunzip -c %{_builddir}/dms-cli.gz > %{_builddir}/dms-cli
|
||||
chmod +x %{_builddir}/dms-cli
|
||||
|
||||
# Download dgop for target architecture
|
||||
wget -O %{_builddir}/dgop.gz "https://github.com/AvengeMedia/dgop/releases/latest/download/dgop-linux-${ARCH_SUFFIX}.gz" || {
|
||||
echo "Failed to download dgop for architecture %{_arch}"
|
||||
exit 1
|
||||
}
|
||||
gunzip -c %{_builddir}/dgop.gz > %{_builddir}/dgop
|
||||
chmod +x %{_builddir}/dgop
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
|
||||
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
|
||||
|
||||
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms
|
||||
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/
|
||||
|
||||
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.git*
|
||||
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.gitignore
|
||||
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.github
|
||||
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/*.spec
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc README.md CONTRIBUTING.md
|
||||
%{_sysconfdir}/xdg/quickshell/dms/
|
||||
|
||||
%files -n dms-cli
|
||||
%{_bindir}/dms
|
||||
|
||||
%files -n dgop
|
||||
%{_bindir}/dgop
|
||||
|
||||
%changelog
|
||||
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
|
||||
- Stable release VERSION_PLACEHOLDER
|
||||
- Built from GitHub release
|
||||
- Includes latest dms-cli and dgop binaries
|
||||
SPECEOF
|
||||
|
||||
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||
|
||||
echo "✅ Spec file generated for v${VERSION}"
|
||||
echo ""
|
||||
echo "=== Spec file preview ==="
|
||||
head -40 ~/rpmbuild/SPECS/dms.spec
|
||||
|
||||
- name: Build SRPM
|
||||
id: build
|
||||
run: |
|
||||
cd ~/rpmbuild/SPECS
|
||||
|
||||
echo "🔨 Building SRPM..."
|
||||
rpmbuild -bs dms.spec
|
||||
|
||||
SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
|
||||
SRPM_NAME=$(basename "$SRPM")
|
||||
|
||||
echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
|
||||
echo "srpm_name=$SRPM_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "✅ SRPM built: $SRPM_NAME"
|
||||
echo ""
|
||||
echo "=== SRPM Info ==="
|
||||
rpm -qpi "$SRPM"
|
||||
|
||||
- name: Upload SRPM artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dms-stable-srpm-${{ steps.version.outputs.version }}
|
||||
path: ${{ steps.build.outputs.srpm_path }}
|
||||
retention-days: 90
|
||||
|
||||
- name: Install Copr CLI
|
||||
run: |
|
||||
sudo apt-get install -y python3-pip
|
||||
pip3 install copr-cli
|
||||
|
||||
mkdir -p ~/.config
|
||||
cat > ~/.config/copr << EOF
|
||||
[copr-cli]
|
||||
login = ${{ secrets.COPR_LOGIN }}
|
||||
username = avengemedia
|
||||
token = ${{ secrets.COPR_TOKEN }}
|
||||
copr_url = https://copr.fedorainfracloud.org
|
||||
EOF
|
||||
chmod 600 ~/.config/copr
|
||||
|
||||
echo "✅ Copr CLI configured"
|
||||
|
||||
- name: Upload to Copr
|
||||
run: |
|
||||
SRPM="${{ steps.build.outputs.srpm_path }}"
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
|
||||
echo "🚀 Uploading SRPM to avengemedia/dms..."
|
||||
echo " SRPM: $(basename $SRPM)"
|
||||
echo " Version: $VERSION"
|
||||
|
||||
BUILD_OUTPUT=$(copr-cli build avengemedia/dms "$SRPM" --nowait 2>&1)
|
||||
echo "$BUILD_OUTPUT"
|
||||
|
||||
BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
|
||||
|
||||
if [ "$BUILD_ID" != "unknown" ]; then
|
||||
echo "✅ Build submitted successfully!"
|
||||
echo "🔗 https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
|
||||
else
|
||||
echo "⚠️ Could not extract build ID, but upload may have succeeded"
|
||||
fi
|
||||
|
||||
- name: Build summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "### 🎉 DMS Stable Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **SRPM:** ${{ steps.build.outputs.srpm_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Stable release has been built and uploaded to Copr!" >> $GITHUB_STEP_SUMMARY
|
||||
189
.github/workflows/poeditor-export.yml
vendored
Normal file
189
.github/workflows/poeditor-export.yml
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
name: POEditor Diff & Sync
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
workflow_dispatch: {}
|
||||
|
||||
concurrency:
|
||||
group: poeditor-sync
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sync-translations:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install jq
|
||||
run: sudo apt-get update && sudo apt-get install -y jq
|
||||
|
||||
- name: Extract source strings from codebase
|
||||
env:
|
||||
API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }}
|
||||
PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "::group::Extracting strings from QML files"
|
||||
python3 translations/extract_translations.py
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Checking for changes in en.json"
|
||||
if [[ -f "translations/en.json" ]]; then
|
||||
jq -S . "translations/en.json" > /tmp/en_new.json
|
||||
if [[ -f "translations/en.json.orig" ]]; then
|
||||
jq -S . "translations/en.json.orig" > /tmp/en_old.json
|
||||
else
|
||||
git show HEAD:translations/en.json > /tmp/en_old.json 2>/dev/null || echo "[]" > /tmp/en_old.json
|
||||
jq -S . /tmp/en_old.json > /tmp/en_old.json.tmp && mv /tmp/en_old.json.tmp /tmp/en_old.json
|
||||
fi
|
||||
|
||||
if diff -q /tmp/en_new.json /tmp/en_old.json >/dev/null 2>&1; then
|
||||
echo "No changes in source strings"
|
||||
echo "source_changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Detected changes in source strings"
|
||||
echo "source_changed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "::group::Uploading source strings to POEditor"
|
||||
RESP=$(curl -sS -X POST https://api.poeditor.com/v2/projects/upload \
|
||||
-F api_token="$API_TOKEN" \
|
||||
-F id="$PROJECT_ID" \
|
||||
-F updating="terms" \
|
||||
-F file=@"translations/en.json")
|
||||
|
||||
STATUS=$(echo "$RESP" | jq -r '.response.status')
|
||||
if [[ "$STATUS" != "success" ]]; then
|
||||
echo "::warning::POEditor upload failed: $RESP"
|
||||
else
|
||||
TERMS_ADDED=$(echo "$RESP" | jq -r '.result.terms.added // 0')
|
||||
TERMS_UPDATED=$(echo "$RESP" | jq -r '.result.terms.updated // 0')
|
||||
TERMS_DELETED=$(echo "$RESP" | jq -r '.result.terms.deleted // 0')
|
||||
echo "Terms added: $TERMS_ADDED, updated: $TERMS_UPDATED, deleted: $TERMS_DELETED"
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
else
|
||||
echo "::warning::translations/en.json not found"
|
||||
echo "source_changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
id: extract
|
||||
|
||||
- name: Commit and push source strings
|
||||
if: steps.extract.outputs.source_changed == 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git add translations/en.json translations/template.json
|
||||
git commit -m "i18n: update source strings from codebase"
|
||||
|
||||
for attempt in 1 2 3; do
|
||||
if git push; then
|
||||
echo "Successfully pushed source string updates"
|
||||
exit 0
|
||||
fi
|
||||
echo "Push attempt $attempt failed, pulling and retrying..."
|
||||
git pull --rebase
|
||||
sleep $((attempt*2))
|
||||
done
|
||||
|
||||
echo "Failed to push after retries" >&2
|
||||
exit 1
|
||||
|
||||
- name: Export and update translations from POEditor
|
||||
env:
|
||||
API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }}
|
||||
PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
LANGUAGES=(
|
||||
"ja:translations/poexports/ja.json"
|
||||
"zh-Hans:translations/poexports/zh_CN.json"
|
||||
"pt-br:translations/poexports/pt.json"
|
||||
)
|
||||
|
||||
ANY_CHANGED=false
|
||||
|
||||
for lang_pair in "${LANGUAGES[@]}"; do
|
||||
IFS=':' read -r PO_LANG REPO_FILE <<< "$lang_pair"
|
||||
|
||||
echo "::group::Processing $PO_LANG"
|
||||
|
||||
RESP=$(curl -sS -X POST https://api.poeditor.com/v2/projects/export \
|
||||
-d api_token="$API_TOKEN" \
|
||||
-d id="$PROJECT_ID" \
|
||||
-d language="$PO_LANG" \
|
||||
-d type="key_value_json")
|
||||
|
||||
STATUS=$(echo "$RESP" | jq -r '.response.status')
|
||||
if [[ "$STATUS" != "success" ]]; then
|
||||
echo "POEditor export request failed for $PO_LANG: $RESP" >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
URL=$(echo "$RESP" | jq -r '.result.url')
|
||||
if [[ -z "$URL" || "$URL" == "null" ]]; then
|
||||
echo "No export URL returned for $PO_LANG" >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
curl -sS -L "$URL" -o "/tmp/po_export_${PO_LANG}.json"
|
||||
jq -S . "/tmp/po_export_${PO_LANG}.json" > "/tmp/po_export_${PO_LANG}.norm.json"
|
||||
|
||||
if [[ -f "$REPO_FILE" ]]; then
|
||||
jq -S . "$REPO_FILE" > "/tmp/repo_${PO_LANG}.norm.json" || echo "{}" > "/tmp/repo_${PO_LANG}.norm.json"
|
||||
else
|
||||
echo "{}" > "/tmp/repo_${PO_LANG}.norm.json"
|
||||
fi
|
||||
|
||||
if diff -q "/tmp/po_export_${PO_LANG}.norm.json" "/tmp/repo_${PO_LANG}.norm.json" >/dev/null; then
|
||||
echo "No changes for $PO_LANG"
|
||||
else
|
||||
echo "Detected changes for $PO_LANG"
|
||||
mkdir -p "$(dirname "$REPO_FILE")"
|
||||
cp "/tmp/po_export_${PO_LANG}.norm.json" "$REPO_FILE"
|
||||
ANY_CHANGED=true
|
||||
fi
|
||||
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
echo "any_changed=$ANY_CHANGED" >> "$GITHUB_OUTPUT"
|
||||
id: export
|
||||
|
||||
- name: Commit and push translation updates
|
||||
if: steps.export.outputs.any_changed == 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git add translations/poexports/*.json
|
||||
git commit -m "i18n: update translations"
|
||||
|
||||
for attempt in 1 2 3; do
|
||||
if git push; then
|
||||
echo "Successfully pushed translation updates"
|
||||
exit 0
|
||||
fi
|
||||
echo "Push attempt $attempt failed, pulling and retrying..."
|
||||
git pull --rebase
|
||||
sleep $((attempt*2))
|
||||
done
|
||||
|
||||
echo "Failed to push after retries" >&2
|
||||
exit 1
|
||||
160
.github/workflows/release.yml
vendored
160
.github/workflows/release.yml
vendored
@@ -29,22 +29,18 @@ jobs:
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# create/update VERSION file to match incoming tag
|
||||
echo "${TAG}" > VERSION
|
||||
if ! git diff --quiet -- VERSION; then
|
||||
git add VERSION
|
||||
git commit -m "Add VERSION file for ${TAG} (from DMS)"
|
||||
|
||||
git add -A VERSION
|
||||
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "Update VERSION to ${TAG} (from DMS)"
|
||||
fi
|
||||
|
||||
# If tag doesn't exist (or differs), (re)create it
|
||||
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||
echo "Tag ${TAG} already exists"
|
||||
else
|
||||
git tag "${TAG}"
|
||||
fi
|
||||
git tag -f "${TAG}"
|
||||
|
||||
# Push commit (if any) and tag
|
||||
git push --follow-tags origin HEAD
|
||||
git push origin HEAD
|
||||
git push -f origin "${TAG}"
|
||||
|
||||
- name: Generate Changelog
|
||||
id: changelog
|
||||
@@ -57,7 +53,30 @@ jobs:
|
||||
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}")
|
||||
fi
|
||||
|
||||
cat > CHANGELOG.md << EOF
|
||||
cat > RELEASE_BODY.md << 'EOF'
|
||||
## Assets
|
||||
|
||||
### Complete Packages
|
||||
- **`dms-full-amd64.tar.gz`** - Complete package for x86_64 systems (CLI binaries + QML source + installation guide)
|
||||
- **`dms-full-arm64.tar.gz`** - Complete package for ARM64 systems (CLI binaries + QML source + installation guide)
|
||||
|
||||
### Individual Components
|
||||
- **`dms-cli-amd64.gz`** - DMS CLI binary for x86_64 systems
|
||||
- **`dms-cli-arm64.gz`** - DMS CLI binary for ARM64 systems
|
||||
- **`dms-distropkg-amd64.gz`** - DMS CLI binary built with distro_package tag for AMD64 systems
|
||||
- **`dms-distropkg-arm64.gz`** - DMS CLI binary built with distro_package tag for ARM64 systems
|
||||
- **`dms-qml.tar.gz`** - QML source code only
|
||||
|
||||
### Checksums
|
||||
- **`*.sha256`** - SHA256 checksums for verifying download integrity
|
||||
|
||||
**Installation:** Extract the `dms-full-*.tar.gz` package for your architecture and follow the `INSTALL.md` instructions inside.
|
||||
|
||||
---
|
||||
|
||||
EOF
|
||||
|
||||
cat >> RELEASE_BODY.md << EOF
|
||||
## What's Changed
|
||||
|
||||
$CHANGELOG
|
||||
@@ -66,7 +85,7 @@ jobs:
|
||||
EOF
|
||||
|
||||
echo "changelog<<EOF" >> $GITHUB_OUTPUT
|
||||
cat CHANGELOG.md >> $GITHUB_OUTPUT
|
||||
cat RELEASE_BODY.md >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create/Update DankMaterialShell Release
|
||||
@@ -80,18 +99,121 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Download DMS release assets
|
||||
- name: Download and prepare release assets
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
# gh is preinstalled on ubuntu-24.04; auth via GH_TOKEN env
|
||||
|
||||
mkdir -p _release_assets
|
||||
|
||||
# Download DMS CLI binaries from the danklinux repo
|
||||
gh release download "${TAG}" -R "${DMS_REPO}" --dir ./_dms_assets
|
||||
|
||||
- name: Attach DMS assets to DankMaterialShell release
|
||||
# Rename CLI binaries to dms-cli-* format and copy distropkg binaries
|
||||
for file in _dms_assets/dms-*.gz*; do
|
||||
if [ -f "$file" ]; then
|
||||
basename=$(basename "$file")
|
||||
if [[ "$basename" == dms-distropkg-* ]]; then
|
||||
cp "$file" "_release_assets/$basename"
|
||||
else
|
||||
newname=$(echo "$basename" | sed 's/^dms-/dms-cli-/')
|
||||
cp "$file" "_release_assets/$newname"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Create QML source package (exclude .git, .github, build artifacts)
|
||||
tar --exclude='.git' \
|
||||
--exclude='.github' \
|
||||
--exclude='_dms_assets' \
|
||||
--exclude='_release_assets' \
|
||||
--exclude='*.tar.gz' \
|
||||
-czf _release_assets/dms-qml.tar.gz .
|
||||
|
||||
# Generate checksum for QML package
|
||||
(cd _release_assets && sha256sum dms-qml.tar.gz > dms-qml.tar.gz.sha256)
|
||||
|
||||
# Create full packages for each architecture
|
||||
for arch in amd64 arm64; do
|
||||
mkdir -p _temp_full/dms
|
||||
mkdir -p _temp_full/bin
|
||||
|
||||
# Extract QML source to temp directory
|
||||
tar -xzf _release_assets/dms-qml.tar.gz -C _temp_full/dms
|
||||
|
||||
# Copy CLI binary if it exists
|
||||
if [ -f "_dms_assets/dms-${arch}.gz" ]; then
|
||||
gunzip -c "_dms_assets/dms-${arch}.gz" > _temp_full/bin/dms
|
||||
chmod +x _temp_full/bin/dms
|
||||
fi
|
||||
|
||||
# Copy distropkg binary if it exists
|
||||
if [ -f "_dms_assets/dms-distropkg-${arch}.gz" ]; then
|
||||
gunzip -c "_dms_assets/dms-distropkg-${arch}.gz" > _temp_full/bin/dms-distropkg
|
||||
chmod +x _temp_full/bin/dms-distropkg
|
||||
fi
|
||||
|
||||
# Create INSTALL.md
|
||||
cat > _temp_full/INSTALL.md << 'EOF'
|
||||
# DankMaterialShell Installation
|
||||
|
||||
## Requirements
|
||||
|
||||
- Wayland compositor (niri or Hyprland recommended)
|
||||
- Quickshell framework
|
||||
- Qt6
|
||||
|
||||
## Installation Steps
|
||||
|
||||
1. **Install quickshell assets:**
|
||||
```bash
|
||||
mkdir -p ~/.config/quickshell
|
||||
cp -r dms ~/.config/quickshell/
|
||||
```
|
||||
|
||||
2. **Install the DMS CLI binaries:**
|
||||
```bash
|
||||
sudo install -m 755 bin/dms /usr/local/bin/dms
|
||||
# or install to a local directory:
|
||||
mkdir -p ~/.local/bin
|
||||
install -m 755 bin/dms ~/.local/bin/dms
|
||||
```
|
||||
|
||||
3. **Start the shell:**
|
||||
```bash
|
||||
dms run
|
||||
# or directly with quickshell (will lack some dbus integrations & plugin management):
|
||||
quickshell -p ~/.config/quickshell/dms
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
- Settings are stored in `~/.config/DankMaterialShell/settings.json`
|
||||
- Plugins go in `~/.config/DankMaterialShell/plugins/`
|
||||
- See the documentation in the `dms/` directory for more details
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Run with verbose output: `quickshell -v -p ~/.config/quickshell/dms`
|
||||
- Check logs in `~/.local/state/DankMaterialShell/`
|
||||
- Ensure all dependencies are installed
|
||||
EOF
|
||||
|
||||
# Create the full package
|
||||
(cd _temp_full && tar -czf "../_release_assets/dms-full-${arch}.tar.gz" .)
|
||||
|
||||
# Generate checksum
|
||||
(cd _release_assets && sha256sum "dms-full-${arch}.tar.gz" > "dms-full-${arch}.tar.gz.sha256")
|
||||
|
||||
# Cleanup
|
||||
rm -rf _temp_full
|
||||
done
|
||||
|
||||
- name: Attach all assets to release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ env.TAG }}
|
||||
files: _dms_assets/**
|
||||
files: _release_assets/**
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
22
CLAUDE.md
22
CLAUDE.md
@@ -191,9 +191,9 @@ shell.qml # Main entry point (minimal orchestration)
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
|
||||
property type value: defaultValue
|
||||
|
||||
|
||||
function performAction() { /* implementation */ }
|
||||
}
|
||||
```
|
||||
@@ -251,23 +251,23 @@ shell.qml # Main entry point (minimal orchestration)
|
||||
// For regular components
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
||||
property type name: value
|
||||
|
||||
|
||||
signal customSignal(type param)
|
||||
|
||||
|
||||
onSignal: { /* handler */ }
|
||||
|
||||
|
||||
Component { /* children */ }
|
||||
}
|
||||
|
||||
|
||||
// For services (singletons)
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
|
||||
property bool featureAvailable: false
|
||||
property type currentValue: defaultValue
|
||||
|
||||
|
||||
function performAction(param) { /* implementation */ }
|
||||
}
|
||||
```
|
||||
@@ -305,7 +305,7 @@ shell.qml # Main entry point (minimal orchestration)
|
||||
```qml
|
||||
// In services - detect capabilities
|
||||
property bool brightnessAvailable: false
|
||||
|
||||
|
||||
// In modules - adapt UI accordingly
|
||||
DankSlider {
|
||||
visible: DisplayService.brightnessAvailable
|
||||
@@ -335,7 +335,7 @@ shell.qml # Main entry point (minimal orchestration)
|
||||
console.log("Info message") // General info
|
||||
console.warn("Warning message") // Warnings
|
||||
console.error("Error message") // Errors
|
||||
|
||||
|
||||
// Include context in service operations
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
|
||||
122
Common/CacheData.qml
Normal file
122
Common/CacheData.qml
Normal file
@@ -0,0 +1,122 @@
|
||||
pragma Singleton
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property int cacheConfigVersion: 1
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericCacheLocation)
|
||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||
|
||||
property bool _loading: false
|
||||
|
||||
property string wallpaperLastPath: ""
|
||||
property string profileLastPath: ""
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!isGreeterMode) {
|
||||
loadCache()
|
||||
}
|
||||
}
|
||||
|
||||
function loadCache() {
|
||||
_loading = true
|
||||
parseCache(cacheFile.text())
|
||||
_loading = false
|
||||
}
|
||||
|
||||
function parseCache(content) {
|
||||
_loading = true
|
||||
try {
|
||||
if (content && content.trim()) {
|
||||
const cache = JSON.parse(content)
|
||||
|
||||
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : ""
|
||||
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : ""
|
||||
|
||||
if (cache.configVersion === undefined) {
|
||||
migrateFromUndefinedToV1(cache)
|
||||
cleanupUnusedKeys()
|
||||
saveCache()
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("CacheData: Failed to parse cache:", e.message)
|
||||
} finally {
|
||||
_loading = false
|
||||
}
|
||||
}
|
||||
|
||||
function saveCache() {
|
||||
if (_loading)
|
||||
return
|
||||
cacheFile.setText(JSON.stringify({
|
||||
"wallpaperLastPath": wallpaperLastPath,
|
||||
"profileLastPath": profileLastPath,
|
||||
"configVersion": cacheConfigVersion
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
function migrateFromUndefinedToV1(cache) {
|
||||
console.log("CacheData: Migrating configuration from undefined to version 1")
|
||||
}
|
||||
|
||||
function cleanupUnusedKeys() {
|
||||
const validKeys = [
|
||||
"wallpaperLastPath",
|
||||
"profileLastPath",
|
||||
"configVersion"
|
||||
]
|
||||
|
||||
try {
|
||||
const content = cacheFile.text()
|
||||
if (!content || !content.trim()) return
|
||||
|
||||
const cache = JSON.parse(content)
|
||||
let needsSave = false
|
||||
|
||||
for (const key in cache) {
|
||||
if (!validKeys.includes(key)) {
|
||||
console.log("CacheData: Removing unused key:", key)
|
||||
delete cache[key]
|
||||
needsSave = true
|
||||
}
|
||||
}
|
||||
|
||||
if (needsSave) {
|
||||
cacheFile.setText(JSON.stringify(cache, null, 2))
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("CacheData: Failed to cleanup unused keys:", e.message)
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: cacheFile
|
||||
|
||||
path: isGreeterMode ? "" : _stateDir + "/DankMaterialShell/cache.json"
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
watchChanges: !isGreeterMode
|
||||
onLoaded: {
|
||||
if (!isGreeterMode) {
|
||||
parseCache(cacheFile.text())
|
||||
}
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
if (!isGreeterMode) {
|
||||
console.log("CacheData: No cache file found, starting fresh")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ Singleton {
|
||||
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
|
||||
}
|
||||
|
||||
|
||||
|
||||
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
|
||||
|
||||
property string currentLocale: "en"
|
||||
|
||||
70
Common/Proc.qml
Normal file
70
Common/Proc.qml
Normal file
@@ -0,0 +1,70 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int defaultDebounceMs: 50
|
||||
property var _procDebouncers: ({}) // id -> { timer, command, callback, waitMs }
|
||||
|
||||
function runCommand(id, command, callback, debounceMs) {
|
||||
const wait = (typeof debounceMs === "number" && debounceMs >= 0) ? debounceMs : defaultDebounceMs
|
||||
let procId = id ? id : Math.random()
|
||||
|
||||
if (!_procDebouncers[procId]) {
|
||||
const t = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root)
|
||||
t.triggered.connect(function() { _launchProc(procId) })
|
||||
_procDebouncers[procId] = { timer: t, command: command, callback: callback, waitMs: wait }
|
||||
} else {
|
||||
_procDebouncers[procId].command = command
|
||||
_procDebouncers[procId].callback = callback
|
||||
_procDebouncers[procId].waitMs = wait
|
||||
}
|
||||
|
||||
const entry = _procDebouncers[procId]
|
||||
entry.timer.interval = entry.waitMs
|
||||
entry.timer.restart()
|
||||
}
|
||||
|
||||
function _launchProc(id) {
|
||||
const entry = _procDebouncers[id]
|
||||
if (!entry) return
|
||||
|
||||
const proc = Qt.createQmlObject('import Quickshell.Io; Process { running: false }', root)
|
||||
const out = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc)
|
||||
const err = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc)
|
||||
|
||||
proc.stdout = out
|
||||
proc.stderr = err
|
||||
proc.command = entry.command
|
||||
|
||||
let capturedOut = ""
|
||||
let exitSeen = false
|
||||
let exitCodeValue = -1
|
||||
|
||||
out.streamFinished.connect(function() {
|
||||
capturedOut = out.text || ""
|
||||
maybeComplete()
|
||||
})
|
||||
|
||||
proc.exited.connect(function(code) {
|
||||
exitSeen = true
|
||||
exitCodeValue = code
|
||||
maybeComplete()
|
||||
})
|
||||
|
||||
function maybeComplete() {
|
||||
if (!exitSeen) return
|
||||
if (typeof entry.callback === "function") {
|
||||
try { entry.callback(capturedOut, exitCodeValue) } catch (e) { console.warn("runCommand callback error:", e) }
|
||||
}
|
||||
try { proc.destroy() } catch (_) {}
|
||||
}
|
||||
|
||||
proc.running = true
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,19 @@ import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
|
||||
id: root
|
||||
|
||||
readonly property int sessionConfigVersion: 1
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
property bool hasTriedDefaultSession: false
|
||||
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
|
||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||
|
||||
property bool isLightMode: false
|
||||
property bool doNotDisturb: false
|
||||
|
||||
property string wallpaperPath: ""
|
||||
property string wallpaperLastPath: ""
|
||||
property string profileLastPath: ""
|
||||
property bool perMonitorWallpaper: false
|
||||
property var monitorWallpapers: ({})
|
||||
property bool perModeWallpaper: false
|
||||
@@ -25,55 +29,39 @@ Singleton {
|
||||
property string wallpaperPathDark: ""
|
||||
property var monitorWallpapersLight: ({})
|
||||
property var monitorWallpapersDark: ({})
|
||||
property bool doNotDisturb: false
|
||||
property string wallpaperTransition: "fade"
|
||||
readonly property var availableWallpaperTransitions: ["none", "fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"]
|
||||
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
|
||||
|
||||
property bool wallpaperCyclingEnabled: false
|
||||
property string wallpaperCyclingMode: "interval"
|
||||
property int wallpaperCyclingInterval: 300
|
||||
property string wallpaperCyclingTime: "06:00"
|
||||
property var monitorCyclingSettings: ({})
|
||||
|
||||
property bool nightModeEnabled: false
|
||||
property int nightModeTemperature: 4500
|
||||
property bool nightModeAutoEnabled: false
|
||||
property string nightModeAutoMode: "time"
|
||||
|
||||
property bool hasTriedDefaultSession: false
|
||||
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
|
||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||
property int nightModeStartHour: 18
|
||||
property int nightModeStartMinute: 0
|
||||
property int nightModeEndHour: 6
|
||||
property int nightModeEndMinute: 0
|
||||
property real latitude: 0.0
|
||||
property real longitude: 0.0
|
||||
property bool nightModeUseIPLocation: false
|
||||
property string nightModeLocationProvider: ""
|
||||
|
||||
property var pinnedApps: []
|
||||
property var recentColors: []
|
||||
property bool showThirdPartyPlugins: false
|
||||
property string launchPrefix: ""
|
||||
property string lastBrightnessDevice: ""
|
||||
|
||||
property int selectedGpuIndex: 0
|
||||
property bool nvidiaGpuTempEnabled: false
|
||||
property bool nonNvidiaGpuTempEnabled: false
|
||||
property var enabledGpuPciIds: []
|
||||
property bool wallpaperCyclingEnabled: false
|
||||
property string wallpaperCyclingMode: "interval" // "interval" or "time"
|
||||
property int wallpaperCyclingInterval: 300 // seconds (5 minutes)
|
||||
property string wallpaperCyclingTime: "06:00" // HH:mm format
|
||||
property var monitorCyclingSettings: ({})
|
||||
property string lastBrightnessDevice: ""
|
||||
property string launchPrefix: ""
|
||||
property string wallpaperTransition: "fade"
|
||||
readonly property var availableWallpaperTransitions: ["none", "fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"]
|
||||
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
|
||||
|
||||
// Power management settings - AC Power
|
||||
property int acMonitorTimeout: 0 // Never
|
||||
property int acLockTimeout: 0 // Never
|
||||
property int acSuspendTimeout: 0 // Never
|
||||
property int acHibernateTimeout: 0 // Never
|
||||
|
||||
// Power management settings - Battery
|
||||
property int batteryMonitorTimeout: 0 // Never
|
||||
property int batteryLockTimeout: 0 // Never
|
||||
property int batterySuspendTimeout: 0 // Never
|
||||
property int batteryHibernateTimeout: 0 // Never
|
||||
|
||||
property bool lockBeforeSuspend: false
|
||||
property bool loginctlLockIntegration: true
|
||||
property var recentColors: []
|
||||
property bool showThirdPartyPlugins: false
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!isGreeterMode) {
|
||||
@@ -95,8 +83,6 @@ Singleton {
|
||||
var settings = JSON.parse(content)
|
||||
isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false
|
||||
wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : ""
|
||||
wallpaperLastPath = settings.wallpaperLastPath !== undefined ? settings.wallpaperLastPath : ""
|
||||
profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : ""
|
||||
perMonitorWallpaper = settings.perMonitorWallpaper !== undefined ? settings.perMonitorWallpaper : false
|
||||
monitorWallpapers = settings.monitorWallpapers !== undefined ? settings.monitorWallpapers : {}
|
||||
perModeWallpaper = settings.perModeWallpaper !== undefined ? settings.perModeWallpaper : false
|
||||
@@ -109,7 +95,6 @@ Singleton {
|
||||
nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500
|
||||
nightModeAutoEnabled = settings.nightModeAutoEnabled !== undefined ? settings.nightModeAutoEnabled : false
|
||||
nightModeAutoMode = settings.nightModeAutoMode !== undefined ? settings.nightModeAutoMode : "time"
|
||||
// Handle legacy time format
|
||||
if (settings.nightModeStartTime !== undefined) {
|
||||
const parts = settings.nightModeStartTime.split(":")
|
||||
nightModeStartHour = parseInt(parts[0]) || 18
|
||||
@@ -128,6 +113,7 @@ Singleton {
|
||||
}
|
||||
latitude = settings.latitude !== undefined ? settings.latitude : 0.0
|
||||
longitude = settings.longitude !== undefined ? settings.longitude : 0.0
|
||||
nightModeUseIPLocation = settings.nightModeUseIPLocation !== undefined ? settings.nightModeUseIPLocation : false
|
||||
nightModeLocationProvider = settings.nightModeLocationProvider !== undefined ? settings.nightModeLocationProvider : ""
|
||||
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : []
|
||||
selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0
|
||||
@@ -143,20 +129,16 @@ Singleton {
|
||||
launchPrefix = settings.launchPrefix !== undefined ? settings.launchPrefix : ""
|
||||
wallpaperTransition = settings.wallpaperTransition !== undefined ? settings.wallpaperTransition : "fade"
|
||||
includedTransitions = settings.includedTransitions !== undefined ? settings.includedTransitions : availableWallpaperTransitions.filter(t => t !== "none")
|
||||
|
||||
acMonitorTimeout = settings.acMonitorTimeout !== undefined ? settings.acMonitorTimeout : 0
|
||||
acLockTimeout = settings.acLockTimeout !== undefined ? settings.acLockTimeout : 0
|
||||
acSuspendTimeout = settings.acSuspendTimeout !== undefined ? settings.acSuspendTimeout : 0
|
||||
acHibernateTimeout = settings.acHibernateTimeout !== undefined ? settings.acHibernateTimeout : 0
|
||||
batteryMonitorTimeout = settings.batteryMonitorTimeout !== undefined ? settings.batteryMonitorTimeout : 0
|
||||
batteryLockTimeout = settings.batteryLockTimeout !== undefined ? settings.batteryLockTimeout : 0
|
||||
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
|
||||
batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
|
||||
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
|
||||
loginctlLockIntegration = settings.loginctlLockIntegration !== undefined ? settings.loginctlLockIntegration : true
|
||||
recentColors = settings.recentColors !== undefined ? settings.recentColors : []
|
||||
showThirdPartyPlugins = settings.showThirdPartyPlugins !== undefined ? settings.showThirdPartyPlugins : false
|
||||
|
||||
if (settings.configVersion === undefined) {
|
||||
migrateFromUndefinedToV1(settings)
|
||||
saveSettings()
|
||||
} else if (settings.configVersion === sessionConfigVersion) {
|
||||
cleanupUnusedKeys()
|
||||
}
|
||||
|
||||
if (!isGreeterMode) {
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
@@ -173,8 +155,6 @@ Singleton {
|
||||
settingsFile.setText(JSON.stringify({
|
||||
"isLightMode": isLightMode,
|
||||
"wallpaperPath": wallpaperPath,
|
||||
"wallpaperLastPath": wallpaperLastPath,
|
||||
"profileLastPath": profileLastPath,
|
||||
"perMonitorWallpaper": perMonitorWallpaper,
|
||||
"monitorWallpapers": monitorWallpapers,
|
||||
"perModeWallpaper": perModeWallpaper,
|
||||
@@ -193,6 +173,7 @@ Singleton {
|
||||
"nightModeEndMinute": nightModeEndMinute,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"nightModeUseIPLocation": nightModeUseIPLocation,
|
||||
"nightModeLocationProvider": nightModeLocationProvider,
|
||||
"pinnedApps": pinnedApps,
|
||||
"selectedGpuIndex": selectedGpuIndex,
|
||||
@@ -208,101 +189,109 @@ Singleton {
|
||||
"launchPrefix": launchPrefix,
|
||||
"wallpaperTransition": wallpaperTransition,
|
||||
"includedTransitions": includedTransitions,
|
||||
"acMonitorTimeout": acMonitorTimeout,
|
||||
"acLockTimeout": acLockTimeout,
|
||||
"acSuspendTimeout": acSuspendTimeout,
|
||||
"acHibernateTimeout": acHibernateTimeout,
|
||||
"batteryMonitorTimeout": batteryMonitorTimeout,
|
||||
"batteryLockTimeout": batteryLockTimeout,
|
||||
"batterySuspendTimeout": batterySuspendTimeout,
|
||||
"batteryHibernateTimeout": batteryHibernateTimeout,
|
||||
"lockBeforeSuspend": lockBeforeSuspend,
|
||||
"loginctlLockIntegration": loginctlLockIntegration,
|
||||
"recentColors": recentColors,
|
||||
"showThirdPartyPlugins": showThirdPartyPlugins
|
||||
"showThirdPartyPlugins": showThirdPartyPlugins,
|
||||
"configVersion": sessionConfigVersion
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
function migrateFromUndefinedToV1(settings) {
|
||||
console.log("SessionData: Migrating configuration from undefined to version 1")
|
||||
if (typeof SettingsData !== "undefined") {
|
||||
if (settings.acMonitorTimeout !== undefined) {
|
||||
SettingsData.setAcMonitorTimeout(settings.acMonitorTimeout)
|
||||
}
|
||||
if (settings.acLockTimeout !== undefined) {
|
||||
SettingsData.setAcLockTimeout(settings.acLockTimeout)
|
||||
}
|
||||
if (settings.acSuspendTimeout !== undefined) {
|
||||
SettingsData.setAcSuspendTimeout(settings.acSuspendTimeout)
|
||||
}
|
||||
if (settings.acHibernateTimeout !== undefined) {
|
||||
SettingsData.setAcHibernateTimeout(settings.acHibernateTimeout)
|
||||
}
|
||||
if (settings.batteryMonitorTimeout !== undefined) {
|
||||
SettingsData.setBatteryMonitorTimeout(settings.batteryMonitorTimeout)
|
||||
}
|
||||
if (settings.batteryLockTimeout !== undefined) {
|
||||
SettingsData.setBatteryLockTimeout(settings.batteryLockTimeout)
|
||||
}
|
||||
if (settings.batterySuspendTimeout !== undefined) {
|
||||
SettingsData.setBatterySuspendTimeout(settings.batterySuspendTimeout)
|
||||
}
|
||||
if (settings.batteryHibernateTimeout !== undefined) {
|
||||
SettingsData.setBatteryHibernateTimeout(settings.batteryHibernateTimeout)
|
||||
}
|
||||
if (settings.lockBeforeSuspend !== undefined) {
|
||||
SettingsData.setLockBeforeSuspend(settings.lockBeforeSuspend)
|
||||
}
|
||||
if (settings.loginctlLockIntegration !== undefined) {
|
||||
SettingsData.setLoginctlLockIntegration(settings.loginctlLockIntegration)
|
||||
}
|
||||
if (settings.launchPrefix !== undefined) {
|
||||
SettingsData.setLaunchPrefix(settings.launchPrefix)
|
||||
}
|
||||
}
|
||||
if (typeof CacheData !== "undefined") {
|
||||
if (settings.wallpaperLastPath !== undefined) {
|
||||
CacheData.wallpaperLastPath = settings.wallpaperLastPath
|
||||
}
|
||||
if (settings.profileLastPath !== undefined) {
|
||||
CacheData.profileLastPath = settings.profileLastPath
|
||||
}
|
||||
CacheData.saveCache()
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupUnusedKeys() {
|
||||
const validKeys = [
|
||||
"isLightMode", "wallpaperPath", "perMonitorWallpaper", "monitorWallpapers", "perModeWallpaper",
|
||||
"wallpaperPathLight", "wallpaperPathDark", "monitorWallpapersLight",
|
||||
"monitorWallpapersDark", "doNotDisturb", "nightModeEnabled",
|
||||
"nightModeTemperature", "nightModeAutoEnabled", "nightModeAutoMode",
|
||||
"nightModeStartHour", "nightModeStartMinute", "nightModeEndHour",
|
||||
"nightModeEndMinute", "latitude", "longitude", "nightModeUseIPLocation", "nightModeLocationProvider",
|
||||
"pinnedApps", "selectedGpuIndex", "nvidiaGpuTempEnabled",
|
||||
"nonNvidiaGpuTempEnabled", "enabledGpuPciIds", "wallpaperCyclingEnabled",
|
||||
"wallpaperCyclingMode", "wallpaperCyclingInterval", "wallpaperCyclingTime",
|
||||
"monitorCyclingSettings", "lastBrightnessDevice", "launchPrefix", "wallpaperTransition",
|
||||
"includedTransitions", "recentColors", "showThirdPartyPlugins", "configVersion"
|
||||
]
|
||||
|
||||
try {
|
||||
const content = settingsFile.text()
|
||||
if (!content || !content.trim()) return
|
||||
|
||||
const settings = JSON.parse(content)
|
||||
let needsSave = false
|
||||
|
||||
for (const key in settings) {
|
||||
if (!validKeys.includes(key)) {
|
||||
console.log("SessionData: Removing unused key:", key)
|
||||
delete settings[key]
|
||||
needsSave = true
|
||||
}
|
||||
}
|
||||
|
||||
if (needsSave) {
|
||||
settingsFile.setText(JSON.stringify(settings, null, 2))
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("SessionData: Failed to cleanup unused keys:", e.message)
|
||||
}
|
||||
}
|
||||
|
||||
function setLightMode(lightMode) {
|
||||
isLightMode = lightMode
|
||||
syncWallpaperForCurrentMode()
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function syncWallpaperForCurrentMode() {
|
||||
if (!perModeWallpaper) return
|
||||
|
||||
if (perMonitorWallpaper) {
|
||||
monitorWallpapers = isLightMode ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark)
|
||||
return
|
||||
}
|
||||
|
||||
wallpaperPath = isLightMode ? wallpaperPathLight : wallpaperPathDark
|
||||
}
|
||||
|
||||
function setDoNotDisturb(enabled) {
|
||||
doNotDisturb = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEnabled(enabled) {
|
||||
nightModeEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeTemperature(temperature) {
|
||||
nightModeTemperature = temperature
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeAutoEnabled(enabled) {
|
||||
console.log("SessionData: Setting nightModeAutoEnabled to", enabled)
|
||||
nightModeAutoEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeAutoMode(mode) {
|
||||
nightModeAutoMode = mode
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeStartHour(hour) {
|
||||
nightModeStartHour = hour
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeStartMinute(minute) {
|
||||
nightModeStartMinute = minute
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEndHour(hour) {
|
||||
nightModeEndHour = hour
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEndMinute(minute) {
|
||||
nightModeEndMinute = minute
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLatitude(lat) {
|
||||
console.log("SessionData: Setting latitude to", lat)
|
||||
latitude = lat
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLongitude(lng) {
|
||||
console.log("SessionData: Setting longitude to", lng)
|
||||
longitude = lng
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeLocationProvider(provider) {
|
||||
nightModeLocationProvider = provider
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperPath(path) {
|
||||
wallpaperPath = path
|
||||
saveSettings()
|
||||
@@ -353,141 +342,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function setWallpaperLastPath(path) {
|
||||
wallpaperLastPath = path
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setProfileLastPath(path) {
|
||||
profileLastPath = path
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setPinnedApps(apps) {
|
||||
pinnedApps = apps
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function addRecentColor(color) {
|
||||
const colorStr = color.toString()
|
||||
let recent = recentColors.slice()
|
||||
recent = recent.filter(c => c !== colorStr)
|
||||
recent.unshift(colorStr)
|
||||
if (recent.length > 5) recent = recent.slice(0, 5)
|
||||
recentColors = recent
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function addPinnedApp(appId) {
|
||||
if (!appId)
|
||||
return
|
||||
var currentPinned = [...pinnedApps]
|
||||
if (currentPinned.indexOf(appId) === -1) {
|
||||
currentPinned.push(appId)
|
||||
setPinnedApps(currentPinned)
|
||||
}
|
||||
}
|
||||
|
||||
function removePinnedApp(appId) {
|
||||
if (!appId)
|
||||
return
|
||||
var currentPinned = pinnedApps.filter(id => id !== appId)
|
||||
setPinnedApps(currentPinned)
|
||||
}
|
||||
|
||||
function isPinnedApp(appId) {
|
||||
return appId && pinnedApps.indexOf(appId) !== -1
|
||||
}
|
||||
|
||||
function setSelectedGpuIndex(index) {
|
||||
selectedGpuIndex = index
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNvidiaGpuTempEnabled(enabled) {
|
||||
nvidiaGpuTempEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNonNvidiaGpuTempEnabled(enabled) {
|
||||
nonNvidiaGpuTempEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setEnabledGpuPciIds(pciIds) {
|
||||
enabledGpuPciIds = pciIds
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingEnabled(enabled) {
|
||||
wallpaperCyclingEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingMode(mode) {
|
||||
wallpaperCyclingMode = mode
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingInterval(interval) {
|
||||
wallpaperCyclingInterval = interval
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingTime(time) {
|
||||
wallpaperCyclingTime = time
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function getMonitorCyclingSettings(screenName) {
|
||||
return monitorCyclingSettings[screenName] || {
|
||||
enabled: false,
|
||||
mode: "interval",
|
||||
interval: 300,
|
||||
time: "06:00"
|
||||
}
|
||||
}
|
||||
|
||||
function setMonitorCyclingEnabled(screenName, enabled) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].enabled = enabled
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingMode(screenName, mode) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].mode = mode
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingInterval(screenName, interval) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].interval = interval
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingTime(screenName, time) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].time = time
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setPerMonitorWallpaper(enabled) {
|
||||
perMonitorWallpaper = enabled
|
||||
if (enabled && perModeWallpaper) {
|
||||
@@ -579,15 +433,172 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function getMonitorWallpaper(screenName) {
|
||||
if (!perMonitorWallpaper) {
|
||||
return wallpaperPath
|
||||
}
|
||||
return monitorWallpapers[screenName] || wallpaperPath
|
||||
function setWallpaperTransition(transition) {
|
||||
wallpaperTransition = transition
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLastBrightnessDevice(device) {
|
||||
lastBrightnessDevice = device
|
||||
function setWallpaperCyclingEnabled(enabled) {
|
||||
wallpaperCyclingEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingMode(mode) {
|
||||
wallpaperCyclingMode = mode
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingInterval(interval) {
|
||||
wallpaperCyclingInterval = interval
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperCyclingTime(time) {
|
||||
wallpaperCyclingTime = time
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingEnabled(screenName, enabled) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].enabled = enabled
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingMode(screenName, mode) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].mode = mode
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingInterval(screenName, interval) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].interval = interval
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMonitorCyclingTime(screenName, time) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
if (!newSettings[screenName]) {
|
||||
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
|
||||
}
|
||||
newSettings[screenName].time = time
|
||||
monitorCyclingSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEnabled(enabled) {
|
||||
nightModeEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeTemperature(temperature) {
|
||||
nightModeTemperature = temperature
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeAutoEnabled(enabled) {
|
||||
console.log("SessionData: Setting nightModeAutoEnabled to", enabled)
|
||||
nightModeAutoEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeAutoMode(mode) {
|
||||
nightModeAutoMode = mode
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeStartHour(hour) {
|
||||
nightModeStartHour = hour
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeStartMinute(minute) {
|
||||
nightModeStartMinute = minute
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEndHour(hour) {
|
||||
nightModeEndHour = hour
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEndMinute(minute) {
|
||||
nightModeEndMinute = minute
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeUseIPLocation(use) {
|
||||
nightModeUseIPLocation = use
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLatitude(lat) {
|
||||
console.log("SessionData: Setting latitude to", lat)
|
||||
latitude = lat
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLongitude(lng) {
|
||||
console.log("SessionData: Setting longitude to", lng)
|
||||
longitude = lng
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeLocationProvider(provider) {
|
||||
nightModeLocationProvider = provider
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setPinnedApps(apps) {
|
||||
pinnedApps = apps
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function addPinnedApp(appId) {
|
||||
if (!appId)
|
||||
return
|
||||
var currentPinned = [...pinnedApps]
|
||||
if (currentPinned.indexOf(appId) === -1) {
|
||||
currentPinned.push(appId)
|
||||
setPinnedApps(currentPinned)
|
||||
}
|
||||
}
|
||||
|
||||
function removePinnedApp(appId) {
|
||||
if (!appId)
|
||||
return
|
||||
var currentPinned = pinnedApps.filter(id => id !== appId)
|
||||
setPinnedApps(currentPinned)
|
||||
}
|
||||
|
||||
function isPinnedApp(appId) {
|
||||
return appId && pinnedApps.indexOf(appId) !== -1
|
||||
}
|
||||
|
||||
function addRecentColor(color) {
|
||||
const colorStr = color.toString()
|
||||
let recent = recentColors.slice()
|
||||
recent = recent.filter(c => c !== colorStr)
|
||||
recent.unshift(colorStr)
|
||||
if (recent.length > 5) recent = recent.slice(0, 5)
|
||||
recentColors = recent
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setShowThirdPartyPlugins(enabled) {
|
||||
showThirdPartyPlugins = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
@@ -596,64 +607,56 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperTransition(transition) {
|
||||
wallpaperTransition = transition
|
||||
function setLastBrightnessDevice(device) {
|
||||
lastBrightnessDevice = device
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setAcMonitorTimeout(timeout) {
|
||||
acMonitorTimeout = timeout
|
||||
function setSelectedGpuIndex(index) {
|
||||
selectedGpuIndex = index
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setAcLockTimeout(timeout) {
|
||||
acLockTimeout = timeout
|
||||
function setNvidiaGpuTempEnabled(enabled) {
|
||||
nvidiaGpuTempEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setAcSuspendTimeout(timeout) {
|
||||
acSuspendTimeout = timeout
|
||||
function setNonNvidiaGpuTempEnabled(enabled) {
|
||||
nonNvidiaGpuTempEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setBatteryMonitorTimeout(timeout) {
|
||||
batteryMonitorTimeout = timeout
|
||||
function setEnabledGpuPciIds(pciIds) {
|
||||
enabledGpuPciIds = pciIds
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setBatteryLockTimeout(timeout) {
|
||||
batteryLockTimeout = timeout
|
||||
saveSettings()
|
||||
function syncWallpaperForCurrentMode() {
|
||||
if (!perModeWallpaper) return
|
||||
|
||||
if (perMonitorWallpaper) {
|
||||
monitorWallpapers = isLightMode ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark)
|
||||
return
|
||||
}
|
||||
|
||||
wallpaperPath = isLightMode ? wallpaperPathLight : wallpaperPathDark
|
||||
}
|
||||
|
||||
function setBatterySuspendTimeout(timeout) {
|
||||
batterySuspendTimeout = timeout
|
||||
saveSettings()
|
||||
function getMonitorWallpaper(screenName) {
|
||||
if (!perMonitorWallpaper) {
|
||||
return wallpaperPath
|
||||
}
|
||||
return monitorWallpapers[screenName] || wallpaperPath
|
||||
}
|
||||
|
||||
function setAcHibernateTimeout(timeout) {
|
||||
acHibernateTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setBatteryHibernateTimeout(timeout) {
|
||||
batteryHibernateTimeout = timeout
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLockBeforeSuspend(enabled) {
|
||||
lockBeforeSuspend = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLoginctlLockIntegration(enabled) {
|
||||
loginctlLockIntegration = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setShowThirdPartyPlugins(enabled) {
|
||||
showThirdPartyPlugins = enabled
|
||||
saveSettings()
|
||||
function getMonitorCyclingSettings(screenName) {
|
||||
return monitorCyclingSettings[screenName] || {
|
||||
enabled: false,
|
||||
mode: "interval",
|
||||
interval: 300,
|
||||
time: "06:00"
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
346
Common/Theme.qml
346
Common/Theme.qml
@@ -14,14 +14,19 @@ import "StockThemes.js" as StockThemes
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericCacheLocation).toString()) + "/DankMaterialShell"
|
||||
|
||||
readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true"
|
||||
|
||||
// ! TODO - Synchronize with niri/hyprland gaps?
|
||||
readonly property real popupDistance: 2
|
||||
readonly property real popupDistance: {
|
||||
if (typeof SettingsData === "undefined") return 4
|
||||
return SettingsData.popupGapsAuto ? Math.max(4, SettingsData.dankBarSpacing) : SettingsData.popupGapsManual
|
||||
}
|
||||
|
||||
property string currentTheme: "blue"
|
||||
property string currentThemeCategory: "generic"
|
||||
property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false
|
||||
property bool colorsFileLoadFailed: false
|
||||
|
||||
readonly property string dynamic: "dynamic"
|
||||
readonly property string custom : "custom"
|
||||
@@ -56,7 +61,7 @@ Singleton {
|
||||
}
|
||||
readonly property string rawWallpaperPath: {
|
||||
if (typeof SessionData === "undefined") return ""
|
||||
|
||||
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
// Use first monitor's wallpaper for dynamic theming
|
||||
var screens = Quickshell.screens
|
||||
@@ -76,11 +81,62 @@ Singleton {
|
||||
property var matugenColors: ({})
|
||||
property var customThemeData: null
|
||||
|
||||
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell"
|
||||
|
||||
Component.onCompleted: {
|
||||
Quickshell.execDetached(["mkdir", "-p", stateDir])
|
||||
matugenCheck.running = true
|
||||
Proc.runCommand("matugenCheck", ["which", "matugen"], (output, code) => {
|
||||
matugenAvailable = (code === 0) && !envDisableMatugen
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
|
||||
|
||||
if (!matugenAvailable || isGreeterMode) {
|
||||
return
|
||||
}
|
||||
|
||||
if (colorsFileLoadFailed && currentTheme === dynamic && wallpaperPath) {
|
||||
console.log("Theme: Matugen now available, regenerating colors for dynamic theme")
|
||||
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
|
||||
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
|
||||
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
|
||||
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
|
||||
if (wallpaperPath.startsWith("#")) {
|
||||
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
} else {
|
||||
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
|
||||
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
|
||||
|
||||
if (currentTheme === dynamic) {
|
||||
if (wallpaperPath) {
|
||||
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
|
||||
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
|
||||
if (wallpaperPath.startsWith("#")) {
|
||||
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
} else {
|
||||
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let primaryColor
|
||||
let matugenType
|
||||
if (currentTheme === "custom") {
|
||||
if (customThemeData && customThemeData.primary) {
|
||||
primaryColor = customThemeData.primary
|
||||
matugenType = customThemeData.matugen_type
|
||||
}
|
||||
} else {
|
||||
primaryColor = currentThemeData.primary
|
||||
matugenType = currentThemeData.matugen_type
|
||||
}
|
||||
|
||||
if (primaryColor) {
|
||||
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
|
||||
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType)
|
||||
}
|
||||
}
|
||||
}, 0)
|
||||
if (typeof SessionData !== "undefined") {
|
||||
SessionData.isLightModeChanged.connect(root.onLightModeChanged)
|
||||
}
|
||||
@@ -139,14 +195,16 @@ Singleton {
|
||||
}
|
||||
|
||||
readonly property var availableMatugenSchemes: [
|
||||
({ "value": "scheme-tonal-spot", "label": "Tonal Spot", "description": "Balanced palette with focused accents (default)." }),
|
||||
({ "value": "scheme-content", "label": "Content", "description": "Derives colors that closely match the underlying image." }),
|
||||
({ "value": "scheme-expressive", "label": "Expressive", "description": "Vibrant palette with playful saturation." }),
|
||||
({ "value": "scheme-fidelity", "label": "Fidelity", "description": "High-fidelity palette that preserves source hues." }),
|
||||
({ "value": "scheme-fruit-salad", "label": "Fruit Salad", "description": "Colorful mix of bright contrasting accents." }),
|
||||
({ "value": "scheme-monochrome", "label": "Monochrome", "description": "Minimal palette built around a single hue." }),
|
||||
({ "value": "scheme-neutral", "label": "Neutral", "description": "Muted palette with subdued, calming tones." }),
|
||||
({ "value": "scheme-rainbow", "label": "Rainbow", "description": "Diverse palette spanning the full spectrum." })
|
||||
({ "value": "scheme-tonal-spot", "label": "Tonal Spot", "description": I18n.tr("Balanced palette with focused accents (default).") }),
|
||||
({ "value": "scheme-vibrant-spot", "label": "Vibrant Spot", "description": I18n.tr("Lively palette with saturated accents.") }),
|
||||
({ "value": "scheme-dynamic-contrast", "label": "Dynamic Contrast", "description": I18n.tr("High-contrast palette for strong visual distinction.") }),
|
||||
({ "value": "scheme-content", "label": "Content", "description": I18n.tr("Derives colors that closely match the underlying image.") }),
|
||||
({ "value": "scheme-expressive", "label": "Expressive", "description": I18n.tr("Vibrant palette with playful saturation.") }),
|
||||
({ "value": "scheme-fidelity", "label": "Fidelity", "description": I18n.tr("High-fidelity palette that preserves source hues.") }),
|
||||
({ "value": "scheme-fruit-salad", "label": "Fruit Salad", "description": I18n.tr("Colorful mix of bright contrasting accents.") }),
|
||||
({ "value": "scheme-monochrome", "label": "Monochrome", "description": I18n.tr("Minimal palette built around a single hue.") }),
|
||||
({ "value": "scheme-neutral", "label": "Neutral", "description": I18n.tr("Muted palette with subdued, calming tones.") }),
|
||||
({ "value": "scheme-rainbow", "label": "Rainbow", "description": I18n.tr("Diverse palette spanning the full spectrum.") })
|
||||
]
|
||||
|
||||
function getMatugenScheme(value) {
|
||||
@@ -257,6 +315,61 @@ Singleton {
|
||||
property int standardEasing: Easing.OutCubic
|
||||
property int emphasizedEasing: Easing.OutQuart
|
||||
|
||||
readonly property var expressiveCurves: {
|
||||
"emphasized": [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1],
|
||||
"emphasizedAccel": [0.3, 0, 0.8, 0.15, 1, 1],
|
||||
"emphasizedDecel": [0.05, 0.7, 0.1, 1, 1, 1],
|
||||
"standard": [0.2, 0, 0, 1, 1, 1],
|
||||
"standardAccel": [0.3, 0, 1, 1, 1, 1],
|
||||
"standardDecel": [0, 0, 0, 1, 1, 1],
|
||||
"expressiveFastSpatial": [0.42, 1.67, 0.21, 0.9, 1, 1],
|
||||
"expressiveDefaultSpatial": [0.38, 1.21, 0.22, 1, 1, 1],
|
||||
"expressiveEffects": [0.34, 0.8, 0.34, 1, 1, 1]
|
||||
}
|
||||
|
||||
readonly property var animationPresetDurations: {
|
||||
"none": 0,
|
||||
"short": 250,
|
||||
"medium": 500,
|
||||
"long": 750
|
||||
}
|
||||
|
||||
readonly property int currentAnimationBaseDuration: {
|
||||
if (typeof SettingsData === "undefined") return 500
|
||||
|
||||
if (SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) {
|
||||
return SettingsData.customAnimationDuration
|
||||
}
|
||||
|
||||
const presetMap = [0, 250, 500, 750]
|
||||
return presetMap[SettingsData.animationSpeed] !== undefined ? presetMap[SettingsData.animationSpeed] : 500
|
||||
}
|
||||
|
||||
readonly property var expressiveDurations: {
|
||||
if (typeof SettingsData === "undefined") {
|
||||
return {
|
||||
"fast": 200,
|
||||
"normal": 400,
|
||||
"large": 600,
|
||||
"extraLarge": 1000,
|
||||
"expressiveFastSpatial": 350,
|
||||
"expressiveDefaultSpatial": 500,
|
||||
"expressiveEffects": 200
|
||||
}
|
||||
}
|
||||
|
||||
const baseDuration = currentAnimationBaseDuration
|
||||
return {
|
||||
"fast": baseDuration * 0.4,
|
||||
"normal": baseDuration * 0.8,
|
||||
"large": baseDuration * 1.2,
|
||||
"extraLarge": baseDuration * 2.0,
|
||||
"expressiveFastSpatial": baseDuration * 0.7,
|
||||
"expressiveDefaultSpatial": baseDuration,
|
||||
"expressiveEffects": baseDuration * 0.4
|
||||
}
|
||||
}
|
||||
|
||||
property real cornerRadius: typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
|
||||
property real spacingXS: 4
|
||||
property real spacingS: 8
|
||||
@@ -325,11 +438,13 @@ Singleton {
|
||||
}
|
||||
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
|
||||
isLightMode = light
|
||||
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
|
||||
SessionData.setLightMode(isLightMode)
|
||||
SessionData.setLightMode(light)
|
||||
if (!isGreeterMode) {
|
||||
PortalService.setLightMode(isLightMode)
|
||||
// Skip with matugen becuase, our script runner will do it.
|
||||
if (!matugenAvailable) {
|
||||
PortalService.setLightMode(light)
|
||||
}
|
||||
generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
@@ -582,10 +697,12 @@ Singleton {
|
||||
|
||||
function setDesiredTheme(kind, value, isLight, iconTheme, matugenType) {
|
||||
if (!matugenAvailable) {
|
||||
console.warn("matugen not available or disabled - cannot set system theme")
|
||||
console.warn("Theme: matugen not available or disabled - cannot set system theme")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", "type:", matugenType)
|
||||
|
||||
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
|
||||
NiriService.suppressNextToast()
|
||||
}
|
||||
@@ -596,7 +713,8 @@ Singleton {
|
||||
"mode": isLight ? "light" : "dark",
|
||||
"iconTheme": iconTheme || "System Default",
|
||||
"matugenType": matugenType || "scheme-tonal-spot",
|
||||
"surfaceBase": (typeof SettingsData !== "undefined" && SettingsData.surfaceBase) ? SettingsData.surfaceBase : "sc"
|
||||
"surfaceBase": (typeof SettingsData !== "undefined" && SettingsData.surfaceBase) ? SettingsData.surfaceBase : "sc",
|
||||
"runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true
|
||||
}
|
||||
|
||||
const json = JSON.stringify(desired)
|
||||
@@ -604,14 +722,16 @@ Singleton {
|
||||
|
||||
Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`])
|
||||
workerRunning = true
|
||||
const syncModeWithPortal = (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) ? "true" : "false"
|
||||
if (rawWallpaperPath.startsWith("we:")) {
|
||||
console.log("calling matugen worker")
|
||||
console.log("Theme: Starting matugen worker (WE wallpaper)")
|
||||
systemThemeGenerator.command = [
|
||||
"sh", "-c",
|
||||
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' --run`
|
||||
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' '${syncModeWithPortal}' --run`
|
||||
]
|
||||
} else {
|
||||
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, "--run"]
|
||||
console.log("Theme: Starting matugen worker")
|
||||
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, syncModeWithPortal, "--run"]
|
||||
}
|
||||
systemThemeGenerator.running = true
|
||||
}
|
||||
@@ -666,8 +786,17 @@ Singleton {
|
||||
}
|
||||
|
||||
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false"
|
||||
gtkApplier.command = [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir]
|
||||
gtkApplier.running = true
|
||||
Proc.runCommand("gtkApplier", [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir], (output, exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
|
||||
ToastService.showInfo("GTK colors applied successfully")
|
||||
}
|
||||
} else {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showError("Failed to apply GTK colors")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function applyQtColors() {
|
||||
@@ -678,14 +807,48 @@ Singleton {
|
||||
return
|
||||
}
|
||||
|
||||
qtApplier.command = [shellDir + "/scripts/qt.sh", configDir]
|
||||
qtApplier.running = true
|
||||
Proc.runCommand("qtApplier", [shellDir + "/scripts/qt.sh", configDir], (output, exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showInfo("Qt colors applied successfully")
|
||||
}
|
||||
} else {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showError("Failed to apply Qt colors")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
|
||||
|
||||
function getFillMode(modeName) {
|
||||
switch(modeName) {
|
||||
case "Stretch": return Image.Stretch
|
||||
case "Fit":
|
||||
case "PreserveAspectFit": return Image.PreserveAspectFit
|
||||
case "Fill":
|
||||
case "PreserveAspectCrop": return Image.PreserveAspectCrop
|
||||
case "Tile": return Image.Tile
|
||||
case "TileVertically": return Image.TileVertically
|
||||
case "TileHorizontally": return Image.TileHorizontally
|
||||
case "Pad": return Image.Pad
|
||||
default: return Image.PreserveAspectCrop
|
||||
}
|
||||
}
|
||||
|
||||
function snap(value, dpr) {
|
||||
return Math.round(value * dpr) / dpr
|
||||
const s = dpr || 1
|
||||
return Math.round(value * s) / s
|
||||
}
|
||||
|
||||
function px(value, dpr) {
|
||||
const s = dpr || 1
|
||||
return Math.round(value * s) / s
|
||||
}
|
||||
|
||||
function hairline(dpr) {
|
||||
return 1 / (dpr || 1)
|
||||
}
|
||||
|
||||
function invertHex(hex) {
|
||||
@@ -737,57 +900,6 @@ Singleton {
|
||||
|
||||
|
||||
|
||||
Process {
|
||||
id: matugenCheck
|
||||
command: ["which", "matugen"]
|
||||
onExited: code => {
|
||||
matugenAvailable = (code === 0) && !envDisableMatugen
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
|
||||
|
||||
if (!matugenAvailable || isGreeterMode) {
|
||||
return
|
||||
}
|
||||
|
||||
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
|
||||
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
|
||||
|
||||
if (currentTheme === dynamic) {
|
||||
if (wallpaperPath) {
|
||||
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
|
||||
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
|
||||
if (wallpaperPath.startsWith("#")) {
|
||||
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
} else {
|
||||
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let primaryColor
|
||||
let matugenType
|
||||
if (currentTheme === "custom") {
|
||||
if (customThemeData && customThemeData.primary) {
|
||||
primaryColor = customThemeData.primary
|
||||
matugenType = customThemeData.matugen_type
|
||||
}
|
||||
} else {
|
||||
primaryColor = currentThemeData.primary
|
||||
matugenType = currentThemeData.matugen_type
|
||||
}
|
||||
|
||||
if (primaryColor) {
|
||||
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
|
||||
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Process {
|
||||
id: ensureStateDir
|
||||
}
|
||||
|
||||
Process {
|
||||
id: systemThemeGenerator
|
||||
running: false
|
||||
@@ -795,61 +907,19 @@ Singleton {
|
||||
onExited: exitCode => {
|
||||
workerRunning = false
|
||||
|
||||
if (exitCode !== 0 && exitCode !== 2) {
|
||||
if (exitCode === 0) {
|
||||
console.log("Theme: Matugen worker completed successfully")
|
||||
if (currentTheme === dynamic) {
|
||||
console.log("Theme: Reloading dynamic colors file")
|
||||
dynamicColorsFileView.reload()
|
||||
}
|
||||
} else if (exitCode === 2) {
|
||||
console.log("Theme: Matugen worker completed with code 2 (no changes needed)")
|
||||
} else {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showError("Theme worker failed (" + exitCode + ")")
|
||||
}
|
||||
console.warn("Theme worker failed with exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gtkApplier
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: gtkStdout
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
id: gtkStderr
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
|
||||
ToastService.showInfo("GTK colors applied successfully")
|
||||
}
|
||||
} else {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showError("Failed to apply GTK colors: " + gtkStderr.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: qtApplier
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: qtStdout
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
id: qtStderr
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showInfo("Qt colors applied successfully")
|
||||
}
|
||||
} else {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showError("Failed to apply Qt colors: " + qtStderr.text)
|
||||
}
|
||||
console.warn("Theme: Matugen worker failed with exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -913,6 +983,8 @@ Singleton {
|
||||
|
||||
onLoaded: {
|
||||
if (currentTheme === dynamic) {
|
||||
console.log("Theme: Dynamic colors file loaded successfully")
|
||||
colorsFileLoadFailed = false
|
||||
parseAndLoadColors()
|
||||
}
|
||||
}
|
||||
@@ -924,10 +996,20 @@ Singleton {
|
||||
}
|
||||
|
||||
onLoadFailed: function (error) {
|
||||
if (currentTheme === dynamic && typeof ToastService !== "undefined") {
|
||||
ToastService.showError("Failed to read dynamic colors: " + error)
|
||||
if (currentTheme === dynamic) {
|
||||
console.log("Theme: Dynamic colors file load failed, marking for regeneration")
|
||||
colorsFileLoadFailed = true
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
|
||||
if (!isGreeterMode && matugenAvailable && wallpaperPath) {
|
||||
console.log("Theme: Matugen available, triggering immediate regeneration")
|
||||
generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPathChanged: {
|
||||
colorsFileLoadFailed = false
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
|
||||
@@ -1,29 +1,11 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Greetd
|
||||
import qs.Common
|
||||
import qs.Modules.Greetd
|
||||
|
||||
Item {
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
WlSessionLock {
|
||||
id: sessionLock
|
||||
locked: false
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(() => { locked = true })
|
||||
}
|
||||
|
||||
onLockedChanged: {
|
||||
if (!locked) {
|
||||
console.log("Greetd session unlocked, exiting")
|
||||
}
|
||||
}
|
||||
|
||||
GreeterSurface {
|
||||
lock: sessionLock
|
||||
}
|
||||
}
|
||||
GreeterSurface {}
|
||||
}
|
||||
|
||||
1004
DMSShell.qml
1004
DMSShell.qml
File diff suppressed because it is too large
Load Diff
135
DMSShellIPC.qml
135
DMSShellIPC.qml
@@ -13,6 +13,9 @@ Item {
|
||||
required property var controlCenterLoader
|
||||
required property var dankDashPopoutLoader
|
||||
required property var notepadSlideoutVariants
|
||||
required property var hyprKeybindsModalLoader
|
||||
required property var dankBarLoader
|
||||
required property var hyprlandOverviewLoader
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
@@ -75,9 +78,8 @@ Item {
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
root.controlCenterLoader.active = true
|
||||
if (root.controlCenterLoader.item) {
|
||||
root.controlCenterLoader.item.open()
|
||||
if (root.dankBarLoader.item) {
|
||||
root.dankBarLoader.item.triggerControlCenterOnFocusedScreen()
|
||||
return "CONTROL_CENTER_OPEN_SUCCESS"
|
||||
}
|
||||
return "CONTROL_CENTER_OPEN_FAILED"
|
||||
@@ -92,9 +94,8 @@ Item {
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
root.controlCenterLoader.active = true
|
||||
if (root.controlCenterLoader.item) {
|
||||
root.controlCenterLoader.item.toggle()
|
||||
if (root.dankBarLoader.item) {
|
||||
root.dankBarLoader.item.triggerControlCenterOnFocusedScreen()
|
||||
return "CONTROL_CENTER_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "CONTROL_CENTER_TOGGLE_FAILED"
|
||||
@@ -262,4 +263,126 @@ Item {
|
||||
|
||||
target: "inhibit"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function list(): string {
|
||||
return MprisController.availablePlayers.map(p => p.identity).join("\n")
|
||||
}
|
||||
|
||||
function play(): void {
|
||||
if (MprisController.activePlayer && MprisController.activePlayer.canPlay) {
|
||||
MprisController.activePlayer.play()
|
||||
}
|
||||
}
|
||||
|
||||
function pause(): void {
|
||||
if (MprisController.activePlayer && MprisController.activePlayer.canPause) {
|
||||
MprisController.activePlayer.pause()
|
||||
}
|
||||
}
|
||||
|
||||
function playPause(): void {
|
||||
if (MprisController.activePlayer && MprisController.activePlayer.canTogglePlaying) {
|
||||
MprisController.activePlayer.togglePlaying()
|
||||
}
|
||||
}
|
||||
|
||||
function previous(): void {
|
||||
if (MprisController.activePlayer && MprisController.activePlayer.canGoPrevious) {
|
||||
MprisController.activePlayer.previous()
|
||||
}
|
||||
}
|
||||
|
||||
function next(): void {
|
||||
if (MprisController.activePlayer && MprisController.activePlayer.canGoNext) {
|
||||
MprisController.activePlayer.next()
|
||||
}
|
||||
}
|
||||
|
||||
function stop(): void {
|
||||
if (MprisController.activePlayer) {
|
||||
MprisController.activePlayer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
target: "mpris"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function openBinds(): string {
|
||||
if (!CompositorService.isHyprland) {
|
||||
return "HYPR_NOT_AVAILABLE"
|
||||
}
|
||||
root.hyprKeybindsModalLoader.active = true
|
||||
if (root.hyprKeybindsModalLoader.item) {
|
||||
root.hyprKeybindsModalLoader.item.open()
|
||||
return "HYPR_KEYBINDS_OPEN_SUCCESS"
|
||||
}
|
||||
return "HYPR_KEYBINDS_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function closeBinds(): string {
|
||||
if (!CompositorService.isHyprland) {
|
||||
return "HYPR_NOT_AVAILABLE"
|
||||
}
|
||||
if (root.hyprKeybindsModalLoader.item) {
|
||||
root.hyprKeybindsModalLoader.item.close()
|
||||
return "HYPR_KEYBINDS_CLOSE_SUCCESS"
|
||||
}
|
||||
return "HYPR_KEYBINDS_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggleBinds(): string {
|
||||
if (!CompositorService.isHyprland) {
|
||||
return "HYPR_NOT_AVAILABLE"
|
||||
}
|
||||
root.hyprKeybindsModalLoader.active = true
|
||||
if (root.hyprKeybindsModalLoader.item) {
|
||||
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
|
||||
root.hyprKeybindsModalLoader.item.close()
|
||||
} else {
|
||||
root.hyprKeybindsModalLoader.item.open()
|
||||
}
|
||||
return "HYPR_KEYBINDS_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "HYPR_KEYBINDS_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
function toggleOverview(): string {
|
||||
if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) {
|
||||
return "HYPR_NOT_AVAILABLE"
|
||||
}
|
||||
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen
|
||||
return root.hyprlandOverviewLoader.item.overviewOpen ? "OVERVIEW_OPEN_SUCCESS" : "OVERVIEW_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function closeOverview(): string {
|
||||
if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) {
|
||||
return "HYPR_NOT_AVAILABLE"
|
||||
}
|
||||
root.hyprlandOverviewLoader.item.overviewOpen = false
|
||||
return "OVERVIEW_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function openOverview(): string {
|
||||
if (!CompositorService.isHyprland || !root.hyprlandOverviewLoader.item) {
|
||||
return "HYPR_NOT_AVAILABLE"
|
||||
}
|
||||
root.hyprlandOverviewLoader.item.overviewOpen = true
|
||||
return "OVERVIEW_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
target: "hypr"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function wallpaper(): string {
|
||||
if (root.dankBarLoader.item && root.dankBarLoader.item.triggerWallpaperBrowserOnFocusedScreen()) {
|
||||
return "SUCCESS: Toggled wallpaper browser"
|
||||
}
|
||||
return "ERROR: Failed to toggle wallpaper browser"
|
||||
}
|
||||
|
||||
target: "dankdash"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ Item {
|
||||
leftIconName: "search"
|
||||
showClearButton: true
|
||||
focus: true
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [modal.modalFocusScope]
|
||||
onTextChanged: {
|
||||
modal.searchText = text
|
||||
@@ -83,7 +84,7 @@ Item {
|
||||
id: clipboardListView
|
||||
anchors.fill: parent
|
||||
model: filteredModel
|
||||
|
||||
|
||||
currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0
|
||||
spacing: Theme.spacingXS
|
||||
interactive: true
|
||||
@@ -93,7 +94,7 @@ Item {
|
||||
boundsMovement: Flickable.FollowBoundsBehavior
|
||||
pressDelay: 0
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count) {
|
||||
return
|
||||
@@ -107,13 +108,13 @@ Item {
|
||||
contentY = itemBottom - height
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
if (clipboardContent.modal && clipboardContent.modal.keyboardNavigationActive && currentIndex >= 0) {
|
||||
ensureVisible(currentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("No clipboard entries found")
|
||||
anchors.centerIn: parent
|
||||
@@ -121,11 +122,11 @@ Item {
|
||||
color: Theme.surfaceVariantText
|
||||
visible: filteredModel.count === 0
|
||||
}
|
||||
|
||||
|
||||
delegate: ClipboardEntry {
|
||||
required property int index
|
||||
required property var model
|
||||
|
||||
|
||||
width: clipboardListView.width
|
||||
height: ClipboardConstants.itemHeight
|
||||
entryData: model.entry
|
||||
|
||||
@@ -60,6 +60,7 @@ DankModal {
|
||||
open()
|
||||
clipboardHistoryModal.searchText = ""
|
||||
clipboardHistoryModal.activeImageLoads = 0
|
||||
clipboardHistoryModal.shouldHaveFocus = true
|
||||
refreshClipboard()
|
||||
keyboardController.reset()
|
||||
|
||||
|
||||
@@ -68,9 +68,11 @@ DankModal {
|
||||
}
|
||||
}
|
||||
onOpened: {
|
||||
modalFocusScope.forceActiveFocus()
|
||||
modalFocusScope.focus = true
|
||||
shouldHaveFocus = true
|
||||
Qt.callLater(function () {
|
||||
modalFocusScope.forceActiveFocus()
|
||||
modalFocusScope.focus = true
|
||||
shouldHaveFocus = true
|
||||
})
|
||||
}
|
||||
modalFocusScope.Keys.onPressed: function (event) {
|
||||
switch (event.key) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
@@ -10,18 +12,21 @@ PanelWindow {
|
||||
|
||||
property alias content: contentLoader.sourceComponent
|
||||
property alias contentLoader: contentLoader
|
||||
property Item directContent: null
|
||||
property real width: 400
|
||||
property real height: 300
|
||||
readonly property real screenWidth: screen ? screen.width : 1920
|
||||
readonly property real screenHeight: screen ? screen.height : 1080
|
||||
readonly property real dpr: (screen && screen.devicePixelRatio) || 1
|
||||
|
||||
function snap(v) {
|
||||
return Math.round(v * dpr) / dpr
|
||||
}
|
||||
|
||||
function px(v) {
|
||||
return Math.round(v)
|
||||
readonly property real dpr: {
|
||||
if (CompositorService.isNiri && screen) {
|
||||
const niriScale = NiriService.displayScales[screen.name]
|
||||
if (niriScale !== undefined) return niriScale
|
||||
}
|
||||
if (CompositorService.isHyprland && screen) {
|
||||
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === screen.name)
|
||||
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
|
||||
}
|
||||
return (screen?.devicePixelRatio) || 1
|
||||
}
|
||||
property bool showBackground: true
|
||||
property real backgroundOpacity: 0.5
|
||||
@@ -30,8 +35,11 @@ PanelWindow {
|
||||
property bool closeOnEscapeKey: true
|
||||
property bool closeOnBackgroundClick: true
|
||||
property string animationType: "scale"
|
||||
property int animationDuration: Theme.shortDuration
|
||||
property var animationEasing: Theme.emphasizedEasing
|
||||
property int animationDuration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
property real animationScaleCollapsed: 0.96
|
||||
property real animationOffset: Theme.spacingL
|
||||
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
|
||||
property color backgroundColor: Theme.surfaceContainer
|
||||
property color borderColor: Theme.outlineMedium
|
||||
property real borderWidth: 1
|
||||
@@ -99,7 +107,7 @@ PanelWindow {
|
||||
Timer {
|
||||
id: closeTimer
|
||||
|
||||
interval: animationDuration + 100
|
||||
interval: animationDuration + 120
|
||||
onTriggered: {
|
||||
visible = false
|
||||
}
|
||||
@@ -134,7 +142,8 @@ PanelWindow {
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.animationEasing
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,26 +151,26 @@ PanelWindow {
|
||||
Rectangle {
|
||||
id: contentContainer
|
||||
|
||||
width: px(root.width)
|
||||
height: px(root.height)
|
||||
width: Theme.px(root.width, dpr)
|
||||
height: Theme.px(root.height, dpr)
|
||||
anchors.centerIn: undefined
|
||||
x: {
|
||||
if (positioning === "center") {
|
||||
return snap((root.screenWidth - width) / 2)
|
||||
return Theme.snap((root.screenWidth - width) / 2, dpr)
|
||||
} else if (positioning === "top-right") {
|
||||
return px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL))
|
||||
return Theme.px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL), dpr)
|
||||
} else if (positioning === "custom") {
|
||||
return snap(root.customPosition.x)
|
||||
return Theme.snap(root.customPosition.x, dpr)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
y: {
|
||||
if (positioning === "center") {
|
||||
return snap((root.screenHeight - height) / 2)
|
||||
return Theme.snap((root.screenHeight - height) / 2, dpr)
|
||||
} else if (positioning === "top-right") {
|
||||
return px(Theme.barHeight + Theme.spacingXS)
|
||||
return Theme.px(Theme.barHeight + Theme.spacingXS, dpr)
|
||||
} else if (positioning === "custom") {
|
||||
return snap(root.customPosition.y)
|
||||
return Theme.snap(root.customPosition.y, dpr)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -170,23 +179,68 @@ PanelWindow {
|
||||
border.color: root.borderColor
|
||||
border.width: root.borderWidth
|
||||
clip: false
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
opacity: root.shouldBeVisible ? 1 : 0
|
||||
transform: root.animationType === "slide" ? slideTransform : null
|
||||
transform: [scaleTransform, motionTransform]
|
||||
|
||||
Scale {
|
||||
id: scaleTransform
|
||||
|
||||
origin.x: contentContainer.width / 2
|
||||
origin.y: contentContainer.height / 2
|
||||
xScale: root.shouldBeVisible ? 1 : root.animationScaleCollapsed
|
||||
yScale: root.shouldBeVisible ? 1 : root.animationScaleCollapsed
|
||||
|
||||
Behavior on xScale {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on yScale {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Translate {
|
||||
id: slideTransform
|
||||
id: motionTransform
|
||||
|
||||
readonly property real rawX: root.shouldBeVisible ? 0 : 15
|
||||
readonly property real rawY: root.shouldBeVisible ? 0 : -30
|
||||
readonly property bool slide: root.animationType === "slide"
|
||||
readonly property real hiddenX: slide ? 15 : 0
|
||||
readonly property real hiddenY: slide ? -30 : root.animationOffset
|
||||
|
||||
x: snap(rawX)
|
||||
y: snap(rawY)
|
||||
x: Theme.snap(root.shouldBeVisible ? 0 : hiddenX, root.dpr)
|
||||
y: Theme.snap(root.shouldBeVisible ? 0 : hiddenY, root.dpr)
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: animationDuration
|
||||
easing.type: animationEasing
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,14 +249,44 @@ PanelWindow {
|
||||
focus: root.shouldBeVisible
|
||||
clip: false
|
||||
|
||||
Item {
|
||||
id: directContentWrapper
|
||||
|
||||
anchors.fill: parent
|
||||
visible: root.directContent !== null
|
||||
focus: true
|
||||
clip: false
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.directContent) {
|
||||
root.directContent.parent = directContentWrapper
|
||||
root.directContent.anchors.fill = directContentWrapper
|
||||
Qt.callLater(() => root.directContent.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onDirectContentChanged() {
|
||||
if (root.directContent) {
|
||||
root.directContent.parent = directContentWrapper
|
||||
root.directContent.anchors.fill = directContentWrapper
|
||||
Qt.callLater(() => root.directContent.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
|
||||
target: root
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.keepContentLoaded || root.shouldBeVisible || root.visible
|
||||
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || root.visible)
|
||||
asynchronous: false
|
||||
focus: true
|
||||
clip: false
|
||||
visible: root.directContent === null
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
|
||||
@@ -2,17 +2,16 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
DankModal {
|
||||
id: root
|
||||
|
||||
property string pickerTitle: "Choose Color"
|
||||
property color selectedColor: Theme.primary
|
||||
property bool shouldBeVisible: false
|
||||
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
|
||||
property var onColorSelectedCallback: null
|
||||
|
||||
signal colorSelected(color selectedColor)
|
||||
@@ -25,23 +24,24 @@ PanelWindow {
|
||||
property real gradientX: 0
|
||||
property real gradientY: 0
|
||||
|
||||
function open() {
|
||||
currentColor = selectedColor
|
||||
updateFromColor(currentColor)
|
||||
shouldBeVisible = true
|
||||
Qt.callLater(() => colorContent.forceActiveFocus())
|
||||
}
|
||||
|
||||
function close() {
|
||||
shouldBeVisible = false
|
||||
onColorSelectedCallback = null
|
||||
}
|
||||
readonly property var standardColors: [
|
||||
"#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4",
|
||||
"#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722",
|
||||
"#d32f2f", "#c2185b", "#7b1fa2", "#512da8", "#303f9f", "#1976d2", "#0288d1", "#0097a7",
|
||||
"#00796b", "#388e3c", "#689f38", "#afb42b", "#fbc02d", "#ffa000", "#f57c00", "#e64a19",
|
||||
"#c62828", "#ad1457", "#6a1b9a", "#4527a0", "#283593", "#1565c0", "#0277bd", "#00838f",
|
||||
"#00695c", "#2e7d32", "#558b2f", "#9e9d24", "#f9a825", "#ff8f00", "#ef6c00", "#d84315",
|
||||
"#ffffff", "#9e9e9e", "#212121"
|
||||
]
|
||||
|
||||
function show() {
|
||||
currentColor = selectedColor
|
||||
updateFromColor(currentColor)
|
||||
open()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
onColorSelectedCallback = null
|
||||
close()
|
||||
}
|
||||
|
||||
@@ -74,96 +74,50 @@ PanelWindow {
|
||||
saturation = Math.max(0, Math.min(1, x))
|
||||
value = Math.max(0, Math.min(1, 1 - y))
|
||||
updateColor()
|
||||
selectedColor = currentColor
|
||||
}
|
||||
|
||||
function pickColorFromScreen() {
|
||||
close()
|
||||
hyprpickerProcess.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: hyprpickerProcess
|
||||
running: false
|
||||
command: ["hyprpicker", "--format=hex"]
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
const colorStr = data.trim()
|
||||
if (colorStr.length >= 7 && colorStr.startsWith('#')) {
|
||||
root.currentColor = colorStr
|
||||
root.updateFromColor(root.currentColor)
|
||||
hexInput.text = root.currentColor.toString()
|
||||
copyColorToClipboard(colorStr)
|
||||
root.open()
|
||||
}
|
||||
hide()
|
||||
Proc.runCommand("hyprpicker", ["hyprpicker", "--format=hex"], (output, errorCode) => {
|
||||
if (errorCode !== 0) {
|
||||
console.warn("hyprpicker exited with code:", errorCode)
|
||||
root.show()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("hyprpicker exited with code:", exitCode)
|
||||
const colorStr = output.trim()
|
||||
if (colorStr.length >= 7 && colorStr.startsWith('#')) {
|
||||
const pickedColor = Qt.color(colorStr)
|
||||
root.selectedColor = pickedColor
|
||||
root.currentColor = pickedColor
|
||||
root.updateFromColor(pickedColor)
|
||||
copyColorToClipboard(colorStr)
|
||||
root.show()
|
||||
}
|
||||
root.open()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
readonly property var standardColors: [
|
||||
"#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4",
|
||||
"#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722",
|
||||
"#d32f2f", "#c2185b", "#7b1fa2", "#512da8", "#303f9f", "#1976d2", "#0288d1", "#0097a7",
|
||||
"#00796b", "#388e3c", "#689f38", "#afb42b", "#fbc02d", "#ffa000", "#f57c00", "#e64a19",
|
||||
"#c62828", "#ad1457", "#6a1b9a", "#4527a0", "#283593", "#1565c0", "#0277bd", "#00838f",
|
||||
"#00695c", "#2e7d32", "#558b2f", "#9e9d24", "#f9a825", "#ff8f00", "#ef6c00", "#d84315",
|
||||
"#ffffff", "#9e9e9e", "#212121"
|
||||
]
|
||||
width: 680
|
||||
height: 680
|
||||
backgroundColor: Theme.surfaceContainer
|
||||
cornerRadius: Theme.cornerRadius
|
||||
borderColor: Theme.outlineMedium
|
||||
borderWidth: 1
|
||||
keepContentLoaded: true
|
||||
|
||||
visible: shouldBeVisible
|
||||
|
||||
WlrLayershell.namespace: "quickshell:color-picker"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.close()
|
||||
|
||||
Rectangle {
|
||||
color: "#80000000"
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 680
|
||||
height: 680
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {} // Prevent clicks from propagating to background
|
||||
}
|
||||
onBackgroundClicked: hide()
|
||||
|
||||
content: Component {
|
||||
FocusScope {
|
||||
id: colorContent
|
||||
|
||||
property alias hexInput: hexInput
|
||||
|
||||
anchors.fill: parent
|
||||
focus: root.shouldBeVisible
|
||||
focus: true
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
root.close()
|
||||
root.hide()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
@@ -199,7 +153,7 @@ PanelWindow {
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: () => {
|
||||
pickColorFromScreen()
|
||||
root.pickColorFromScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +162,7 @@ PanelWindow {
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: () => {
|
||||
root.close()
|
||||
root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,12 +283,14 @@ PanelWindow {
|
||||
const h = Math.max(0, Math.min(1, mouse.y / height))
|
||||
root.hue = h
|
||||
root.updateColor()
|
||||
root.selectedColor = root.currentColor
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
const h = Math.max(0, Math.min(1, mouse.y / height))
|
||||
root.hue = h
|
||||
root.updateColor()
|
||||
root.selectedColor = root.currentColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,8 +329,10 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
root.currentColor = modelData
|
||||
root.updateFromColor(root.currentColor)
|
||||
const pickedColor = Qt.color(modelData)
|
||||
root.selectedColor = pickedColor
|
||||
root.currentColor = pickedColor
|
||||
root.updateFromColor(pickedColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,8 +387,10 @@ PanelWindow {
|
||||
enabled: index < SessionData.recentColors.length
|
||||
onClicked: () => {
|
||||
if (index < SessionData.recentColors.length) {
|
||||
root.currentColor = SessionData.recentColors[index]
|
||||
root.updateFromColor(root.currentColor)
|
||||
const pickedColor = SessionData.recentColors[index]
|
||||
root.selectedColor = pickedColor
|
||||
root.currentColor = pickedColor
|
||||
root.updateFromColor(pickedColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,6 +419,7 @@ PanelWindow {
|
||||
onSliderValueChanged: (newValue) => {
|
||||
root.alpha = newValue / 100
|
||||
root.updateColor()
|
||||
root.selectedColor = root.currentColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -509,6 +470,7 @@ PanelWindow {
|
||||
if (!hexPattern.test(text)) return
|
||||
const color = Qt.color(text)
|
||||
if (color) {
|
||||
root.selectedColor = color
|
||||
root.currentColor = color
|
||||
root.updateFromColor(color)
|
||||
}
|
||||
@@ -530,9 +492,9 @@ PanelWindow {
|
||||
root.currentColor = color
|
||||
root.updateFromColor(color)
|
||||
root.selectedColor = root.currentColor
|
||||
colorSelected(root.currentColor)
|
||||
root.colorSelected(root.currentColor)
|
||||
SessionData.addRecentColor(root.currentColor)
|
||||
root.close()
|
||||
root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,8 +511,8 @@ PanelWindow {
|
||||
backgroundColor: "transparent"
|
||||
textColor: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.close()
|
||||
|
||||
onClicked: root.hide()
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
@@ -570,7 +532,7 @@ PanelWindow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
const colorString = root.currentColor.toString()
|
||||
copyColorToClipboard(colorString)
|
||||
root.copyColorToClipboard(colorString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,15 +48,17 @@ DankModal {
|
||||
}
|
||||
|
||||
function getLastPath() {
|
||||
const lastPath = browserType === "wallpaper" ? SessionData.wallpaperLastPath : browserType === "profile" ? SessionData.profileLastPath : ""
|
||||
const lastPath = browserType === "wallpaper" ? CacheData.wallpaperLastPath : browserType === "profile" ? CacheData.profileLastPath : ""
|
||||
return (lastPath && lastPath !== "") ? lastPath : homeDir
|
||||
}
|
||||
|
||||
function saveLastPath(path) {
|
||||
if (browserType === "wallpaper") {
|
||||
SessionData.setWallpaperLastPath(path)
|
||||
CacheData.wallpaperLastPath = path
|
||||
CacheData.saveCache()
|
||||
} else if (browserType === "profile") {
|
||||
SessionData.setProfileLastPath(path)
|
||||
CacheData.profileLastPath = path
|
||||
CacheData.saveCache()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,18 +111,18 @@ DankModal {
|
||||
if (!normalizedPath.startsWith("file://")) {
|
||||
normalizedPath = "file://" + filePath
|
||||
}
|
||||
|
||||
|
||||
// Check if file exists by looking through the folder model
|
||||
var exists = false
|
||||
var fileName = filePath.split('/').pop()
|
||||
|
||||
|
||||
for (var i = 0; i < folderModel.count; i++) {
|
||||
if (folderModel.get(i, "fileName") === fileName && !folderModel.get(i, "fileIsDir")) {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (exists) {
|
||||
pendingFilePath = normalizedPath
|
||||
showOverwriteConfirmation = true
|
||||
@@ -137,7 +139,7 @@ DankModal {
|
||||
Component.onCompleted: {
|
||||
currentPath = getLastPath()
|
||||
}
|
||||
|
||||
|
||||
property var steamPaths: [
|
||||
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.steam/steam/steamapps/workshop/content/431960",
|
||||
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.local/share/Steam/steamapps/workshop/content/431960",
|
||||
@@ -145,17 +147,17 @@ DankModal {
|
||||
StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/snap/steam/common/.local/share/Steam/steamapps/workshop/content/431960"
|
||||
]
|
||||
property int currentPathIndex: 0
|
||||
|
||||
|
||||
function discoverWallpaperEngine() {
|
||||
currentPathIndex = 0
|
||||
checkNextPath()
|
||||
}
|
||||
|
||||
|
||||
function checkNextPath() {
|
||||
if (currentPathIndex >= steamPaths.length) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const wePath = steamPaths[currentPathIndex]
|
||||
const cleanPath = wePath.replace(/^file:\/\//, '')
|
||||
weDiscoveryProcess.command = ["test", "-d", cleanPath]
|
||||
@@ -449,13 +451,13 @@ DankModal {
|
||||
executeKeyboardSelection(targetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Process {
|
||||
id: weDiscoveryProcess
|
||||
|
||||
|
||||
property string wePath: ""
|
||||
running: false
|
||||
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
fileBrowserModal.weAvailable = true
|
||||
@@ -530,7 +532,7 @@ DankModal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DankActionButton {
|
||||
circular: false
|
||||
iconName: "info"
|
||||
@@ -873,26 +875,26 @@ DankModal {
|
||||
id: overwriteDialog
|
||||
anchors.fill: parent
|
||||
visible: showOverwriteConfirmation
|
||||
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
showOverwriteConfirmation = false
|
||||
pendingFilePath = ""
|
||||
}
|
||||
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
showOverwriteConfirmation = false
|
||||
fileSelected(pendingFilePath)
|
||||
pendingFilePath = ""
|
||||
Qt.callLater(() => fileBrowserModal.close())
|
||||
}
|
||||
|
||||
|
||||
focus: showOverwriteConfirmation
|
||||
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.shadowStrong
|
||||
opacity: 0.8
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
@@ -901,7 +903,7 @@ DankModal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StyledRect {
|
||||
anchors.centerIn: parent
|
||||
width: 400
|
||||
@@ -910,12 +912,12 @@ DankModal {
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
spacing: Theme.spacingM
|
||||
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("File Already Exists")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
@@ -923,7 +925,7 @@ DankModal {
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("A file with this name already exists. Do you want to overwrite it?")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -932,11 +934,11 @@ DankModal {
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
|
||||
StyledRect {
|
||||
width: 80
|
||||
height: 36
|
||||
@@ -944,7 +946,7 @@ DankModal {
|
||||
color: cancelArea.containsMouse ? Theme.surfaceVariantHover : Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Cancel")
|
||||
@@ -952,7 +954,7 @@ DankModal {
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: cancelArea
|
||||
anchors.fill: parent
|
||||
@@ -964,13 +966,13 @@ DankModal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StyledRect {
|
||||
width: 90
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: overwriteArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Overwrite")
|
||||
@@ -978,7 +980,7 @@ DankModal {
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: overwriteArea
|
||||
anchors.fill: parent
|
||||
|
||||
266
Modals/HyprKeybindsModal.qml
Normal file
266
Modals/HyprKeybindsModal.qml
Normal file
@@ -0,0 +1,266 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
|
||||
width: 1400
|
||||
height: 900
|
||||
onBackgroundClicked: close()
|
||||
|
||||
function categorizeKeybinds() {
|
||||
const categories = {
|
||||
"Workspace": [],
|
||||
"Window": [],
|
||||
"Monitor": [],
|
||||
"Execute": [],
|
||||
"System": [],
|
||||
"Other": []
|
||||
}
|
||||
|
||||
function addKeybind(keybind) {
|
||||
const dispatcher = keybind.dispatcher || ""
|
||||
if (dispatcher.includes("workspace")) {
|
||||
categories["Workspace"].push(keybind)
|
||||
} else if (dispatcher.includes("monitor")) {
|
||||
categories["Monitor"].push(keybind)
|
||||
} else if (dispatcher.includes("window") || dispatcher.includes("focus") || dispatcher.includes("move") || dispatcher.includes("swap") || dispatcher.includes("resize") || dispatcher === "killactive" || dispatcher === "fullscreen" || dispatcher === "togglefloating") {
|
||||
categories["Window"].push(keybind)
|
||||
} else if (dispatcher === "exec") {
|
||||
categories["Execute"].push(keybind)
|
||||
} else if (dispatcher === "exit" || dispatcher.includes("dpms")) {
|
||||
categories["System"].push(keybind)
|
||||
} else {
|
||||
categories["Other"].push(keybind)
|
||||
}
|
||||
}
|
||||
|
||||
const allKeybinds = HyprKeybindsService.keybinds.keybinds || []
|
||||
for (let i = 0; i < allKeybinds.length; i++) {
|
||||
addKeybind(allKeybinds[i])
|
||||
}
|
||||
|
||||
const children = HyprKeybindsService.keybinds.children || []
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
const childKeybinds = child.keybinds || []
|
||||
for (let j = 0; j < childKeybinds.length; j++) {
|
||||
addKeybind(childKeybinds[j])
|
||||
}
|
||||
}
|
||||
|
||||
categories["Workspace"].sort((a, b) => {
|
||||
const dispA = a.dispatcher || ""
|
||||
const dispB = b.dispatcher || ""
|
||||
return dispA.localeCompare(dispB)
|
||||
})
|
||||
|
||||
categories["Window"].sort((a, b) => {
|
||||
const dispA = a.dispatcher || ""
|
||||
const dispB = b.dispatcher || ""
|
||||
return dispA.localeCompare(dispB)
|
||||
})
|
||||
|
||||
categories["Monitor"].sort((a, b) => {
|
||||
const dispA = a.dispatcher || ""
|
||||
const dispB = b.dispatcher || ""
|
||||
return dispA.localeCompare(dispB)
|
||||
})
|
||||
|
||||
categories["Execute"].sort((a, b) => {
|
||||
const modsA = a.mods || []
|
||||
const keyA = a.key || ""
|
||||
const bindA = [...modsA, keyA].join("+")
|
||||
|
||||
const modsB = b.mods || []
|
||||
const keyB = b.key || ""
|
||||
const bindB = [...modsB, keyB].join("+")
|
||||
|
||||
return bindA.localeCompare(bindB)
|
||||
})
|
||||
|
||||
return categories
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
DankFlickable {
|
||||
id: mainFlickable
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
contentWidth: rowLayout.implicitWidth
|
||||
contentHeight: rowLayout.implicitHeight
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
id: rowLayout
|
||||
spacing: Theme.spacingM
|
||||
|
||||
property var categories: root.categorizeKeybinds()
|
||||
property real columnWidth: (mainFlickable.width - spacing * 2) / 3
|
||||
|
||||
Column {
|
||||
width: rowLayout.columnWidth
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Window / Monitor"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.primary
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
Item { width: 1; height: Theme.spacingXS }
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: [...(rowLayout.categories["Window"] || []), ...(rowLayout.categories["Monitor"] || [])]
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledRect {
|
||||
width: Math.min(140, parent.width * 0.42)
|
||||
height: 22
|
||||
radius: 4
|
||||
opacity: 0.3
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: 2
|
||||
width: parent.width - 4
|
||||
text: {
|
||||
const mods = modelData.mods || []
|
||||
const key = modelData.key || ""
|
||||
const parts = [...mods, key]
|
||||
return parts.join("+")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
isMonospace: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width - 150
|
||||
text: {
|
||||
const comment = modelData.comment || ""
|
||||
if (comment) return comment
|
||||
|
||||
const dispatcher = modelData.dispatcher || ""
|
||||
const params = modelData.params || ""
|
||||
return params ? `${dispatcher} ${params}` : dispatcher
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
opacity: 0.9
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ["Workspace", "Execute"]
|
||||
|
||||
Column {
|
||||
width: rowLayout.columnWidth
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.primary
|
||||
opacity: 0.3
|
||||
}
|
||||
|
||||
Item { width: 1; height: Theme.spacingXS }
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: rowLayout.categories[modelData] || []
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledRect {
|
||||
width: Math.min(140, parent.width * 0.42)
|
||||
height: 22
|
||||
radius: 4
|
||||
opacity: 0.3
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: 2
|
||||
width: parent.width - 4
|
||||
text: {
|
||||
const mods = modelData.mods || []
|
||||
const key = modelData.key || ""
|
||||
const parts = [...mods, key]
|
||||
return parts.join("+")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
isMonospace: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width - 150
|
||||
text: {
|
||||
const comment = modelData.comment || ""
|
||||
if (comment) return comment
|
||||
|
||||
const dispatcher = modelData.dispatcher || ""
|
||||
const params = modelData.params || ""
|
||||
return params ? `${dispatcher} ${params}` : dispatcher
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
opacity: 0.9
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
162
Modals/NetworkWiredInfoModal.qml
Normal file
162
Modals/NetworkWiredInfoModal.qml
Normal file
@@ -0,0 +1,162 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
|
||||
property bool networkWiredInfoModalVisible: false
|
||||
property string networkID: ""
|
||||
property var networkData: null
|
||||
|
||||
function showNetworkInfo(id, data) {
|
||||
networkID = id
|
||||
networkData = data
|
||||
networkWiredInfoModalVisible = true
|
||||
open()
|
||||
NetworkService.fetchWiredNetworkInfo(data.uuid)
|
||||
}
|
||||
|
||||
function hideDialog() {
|
||||
networkWiredInfoModalVisible = false
|
||||
close()
|
||||
networkID = ""
|
||||
networkData = null
|
||||
}
|
||||
|
||||
visible: networkWiredInfoModalVisible
|
||||
width: 600
|
||||
height: 500
|
||||
enableShadow: true
|
||||
onBackgroundClicked: hideDialog()
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
networkID = ""
|
||||
networkData = null
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
Column {
|
||||
width: parent.width - 40
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Network Information")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `Details for "${networkID}"`
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.hideDialog()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: detailsRect
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - 140
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceHover
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
contentHeight: detailsText.contentHeight
|
||||
|
||||
StyledText {
|
||||
id: detailsText
|
||||
|
||||
width: parent.width
|
||||
text: NetworkService.networkWiredInfoDetails && NetworkService.networkWiredInfoDetails.replace(/\\n/g, '\n') || "No information available"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Math.max(70, closeText.contentWidth + Theme.spacingM * 2)
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||
|
||||
StyledText {
|
||||
id: closeText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Close")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.hideDialog()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ DankModal {
|
||||
if (modalKeyboardController && notificationListRef) {
|
||||
modalKeyboardController.listView = notificationListRef
|
||||
modalKeyboardController.rebuildFlatNavigation()
|
||||
|
||||
|
||||
Qt.callLater(() => {
|
||||
modalKeyboardController.keyboardNavigationActive = true
|
||||
modalKeyboardController.selectedFlatIndex = 0
|
||||
@@ -54,6 +54,9 @@ DankModal {
|
||||
height: 700
|
||||
visible: false
|
||||
onBackgroundClicked: hide()
|
||||
onOpened: () => {
|
||||
Qt.callLater(() => modalFocusScope.forceActiveFocus());
|
||||
}
|
||||
onShouldBeVisibleChanged: (shouldBeVisible) => {
|
||||
if (!shouldBeVisible) {
|
||||
notificationModalOpen = false
|
||||
|
||||
@@ -78,7 +78,7 @@ DankModal {
|
||||
}
|
||||
onOpened: () => {
|
||||
selectedIndex = 0;
|
||||
modalFocusScope.forceActiveFocus();
|
||||
Qt.callLater(() => modalFocusScope.forceActiveFocus());
|
||||
}
|
||||
modalFocusScope.Keys.onPressed: (event) => {
|
||||
switch (event.key) {
|
||||
|
||||
@@ -25,6 +25,90 @@ Item {
|
||||
visible: !BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: lockScreenSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: lockScreenSection
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "lock"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Lock Screen")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Show Power Actions")
|
||||
description: I18n.tr("Show power, restart, and logout buttons on the lock screen")
|
||||
checked: SettingsData.lockScreenShowPowerActions
|
||||
onToggled: checked => SettingsData.setLockScreenShowPowerActions(checked)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("loginctl not available - lock integration requires DMS socket connection")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.warning
|
||||
visible: !SessionService.loginctlAvailable
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Enable loginctl lock integration")
|
||||
description: I18n.tr("Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen")
|
||||
checked: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration
|
||||
enabled: SessionService.loginctlAvailable
|
||||
onToggled: checked => {
|
||||
if (SessionService.loginctlAvailable) {
|
||||
SettingsData.setLoginctlLockIntegration(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Lock before suspend")
|
||||
description: I18n.tr("Automatically lock the screen when the system prepares to suspend")
|
||||
checked: SettingsData.lockBeforeSuspend
|
||||
visible: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration
|
||||
onToggled: checked => SettingsData.setLockBeforeSuspend(checked)
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Enable fingerprint authentication")
|
||||
description: I18n.tr("Use fingerprint reader for lock screen authentication (requires enrolled fingerprints)")
|
||||
checked: SettingsData.enableFprint
|
||||
visible: SettingsData.fprintdAvailable
|
||||
onToggled: checked => SettingsData.setEnableFprint(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: timeoutSection.implicitHeight + Theme.spacingL * 2
|
||||
@@ -85,14 +169,14 @@ Item {
|
||||
Connections {
|
||||
target: powerCategory
|
||||
function onCurrentIndexChanged() {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acLockTimeout : SessionData.batteryLockTimeout
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout
|
||||
const index = lockDropdown.timeoutValues.indexOf(currentTimeout)
|
||||
lockDropdown.currentValue = index >= 0 ? lockDropdown.timeoutOptions[index] : "Never"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acLockTimeout : SessionData.batteryLockTimeout
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout
|
||||
const index = timeoutValues.indexOf(currentTimeout)
|
||||
currentValue = index >= 0 ? timeoutOptions[index] : "Never"
|
||||
}
|
||||
@@ -102,9 +186,9 @@ Item {
|
||||
if (index >= 0) {
|
||||
const timeout = timeoutValues[index]
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SessionData.setAcLockTimeout(timeout)
|
||||
SettingsData.setAcLockTimeout(timeout)
|
||||
} else {
|
||||
SessionData.setBatteryLockTimeout(timeout)
|
||||
SettingsData.setBatteryLockTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,14 +205,14 @@ Item {
|
||||
Connections {
|
||||
target: powerCategory
|
||||
function onCurrentIndexChanged() {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acMonitorTimeout : SessionData.batteryMonitorTimeout
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout
|
||||
const index = monitorDropdown.timeoutValues.indexOf(currentTimeout)
|
||||
monitorDropdown.currentValue = index >= 0 ? monitorDropdown.timeoutOptions[index] : "Never"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acMonitorTimeout : SessionData.batteryMonitorTimeout
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout
|
||||
const index = timeoutValues.indexOf(currentTimeout)
|
||||
currentValue = index >= 0 ? timeoutOptions[index] : "Never"
|
||||
}
|
||||
@@ -138,9 +222,9 @@ Item {
|
||||
if (index >= 0) {
|
||||
const timeout = timeoutValues[index]
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SessionData.setAcMonitorTimeout(timeout)
|
||||
SettingsData.setAcMonitorTimeout(timeout)
|
||||
} else {
|
||||
SessionData.setBatteryMonitorTimeout(timeout)
|
||||
SettingsData.setBatteryMonitorTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,14 +241,14 @@ Item {
|
||||
Connections {
|
||||
target: powerCategory
|
||||
function onCurrentIndexChanged() {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acSuspendTimeout : SessionData.batterySuspendTimeout
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout
|
||||
const index = suspendDropdown.timeoutValues.indexOf(currentTimeout)
|
||||
suspendDropdown.currentValue = index >= 0 ? suspendDropdown.timeoutOptions[index] : "Never"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acSuspendTimeout : SessionData.batterySuspendTimeout
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout
|
||||
const index = timeoutValues.indexOf(currentTimeout)
|
||||
currentValue = index >= 0 ? timeoutOptions[index] : "Never"
|
||||
}
|
||||
@@ -174,9 +258,9 @@ Item {
|
||||
if (index >= 0) {
|
||||
const timeout = timeoutValues[index]
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SessionData.setAcSuspendTimeout(timeout)
|
||||
SettingsData.setAcSuspendTimeout(timeout)
|
||||
} else {
|
||||
SessionData.setBatterySuspendTimeout(timeout)
|
||||
SettingsData.setBatterySuspendTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,14 +278,14 @@ Item {
|
||||
Connections {
|
||||
target: powerCategory
|
||||
function onCurrentIndexChanged() {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acHibernateTimeout : SessionData.batteryHibernateTimeout
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acHibernateTimeout : SettingsData.batteryHibernateTimeout
|
||||
const index = hibernateDropdown.timeoutValues.indexOf(currentTimeout)
|
||||
hibernateDropdown.currentValue = index >= 0 ? hibernateDropdown.timeoutOptions[index] : "Never"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acHibernateTimeout : SessionData.batteryHibernateTimeout
|
||||
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acHibernateTimeout : SettingsData.batteryHibernateTimeout
|
||||
const index = timeoutValues.indexOf(currentTimeout)
|
||||
currentValue = index >= 0 ? timeoutOptions[index] : "Never"
|
||||
}
|
||||
@@ -211,31 +295,14 @@ Item {
|
||||
if (index >= 0) {
|
||||
const timeout = timeoutValues[index]
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SessionData.setAcHibernateTimeout(timeout)
|
||||
SettingsData.setAcHibernateTimeout(timeout)
|
||||
} else {
|
||||
SessionData.setBatteryHibernateTimeout(timeout)
|
||||
SettingsData.setBatteryHibernateTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Enable loginctl lock integration")
|
||||
description: "Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen."
|
||||
checked: SessionData.loginctlLockIntegration
|
||||
onToggled: checked => SessionData.setLoginctlLockIntegration(checked)
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Lock before suspend")
|
||||
description: "Automatically lock the screen when the system prepares to suspend"
|
||||
checked: SessionData.lockBeforeSuspend
|
||||
visible: SessionData.loginctlLockIntegration
|
||||
onToggled: checked => SessionData.setLockBeforeSuspend(checked)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Idle monitoring not supported - requires newer Quickshell version")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -246,6 +313,277 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: powerCommandConfirmSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: powerCommandConfirmSection
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "check_circle"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Power Action Confirmation")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Show Confirmation on Power Actions")
|
||||
description: I18n.tr("Request confirmation on power off, restart, suspend, hibernate and logout actions")
|
||||
checked: SettingsData.powerActionConfirm
|
||||
onToggled: checked => SettingsData.setPowerActionConfirm(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: powerCommandCustomization.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: powerCommandCustomization
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "developer_mode"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Custom Power Actions")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Command or script to run instead of the standard lock procedure")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customLockCommand
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "/usr/bin/myLock.sh"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.customPowerActionLock) {
|
||||
text = SettingsData.customPowerActionLock;
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
SettingsData.setCustomPowerActionLock(text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Command or script to run instead of the standard logout procedure")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customLogoutCommand
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "/usr/bin/myLogout.sh"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.customPowerActionLogout) {
|
||||
text = SettingsData.customPowerActionLogout;
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
SettingsData.setCustomPowerActionLogout(text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Command or script to run instead of the standard suspend procedure")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customSuspendCommand
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "/usr/bin/mySuspend.sh"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.customPowerActionSuspend) {
|
||||
text = SettingsData.customPowerActionSuspend;
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
SettingsData.setCustomPowerActionSuspend(text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Command or script to run instead of the standard hibernate procedure")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customHibernateCommand
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "/usr/bin/myHibernate.sh"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.customPowerActionHibernate) {
|
||||
text = SettingsData.customPowerActionHibernate;
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
SettingsData.setCustomPowerActionHibernate(text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Command or script to run instead of the standard reboot procedure")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customRebootCommand
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "/usr/bin/myReboot.sh"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.customPowerActionReboot) {
|
||||
text = SettingsData.customPowerActionReboot;
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
SettingsData.setCustomPowerActionReboot(text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Command or script to run instead of the standard power off procedure")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customPowerOffCommand
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "/usr/bin/myPowerOff.sh"
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Theme.primarySelected
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsData.customPowerActionPowerOff) {
|
||||
text = SettingsData.customPowerActionPowerOff;
|
||||
}
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
SettingsData.setCustomPowerActionPowerOff(text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Settings
|
||||
|
||||
FocusScope {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property int currentIndex: 0
|
||||
property var parentModal: null
|
||||
focus: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -23,7 +22,6 @@ FocusScope {
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 0
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Component {
|
||||
PersonalizationTab {
|
||||
@@ -35,27 +33,13 @@ FocusScope {
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: timeLoader
|
||||
id: timeWeatherLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 1
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: TimeTab {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: weatherLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 2
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: WeatherTab {
|
||||
sourceComponent: TimeWeatherTab {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,9 +48,8 @@ FocusScope {
|
||||
id: topBarLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 3
|
||||
active: root.currentIndex === 2
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: DankBarTab {
|
||||
parentModal: root.parentModal
|
||||
@@ -78,9 +61,8 @@ FocusScope {
|
||||
id: widgetsLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 4
|
||||
active: root.currentIndex === 3
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: WidgetTweaksTab {
|
||||
}
|
||||
@@ -91,9 +73,8 @@ FocusScope {
|
||||
id: dockLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 5
|
||||
active: root.currentIndex === 4
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Component {
|
||||
DockTab {
|
||||
@@ -107,9 +88,8 @@ FocusScope {
|
||||
id: displaysLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 6
|
||||
active: root.currentIndex === 5
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: DisplaysTab {
|
||||
}
|
||||
@@ -120,9 +100,8 @@ FocusScope {
|
||||
id: launcherLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 7
|
||||
active: root.currentIndex === 6
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: LauncherTab {
|
||||
}
|
||||
@@ -133,9 +112,8 @@ FocusScope {
|
||||
id: themeColorsLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 8
|
||||
active: root.currentIndex === 7
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: ThemeColorsTab {
|
||||
}
|
||||
@@ -146,9 +124,8 @@ FocusScope {
|
||||
id: powerLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 9
|
||||
active: root.currentIndex === 8
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: PowerSettings {
|
||||
}
|
||||
@@ -159,9 +136,8 @@ FocusScope {
|
||||
id: pluginsLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 10
|
||||
active: root.currentIndex === 9
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: PluginsTab {
|
||||
parentModal: root.parentModal
|
||||
@@ -173,9 +149,8 @@ FocusScope {
|
||||
id: aboutLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 11
|
||||
active: root.currentIndex === 10
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: AboutTab {
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ DankModal {
|
||||
|
||||
property Component settingsContent
|
||||
property alias profileBrowser: profileBrowser
|
||||
property int currentTabIndex: 0
|
||||
|
||||
signal closingModal()
|
||||
|
||||
@@ -40,6 +41,25 @@ DankModal {
|
||||
return hide();
|
||||
}
|
||||
content: settingsContent
|
||||
onOpened: () => {
|
||||
Qt.callLater(() => modalFocusScope.forceActiveFocus())
|
||||
}
|
||||
modalFocusScope.Keys.onPressed: event => {
|
||||
const tabCount = 11
|
||||
if (event.key === Qt.Key_Down) {
|
||||
currentTabIndex = (currentTabIndex + 1) % tabCount
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
currentTabIndex = (currentTabIndex - 1 + tabCount) % tabCount
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Tab && !event.modifiers) {
|
||||
currentTabIndex = (currentTabIndex + 1) % tabCount
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && event.modifiers & Qt.ShiftModifier)) {
|
||||
currentTabIndex = (currentTabIndex - 1 + tabCount) % tabCount
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
@@ -82,6 +102,7 @@ DankModal {
|
||||
browserTitle: "Select Profile Image"
|
||||
browserIcon: "person"
|
||||
browserType: "profile"
|
||||
showHiddenFiles: true
|
||||
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
|
||||
onFileSelected: (path) => {
|
||||
PortalService.setProfileImage(path);
|
||||
@@ -100,6 +121,7 @@ DankModal {
|
||||
browserTitle: "Select Wallpaper"
|
||||
browserIcon: "wallpaper"
|
||||
browserType: "wallpaper"
|
||||
showHiddenFiles: true
|
||||
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
|
||||
onFileSelected: (path) => {
|
||||
SessionData.setWallpaper(path);
|
||||
@@ -111,9 +133,9 @@ DankModal {
|
||||
}
|
||||
|
||||
settingsContent: Component {
|
||||
FocusScope {
|
||||
Item {
|
||||
id: rootScope
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
@@ -172,7 +194,10 @@ DankModal {
|
||||
id: sidebar
|
||||
|
||||
parentModal: settingsModal
|
||||
onCurrentIndexChanged: content.currentIndex = currentIndex
|
||||
currentIndex: settingsModal.currentTabIndex
|
||||
onCurrentIndexChanged: {
|
||||
settingsModal.currentTabIndex = currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
SettingsContent {
|
||||
@@ -181,7 +206,7 @@ DankModal {
|
||||
width: parent.width - sidebar.width
|
||||
height: parent.height
|
||||
parentModal: settingsModal
|
||||
currentIndex: sidebar.currentIndex
|
||||
currentIndex: settingsModal.currentTabIndex
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modals.Settings
|
||||
@@ -12,11 +14,8 @@ Rectangle {
|
||||
"text": I18n.tr("Personalization"),
|
||||
"icon": "person"
|
||||
}, {
|
||||
"text": I18n.tr("Time & Date"),
|
||||
"text": I18n.tr("Time & Weather"),
|
||||
"icon": "schedule"
|
||||
}, {
|
||||
"text": I18n.tr("Weather"),
|
||||
"icon": "cloud"
|
||||
}, {
|
||||
"text": I18n.tr("Dank Bar"),
|
||||
"icon": "toolbar"
|
||||
@@ -36,8 +35,8 @@ Rectangle {
|
||||
"text": I18n.tr("Theme & Colors"),
|
||||
"icon": "palette"
|
||||
}, {
|
||||
"text": I18n.tr("Power"),
|
||||
"icon": "power_settings_new"
|
||||
"text": I18n.tr("Power & Security"),
|
||||
"icon": "power"
|
||||
}, {
|
||||
"text": I18n.tr("Plugins"),
|
||||
"icon": "extension"
|
||||
@@ -46,6 +45,14 @@ Rectangle {
|
||||
"icon": "info"
|
||||
}]
|
||||
|
||||
function navigateNext() {
|
||||
currentIndex = (currentIndex + 1) % sidebarItems.length
|
||||
}
|
||||
|
||||
function navigatePrevious() {
|
||||
currentIndex = (currentIndex - 1 + sidebarItems.length) % sidebarItems.length
|
||||
}
|
||||
|
||||
width: 270
|
||||
height: parent.height
|
||||
color: Theme.surfaceContainer
|
||||
@@ -80,7 +87,10 @@ Rectangle {
|
||||
|
||||
model: sidebarContainer.sidebarItems
|
||||
|
||||
Rectangle {
|
||||
delegate: Rectangle {
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
property bool isActive: sidebarContainer.currentIndex === index
|
||||
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
|
||||
@@ -128,6 +128,7 @@ Item {
|
||||
enabled: parentModal ? parentModal.spotlightOpen : true
|
||||
placeholderText: ""
|
||||
ignoreLeftRightKeys: appLauncher.viewMode !== "list"
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [spotlightKeyHandler]
|
||||
text: appLauncher.searchQuery
|
||||
onTextEdited: () => {
|
||||
|
||||
@@ -12,6 +12,7 @@ Popup {
|
||||
property var currentApp: null
|
||||
property var appLauncher: null
|
||||
property var parentHandler: null
|
||||
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
|
||||
|
||||
function show(x, y, app) {
|
||||
currentApp = app
|
||||
@@ -83,6 +84,7 @@ Popup {
|
||||
color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: pinRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -90,10 +92,10 @@ Popup {
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
|
||||
if (!desktopEntry)
|
||||
return "push_pin"
|
||||
|
||||
const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
|
||||
const appId = desktopEntry.id || desktopEntry.execString || ""
|
||||
return SessionData.isPinnedApp(appId) ? "keep_off" : "push_pin"
|
||||
}
|
||||
size: Theme.iconSize - 2
|
||||
@@ -104,10 +106,10 @@ Popup {
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
|
||||
if (!desktopEntry)
|
||||
return I18n.tr("Pin to Dock")
|
||||
|
||||
const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
|
||||
const appId = desktopEntry.id || desktopEntry.execString || ""
|
||||
return SessionData.isPinnedApp(appId) ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -124,10 +126,10 @@ Popup {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
|
||||
if (!desktopEntry)
|
||||
return
|
||||
|
||||
const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
|
||||
const appId = desktopEntry.id || desktopEntry.execString || ""
|
||||
if (SessionData.isPinnedApp(appId))
|
||||
SessionData.removePinnedApp(appId)
|
||||
else
|
||||
@@ -152,7 +154,7 @@ Popup {
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
|
||||
model: desktopEntry && desktopEntry.actions ? desktopEntry.actions : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
@@ -161,10 +163,9 @@ Popup {
|
||||
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: actionRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
@@ -189,8 +190,6 @@ Popup {
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
width: parent.width - (modelData.icon && modelData.icon !== "" ? (Theme.iconSize - 2 + Theme.spacingS) : 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,9 +199,9 @@ Popup {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData && contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
|
||||
SessionService.launchDesktopAction(contextMenu.currentApp.desktopEntry, modelData)
|
||||
if (appLauncher) {
|
||||
if (modelData && desktopEntry) {
|
||||
SessionService.launchDesktopAction(desktopEntry, modelData)
|
||||
if (appLauncher && contextMenu.currentApp) {
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
}
|
||||
@@ -213,7 +212,7 @@ Popup {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions && contextMenu.currentApp.desktopEntry.actions.length > 0
|
||||
visible: desktopEntry && desktopEntry.actions && desktopEntry.actions.length > 0
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -234,6 +233,7 @@ Popup {
|
||||
color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: launchRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -294,6 +294,7 @@ Popup {
|
||||
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: primeRunRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -323,9 +324,9 @@ Popup {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
if (contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
|
||||
SessionService.launchDesktopEntry(contextMenu.currentApp.desktopEntry, true)
|
||||
if (appLauncher) {
|
||||
if (desktopEntry) {
|
||||
SessionService.launchDesktopEntry(desktopEntry, true)
|
||||
if (appLauncher && contextMenu.currentApp) {
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,35 @@ DankModal {
|
||||
id: spotlightModal
|
||||
|
||||
property bool spotlightOpen: false
|
||||
property Component spotlightContent
|
||||
property alias spotlightContent: spotlightContentInstance
|
||||
|
||||
function show() {
|
||||
spotlightOpen = true
|
||||
open()
|
||||
|
||||
Qt.callLater(() => {
|
||||
if (contentLoader.item && contentLoader.item.searchField) {
|
||||
contentLoader.item.searchField.forceActiveFocus()
|
||||
if (spotlightContent && spotlightContent.searchField) {
|
||||
spotlightContent.searchField.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function showWithQuery(query) {
|
||||
if (spotlightContent) {
|
||||
if (spotlightContent.appLauncher) {
|
||||
spotlightContent.appLauncher.searchQuery = query
|
||||
}
|
||||
if (spotlightContent.searchField) {
|
||||
spotlightContent.searchField.text = query
|
||||
}
|
||||
}
|
||||
|
||||
spotlightOpen = true
|
||||
open()
|
||||
|
||||
Qt.callLater(() => {
|
||||
if (spotlightContent && spotlightContent.searchField) {
|
||||
spotlightContent.searchField.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -32,17 +52,17 @@ DankModal {
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
if (contentLoader.item) {
|
||||
if (contentLoader.item.appLauncher) {
|
||||
contentLoader.item.appLauncher.searchQuery = ""
|
||||
contentLoader.item.appLauncher.selectedIndex = 0
|
||||
contentLoader.item.appLauncher.setCategory(I18n.tr("All"))
|
||||
if (spotlightContent) {
|
||||
if (spotlightContent.appLauncher) {
|
||||
spotlightContent.appLauncher.searchQuery = ""
|
||||
spotlightContent.appLauncher.selectedIndex = 0
|
||||
spotlightContent.appLauncher.setCategory(I18n.tr("All"))
|
||||
}
|
||||
if (contentLoader.item.resetScroll) {
|
||||
contentLoader.item.resetScroll()
|
||||
if (spotlightContent.resetScroll) {
|
||||
spotlightContent.resetScroll()
|
||||
}
|
||||
if (contentLoader.item.searchField) {
|
||||
contentLoader.item.searchField.text = ""
|
||||
if (spotlightContent.searchField) {
|
||||
spotlightContent.searchField.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,10 +88,10 @@ DankModal {
|
||||
if (visible && !spotlightOpen) {
|
||||
show()
|
||||
}
|
||||
if (visible && contentLoader.item) {
|
||||
if (visible && spotlightContent) {
|
||||
Qt.callLater(() => {
|
||||
if (contentLoader.item.searchField) {
|
||||
contentLoader.item.searchField.forceActiveFocus()
|
||||
if (spotlightContent.searchField) {
|
||||
spotlightContent.searchField.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -79,7 +99,6 @@ DankModal {
|
||||
onBackgroundClicked: () => {
|
||||
return hide()
|
||||
}
|
||||
content: spotlightContent
|
||||
|
||||
Connections {
|
||||
function onCloseAllModalsExcept(excludedModal) {
|
||||
@@ -107,12 +126,28 @@ DankModal {
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
function openQuery(query: string): string {
|
||||
spotlightModal.showWithQuery(query)
|
||||
return "SPOTLIGHT_OPEN_QUERY_SUCCESS"
|
||||
}
|
||||
|
||||
function toggleQuery(query: string): string {
|
||||
if (spotlightModal.spotlightOpen) {
|
||||
spotlightModal.hide()
|
||||
} else {
|
||||
spotlightModal.showWithQuery(query)
|
||||
}
|
||||
return "SPOTLIGHT_TOGGLE_QUERY_SUCCESS"
|
||||
}
|
||||
|
||||
target: "spotlight"
|
||||
}
|
||||
|
||||
spotlightContent: Component {
|
||||
SpotlightContent {
|
||||
parentModal: spotlightModal
|
||||
}
|
||||
SpotlightContent {
|
||||
id: spotlightContentInstance
|
||||
|
||||
parentModal: spotlightModal
|
||||
}
|
||||
|
||||
directContent: spotlightContentInstance
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: resultsContainer
|
||||
|
||||
// DEVELOPER NOTE: This component renders the Spotlight launcher (accessed via Mod+Space).
|
||||
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
|
||||
// likely require corresponding updates in Modules/AppDrawer/AppLauncher.qml and vice versa.
|
||||
|
||||
property var appLauncher: null
|
||||
property var contextMenu: null
|
||||
|
||||
@@ -90,19 +94,32 @@ Rectangle {
|
||||
width: resultsList.iconSize
|
||||
height: resultsList.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: model.icon !== undefined && model.icon !== ""
|
||||
|
||||
property string iconValue: model.icon || ""
|
||||
property bool isMaterial: iconValue.indexOf("material:") === 0
|
||||
property string materialName: isMaterial ? iconValue.substring(9) : ""
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: parent.materialName
|
||||
size: resultsList.iconSize
|
||||
color: Theme.surfaceText
|
||||
visible: parent.isMaterial
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: listIconImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: Quickshell.iconPath(model.icon, true)
|
||||
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
visible: !parent.isMaterial && status === Image.Ready
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !listIconImg.visible
|
||||
visible: !parent.isMaterial && !listIconImg.visible
|
||||
color: Theme.surfaceLight
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
@@ -120,7 +137,7 @@ Rectangle {
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - resultsList.iconSize - Theme.spacingL
|
||||
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - resultsList.iconSize - Theme.spacingL) : parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
@@ -130,6 +147,8 @@ Rectangle {
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -162,7 +181,7 @@ Rectangle {
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
resultsList.itemClicked(index, model)
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
} else if (mouse.button === Qt.RightButton && !model.isPlugin) {
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
|
||||
resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y)
|
||||
@@ -253,20 +272,33 @@ Rectangle {
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: model.icon !== undefined && model.icon !== ""
|
||||
|
||||
property string iconValue: model.icon || ""
|
||||
property bool isMaterial: iconValue.indexOf("material:") === 0
|
||||
property string materialName: isMaterial ? iconValue.substring(9) : ""
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: parent.materialName
|
||||
size: parent.iconSize
|
||||
color: Theme.surfaceText
|
||||
visible: parent.isMaterial
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: gridIconImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: Quickshell.iconPath(model.icon, true)
|
||||
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
visible: !parent.isMaterial && status === Image.Ready
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !gridIconImg.visible
|
||||
visible: !parent.isMaterial && !gridIconImg.visible
|
||||
color: Theme.surfaceLight
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
@@ -291,8 +323,8 @@ Rectangle {
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 1
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +346,7 @@ Rectangle {
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
resultsGrid.itemClicked(index, model)
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
} else if (mouse.button === Qt.RightButton && !model.isPlugin) {
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
|
||||
resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y)
|
||||
|
||||
@@ -12,10 +12,26 @@ DankModal {
|
||||
property string wifiUsernameInput: ""
|
||||
property bool requiresEnterprise: false
|
||||
|
||||
property string wifiAnonymousIdentityInput: ""
|
||||
property string wifiDomainInput: ""
|
||||
|
||||
property bool isPromptMode: false
|
||||
property string promptToken: ""
|
||||
property string promptReason: ""
|
||||
property var promptFields: []
|
||||
property string promptSetting: ""
|
||||
|
||||
function show(ssid) {
|
||||
wifiPasswordSSID = ssid
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
wifiAnonymousIdentityInput = ""
|
||||
wifiDomainInput = ""
|
||||
isPromptMode = false
|
||||
promptToken = ""
|
||||
promptReason = ""
|
||||
promptFields = []
|
||||
promptSetting = ""
|
||||
|
||||
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid)
|
||||
requiresEnterprise = network?.enterprise || false
|
||||
@@ -32,13 +48,50 @@ DankModal {
|
||||
})
|
||||
}
|
||||
|
||||
function showFromPrompt(token, ssid, setting, fields, hints, reason) {
|
||||
wifiPasswordSSID = ssid
|
||||
isPromptMode = true
|
||||
promptToken = token
|
||||
promptReason = reason
|
||||
promptFields = fields || []
|
||||
promptSetting = setting || "802-11-wireless-security"
|
||||
|
||||
requiresEnterprise = setting === "802-1x"
|
||||
|
||||
if (reason === "wrong-password") {
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
} else {
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
wifiAnonymousIdentityInput = ""
|
||||
wifiDomainInput = ""
|
||||
}
|
||||
|
||||
open()
|
||||
Qt.callLater(() => {
|
||||
if (contentLoader.item) {
|
||||
if (reason === "wrong-password" && contentLoader.item.passwordInput) {
|
||||
contentLoader.item.passwordInput.text = ""
|
||||
contentLoader.item.passwordInput.forceActiveFocus()
|
||||
} else if (requiresEnterprise && contentLoader.item.usernameInput) {
|
||||
contentLoader.item.usernameInput.forceActiveFocus()
|
||||
} else if (contentLoader.item.passwordInput) {
|
||||
contentLoader.item.passwordInput.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
shouldBeVisible: false
|
||||
width: 420
|
||||
height: requiresEnterprise ? 310 : 230
|
||||
height: requiresEnterprise ? 430 : 230
|
||||
onShouldBeVisibleChanged: () => {
|
||||
if (!shouldBeVisible) {
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
wifiAnonymousIdentityInput = ""
|
||||
wifiDomainInput = ""
|
||||
}
|
||||
}
|
||||
onOpened: {
|
||||
@@ -53,9 +106,14 @@ DankModal {
|
||||
})
|
||||
}
|
||||
onBackgroundClicked: () => {
|
||||
if (isPromptMode) {
|
||||
NetworkService.cancelCredentials(promptToken)
|
||||
}
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
wifiAnonymousIdentityInput = ""
|
||||
wifiDomainInput = ""
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -81,9 +139,14 @@ DankModal {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Keys.onEscapePressed: event => {
|
||||
if (isPromptMode) {
|
||||
NetworkService.cancelCredentials(promptToken)
|
||||
}
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
wifiAnonymousIdentityInput = ""
|
||||
wifiDomainInput = ""
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
@@ -106,12 +169,28 @@ DankModal {
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: requiresEnterprise ? `Enter credentials for "${wifiPasswordSSID}"` : `Enter password for "${wifiPasswordSSID}"`
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
Column {
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for ")
|
||||
return prefix + wifiPasswordSSID
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: isPromptMode && promptReason === "wrong-password"
|
||||
text: I18n.tr("Incorrect password")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +199,14 @@ DankModal {
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: () => {
|
||||
if (isPromptMode) {
|
||||
NetworkService.cancelCredentials(promptToken)
|
||||
}
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
wifiAnonymousIdentityInput = ""
|
||||
wifiDomainInput = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,7 +234,7 @@ DankModal {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
textColor: Theme.surfaceText
|
||||
text: wifiUsernameInput
|
||||
placeholderText: "Username"
|
||||
placeholderText: I18n.tr("Username")
|
||||
backgroundColor: "transparent"
|
||||
enabled: root.shouldBeVisible
|
||||
onTextEdited: () => {
|
||||
@@ -187,7 +271,7 @@ DankModal {
|
||||
textColor: Theme.surfaceText
|
||||
text: wifiPasswordInput
|
||||
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
|
||||
placeholderText: requiresEnterprise ? "Password" : ""
|
||||
placeholderText: requiresEnterprise ? I18n.tr("Password") : ""
|
||||
backgroundColor: "transparent"
|
||||
focus: !requiresEnterprise
|
||||
enabled: root.shouldBeVisible
|
||||
@@ -195,11 +279,31 @@ DankModal {
|
||||
wifiPasswordInput = text
|
||||
}
|
||||
onAccepted: () => {
|
||||
const username = requiresEnterprise ? usernameInput.text : ""
|
||||
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text, username)
|
||||
if (isPromptMode) {
|
||||
const secrets = {}
|
||||
if (promptSetting === "802-11-wireless-security") {
|
||||
secrets["psk"] = passwordInput.text
|
||||
} else if (promptSetting === "802-1x") {
|
||||
if (usernameInput.text) secrets["identity"] = usernameInput.text
|
||||
if (passwordInput.text) secrets["password"] = passwordInput.text
|
||||
if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput
|
||||
}
|
||||
NetworkService.submitCredentials(promptToken, secrets, true)
|
||||
} else {
|
||||
const username = requiresEnterprise ? usernameInput.text : ""
|
||||
NetworkService.connectToWifi(
|
||||
wifiPasswordSSID,
|
||||
passwordInput.text,
|
||||
username,
|
||||
wifiAnonymousIdentityInput,
|
||||
wifiDomainInput
|
||||
)
|
||||
}
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
wifiAnonymousIdentityInput = ""
|
||||
wifiDomainInput = ""
|
||||
passwordInput.text = ""
|
||||
if (requiresEnterprise) usernameInput.text = ""
|
||||
}
|
||||
@@ -235,6 +339,70 @@ DankModal {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: requiresEnterprise
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceHover
|
||||
border.color: anonInput.activeFocus ? Theme.primary : Theme.outlineStrong
|
||||
border.width: anonInput.activeFocus ? 2 : 1
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: () => {
|
||||
anonInput.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: anonInput
|
||||
|
||||
anchors.fill: parent
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
textColor: Theme.surfaceText
|
||||
text: wifiAnonymousIdentityInput
|
||||
placeholderText: I18n.tr("Anonymous Identity (optional)")
|
||||
backgroundColor: "transparent"
|
||||
enabled: root.shouldBeVisible
|
||||
onTextEdited: () => {
|
||||
wifiAnonymousIdentityInput = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: requiresEnterprise
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceHover
|
||||
border.color: domainMatchInput.activeFocus ? Theme.primary : Theme.outlineStrong
|
||||
border.width: domainMatchInput.activeFocus ? 2 : 1
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: () => {
|
||||
domainMatchInput.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: domainMatchInput
|
||||
|
||||
anchors.fill: parent
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
textColor: Theme.surfaceText
|
||||
text: wifiDomainInput
|
||||
placeholderText: I18n.tr("Domain (optional)")
|
||||
backgroundColor: "transparent"
|
||||
enabled: root.shouldBeVisible
|
||||
onTextEdited: () => {
|
||||
wifiDomainInput = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
@@ -310,9 +478,14 @@ DankModal {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
if (isPromptMode) {
|
||||
NetworkService.cancelCredentials(promptToken)
|
||||
}
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
wifiAnonymousIdentityInput = ""
|
||||
wifiDomainInput = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,11 +516,31 @@ DankModal {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: parent.enabled
|
||||
onClicked: () => {
|
||||
const username = requiresEnterprise ? usernameInput.text : ""
|
||||
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text, username)
|
||||
if (isPromptMode) {
|
||||
const secrets = {}
|
||||
if (promptSetting === "802-11-wireless-security") {
|
||||
secrets["psk"] = passwordInput.text
|
||||
} else if (promptSetting === "802-1x") {
|
||||
if (usernameInput.text) secrets["identity"] = usernameInput.text
|
||||
if (passwordInput.text) secrets["password"] = passwordInput.text
|
||||
if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput
|
||||
}
|
||||
NetworkService.submitCredentials(promptToken, secrets, true)
|
||||
} else {
|
||||
const username = requiresEnterprise ? usernameInput.text : ""
|
||||
NetworkService.connectToWifi(
|
||||
wifiPasswordSSID,
|
||||
passwordInput.text,
|
||||
username,
|
||||
wifiAnonymousIdentityInput,
|
||||
wifiDomainInput
|
||||
)
|
||||
}
|
||||
close()
|
||||
wifiPasswordInput = ""
|
||||
wifiUsernameInput = ""
|
||||
wifiAnonymousIdentityInput = ""
|
||||
wifiDomainInput = ""
|
||||
passwordInput.text = ""
|
||||
if (requiresEnterprise) usernameInput.text = ""
|
||||
}
|
||||
|
||||
@@ -112,6 +112,8 @@ DankPopout {
|
||||
mappings[Qt.Key_Up] = () => appLauncher.selectPrevious()
|
||||
mappings[Qt.Key_Return] = () => appLauncher.launchSelected()
|
||||
mappings[Qt.Key_Enter] = () => appLauncher.launchSelected()
|
||||
mappings[Qt.Key_Tab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : appLauncher.selectNext()
|
||||
mappings[Qt.Key_Backtab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : appLauncher.selectPrevious()
|
||||
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow()
|
||||
@@ -217,6 +219,7 @@ DankPopout {
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
enabled: appDrawerPopout.shouldBeVisible
|
||||
ignoreLeftRightKeys: appLauncher.viewMode !== "list"
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [keyHandler]
|
||||
onTextEdited: {
|
||||
appLauncher.searchQuery = text
|
||||
@@ -241,7 +244,7 @@ DankPopout {
|
||||
return
|
||||
}
|
||||
|
||||
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right]
|
||||
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right, Qt.Key_Tab, Qt.Key_Backtab]
|
||||
const isNavigationKey = navigationKeys.includes(event.key)
|
||||
const isEmptyEnter = isEnterKey && !hasText
|
||||
|
||||
@@ -401,16 +404,29 @@ DankPopout {
|
||||
width: appList.iconSize
|
||||
height: appList.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: model.icon !== undefined && model.icon !== ""
|
||||
|
||||
property string iconValue: model.icon || ""
|
||||
property bool isMaterial: iconValue.indexOf("material:") === 0
|
||||
property string materialName: isMaterial ? iconValue.substring(9) : ""
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: parent.materialName
|
||||
size: appList.iconSize - Theme.spacingM
|
||||
color: Theme.surfaceText
|
||||
visible: parent.isMaterial
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: listIconImg
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
source: Quickshell.iconPath(model.icon, true)
|
||||
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
visible: !parent.isMaterial && status === Image.Ready
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -418,7 +434,7 @@ DankPopout {
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingM
|
||||
visible: !listIconImg.visible
|
||||
visible: !parent.isMaterial && listIconImg.status !== Image.Ready
|
||||
color: Theme.surfaceLight
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
@@ -432,11 +448,12 @@ DankPopout {
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - appList.iconSize - Theme.spacingL
|
||||
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - appList.iconSize - Theme.spacingL) : parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
@@ -446,6 +463,8 @@ DankPopout {
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -508,6 +527,7 @@ DankPopout {
|
||||
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
|
||||
property int baseCellHeight: baseCellWidth + 20
|
||||
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
|
||||
|
||||
property int remainingSpace: width - (actualColumns * cellWidth)
|
||||
|
||||
signal keyboardNavigationReset
|
||||
@@ -573,6 +593,19 @@ DankPopout {
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: model.icon !== undefined && model.icon !== ""
|
||||
|
||||
property string iconValue: model.icon || ""
|
||||
property bool isMaterial: iconValue.indexOf("material:") === 0
|
||||
property string materialName: isMaterial ? iconValue.substring(9) : ""
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: parent.materialName
|
||||
size: parent.iconSize - Theme.spacingL
|
||||
color: Theme.surfaceText
|
||||
visible: parent.isMaterial
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: gridIconImg
|
||||
@@ -581,10 +614,10 @@ DankPopout {
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
source: Quickshell.iconPath(model.icon, true)
|
||||
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
visible: !parent.isMaterial && status === Image.Ready
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -592,7 +625,7 @@ DankPopout {
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
visible: !gridIconImg.visible
|
||||
visible: !parent.isMaterial && gridIconImg.status !== Image.Ready
|
||||
color: Theme.surfaceLight
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
@@ -617,8 +650,8 @@ DankPopout {
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 1
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,8 +695,8 @@ DankPopout {
|
||||
id: contextMenu
|
||||
|
||||
property var currentApp: null
|
||||
|
||||
readonly property string appId: (currentApp && currentApp.desktopEntry) ? (currentApp.desktopEntry.id || currentApp.desktopEntry.execString || "") : ""
|
||||
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
|
||||
readonly property string appId: desktopEntry ? (desktopEntry.id || desktopEntry.execString || "") : ""
|
||||
readonly property bool isPinned: appId && SessionData.isPinnedApp(appId)
|
||||
|
||||
function show(x, y, app) {
|
||||
@@ -765,7 +798,7 @@ DankPopout {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) {
|
||||
if (!contextMenu.desktopEntry) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -794,15 +827,16 @@ DankPopout {
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
|
||||
model: contextMenu.desktopEntry && contextMenu.desktopEntry.actions ? contextMenu.desktopEntry.actions : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
width: Math.max(parent.width, actionRow.implicitWidth + Theme.spacingS * 2)
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: actionRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -838,9 +872,11 @@ DankPopout {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData && contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
|
||||
SessionService.launchDesktopAction(contextMenu.currentApp.desktopEntry, modelData)
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
if (modelData && contextMenu.desktopEntry) {
|
||||
SessionService.launchDesktopAction(contextMenu.desktopEntry, modelData)
|
||||
if (contextMenu.currentApp) {
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
}
|
||||
contextMenu.hide()
|
||||
}
|
||||
@@ -849,7 +885,7 @@ DankPopout {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions && contextMenu.currentApp.desktopEntry.actions.length > 0
|
||||
visible: contextMenu.desktopEntry && contextMenu.desktopEntry.actions && contextMenu.desktopEntry.actions.length > 0
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -959,9 +995,11 @@ DankPopout {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
|
||||
SessionService.launchDesktopEntry(contextMenu.currentApp.desktopEntry, true)
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
if (contextMenu.desktopEntry) {
|
||||
SessionService.launchDesktopEntry(contextMenu.desktopEntry, true)
|
||||
if (contextMenu.currentApp) {
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
}
|
||||
contextMenu.hide()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ import qs.Widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// DEVELOPER NOTE: This component manages the AppDrawer launcher (accessed via DankBar icon).
|
||||
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
|
||||
// likely require corresponding updates in Modals/Spotlight/SpotlightResults.qml and vice versa.
|
||||
|
||||
property string searchQuery: ""
|
||||
property string selectedCategory: I18n.tr("All")
|
||||
property string viewMode: "list" // "list" or "grid"
|
||||
@@ -18,7 +22,7 @@ Item {
|
||||
property int debounceInterval: 50
|
||||
property bool keyboardNavigationActive: false
|
||||
property bool suppressUpdatesWhileLaunching: false
|
||||
readonly property var categories: {
|
||||
property var categories: {
|
||||
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
|
||||
const result = [I18n.tr("All")]
|
||||
return result.concat(allCategories.filter(cat => cat !== I18n.tr("All")))
|
||||
@@ -27,11 +31,37 @@ Item {
|
||||
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
|
||||
property alias model: filteredModel
|
||||
property var _watchApplications: AppSearchService.applications
|
||||
property var _uniqueApps: []
|
||||
property bool _isTriggered: false
|
||||
property string _triggeredCategory: ""
|
||||
property bool _updatingFromTrigger: false
|
||||
|
||||
signal appLaunched(var app)
|
||||
signal categorySelected(string category)
|
||||
signal viewModeSelected(string mode)
|
||||
|
||||
function updateCategories() {
|
||||
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
|
||||
const result = [I18n.tr("All")]
|
||||
categories = result.concat(allCategories.filter(cat => cat !== I18n.tr("All")))
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PluginService
|
||||
function onPluginLoaded() { updateCategories() }
|
||||
function onPluginUnloaded() { updateCategories() }
|
||||
function onPluginListUpdated() { updateCategories() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onSortAppsAlphabeticallyChanged() {
|
||||
updateFilteredModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function updateFilteredModel() {
|
||||
if (suppressUpdatesWhileLaunching) {
|
||||
suppressUpdatesWhileLaunching = false
|
||||
@@ -41,50 +71,112 @@ Item {
|
||||
selectedIndex = 0
|
||||
keyboardNavigationActive = false
|
||||
|
||||
const triggerResult = checkPluginTriggers(searchQuery)
|
||||
if (triggerResult.triggered) {
|
||||
console.log("AppLauncher: Plugin trigger detected:", triggerResult.trigger, "for plugin:", triggerResult.pluginId)
|
||||
}
|
||||
|
||||
let apps = []
|
||||
const allCategory = I18n.tr("All")
|
||||
if (searchQuery.length === 0) {
|
||||
apps = selectedCategory === allCategory ? AppSearchService.getAppsInCategory(allCategory) : AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
|
||||
const emptyTriggerPlugins = typeof PluginService !== "undefined" ? PluginService.getPluginsWithEmptyTrigger() : []
|
||||
|
||||
if (triggerResult.triggered) {
|
||||
_isTriggered = true
|
||||
_triggeredCategory = triggerResult.pluginCategory
|
||||
_updatingFromTrigger = true
|
||||
selectedCategory = triggerResult.pluginCategory
|
||||
_updatingFromTrigger = false
|
||||
apps = AppSearchService.getPluginItems(triggerResult.pluginCategory, triggerResult.query)
|
||||
} else {
|
||||
if (selectedCategory === allCategory) {
|
||||
apps = AppSearchService.searchApplications(searchQuery)
|
||||
} else {
|
||||
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||
if (categoryApps.length > 0) {
|
||||
const allSearchResults = AppSearchService.searchApplications(searchQuery)
|
||||
const categoryNames = new Set(categoryApps.map(app => app.name))
|
||||
apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults)
|
||||
if (_isTriggered) {
|
||||
_updatingFromTrigger = true
|
||||
selectedCategory = allCategory
|
||||
_updatingFromTrigger = false
|
||||
_isTriggered = false
|
||||
_triggeredCategory = ""
|
||||
}
|
||||
if (searchQuery.length === 0) {
|
||||
if (selectedCategory === allCategory) {
|
||||
let emptyTriggerItems = []
|
||||
emptyTriggerPlugins.forEach(pluginId => {
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId)
|
||||
const pluginCategory = plugin.name || pluginId
|
||||
const items = AppSearchService.getPluginItems(pluginCategory, "")
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items)
|
||||
})
|
||||
apps = AppSearchService.applications.concat(emptyTriggerItems)
|
||||
} else {
|
||||
apps = []
|
||||
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
|
||||
}
|
||||
} else {
|
||||
if (selectedCategory === allCategory) {
|
||||
apps = AppSearchService.searchApplications(searchQuery)
|
||||
|
||||
let emptyTriggerItems = []
|
||||
emptyTriggerPlugins.forEach(pluginId => {
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId)
|
||||
const pluginCategory = plugin.name || pluginId
|
||||
const items = AppSearchService.getPluginItems(pluginCategory, searchQuery)
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items)
|
||||
})
|
||||
apps = apps.concat(emptyTriggerItems)
|
||||
} else {
|
||||
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||
if (categoryApps.length > 0) {
|
||||
const allSearchResults = AppSearchService.searchApplications(searchQuery)
|
||||
const categoryNames = new Set(categoryApps.map(app => app.name))
|
||||
apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults)
|
||||
} else {
|
||||
apps = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchQuery.length === 0) {
|
||||
apps = apps.sort((a, b) => {
|
||||
const aId = a.id || a.execString || a.exec || ""
|
||||
const bId = b.id || b.execString || b.exec || ""
|
||||
const aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0
|
||||
const bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0
|
||||
if (aUsage !== bUsage) {
|
||||
return bUsage - aUsage
|
||||
}
|
||||
return (a.name || "").localeCompare(b.name || "")
|
||||
})
|
||||
if (SettingsData.sortAppsAlphabetically) {
|
||||
apps = apps.sort((a, b) => {
|
||||
return (a.name || "").localeCompare(b.name || "")
|
||||
})
|
||||
} else {
|
||||
apps = apps.sort((a, b) => {
|
||||
const aId = a.id || a.execString || a.exec || ""
|
||||
const bId = b.id || b.execString || b.exec || ""
|
||||
const aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0
|
||||
const bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0
|
||||
if (aUsage !== bUsage) {
|
||||
return bUsage - aUsage
|
||||
}
|
||||
return (a.name || "").localeCompare(b.name || "")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const seenNames = new Set()
|
||||
const uniqueApps = []
|
||||
apps.forEach(app => {
|
||||
if (app) {
|
||||
const itemKey = app.name + "|" + (app.execString || app.exec || app.action || "")
|
||||
if (seenNames.has(itemKey)) {
|
||||
return
|
||||
}
|
||||
seenNames.add(itemKey)
|
||||
uniqueApps.push(app)
|
||||
|
||||
const isPluginItem = app.action !== undefined
|
||||
filteredModel.append({
|
||||
"name": app.name || "",
|
||||
"exec": app.execString || "",
|
||||
"icon": app.icon || "application-x-executable",
|
||||
"exec": app.execString || app.exec || app.action || "",
|
||||
"icon": app.icon !== undefined ? app.icon : (isPluginItem ? "" : "application-x-executable"),
|
||||
"comment": app.comment || "",
|
||||
"categories": app.categories || [],
|
||||
"desktopEntry": app
|
||||
"isPlugin": isPluginItem,
|
||||
"appIndex": uniqueApps.length - 1
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
root._uniqueApps = uniqueApps
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
@@ -128,13 +220,25 @@ Item {
|
||||
}
|
||||
|
||||
function launchApp(appData) {
|
||||
if (!appData) {
|
||||
if (!appData || typeof appData.appIndex === "undefined" || appData.appIndex < 0 || appData.appIndex >= _uniqueApps.length) {
|
||||
return
|
||||
}
|
||||
suppressUpdatesWhileLaunching = true
|
||||
SessionService.launchDesktopEntry(appData.desktopEntry)
|
||||
appLaunched(appData)
|
||||
AppUsageHistoryData.addAppUsage(appData.desktopEntry)
|
||||
|
||||
const actualApp = _uniqueApps[appData.appIndex]
|
||||
|
||||
if (appData.isPlugin) {
|
||||
const pluginId = getPluginIdForItem(actualApp)
|
||||
if (pluginId) {
|
||||
AppSearchService.executePluginItem(actualApp, pluginId)
|
||||
appLaunched(appData)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
SessionService.launchDesktopEntry(actualApp)
|
||||
appLaunched(appData)
|
||||
AppUsageHistoryData.addAppUsage(actualApp)
|
||||
}
|
||||
}
|
||||
|
||||
function setCategory(category) {
|
||||
@@ -154,7 +258,12 @@ Item {
|
||||
updateFilteredModel()
|
||||
}
|
||||
}
|
||||
onSelectedCategoryChanged: updateFilteredModel()
|
||||
onSelectedCategoryChanged: {
|
||||
if (_updatingFromTrigger) {
|
||||
return
|
||||
}
|
||||
updateFilteredModel()
|
||||
}
|
||||
onAppUsageRankingChanged: updateFilteredModel()
|
||||
on_WatchApplicationsChanged: updateFilteredModel()
|
||||
Component.onCompleted: {
|
||||
@@ -172,4 +281,63 @@ Item {
|
||||
repeat: false
|
||||
onTriggered: updateFilteredModel()
|
||||
}
|
||||
|
||||
// Plugin trigger system functions
|
||||
function checkPluginTriggers(query) {
|
||||
if (!query || typeof PluginService === "undefined") {
|
||||
return { triggered: false, pluginCategory: "", query: "" }
|
||||
}
|
||||
|
||||
const triggers = PluginService.getAllPluginTriggers()
|
||||
|
||||
for (const trigger in triggers) {
|
||||
if (query.startsWith(trigger)) {
|
||||
const pluginId = triggers[trigger]
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId)
|
||||
|
||||
if (plugin) {
|
||||
const remainingQuery = query.substring(trigger.length).trim()
|
||||
const result = {
|
||||
triggered: true,
|
||||
pluginId: pluginId,
|
||||
pluginCategory: plugin.name || pluginId,
|
||||
query: remainingQuery,
|
||||
trigger: trigger
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { triggered: false, pluginCategory: "", query: "" }
|
||||
}
|
||||
|
||||
function getPluginIdForItem(item) {
|
||||
if (!item || !item.categories || typeof PluginService === "undefined") {
|
||||
return null
|
||||
}
|
||||
|
||||
const launchers = PluginService.getLauncherPlugins()
|
||||
for (const pluginId in launchers) {
|
||||
const plugin = launchers[pluginId]
|
||||
const pluginCategory = plugin.name || pluginId
|
||||
|
||||
let hasCategory = false
|
||||
if (Array.isArray(item.categories)) {
|
||||
hasCategory = item.categories.includes(pluginCategory)
|
||||
} else if (item.categories && typeof item.categories.count !== "undefined") {
|
||||
for (let i = 0; i < item.categories.count; i++) {
|
||||
if (item.categories.get(i) === pluginCategory) {
|
||||
hasCategory = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCategory) {
|
||||
return pluginId
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ Item {
|
||||
|
||||
property var pluginDetailInstance: null
|
||||
property var widgetModel: null
|
||||
property var collapseCallback: null
|
||||
|
||||
Loader {
|
||||
id: pluginDetailLoader
|
||||
@@ -32,6 +33,54 @@ Item {
|
||||
sourceComponent: null
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: coreDetailLoader.item
|
||||
enabled: root.expandedSection.startsWith("brightnessSlider_")
|
||||
ignoreUnknownSignals: true
|
||||
|
||||
function onDeviceNameChanged(newDeviceName) {
|
||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "brightnessSlider") {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const newWidgets = widgets.map(w => {
|
||||
if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||
const updatedWidget = Object.assign({}, w)
|
||||
updatedWidget.deviceName = newDeviceName
|
||||
return updatedWidget
|
||||
}
|
||||
return w
|
||||
})
|
||||
SettingsData.setControlCenterWidgets(newWidgets)
|
||||
if (root.collapseCallback) {
|
||||
root.collapseCallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: coreDetailLoader.item
|
||||
enabled: root.expandedSection.startsWith("diskUsage_")
|
||||
ignoreUnknownSignals: true
|
||||
|
||||
function onMountPathChanged(newMountPath) {
|
||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const newWidgets = widgets.map(w => {
|
||||
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||
const updatedWidget = Object.assign({}, w)
|
||||
updatedWidget.mountPath = newMountPath
|
||||
return updatedWidget
|
||||
}
|
||||
return w
|
||||
})
|
||||
SettingsData.setControlCenterWidgets(newWidgets)
|
||||
if (root.collapseCallback) {
|
||||
root.collapseCallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExpandedSectionChanged: {
|
||||
if (pluginDetailInstance) {
|
||||
pluginDetailInstance.destroy()
|
||||
@@ -91,6 +140,12 @@ Item {
|
||||
return
|
||||
}
|
||||
|
||||
if (root.expandedSection.startsWith("brightnessSlider_")) {
|
||||
coreDetailLoader.sourceComponent = brightnessDetailComponent
|
||||
coreDetailLoader.active = parent.height > 0
|
||||
return
|
||||
}
|
||||
|
||||
switch (root.expandedSection) {
|
||||
case "network":
|
||||
case "wifi": coreDetailLoader.sourceComponent = networkDetailComponent; break
|
||||
@@ -144,22 +199,14 @@ Item {
|
||||
DiskUsageDetail {
|
||||
currentMountPath: root.expandedWidgetData?.mountPath || "/"
|
||||
instanceId: root.expandedWidgetData?.instanceId || ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMountPathChanged: (newMountPath) => {
|
||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const newWidgets = widgets.map(w => {
|
||||
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||
const updatedWidget = Object.assign({}, w)
|
||||
updatedWidget.mountPath = newMountPath
|
||||
return updatedWidget
|
||||
}
|
||||
return w
|
||||
})
|
||||
SettingsData.setControlCenterWidgets(newWidgets)
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: brightnessDetailComponent
|
||||
BrightnessDetail {
|
||||
currentDeviceName: root.expandedWidgetData?.deviceName || ""
|
||||
instanceId: root.expandedWidgetData?.instanceId || ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,11 @@ Column {
|
||||
signal removeWidget(int index)
|
||||
signal moveWidget(int fromIndex, int toIndex)
|
||||
signal toggleWidgetSize(int index)
|
||||
signal collapseRequested()
|
||||
|
||||
function requestCollapse() {
|
||||
collapseRequested()
|
||||
}
|
||||
|
||||
spacing: editMode ? Theme.spacingL : Theme.spacingS
|
||||
|
||||
@@ -82,7 +87,7 @@ Column {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
if (widgets[i].id === modelData.id) {
|
||||
if (modelData.id === "diskUsage") {
|
||||
if (modelData.id === "diskUsage" || modelData.id === "brightnessSlider") {
|
||||
if (widgets[i].instanceId === modelData.instanceId) {
|
||||
return i
|
||||
}
|
||||
@@ -164,6 +169,11 @@ Column {
|
||||
return rowWidgets.some(w => w.id === "diskUsage" && w.instanceId === expandedInstanceId)
|
||||
}
|
||||
|
||||
if (root.expandedSection.startsWith("brightnessSlider_") && root.expandedWidgetData) {
|
||||
const expandedInstanceId = root.expandedWidgetData.instanceId
|
||||
return rowWidgets.some(w => w.id === "brightnessSlider" && w.instanceId === expandedInstanceId)
|
||||
}
|
||||
|
||||
return rowIndex === root.expandedRowIndex
|
||||
}
|
||||
visible: active
|
||||
@@ -171,6 +181,7 @@ Column {
|
||||
expandedWidgetData: root.expandedWidgetData
|
||||
bluetoothCodecSelector: root.bluetoothCodecSelector
|
||||
widgetModel: root.model
|
||||
collapseCallback: root.requestCollapse
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,18 +230,6 @@ Column {
|
||||
return "bluetooth_disabled"
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
|
||||
return "bluetooth_disabled"
|
||||
const primaryDevice = (() => {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
|
||||
return null
|
||||
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
|
||||
for (let device of devices) {
|
||||
if (device && device.connected)
|
||||
return device
|
||||
}
|
||||
return null
|
||||
})()
|
||||
if (primaryDevice)
|
||||
return BluetoothService.getDeviceIcon(primaryDevice)
|
||||
return "bluetooth"
|
||||
}
|
||||
case "audioOutput":
|
||||
@@ -467,10 +466,19 @@ Column {
|
||||
height: 16
|
||||
|
||||
BrightnessSliderRow {
|
||||
id: brightnessSliderRow
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
deviceName: widgetData.deviceName || ""
|
||||
instanceId: widgetData.instanceId || ""
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
|
||||
onIconClicked: {
|
||||
if (!root.editMode && DisplayService.devices && DisplayService.devices.length > 1) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -600,8 +608,9 @@ Column {
|
||||
}
|
||||
case "darkMode":
|
||||
{
|
||||
const newMode = !SessionData.isLightMode
|
||||
Theme.screenTransition()
|
||||
Theme.setLightMode(!SessionData.isLightMode)
|
||||
Theme.setLightMode(newMode)
|
||||
break
|
||||
}
|
||||
case "doNotDisturb":
|
||||
@@ -680,8 +689,9 @@ Column {
|
||||
}
|
||||
case "darkMode":
|
||||
{
|
||||
const newMode = !SessionData.isLightMode
|
||||
Theme.screenTransition()
|
||||
Theme.setLightMode(!SessionData.isLightMode)
|
||||
Theme.setLightMode(newMode)
|
||||
break
|
||||
}
|
||||
case "doNotDisturb":
|
||||
|
||||
@@ -69,6 +69,7 @@ Row {
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: Theme.spacingS
|
||||
clip: true
|
||||
model: root.availableWidgets
|
||||
|
||||
delegate: Rectangle {
|
||||
|
||||
@@ -11,6 +11,7 @@ Rectangle {
|
||||
signal powerButtonClicked()
|
||||
signal lockRequested()
|
||||
signal editModeToggled()
|
||||
signal settingsButtonClicked()
|
||||
|
||||
implicitHeight: 70
|
||||
radius: Theme.cornerRadius
|
||||
@@ -96,6 +97,7 @@ Rectangle {
|
||||
iconColor: Theme.surfaceText
|
||||
backgroundColor: "transparent"
|
||||
onClicked: {
|
||||
root.settingsButtonClicked()
|
||||
settingsModal.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,21 +73,21 @@ DankPopout {
|
||||
onShouldBeVisibleChanged: {
|
||||
if (shouldBeVisible) {
|
||||
Qt.callLater(() => {
|
||||
if (NetworkService.activeService) {
|
||||
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled
|
||||
}
|
||||
if (UserInfoService)
|
||||
UserInfoService.getUptime()
|
||||
})
|
||||
if (NetworkService.activeService) {
|
||||
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled
|
||||
}
|
||||
if (UserInfoService)
|
||||
UserInfoService.getUptime()
|
||||
})
|
||||
} else {
|
||||
Qt.callLater(() => {
|
||||
if (NetworkService.activeService) {
|
||||
NetworkService.activeService.autoRefreshEnabled = false
|
||||
}
|
||||
if (BluetoothService.adapter && BluetoothService.adapter.discovering)
|
||||
BluetoothService.adapter.discovering = false
|
||||
editMode = false
|
||||
})
|
||||
if (NetworkService.activeService) {
|
||||
NetworkService.activeService.autoRefreshEnabled = false
|
||||
}
|
||||
if (BluetoothService.adapter && BluetoothService.adapter.discovering)
|
||||
BluetoothService.adapter.discovering = false
|
||||
editMode = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,8 +108,7 @@ DankPopout {
|
||||
return Qt.rgba(surface.r, surface.g, surface.b, transparency)
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
antialiasing: true
|
||||
smooth: true
|
||||
@@ -140,6 +139,9 @@ DankPopout {
|
||||
root.close()
|
||||
root.lockRequested()
|
||||
}
|
||||
onSettingsButtonClicked: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
DragDropGrid {
|
||||
@@ -153,17 +155,20 @@ DankPopout {
|
||||
bluetoothCodecSelector: bluetoothCodecSelector
|
||||
colorPickerModal: root.colorPickerModal
|
||||
onExpandClicked: (widgetData, globalIndex) => {
|
||||
root.expandedWidgetIndex = globalIndex
|
||||
root.expandedWidgetData = widgetData
|
||||
if (widgetData.id === "diskUsage") {
|
||||
root.toggleSection("diskUsage_" + (widgetData.instanceId || "default"))
|
||||
} else {
|
||||
root.toggleSection(widgetData.id)
|
||||
}
|
||||
}
|
||||
onRemoveWidget: (index) => widgetModel.removeWidget(index)
|
||||
root.expandedWidgetIndex = globalIndex
|
||||
root.expandedWidgetData = widgetData
|
||||
if (widgetData.id === "diskUsage") {
|
||||
root.toggleSection("diskUsage_" + (widgetData.instanceId || "default"))
|
||||
} else if (widgetData.id === "brightnessSlider") {
|
||||
root.toggleSection("brightnessSlider_" + (widgetData.instanceId || "default"))
|
||||
} else {
|
||||
root.toggleSection(widgetData.id)
|
||||
}
|
||||
}
|
||||
onRemoveWidget: index => widgetModel.removeWidget(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex)
|
||||
onToggleWidgetSize: (index) => widgetModel.toggleWidgetSize(index)
|
||||
onToggleWidgetSize: index => widgetModel.toggleWidgetSize(index)
|
||||
onCollapseRequested: root.collapseAll()
|
||||
}
|
||||
|
||||
EditControls {
|
||||
@@ -171,12 +176,13 @@ DankPopout {
|
||||
visible: editMode
|
||||
popoutContent: controlContent
|
||||
availableWidgets: {
|
||||
if (!editMode) return []
|
||||
if (!editMode)
|
||||
return []
|
||||
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id)
|
||||
const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets())
|
||||
return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id))
|
||||
}
|
||||
onAddWidget: (widgetId) => widgetModel.addWidget(widgetId)
|
||||
onAddWidget: widgetId => widgetModel.addWidget(widgetId)
|
||||
onResetToDefault: () => widgetModel.resetToDefault()
|
||||
onClearAll: () => widgetModel.clearAll()
|
||||
}
|
||||
@@ -199,10 +205,10 @@ DankPopout {
|
||||
id: bluetoothDetailComponent
|
||||
BluetoothDetail {
|
||||
id: bluetoothDetail
|
||||
onShowCodecSelector: function(device) {
|
||||
onShowCodecSelector: function (device) {
|
||||
if (contentLoader.item && contentLoader.item.bluetoothCodecSelector) {
|
||||
contentLoader.item.bluetoothCodecSelector.show(device)
|
||||
contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function(deviceAddress, codecName) {
|
||||
contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function (deviceAddress, codecName) {
|
||||
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName)
|
||||
})
|
||||
}
|
||||
@@ -227,4 +233,4 @@ DankPopout {
|
||||
|
||||
property var colorPickerModal: null
|
||||
property var powerMenuModalLoader: null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ Rectangle {
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
@@ -27,7 +27,7 @@ Rectangle {
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingS
|
||||
height: 40
|
||||
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: I18n.tr("Input Devices")
|
||||
@@ -37,7 +37,7 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
id: volumeSlider
|
||||
anchors.left: parent.left
|
||||
@@ -105,7 +105,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DankFlickable {
|
||||
id: audioContent
|
||||
anchors.top: hasInputVolumeSliderInCC ? headerRow.bottom : volumeSlider.bottom
|
||||
@@ -116,34 +116,34 @@ Rectangle {
|
||||
anchors.topMargin: hasInputVolumeSliderInCC ? Theme.spacingM : Theme.spacingS
|
||||
contentHeight: audioColumn.height
|
||||
clip: true
|
||||
|
||||
|
||||
Column {
|
||||
id: audioColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
Repeater {
|
||||
model: Pipewire.nodes.values.filter(node => {
|
||||
return node.audio && !node.isSink && !node.isStream
|
||||
})
|
||||
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
||||
border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (modelData.name.includes("bluez"))
|
||||
@@ -157,11 +157,11 @@ Rectangle {
|
||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM
|
||||
|
||||
|
||||
StyledText {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -171,7 +171,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: modelData === AudioService.source ? "Active" : "Available"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -182,7 +182,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseArea
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -17,7 +17,7 @@ Rectangle {
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
@@ -27,7 +27,7 @@ Rectangle {
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingS
|
||||
height: 40
|
||||
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: I18n.tr("Audio Devices")
|
||||
@@ -121,34 +121,34 @@ Rectangle {
|
||||
anchors.topMargin: volumeSlider.visible ? Theme.spacingS : Theme.spacingM
|
||||
contentHeight: audioColumn.height
|
||||
clip: true
|
||||
|
||||
|
||||
Column {
|
||||
id: audioColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
Repeater {
|
||||
model: Pipewire.nodes.values.filter(node => {
|
||||
return node.audio && node.isSink && !node.isStream
|
||||
})
|
||||
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
||||
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (modelData.name.includes("bluez"))
|
||||
@@ -164,11 +164,11 @@ Rectangle {
|
||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - Theme.iconSize - Theme.spacingM
|
||||
|
||||
|
||||
StyledText {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -178,7 +178,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: modelData === AudioService.sink ? "Active" : "Available"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -189,7 +189,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseArea
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -83,12 +83,12 @@ Item {
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
propagateComposedEvents: false
|
||||
|
||||
|
||||
onClicked: root.hide()
|
||||
onWheel: (wheel) => { wheel.accepted = true }
|
||||
onPositionChanged: (mouse) => { mouse.accepted = true }
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: modalBackground
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -12,11 +12,11 @@ Rectangle {
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
|
||||
property var bluetoothCodecModalRef: null
|
||||
|
||||
|
||||
signal showCodecSelector(var device)
|
||||
|
||||
|
||||
function updateDeviceCodecDisplay(deviceAddress, codecName) {
|
||||
for (let i = 0; i < pairedRepeater.count; i++) {
|
||||
let item = pairedRepeater.itemAt(i)
|
||||
@@ -26,7 +26,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
@@ -36,7 +36,7 @@ Rectangle {
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingS
|
||||
height: 40
|
||||
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: I18n.tr("Bluetooth Settings")
|
||||
@@ -45,12 +45,12 @@ Rectangle {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: Math.max(0, parent.width - headerText.implicitWidth - scanButton.width - Theme.spacingM)
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: scanButton
|
||||
width: 100
|
||||
@@ -64,18 +64,18 @@ Rectangle {
|
||||
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "bluetooth_searching"
|
||||
size: 18
|
||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: BluetoothService.adapter && BluetoothService.adapter.discovering ? "Scanning" : "Scan"
|
||||
color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Theme.surfaceVariantText
|
||||
@@ -84,7 +84,7 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: scanMouseArea
|
||||
anchors.fill: parent
|
||||
@@ -98,7 +98,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DankFlickable {
|
||||
id: bluetoothContent
|
||||
anchors.top: headerRow.bottom
|
||||
@@ -110,19 +110,19 @@ Rectangle {
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
contentHeight: bluetoothColumn.height
|
||||
clip: true
|
||||
|
||||
|
||||
Column {
|
||||
id: bluetoothColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
|
||||
|
||||
Repeater {
|
||||
id: pairedRepeater
|
||||
model: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
|
||||
return []
|
||||
|
||||
|
||||
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
|
||||
devices.sort((a, b) => {
|
||||
if (a.connected && !b.connected) return -1
|
||||
@@ -131,17 +131,17 @@ Rectangle {
|
||||
})
|
||||
return devices
|
||||
}
|
||||
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
|
||||
property string currentCodec: BluetoothService.deviceCodecs[modelData.address] || ""
|
||||
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
if (modelData.connected && BluetoothService.isAudioDevice(modelData)) {
|
||||
BluetoothService.refreshDeviceCodec(modelData)
|
||||
@@ -162,13 +162,13 @@ Rectangle {
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
border.width: 0
|
||||
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getDeviceIcon(modelData)
|
||||
size: Theme.iconSize - 4
|
||||
@@ -181,11 +181,11 @@ Rectangle {
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 200
|
||||
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || modelData.deviceName || "Unknown Device"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -194,10 +194,10 @@ Rectangle {
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||
@@ -218,12 +218,12 @@ Rectangle {
|
||||
return Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (modelData.batteryAvailable && modelData.battery > 0)
|
||||
return "• " + Math.round(modelData.battery * 100) + "%"
|
||||
|
||||
|
||||
var btBattery = BatteryService.bluetoothDevices.find(dev => {
|
||||
return dev.name === (modelData.name || modelData.deviceName) ||
|
||||
dev.name.toLowerCase().includes((modelData.name || modelData.deviceName).toLowerCase()) ||
|
||||
@@ -235,7 +235,7 @@ Rectangle {
|
||||
color: Theme.surfaceVariantText
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -245,7 +245,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DankActionButton {
|
||||
id: pairedOptionsButton
|
||||
anchors.right: parent.right
|
||||
@@ -262,7 +262,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseArea
|
||||
anchors.fill: parent
|
||||
@@ -279,26 +279,26 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
visible: pairedRepeater.count > 0 && availableRepeater.count > 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 80
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0
|
||||
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "sync"
|
||||
size: 24
|
||||
color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.4)
|
||||
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: parent.visible && BluetoothService.adapter && BluetoothService.adapter.discovering && availableRepeater.count === 0
|
||||
loops: Animation.Infinite
|
||||
@@ -308,27 +308,27 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Repeater {
|
||||
id: availableRepeater
|
||||
model: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
|
||||
return []
|
||||
|
||||
|
||||
var filtered = Bluetooth.devices.values.filter(dev => {
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked &&
|
||||
(dev.signalStrength === undefined || dev.signalStrength > 0)
|
||||
})
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
}
|
||||
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
|
||||
property bool canConnect: BluetoothService.canConnect(modelData)
|
||||
property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
||||
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
@@ -336,24 +336,24 @@ Rectangle {
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
opacity: canConnect ? 1 : 0.6
|
||||
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: BluetoothService.getDeviceIcon(modelData)
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 200
|
||||
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || modelData.deviceName || "Unknown Device"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -361,10 +361,10 @@ Rectangle {
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (modelData.pairing) return "Pairing..."
|
||||
@@ -374,7 +374,7 @@ Rectangle {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: modelData.signalStrength !== undefined && modelData.signalStrength > 0 ? "• " + modelData.signalStrength + "%" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -384,7 +384,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
@@ -398,7 +398,7 @@ Rectangle {
|
||||
color: canConnect ? Theme.primary : Theme.surfaceVariantText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: availableMouseArea
|
||||
anchors.fill: parent
|
||||
@@ -411,15 +411,15 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 60
|
||||
visible: !BluetoothService.adapter
|
||||
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("No Bluetooth adapter found")
|
||||
@@ -429,25 +429,25 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Menu {
|
||||
id: bluetoothContextMenu
|
||||
width: 150
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
|
||||
property var currentDevice: null
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
|
||||
MenuItem {
|
||||
text: bluetoothContextMenu.currentDevice && bluetoothContextMenu.currentDevice.connected ? "Disconnect" : "Connect"
|
||||
height: 32
|
||||
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -455,12 +455,12 @@ Rectangle {
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
|
||||
onTriggered: {
|
||||
if (bluetoothContextMenu.currentDevice) {
|
||||
if (bluetoothContextMenu.currentDevice.connected) {
|
||||
@@ -471,12 +471,12 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Audio Codec")
|
||||
height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0
|
||||
visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected
|
||||
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -484,23 +484,23 @@ Rectangle {
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
|
||||
onTriggered: {
|
||||
if (bluetoothContextMenu.currentDevice) {
|
||||
showCodecSelector(bluetoothContextMenu.currentDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Forget Device")
|
||||
height: 32
|
||||
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -508,12 +508,12 @@ Rectangle {
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
|
||||
onTriggered: {
|
||||
if (bluetoothContextMenu.currentDevice) {
|
||||
bluetoothContextMenu.currentDevice.forget()
|
||||
@@ -521,5 +521,5 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
174
Modules/ControlCenter/Details/BrightnessDetail.qml
Normal file
174
Modules/ControlCenter/Details/BrightnessDetail.qml
Normal file
@@ -0,0 +1,174 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string currentDeviceName: ""
|
||||
property string instanceId: ""
|
||||
|
||||
signal deviceNameChanged(string newDeviceName)
|
||||
|
||||
implicitHeight: brightnessContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
|
||||
DankFlickable {
|
||||
id: brightnessContent
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
contentHeight: brightnessColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: brightnessColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 100
|
||||
visible: !DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: DisplayService.brightnessAvailable ? "brightness_6" : "error"
|
||||
size: 32
|
||||
color: DisplayService.brightnessAvailable ? Theme.primary : Theme.error
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: DisplayService.brightnessAvailable ? "No brightness devices available" : "Brightness control not available"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: DisplayService.devices || []
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHighest
|
||||
border.color: modelData.name === currentDeviceName ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: modelData.name === currentDeviceName ? 2 : 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
const deviceClass = modelData.class || ""
|
||||
const deviceName = modelData.name || ""
|
||||
|
||||
if (deviceClass === "backlight" || deviceClass === "ddc") {
|
||||
const brightness = modelData.percentage || 50
|
||||
if (brightness <= 33) return "brightness_low"
|
||||
if (brightness <= 66) return "brightness_medium"
|
||||
return "brightness_high"
|
||||
} else if (deviceName.includes("kbd")) {
|
||||
return "keyboard"
|
||||
} else {
|
||||
return "lightbulb"
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: modelData.name === currentDeviceName ? Theme.primary : Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: (modelData.percentage || 50) + "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - 50 - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const name = modelData.name || ""
|
||||
const deviceClass = modelData.class || ""
|
||||
if (deviceClass === "backlight") {
|
||||
return name.replace("_", " ").replace(/\b\w/g, c => c.toUpperCase())
|
||||
}
|
||||
return name
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData.name === currentDeviceName ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const deviceClass = modelData.class || ""
|
||||
if (deviceClass === "backlight") return "Backlight device"
|
||||
if (deviceClass === "ddc") return "DDC/CI monitor"
|
||||
if (deviceClass === "leds") return "LED device"
|
||||
return deviceClass
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
currentDeviceName = modelData.name
|
||||
deviceNameChanged(modelData.name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,27 @@ Rectangle {
|
||||
Component.onDestruction: {
|
||||
NetworkService.removeRef()
|
||||
}
|
||||
|
||||
|
||||
property int currentPreferenceIndex: {
|
||||
if (DMSService.apiVersion < 5) {
|
||||
return 1
|
||||
}
|
||||
|
||||
const pref = NetworkService.userPreference
|
||||
const status = NetworkService.networkStatus
|
||||
let index = 1
|
||||
|
||||
if (pref === "ethernet") {
|
||||
index = 0
|
||||
} else if (pref === "wifi") {
|
||||
index = 1
|
||||
} else {
|
||||
index = status === "ethernet" ? 0 : 1
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.left: parent.left
|
||||
@@ -38,7 +58,7 @@ Rectangle {
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingS
|
||||
height: 40
|
||||
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: I18n.tr("Network Settings")
|
||||
@@ -47,32 +67,16 @@ Rectangle {
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: Math.max(0, parent.width - headerText.implicitWidth - preferenceControls.width - Theme.spacingM)
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
|
||||
DankButtonGroup {
|
||||
id: preferenceControls
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.ethernetConnected
|
||||
|
||||
property int currentPreferenceIndex: {
|
||||
const pref = NetworkService.userPreference
|
||||
const status = NetworkService.networkStatus
|
||||
let index = 1
|
||||
|
||||
if (pref === "ethernet") {
|
||||
index = 0
|
||||
} else if (pref === "wifi") {
|
||||
index = 1
|
||||
} else {
|
||||
index = status === "ethernet" ? 0 : 1
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
visible: DMSService.apiVersion >= 5
|
||||
|
||||
model: ["Ethernet", "WiFi"]
|
||||
currentIndex: currentPreferenceIndex
|
||||
@@ -84,7 +88,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: wifiToggleContent
|
||||
anchors.top: headerRow.bottom
|
||||
@@ -92,7 +96,7 @@ Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: NetworkService.wifiToggling
|
||||
visible: currentPreferenceIndex === 1 && NetworkService.wifiToggling
|
||||
height: visible ? 80 : 0
|
||||
|
||||
Column {
|
||||
@@ -123,7 +127,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: wifiOffContent
|
||||
anchors.top: headerRow.bottom
|
||||
@@ -131,21 +135,21 @@ Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: !NetworkService.wifiEnabled && !NetworkService.wifiToggling
|
||||
visible: currentPreferenceIndex === 1 && !NetworkService.wifiEnabled && !NetworkService.wifiToggling
|
||||
height: visible ? 120 : 0
|
||||
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingL
|
||||
width: parent.width
|
||||
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "wifi_off"
|
||||
size: 48
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: I18n.tr("WiFi is off")
|
||||
@@ -154,7 +158,7 @@ Rectangle {
|
||||
font.weight: Font.Medium
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 120
|
||||
@@ -163,7 +167,7 @@ Rectangle {
|
||||
color: enableWifiButton.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
border.width: 0
|
||||
border.color: Theme.primary
|
||||
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Enable WiFi")
|
||||
@@ -171,7 +175,7 @@ Rectangle {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: enableWifiButton
|
||||
anchors.fill: parent
|
||||
@@ -179,7 +183,179 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NetworkService.toggleWifiRadio()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: wiredContent
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: currentPreferenceIndex === 0 && DMSService.apiVersion >= 5
|
||||
contentHeight: wiredColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: wiredColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: sortedNetworks
|
||||
|
||||
property var sortedNetworks: {
|
||||
const currentUuid = NetworkService.ethernetConnectionUuid
|
||||
const networks = NetworkService.wiredConnections
|
||||
let sorted = [...networks]
|
||||
sorted.sort((a, b) => {
|
||||
if (a.isActive && !b.isActive) return -1
|
||||
if (!a.isActive && b.isActive) return 1
|
||||
return a.id.localeCompare(b.id)
|
||||
})
|
||||
return sorted
|
||||
}
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: wiredNetworkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
||||
border.color: Theme.primary
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "lan"
|
||||
size: Theme.iconSize - 4
|
||||
color: modelData.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 200
|
||||
|
||||
StyledText {
|
||||
text: modelData.id || "Unknown Config"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: modelData.isActive ? Theme.primary : Theme.surfaceText
|
||||
font.weight: modelData.isActive ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: wiredOptionsButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "more_horiz"
|
||||
buttonSize: 28
|
||||
onClicked: {
|
||||
if (wiredNetworkContextMenu.visible) {
|
||||
wiredNetworkContextMenu.close()
|
||||
} else {
|
||||
wiredNetworkContextMenu.currentID = modelData.id
|
||||
wiredNetworkContextMenu.currentUUID = modelData.uuid
|
||||
wiredNetworkContextMenu.currentConnected = modelData.isActive
|
||||
wiredNetworkContextMenu.popup(wiredOptionsButton, -wiredNetworkContextMenu.width + wiredOptionsButton.width, wiredOptionsButton.height + Theme.spacingXS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wiredNetworkMouseArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: wiredOptionsButton.width + Theme.spacingS
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: function(event) {
|
||||
if (modelData.uuid !== NetworkService.ethernetConnectionUuid) {
|
||||
NetworkService.connectToSpecificWiredConfig(modelData.uuid)
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: wiredNetworkContextMenu
|
||||
width: 150
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
property string currentID: ""
|
||||
property string currentUUID: ""
|
||||
property bool currentConnected: false
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Activate"
|
||||
height: !wiredNetworkContextMenu.currentConnected ? 32 : 0
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (!networkContextMenu.currentConnected) {
|
||||
NetworkService.connectToSpecificWiredConfig(wiredNetworkContextMenu.currentUUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Network Info")
|
||||
height: wiredNetworkContextMenu.currentConnected ? 32 : 0
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
let networkData = NetworkService.getWiredNetworkInfo(wiredNetworkContextMenu.currentUUID)
|
||||
networkWiredInfoModal.showNetworkInfo(wiredNetworkContextMenu.currentID, networkData)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,15 +368,15 @@ Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: NetworkService.wifiInterface && NetworkService.wifiEnabled && !NetworkService.wifiToggling
|
||||
visible: currentPreferenceIndex === 1 && NetworkService.wifiEnabled && !NetworkService.wifiToggling
|
||||
contentHeight: wifiColumn.height
|
||||
clip: true
|
||||
|
||||
|
||||
Column {
|
||||
id: wifiColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 200
|
||||
@@ -221,7 +397,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Repeater {
|
||||
model: sortedNetworks
|
||||
|
||||
@@ -239,20 +415,20 @@ Rectangle {
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
|
||||
border.color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 0
|
||||
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
let strength = modelData.signal || 0
|
||||
@@ -264,11 +440,11 @@ Rectangle {
|
||||
color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 200
|
||||
|
||||
|
||||
StyledText {
|
||||
text: modelData.ssid || "Unknown Network"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -282,27 +458,27 @@ Rectangle {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected" : (modelData.secured ? "Secured" : "Open")
|
||||
text: modelData.ssid === NetworkService.currentWifiSSID ? "Connected •" : (modelData.secured ? "Secured •" : "Open •")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: modelData.saved ? "• Saved" : ""
|
||||
text: modelData.saved ? "Saved" : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: "• " + modelData.signal + "%"
|
||||
text: (modelData.saved ? "• " : "") + modelData.signal + "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DankActionButton {
|
||||
id: optionsButton
|
||||
anchors.right: parent.right
|
||||
@@ -323,7 +499,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: networkMouseArea
|
||||
anchors.fill: parent
|
||||
@@ -333,7 +509,11 @@ Rectangle {
|
||||
onClicked: function(event) {
|
||||
if (modelData.ssid !== NetworkService.currentWifiSSID) {
|
||||
if (modelData.secured && !modelData.saved) {
|
||||
wifiPasswordModal.show(modelData.ssid)
|
||||
if (DMSService.apiVersion >= 7) {
|
||||
NetworkService.connectToWifi(modelData.ssid)
|
||||
} else if (PopoutService.wifiPasswordModal) {
|
||||
PopoutService.wifiPasswordModal.show(modelData.ssid)
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(modelData.ssid)
|
||||
}
|
||||
@@ -341,34 +521,34 @@ Rectangle {
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Menu {
|
||||
id: networkContextMenu
|
||||
width: 150
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
|
||||
property string currentSSID: ""
|
||||
property bool currentSecured: false
|
||||
property bool currentConnected: false
|
||||
property bool currentSaved: false
|
||||
property int currentSignal: 0
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
|
||||
MenuItem {
|
||||
text: networkContextMenu.currentConnected ? "Disconnect" : "Connect"
|
||||
height: 32
|
||||
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -376,29 +556,33 @@ Rectangle {
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
|
||||
onTriggered: {
|
||||
if (networkContextMenu.currentConnected) {
|
||||
NetworkService.disconnectWifi()
|
||||
} else {
|
||||
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
|
||||
wifiPasswordModal.show(networkContextMenu.currentSSID)
|
||||
if (DMSService.apiVersion >= 7) {
|
||||
NetworkService.connectToWifi(networkContextMenu.currentSSID)
|
||||
} else if (PopoutService.wifiPasswordModal) {
|
||||
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID)
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(networkContextMenu.currentSSID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Network Info")
|
||||
height: 32
|
||||
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -406,23 +590,23 @@ Rectangle {
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
|
||||
onTriggered: {
|
||||
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID)
|
||||
networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Forget Network")
|
||||
height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0
|
||||
visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected
|
||||
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -430,25 +614,23 @@ Rectangle {
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
|
||||
onTriggered: {
|
||||
NetworkService.forgetWifiNetwork(networkContextMenu.currentSSID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WifiPasswordModal {
|
||||
id: wifiPasswordModal
|
||||
}
|
||||
|
||||
NetworkInfoModal {
|
||||
id: networkInfoModal
|
||||
}
|
||||
|
||||
|
||||
NetworkWiredInfoModal {
|
||||
id: networkWiredInfoModal
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,8 @@ QtObject {
|
||||
"icon": "brightness_6",
|
||||
"type": "slider",
|
||||
"enabled": DisplayService.brightnessAvailable,
|
||||
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined
|
||||
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined,
|
||||
"allowMultiple": true
|
||||
}, {
|
||||
"id": "inputVolumeSlider",
|
||||
"text": "Input Volume Slider",
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
|
||||
CompoundPill {
|
||||
id: root
|
||||
|
||||
property var primaryDevice: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) {
|
||||
return null
|
||||
}
|
||||
|
||||
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
|
||||
for (let device of devices) {
|
||||
if (device && device.connected) {
|
||||
return device
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
iconName: {
|
||||
if (!BluetoothService.available) {
|
||||
return "bluetooth_disabled"
|
||||
}
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
|
||||
return "bluetooth_disabled"
|
||||
}
|
||||
return "bluetooth"
|
||||
}
|
||||
|
||||
isActive: !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled)
|
||||
showExpandArea: BluetoothService.available
|
||||
|
||||
primaryText: {
|
||||
if (!BluetoothService.available) {
|
||||
return "Bluetooth"
|
||||
}
|
||||
if (!BluetoothService.adapter) {
|
||||
return "No adapter"
|
||||
}
|
||||
if (!BluetoothService.adapter.enabled) {
|
||||
return "Disabled"
|
||||
}
|
||||
return "Enabled"
|
||||
}
|
||||
|
||||
secondaryText: {
|
||||
if (!BluetoothService.available) {
|
||||
return "No adapters"
|
||||
}
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
|
||||
return "Off"
|
||||
}
|
||||
if (primaryDevice) {
|
||||
return primaryDevice.name || primaryDevice.alias || primaryDevice.deviceName || "Connected Device"
|
||||
}
|
||||
return "No devices"
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
if (BluetoothService.available && BluetoothService.adapter) {
|
||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,49 @@ import qs.Widgets
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property string deviceName: ""
|
||||
property string instanceId: ""
|
||||
|
||||
signal iconClicked()
|
||||
|
||||
height: 40
|
||||
spacing: 0
|
||||
|
||||
property string targetDeviceName: {
|
||||
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (deviceName && deviceName.length > 0) {
|
||||
const found = DisplayService.devices.find(dev => dev.name === deviceName)
|
||||
return found ? found.name : ""
|
||||
}
|
||||
|
||||
const currentDeviceName = DisplayService.currentDevice
|
||||
if (currentDeviceName) {
|
||||
const found = DisplayService.devices.find(dev => dev.name === currentDeviceName)
|
||||
return found ? found.name : ""
|
||||
}
|
||||
|
||||
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
|
||||
}
|
||||
|
||||
property var targetDevice: {
|
||||
if (!targetDeviceName || !DisplayService.devices) {
|
||||
return null
|
||||
}
|
||||
|
||||
return DisplayService.devices.find(dev => dev.name === targetDeviceName) || null
|
||||
}
|
||||
|
||||
property real targetBrightness: {
|
||||
if (!targetDeviceName) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return DisplayService.getDeviceBrightness(targetDeviceName)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
@@ -24,23 +64,18 @@ Row {
|
||||
id: iconArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
cursorShape: DisplayService.devices && DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
onClicked: function(event) {
|
||||
if (DisplayService.devices.length > 1) {
|
||||
if (deviceMenu.visible) {
|
||||
deviceMenu.close()
|
||||
} else {
|
||||
deviceMenu.popup(iconArea, 0, iconArea.height + Theme.spacingXS)
|
||||
}
|
||||
event.accepted = true
|
||||
onClicked: {
|
||||
if (DisplayService.devices && DisplayService.devices.length > 1) {
|
||||
root.iconClicked()
|
||||
}
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
tooltipLoader.active = true
|
||||
if (tooltipLoader.item) {
|
||||
const tooltipText = DisplayService.currentDevice ? "bl device: " + DisplayService.currentDevice : "Backlight Control"
|
||||
const tooltipText = targetDevice ? "bl device: " + targetDevice.name : "Backlight Control"
|
||||
const p = iconArea.mapToItem(null, iconArea.width / 2, 0)
|
||||
tooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
|
||||
}
|
||||
@@ -56,15 +91,23 @@ Row {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!DisplayService.brightnessAvailable) return "brightness_low"
|
||||
if (!DisplayService.brightnessAvailable || !targetDevice) {
|
||||
return "brightness_low"
|
||||
}
|
||||
|
||||
let brightness = DisplayService.brightnessLevel
|
||||
if (brightness <= 33) return "brightness_low"
|
||||
if (brightness <= 66) return "brightness_medium"
|
||||
return "brightness_high"
|
||||
if (targetDevice.class === "backlight" || targetDevice.class === "ddc") {
|
||||
const brightness = targetBrightness
|
||||
if (brightness <= 33) return "brightness_low"
|
||||
if (brightness <= 66) return "brightness_medium"
|
||||
return "brightness_high"
|
||||
} else if (targetDevice.name.includes("kbd")) {
|
||||
return "keyboard"
|
||||
} else {
|
||||
return "lightbulb"
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: DisplayService.brightnessAvailable && DisplayService.brightnessLevel > 0 ? Theme.primary : Theme.surfaceText
|
||||
color: DisplayService.brightnessAvailable && targetDevice && targetBrightness > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,88 +115,19 @@ Row {
|
||||
DankSlider {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: DisplayService.brightnessAvailable
|
||||
enabled: DisplayService.brightnessAvailable && targetDeviceName.length > 0
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
value: {
|
||||
let level = DisplayService.brightnessLevel
|
||||
if (level > 100) {
|
||||
let deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
if (deviceInfo && deviceInfo.max > 0) {
|
||||
return Math.round((level / deviceInfo.max) * 100)
|
||||
}
|
||||
return 50
|
||||
}
|
||||
return level
|
||||
}
|
||||
value: targetBrightness
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
DisplayService.setBrightness(newValue)
|
||||
if (DisplayService.brightnessAvailable && targetDeviceName) {
|
||||
DisplayService.setBrightness(newValue, targetDeviceName)
|
||||
}
|
||||
}
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
trackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: deviceMenu
|
||||
width: 200
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
model: DisplayService.devices
|
||||
delegate: MenuItem {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property string deviceName: modelData.name || ""
|
||||
property string deviceClass: modelData.class || ""
|
||||
|
||||
text: deviceName
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
height: 40
|
||||
|
||||
indicator: Rectangle {
|
||||
visible: DisplayService.currentDevice === parent.deviceName
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
width: 4
|
||||
height: parent.height - Theme.spacingS * 2
|
||||
radius: 2
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font: parent.font
|
||||
color: DisplayService.currentDevice === parent.deviceName ? Theme.primary : Theme.surfaceText
|
||||
leftPadding: Theme.spacingL
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
DisplayService.setCurrentDevice(deviceName, true)
|
||||
deviceMenu.close()
|
||||
}
|
||||
}
|
||||
onObjectAdded: (index, object) => deviceMenu.insertItem(index, object)
|
||||
onObjectRemoved: (index, object) => deviceMenu.removeItem(object)
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tooltipLoader
|
||||
active: false
|
||||
|
||||
@@ -14,7 +14,7 @@ Rectangle {
|
||||
property real maximumValue: 1.0
|
||||
property real minimumValue: 0.0
|
||||
property bool enabled: true
|
||||
|
||||
|
||||
signal sliderValueChanged(real value)
|
||||
|
||||
width: parent ? parent.width : 200
|
||||
|
||||
@@ -15,6 +15,11 @@ function addWidget(widgetId) {
|
||||
widget.mountPath = "/"
|
||||
}
|
||||
|
||||
if (widgetId === "brightnessSlider") {
|
||||
widget.instanceId = generateUniqueId()
|
||||
widget.deviceName = ""
|
||||
}
|
||||
|
||||
widgets.push(widget)
|
||||
SettingsData.setControlCenterWidgets(widgets)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -16,6 +18,33 @@ Item {
|
||||
anchors.topMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "bottom" ? barWindow._wingR : 0)
|
||||
anchors.bottomMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "top" ? barWindow._wingR : 0)
|
||||
|
||||
readonly property real dpr: {
|
||||
if (CompositorService.isNiri && barWindow.screen) {
|
||||
const niriScale = NiriService.displayScales[barWindow.screen.name]
|
||||
if (niriScale !== undefined) return niriScale
|
||||
}
|
||||
if (CompositorService.isHyprland && barWindow.screen) {
|
||||
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === barWindow.screen.name)
|
||||
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
|
||||
}
|
||||
return barWindow.screen?.devicePixelRatio || 1
|
||||
}
|
||||
|
||||
function requestRepaint() {
|
||||
debounceTimer.restart()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: debounceTimer
|
||||
interval: 50
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
barShape.requestPaint()
|
||||
barTint.requestPaint()
|
||||
barBorder.requestPaint()
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: barShape
|
||||
anchors.fill: parent
|
||||
@@ -23,46 +52,48 @@ Item {
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
renderStrategy: Canvas.Cooperative
|
||||
|
||||
readonly property real correctWidth: root.width
|
||||
readonly property real correctHeight: root.height
|
||||
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight))
|
||||
readonly property real correctWidth: Theme.px(root.width, dpr)
|
||||
readonly property real correctHeight: Theme.px(root.height, dpr)
|
||||
canvasSize: Qt.size(correctWidth, correctHeight)
|
||||
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? Theme.px(barWindow._wingR, dpr) : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr)
|
||||
|
||||
onWingChanged: requestPaint()
|
||||
onRtChanged: requestPaint()
|
||||
onCorrectWidthChanged: requestPaint()
|
||||
onCorrectHeightChanged: requestPaint()
|
||||
onVisibleChanged: if (visible) requestPaint()
|
||||
Component.onCompleted: requestPaint()
|
||||
onWingChanged: root.requestRepaint()
|
||||
onRtChanged: root.requestRepaint()
|
||||
onCorrectWidthChanged: root.requestRepaint()
|
||||
onCorrectHeightChanged: root.requestRepaint()
|
||||
onVisibleChanged: if (visible) root.requestRepaint()
|
||||
Component.onCompleted: root.requestRepaint()
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onDprChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: barWindow
|
||||
function on_BgColorChanged() { barShape.requestPaint() }
|
||||
function on_DprChanged() { barShape.requestPaint() }
|
||||
function on_BgColorChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onIsLightModeChanged() { barShape.requestPaint() }
|
||||
function onIsLightModeChanged() { root.requestRepaint() }
|
||||
function onSurfaceContainerChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const scale = barWindow._dpr
|
||||
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth)
|
||||
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight)
|
||||
const R = barWindow.px(wing)
|
||||
const RT = barWindow.px(rt)
|
||||
const W = barWindow.isVertical ? correctHeight : correctWidth
|
||||
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
|
||||
const R = wing
|
||||
const RT = rt
|
||||
const H = H_raw - (R > 0 ? R : 0)
|
||||
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
|
||||
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
|
||||
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
|
||||
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
|
||||
|
||||
ctx.scale(scale, scale)
|
||||
|
||||
function drawTopPath() {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(RT, 0)
|
||||
@@ -118,48 +149,50 @@ Item {
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
renderStrategy: Canvas.Cooperative
|
||||
|
||||
readonly property real correctWidth: root.width
|
||||
readonly property real correctHeight: root.height
|
||||
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight))
|
||||
readonly property real correctWidth: Theme.px(root.width, dpr)
|
||||
readonly property real correctHeight: Theme.px(root.height, dpr)
|
||||
canvasSize: Qt.size(correctWidth, correctHeight)
|
||||
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? Theme.px(barWindow._wingR, dpr) : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr)
|
||||
property real alphaTint: (barWindow._bgColor?.a ?? 1) < 0.99 ? (Theme.stateLayerOpacity ?? 0) : 0
|
||||
|
||||
onWingChanged: requestPaint()
|
||||
onRtChanged: requestPaint()
|
||||
onAlphaTintChanged: requestPaint()
|
||||
onCorrectWidthChanged: requestPaint()
|
||||
onCorrectHeightChanged: requestPaint()
|
||||
onVisibleChanged: if (visible) requestPaint()
|
||||
Component.onCompleted: requestPaint()
|
||||
onWingChanged: root.requestRepaint()
|
||||
onRtChanged: root.requestRepaint()
|
||||
onAlphaTintChanged: root.requestRepaint()
|
||||
onCorrectWidthChanged: root.requestRepaint()
|
||||
onCorrectHeightChanged: root.requestRepaint()
|
||||
onVisibleChanged: if (visible) root.requestRepaint()
|
||||
Component.onCompleted: root.requestRepaint()
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onDprChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: barWindow
|
||||
function on_BgColorChanged() { barTint.requestPaint() }
|
||||
function on_DprChanged() { barTint.requestPaint() }
|
||||
function on_BgColorChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onIsLightModeChanged() { barTint.requestPaint() }
|
||||
function onIsLightModeChanged() { root.requestRepaint() }
|
||||
function onSurfaceChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const scale = barWindow._dpr
|
||||
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth)
|
||||
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight)
|
||||
const R = barWindow.px(wing)
|
||||
const RT = barWindow.px(rt)
|
||||
const W = barWindow.isVertical ? correctHeight : correctWidth
|
||||
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
|
||||
const R = wing
|
||||
const RT = rt
|
||||
const H = H_raw - (R > 0 ? R : 0)
|
||||
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
|
||||
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
|
||||
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
|
||||
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
|
||||
|
||||
ctx.scale(scale, scale)
|
||||
|
||||
function drawTopPath() {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(RT, 0)
|
||||
@@ -211,53 +244,60 @@ Item {
|
||||
Canvas {
|
||||
id: barBorder
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
visible: SettingsData.dankBarBorderEnabled
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
renderStrategy: Canvas.Cooperative
|
||||
|
||||
readonly property real correctWidth: root.width
|
||||
readonly property real correctHeight: root.height
|
||||
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight))
|
||||
readonly property real correctWidth: Theme.px(root.width, dpr)
|
||||
readonly property real correctHeight: Theme.px(root.height, dpr)
|
||||
canvasSize: Qt.size(correctWidth, correctHeight)
|
||||
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? Theme.px(barWindow._wingR, dpr) : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr)
|
||||
property bool borderEnabled: SettingsData.dankBarBorderEnabled
|
||||
|
||||
onWingChanged: requestPaint()
|
||||
onRtChanged: requestPaint()
|
||||
onBorderEnabledChanged: requestPaint()
|
||||
onCorrectWidthChanged: requestPaint()
|
||||
onCorrectHeightChanged: requestPaint()
|
||||
onVisibleChanged: if (visible) requestPaint()
|
||||
Component.onCompleted: requestPaint()
|
||||
antialiasing: rt > 0 || wing > 0
|
||||
|
||||
onWingChanged: root.requestRepaint()
|
||||
onRtChanged: root.requestRepaint()
|
||||
onBorderEnabledChanged: root.requestRepaint()
|
||||
onCorrectWidthChanged: root.requestRepaint()
|
||||
onCorrectHeightChanged: root.requestRepaint()
|
||||
onVisibleChanged: if (visible) root.requestRepaint()
|
||||
Component.onCompleted: root.requestRepaint()
|
||||
|
||||
Connections {
|
||||
target: barWindow
|
||||
function on_DprChanged() { barBorder.requestPaint() }
|
||||
target: root
|
||||
function onDprChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onSecondaryChanged() { barBorder.requestPaint() }
|
||||
function onIsLightModeChanged() { root.requestRepaint() }
|
||||
function onSurfaceTextChanged() { root.requestRepaint() }
|
||||
function onPrimaryChanged() { root.requestRepaint() }
|
||||
function onSecondaryChanged() { root.requestRepaint() }
|
||||
function onOutlineChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onDankBarSpacingChanged() { barBorder.requestPaint() }
|
||||
function onDankBarSquareCornersChanged() { barBorder.requestPaint() }
|
||||
function onCornerRadiusChanged() { barBorder.requestPaint() }
|
||||
function onDankBarBorderColorChanged() { root.requestRepaint() }
|
||||
function onDankBarBorderOpacityChanged() { root.requestRepaint() }
|
||||
function onDankBarBorderThicknessChanged() { root.requestRepaint() }
|
||||
function onDankBarSpacingChanged() { root.requestRepaint() }
|
||||
function onDankBarSquareCornersChanged() { root.requestRepaint() }
|
||||
function onDankBarTransparencyChanged() { root.requestRepaint() }
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
if (!borderEnabled) return
|
||||
|
||||
const ctx = getContext("2d")
|
||||
const scale = barWindow._dpr
|
||||
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth)
|
||||
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight)
|
||||
const R = barWindow.px(wing)
|
||||
const RT = barWindow.px(rt)
|
||||
const W = barWindow.isVertical ? correctHeight : correctWidth
|
||||
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
|
||||
const R = wing
|
||||
const RT = rt
|
||||
const H = H_raw - (R > 0 ? R : 0)
|
||||
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
|
||||
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
|
||||
@@ -267,40 +307,6 @@ Item {
|
||||
const spacing = SettingsData.dankBarSpacing
|
||||
const hasEdgeGap = spacing > 0 || RT > 0
|
||||
|
||||
ctx.scale(scale, scale)
|
||||
|
||||
function drawTopBorder() {
|
||||
ctx.beginPath()
|
||||
|
||||
if (!hasEdgeGap) {
|
||||
ctx.moveTo(0, H)
|
||||
ctx.lineTo(W, H)
|
||||
} else {
|
||||
ctx.moveTo(RT, 0)
|
||||
ctx.lineTo(W - RT, 0)
|
||||
ctx.arcTo(W, 0, W, RT, RT)
|
||||
ctx.lineTo(W, H)
|
||||
|
||||
if (R > 0) {
|
||||
ctx.lineTo(W, H + R)
|
||||
ctx.arc(W - R, H + R, R, 0, -Math.PI / 2, true)
|
||||
ctx.lineTo(R, H)
|
||||
ctx.arc(R, H + R, R, -Math.PI / 2, -Math.PI, true)
|
||||
ctx.lineTo(0, H + R)
|
||||
} else {
|
||||
ctx.lineTo(W, H - RT)
|
||||
ctx.arcTo(W, H, W - RT, H, RT)
|
||||
ctx.lineTo(RT, H)
|
||||
ctx.arcTo(0, H, 0, H - RT, RT)
|
||||
}
|
||||
|
||||
ctx.lineTo(0, RT)
|
||||
ctx.arcTo(0, 0, RT, 0, RT)
|
||||
}
|
||||
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, W, H_raw)
|
||||
|
||||
@@ -316,12 +322,85 @@ Item {
|
||||
ctx.rotate(Math.PI / 2)
|
||||
}
|
||||
|
||||
const uiThickness = Math.max(1, SettingsData.dankBarBorderThickness ?? 1)
|
||||
const devThickness = Math.max(1, Math.round(Theme.px(uiThickness, dpr)))
|
||||
|
||||
const key = SettingsData.dankBarBorderColor || "surfaceText"
|
||||
const base = (key === "surfaceText") ? Theme.surfaceText
|
||||
: (key === "primary") ? Theme.primary
|
||||
: Theme.secondary
|
||||
const color = Theme.withAlpha(base, SettingsData.dankBarBorderOpacity ?? 1.0)
|
||||
|
||||
ctx.globalCompositeOperation = "source-over"
|
||||
ctx.fillStyle = color
|
||||
|
||||
function drawTopBorder() {
|
||||
if (!hasEdgeGap) {
|
||||
ctx.beginPath()
|
||||
ctx.rect(0, H - devThickness, W, devThickness)
|
||||
ctx.fill()
|
||||
} else {
|
||||
const thk = devThickness
|
||||
const RTi = Math.max(0, RT - thk)
|
||||
const Ri = Math.max(0, R - thk)
|
||||
|
||||
ctx.beginPath()
|
||||
|
||||
if (R > 0 && Ri > 0) {
|
||||
ctx.moveTo(RT, 0)
|
||||
ctx.lineTo(W - RT, 0)
|
||||
ctx.arcTo(W, 0, W, RT, RT)
|
||||
ctx.lineTo(W, H)
|
||||
ctx.lineTo(W, H + R)
|
||||
ctx.arc(W - R, H + R, R, 0, -Math.PI / 2, true)
|
||||
ctx.lineTo(R, H)
|
||||
ctx.arc(R, H + R, R, -Math.PI / 2, -Math.PI, true)
|
||||
ctx.lineTo(0, H + R)
|
||||
ctx.lineTo(0, RT)
|
||||
ctx.arcTo(0, 0, RT, 0, RT)
|
||||
ctx.closePath()
|
||||
|
||||
ctx.moveTo(RT, thk)
|
||||
ctx.arcTo(thk, thk, thk, RT, RTi)
|
||||
ctx.lineTo(thk, H + R)
|
||||
ctx.arc(R, H + R, Ri, -Math.PI, -Math.PI / 2, false)
|
||||
ctx.lineTo(W - R, H + thk)
|
||||
ctx.arc(W - R, H + R, Ri, -Math.PI / 2, 0, false)
|
||||
ctx.lineTo(W - thk, H + R)
|
||||
ctx.lineTo(W - thk, RT)
|
||||
ctx.arcTo(W - thk, thk, W - RT, thk, RTi)
|
||||
ctx.lineTo(RT, thk)
|
||||
ctx.closePath()
|
||||
} else {
|
||||
ctx.moveTo(RT, 0)
|
||||
ctx.lineTo(W - RT, 0)
|
||||
ctx.arcTo(W, 0, W, RT, RT)
|
||||
ctx.lineTo(W, H - RT)
|
||||
ctx.arcTo(W, H, W - RT, H, RT)
|
||||
ctx.lineTo(RT, H)
|
||||
ctx.arcTo(0, H, 0, H - RT, RT)
|
||||
ctx.lineTo(0, RT)
|
||||
ctx.arcTo(0, 0, RT, 0, RT)
|
||||
ctx.closePath()
|
||||
|
||||
ctx.moveTo(RT, thk)
|
||||
ctx.arcTo(thk, thk, thk, RT, RTi)
|
||||
ctx.lineTo(thk, H - RT)
|
||||
ctx.arcTo(thk, H - thk, RT, H - thk, RTi)
|
||||
ctx.lineTo(W - RT, H - thk)
|
||||
ctx.arcTo(W - thk, H - thk, W - thk, H - RT, RTi)
|
||||
ctx.lineTo(W - thk, RT)
|
||||
ctx.arcTo(W - thk, thk, W - RT, thk, RTi)
|
||||
ctx.lineTo(RT, thk)
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
ctx.fill("evenodd")
|
||||
}
|
||||
}
|
||||
|
||||
drawTopBorder()
|
||||
ctx.restore()
|
||||
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeStyle = Theme.secondary
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -364,6 +364,210 @@ DankPopout {
|
||||
}
|
||||
}
|
||||
|
||||
// Individual battery details for multiple batteries
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: !BatteryService.usePreferred && BatteryService.batteries.length > 1
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Individual Batteries")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: BatteryService.batteries
|
||||
|
||||
delegate: StyledRect {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: batteryColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
id: batteryColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
// Top row: name and percentage
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - percentText.width - chargingIcon.width - Theme.spacingM * 2
|
||||
|
||||
StyledText {
|
||||
text: modelData.model || `Battery ${index + 1}`
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.nativePath
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
elide: Text.ElideMiddle
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: percentText
|
||||
text: `${Math.round(100 * modelData.percentage)}%`
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: chargingIcon
|
||||
name: modelData.state === UPowerDeviceState.Charging ? "bolt" : ""
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: modelData.state === UPowerDeviceState.Charging
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom row: Health, Capacity and Time
|
||||
Flow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledRect {
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Health")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
|
||||
return "N/A"
|
||||
return `${Math.round(modelData.healthPercentage)}%`
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
|
||||
return Theme.surfaceText
|
||||
return modelData.healthPercentage < 80 ? Theme.error : Theme.surfaceText
|
||||
}
|
||||
font.weight: Font.Bold
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Capacity")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.energyCapacity > 0 ? `${modelData.energyCapacity.toFixed(1)}` : "N/A"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: modelData.state === UPowerDeviceState.Charging
|
||||
? I18n.tr("To Full")
|
||||
: modelData.state === UPowerDeviceState.Discharging
|
||||
? I18n.tr("Left") : ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const time = modelData.state === UPowerDeviceState.Charging
|
||||
? modelData.timeToFull
|
||||
: modelData.state === UPowerDeviceState.Discharging && BatteryService.changeRate > 0
|
||||
? (3600 * modelData.energy) / BatteryService.changeRate : 0
|
||||
|
||||
if (!time || time <= 0 || time > 86400)
|
||||
return "N/A"
|
||||
|
||||
const hours = Math.floor(time / 3600)
|
||||
const minutes = Math.floor((time % 3600) / 60)
|
||||
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||
property int currentProfileIndex: {
|
||||
|
||||
@@ -75,10 +75,7 @@ Loader {
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
contentItemReady(item)
|
||||
if (widgetId === "spacer") {
|
||||
item.spacerSize = Qt.binding(() => spacerSize)
|
||||
}
|
||||
contentItemReady(item)
|
||||
if (axis && "isVertical" in item) {
|
||||
try {
|
||||
item.isVertical = axis.isVertical
|
||||
@@ -166,4 +163,4 @@ Loader {
|
||||
function getWidgetEnabled(enabled) {
|
||||
return enabled !== false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,126 +1,114 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: battery
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool batteryPopupVisible: false
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
property var popoutTarget: null
|
||||
|
||||
signal toggleBatteryPopup()
|
||||
|
||||
width: isVertical ? widgetThickness : (batteryContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (batteryColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = batteryArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
visible: true
|
||||
|
||||
Column {
|
||||
id: batteryColumn
|
||||
visible: battery.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: battery.isVerticalOrientation ? (battery.widgetThickness - battery.horizontalPadding * 2) : batteryContent.implicitWidth
|
||||
implicitHeight: battery.isVerticalOrientation ? batteryColumn.implicitHeight : (battery.widgetThickness - battery.horizontalPadding * 2)
|
||||
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return Theme.surfaceText
|
||||
Column {
|
||||
id: batteryColumn
|
||||
visible: battery.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: Theme.barIconSize(battery.barThickness)
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return Theme.surfaceText
|
||||
}
|
||||
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
}
|
||||
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
||||
return Theme.primary
|
||||
}
|
||||
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error
|
||||
StyledText {
|
||||
text: BatteryService.batteryLevel.toString()
|
||||
font.pixelSize: Theme.barTextSize(battery.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
||||
return Theme.primary
|
||||
}
|
||||
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryLevel.toString()
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: BatteryService.batteryAvailable
|
||||
}
|
||||
}
|
||||
Row {
|
||||
id: batteryContent
|
||||
visible: !battery.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: SettingsData.dankBarNoBackground ? 1 : 2
|
||||
|
||||
Row {
|
||||
id: batteryContent
|
||||
visible: !battery.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: SettingsData.dankBarNoBackground ? 1 : 2
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: Theme.barIconSize(battery.barThickness, -4)
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return Theme.surfaceText;
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error;
|
||||
}
|
||||
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
||||
return Theme.primary;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
return Theme.error;
|
||||
StyledText {
|
||||
text: `${BatteryService.batteryLevel}%`
|
||||
font.pixelSize: Theme.barTextSize(battery.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
||||
return Theme.primary;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `${BatteryService.batteryLevel}%`
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: batteryArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
if (popoutTarget && popoutTarget.setTriggerPosition) {
|
||||
const globalPos = battery.visualContent.mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, battery.visualWidth)
|
||||
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
}
|
||||
toggleBatteryPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,25 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isActive: false
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property string section: "right"
|
||||
property var clipboardHistoryModal: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
signal clicked()
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
|
||||
width: widgetThickness
|
||||
height: widgetThickness
|
||||
|
||||
MouseArea {
|
||||
id: clipboardArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clipboardContent
|
||||
|
||||
anchors.fill: parent
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent"
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "content_paste"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
const baseColor = clipboardArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "content_paste"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,247 +1,254 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool compactMode: false
|
||||
property string section: "center"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
|
||||
|
||||
signal clockClicked
|
||||
|
||||
width: isVertical ? widgetThickness : (clockRow.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (clockColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = clockMouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
|
||||
Column {
|
||||
id: clockColumn
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: -2
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (SettingsData.use24HourClock) {
|
||||
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(0)
|
||||
} else {
|
||||
const hours = systemClock?.date?.getHours()
|
||||
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
|
||||
return String(display).padStart(2, '0').charAt(0)
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (SettingsData.use24HourClock) {
|
||||
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(1)
|
||||
} else {
|
||||
const hours = systemClock?.date?.getHours()
|
||||
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
|
||||
return String(display).padStart(2, '0').charAt(1)
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
width: 12
|
||||
height: Theme.spacingM
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : clockRow.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? clockColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
Rectangle {
|
||||
width: 12
|
||||
height: 1
|
||||
color: Theme.outlineButton
|
||||
Column {
|
||||
id: clockColumn
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
spacing: -2
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const locale = Qt.locale()
|
||||
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
|
||||
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
|
||||
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
|
||||
return value.charAt(0)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Light
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
StyledText {
|
||||
text: {
|
||||
if (SettingsData.use24HourClock) {
|
||||
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(0)
|
||||
} else {
|
||||
const hours = systemClock?.date?.getHours()
|
||||
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
|
||||
return String(display).padStart(2, '0').charAt(0)
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const locale = Qt.locale()
|
||||
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
|
||||
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
|
||||
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
|
||||
return value.charAt(1)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Light
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const locale = Qt.locale()
|
||||
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
|
||||
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
|
||||
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
|
||||
return value.charAt(0)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Light
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const locale = Qt.locale()
|
||||
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
|
||||
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
|
||||
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
|
||||
return value.charAt(1)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Light
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: clockRow
|
||||
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const format = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP"
|
||||
return systemClock?.date?.toLocaleTimeString(Qt.locale(), format)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outlineButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !SettingsData.clockCompactMode
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) {
|
||||
return systemClock?.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat)
|
||||
StyledText {
|
||||
text: {
|
||||
if (SettingsData.use24HourClock) {
|
||||
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(1)
|
||||
} else {
|
||||
const hours = systemClock?.date?.getHours()
|
||||
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours
|
||||
return String(display).padStart(2, '0').charAt(1)
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d")
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !SettingsData.clockCompactMode
|
||||
}
|
||||
}
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
precision: SystemClock.Seconds
|
||||
StyledText {
|
||||
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: SettingsData.showSeconds
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 12
|
||||
height: Theme.spacingM
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Rectangle {
|
||||
width: 12
|
||||
height: 1
|
||||
color: Theme.outlineButton
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const locale = Qt.locale()
|
||||
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
|
||||
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
|
||||
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
|
||||
return value.charAt(0)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Light
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const locale = Qt.locale()
|
||||
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
|
||||
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
|
||||
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0')
|
||||
return value.charAt(1)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Light
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const locale = Qt.locale()
|
||||
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
|
||||
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
|
||||
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
|
||||
return value.charAt(0)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Light
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const locale = Qt.locale()
|
||||
const dateFormatShort = locale.dateFormat(Locale.ShortFormat)
|
||||
const dayFirst = dateFormatShort.indexOf('d') < dateFormatShort.indexOf('M')
|
||||
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0')
|
||||
return value.charAt(1)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Light
|
||||
width: 9
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: clockRow
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
return systemClock?.date?.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat())
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outlineButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !SettingsData.clockCompactMode
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) {
|
||||
return systemClock?.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat)
|
||||
}
|
||||
|
||||
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d")
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !SettingsData.clockCompactMode
|
||||
}
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
precision: SystemClock.Seconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clockMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
const currentScreen = root.parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, width)
|
||||
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
|
||||
}
|
||||
root.clockClicked()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,54 +1,36 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool isActive: false
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
signal clicked()
|
||||
signal colorPickerRequested()
|
||||
|
||||
width: isVertical ? widgetThickness : (colorPickerIcon.width + horizontalPadding * 2)
|
||||
height: isVertical ? (colorPickerIcon.height + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "palette"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
color: root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
const baseColor = colorPickerArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: colorPickerIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
name: "palette"
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: colorPickerArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: colorPickerArea
|
||||
|
||||
z: 1
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
root.colorPickerRequested();
|
||||
root.colorPickerRequested()
|
||||
}
|
||||
}
|
||||
|
||||
signal colorPickerRequested()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,268 +1,245 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool isActive: false
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property var popoutTarget: null
|
||||
property var widgetData: null
|
||||
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
|
||||
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
|
||||
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
signal clicked()
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : controlIndicators.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? controlColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
width: isVertical ? widgetThickness : (controlIndicators.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (controlColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
Column {
|
||||
id: controlColumn
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
const baseColor = controlCenterArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
DankIcon {
|
||||
name: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return "sync"
|
||||
}
|
||||
|
||||
Column {
|
||||
id: controlColumn
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return "lan"
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return "sync"
|
||||
return NetworkService.wifiSignalIcon
|
||||
}
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return Theme.primary
|
||||
}
|
||||
|
||||
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
}
|
||||
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return "lan"
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||
}
|
||||
|
||||
return NetworkService.wifiSignalIcon
|
||||
}
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return Theme.primary
|
||||
}
|
||||
Rectangle {
|
||||
width: audioIconV.implicitWidth + 4
|
||||
height: audioIconV.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showAudioIcon
|
||||
|
||||
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
}
|
||||
DankIcon {
|
||||
id: audioIconV
|
||||
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: audioIconV.implicitWidth + 4
|
||||
height: audioIconV.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showAudioIcon
|
||||
|
||||
DankIcon {
|
||||
id: audioIconV
|
||||
|
||||
name: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
|
||||
return "volume_off"
|
||||
} else if (AudioService.sink.audio.volume * 100 < 33) {
|
||||
return "volume_down"
|
||||
} else {
|
||||
name: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
|
||||
return "volume_off"
|
||||
} else if (AudioService.sink.audio.volume * 100 < 33) {
|
||||
return "volume_down"
|
||||
} else {
|
||||
return "volume_up"
|
||||
}
|
||||
}
|
||||
return "volume_up"
|
||||
}
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
return "volume_up"
|
||||
}
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function(wheelEvent) {
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
|
||||
let newVolume
|
||||
if (delta > 0) {
|
||||
newVolume = Math.min(100, currentVolume + 5)
|
||||
} else {
|
||||
newVolume = Math.max(0, currentVolume - 5)
|
||||
}
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false
|
||||
AudioService.sink.audio.volume = newVolume / 100
|
||||
AudioService.volumeChanged()
|
||||
}
|
||||
wheelEvent.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "settings"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: controlIndicators
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
id: networkIcon
|
||||
|
||||
name: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return "sync";
|
||||
}
|
||||
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return "lan";
|
||||
}
|
||||
|
||||
return NetworkService.wifiSignalIcon;
|
||||
}
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return Theme.primary;
|
||||
}
|
||||
|
||||
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
|
||||
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: bluetoothIcon
|
||||
|
||||
name: "bluetooth"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: audioIcon.implicitWidth + 4
|
||||
height: audioIcon.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showAudioIcon
|
||||
|
||||
DankIcon {
|
||||
id: audioIcon
|
||||
|
||||
name: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
|
||||
return "volume_off";
|
||||
} else if (AudioService.sink.audio.volume * 100 < 33) {
|
||||
return "volume_down";
|
||||
} else {
|
||||
return "volume_up";
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function(wheelEvent) {
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
|
||||
let newVolume
|
||||
if (delta > 0) {
|
||||
newVolume = Math.min(100, currentVolume + 5)
|
||||
} else {
|
||||
newVolume = Math.max(0, currentVolume - 5)
|
||||
}
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false
|
||||
AudioService.sink.audio.volume = newVolume / 100
|
||||
}
|
||||
wheelEvent.accepted = true
|
||||
}
|
||||
}
|
||||
return "volume_up";
|
||||
}
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
|
||||
DankIcon {
|
||||
name: "settings"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: root.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: controlIndicators
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
MouseArea {
|
||||
id: audioWheelArea
|
||||
DankIcon {
|
||||
id: networkIcon
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function(wheelEvent) {
|
||||
let delta = wheelEvent.angleDelta.y;
|
||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
|
||||
let newVolume;
|
||||
if (delta > 0) {
|
||||
newVolume = Math.min(100, currentVolume + 5);
|
||||
} else {
|
||||
newVolume = Math.max(0, currentVolume - 5);
|
||||
name: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return "sync";
|
||||
}
|
||||
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return "lan";
|
||||
}
|
||||
|
||||
return NetworkService.wifiSignalIcon;
|
||||
}
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
AudioService.volumeChanged();
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return Theme.primary;
|
||||
}
|
||||
|
||||
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
|
||||
}
|
||||
wheelEvent.accepted = true;
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: bluetoothIcon
|
||||
|
||||
name: "bluetooth"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: audioIcon.implicitWidth + 4
|
||||
height: audioIcon.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showAudioIcon
|
||||
|
||||
DankIcon {
|
||||
id: audioIcon
|
||||
|
||||
name: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
|
||||
return "volume_off";
|
||||
} else if (AudioService.sink.audio.volume * 100 < 33) {
|
||||
return "volume_down";
|
||||
} else {
|
||||
return "volume_up";
|
||||
}
|
||||
}
|
||||
return "volume_up";
|
||||
}
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: audioWheelArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function(wheelEvent) {
|
||||
let delta = wheelEvent.angleDelta.y;
|
||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
|
||||
let newVolume;
|
||||
if (delta > 0) {
|
||||
newVolume = Math.min(100, currentVolume + 5);
|
||||
} else {
|
||||
newVolume = Math.max(0, currentVolume - 5);
|
||||
}
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "mic"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: false
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "settings"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: root.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "mic"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: false // TODO: Add mic detection
|
||||
}
|
||||
|
||||
// Fallback settings icon when all other icons are hidden
|
||||
DankIcon {
|
||||
name: "settings"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: controlCenterArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
if (popoutTarget && popoutTarget.setTriggerPosition) {
|
||||
const globalPos = root.visualContent.mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
|
||||
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
}
|
||||
root.clicked();
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,37 +1,20 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
property var popoutTarget: null
|
||||
property var widgetData: null
|
||||
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
width: isVertical ? widgetThickness : (cpuContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (cpuColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = cpuArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["cpu"]);
|
||||
}
|
||||
@@ -39,120 +22,123 @@ Rectangle {
|
||||
DgopService.removeRef(["cpu"]);
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cpuArea
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuContent.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? cpuColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
Column {
|
||||
id: cpuColumn
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (DgopService.cpuUsage > 80) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.cpuUsage > 60) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return DgopService.cpuUsage.toFixed(0);
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: cpuContent
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (DgopService.cpuUsage > 80) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.cpuUsage > 60) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
|
||||
return "--%";
|
||||
}
|
||||
|
||||
return DgopService.cpuUsage.toFixed(0) + "%";
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: cpuBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100%"
|
||||
}
|
||||
|
||||
width: root.minimumWidth ? Math.max(cpuBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
if (popoutTarget && popoutTarget.setTriggerPosition) {
|
||||
const globalPos = root.visualContent.mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
|
||||
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
}
|
||||
DgopService.setSortBy("cpu");
|
||||
if (root.toggleProcessList) {
|
||||
root.toggleProcessList();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: cpuColumn
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (DgopService.cpuUsage > 80) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.cpuUsage > 60) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return DgopService.cpuUsage.toFixed(0);
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: cpuContent
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (DgopService.cpuUsage > 80) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.cpuUsage > 60) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
|
||||
return "--%";
|
||||
}
|
||||
|
||||
return DgopService.cpuUsage.toFixed(0) + "%";
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: cpuBaseline
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100%"
|
||||
}
|
||||
|
||||
width: root.minimumWidth ? Math.max(cpuBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,37 +1,20 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
property var popoutTarget: null
|
||||
property var widgetData: null
|
||||
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
width: isVertical ? widgetThickness : (cpuTempContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (cpuTempColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = cpuTempArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["cpu"]);
|
||||
}
|
||||
@@ -39,121 +22,123 @@ Rectangle {
|
||||
DgopService.removeRef(["cpu"]);
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cpuTempArea
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuTempContent.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? cpuTempColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
Column {
|
||||
id: cpuTempColumn
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "device_thermostat"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (DgopService.cpuTemperature > 85) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.cpuTemperature > 69) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return Math.round(DgopService.cpuTemperature).toString();
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: cpuTempContent
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "device_thermostat"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (DgopService.cpuTemperature > 85) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.cpuTemperature > 69) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
|
||||
return "--°";
|
||||
}
|
||||
|
||||
return Math.round(DgopService.cpuTemperature) + "°";
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: tempBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100°"
|
||||
}
|
||||
|
||||
width: root.minimumWidth ? Math.max(tempBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
if (popoutTarget && popoutTarget.setTriggerPosition) {
|
||||
const globalPos = root.visualContent.mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
|
||||
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
}
|
||||
DgopService.setSortBy("cpu");
|
||||
if (root.toggleProcessList) {
|
||||
root.toggleProcessList();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: cpuTempColumn
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "device_thermostat"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (DgopService.cpuTemperature > 85) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.cpuTemperature > 69) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return Math.round(DgopService.cpuTemperature).toString();
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: cpuTempContent
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "device_thermostat"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (DgopService.cpuTemperature > 85) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.cpuTemperature > 69) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
|
||||
return "--°";
|
||||
}
|
||||
|
||||
return Math.round(DgopService.cpuTemperature) + "°";
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: tempBaseline
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100°"
|
||||
}
|
||||
|
||||
width: root.minimumWidth ? Math.max(tempBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +1,35 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property var widgetData: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
property string mountPath: (widgetData && widgetData.mountPath !== undefined) ? widgetData.mountPath : "/"
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
property var selectedMount: {
|
||||
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Force re-evaluation when mountPath changes
|
||||
const currentMountPath = root.mountPath || "/"
|
||||
|
||||
// First try to find exact match
|
||||
for (let i = 0; i < DgopService.diskMounts.length; i++) {
|
||||
if (DgopService.diskMounts[i].mount === currentMountPath) {
|
||||
return DgopService.diskMounts[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to root
|
||||
for (let i = 0; i < DgopService.diskMounts.length; i++) {
|
||||
if (DgopService.diskMounts[i].mount === "/") {
|
||||
return DgopService.diskMounts[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort - first mount
|
||||
return DgopService.diskMounts[0] || null
|
||||
}
|
||||
|
||||
@@ -50,17 +41,6 @@ Rectangle {
|
||||
return parseFloat(percentStr) || 0
|
||||
}
|
||||
|
||||
width: isVertical ? widgetThickness : (diskContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (diskColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
const baseColor = Theme.widgetBaseBackgroundColor
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["diskmounts"])
|
||||
}
|
||||
@@ -70,7 +50,6 @@ Rectangle {
|
||||
|
||||
Connections {
|
||||
function onWidgetDataChanged() {
|
||||
// Force property re-evaluation by triggering change detection
|
||||
root.mountPath = Qt.binding(() => {
|
||||
return (root.widgetData && root.widgetData.mountPath !== undefined) ? root.widgetData.mountPath : "/"
|
||||
})
|
||||
@@ -82,21 +61,18 @@ Rectangle {
|
||||
|
||||
const currentMountPath = root.mountPath || "/"
|
||||
|
||||
// First try to find exact match
|
||||
for (let i = 0; i < DgopService.diskMounts.length; i++) {
|
||||
if (DgopService.diskMounts[i].mount === currentMountPath) {
|
||||
return DgopService.diskMounts[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to root
|
||||
for (let i = 0; i < DgopService.diskMounts.length; i++) {
|
||||
if (DgopService.diskMounts[i].mount === "/") {
|
||||
return DgopService.diskMounts[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort - first mount
|
||||
return DgopService.diskMounts[0] || null
|
||||
})
|
||||
}
|
||||
@@ -104,6 +80,116 @@ Rectangle {
|
||||
target: SettingsData
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : diskContent.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? diskColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
Column {
|
||||
id: diskColumn
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "storage"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (root.diskUsagePercent > 90) {
|
||||
return Theme.tempDanger
|
||||
}
|
||||
if (root.diskUsagePercent > 75) {
|
||||
return Theme.tempWarning
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
|
||||
return "--"
|
||||
}
|
||||
return root.diskUsagePercent.toFixed(0)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: diskContent
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "storage"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (root.diskUsagePercent > 90) {
|
||||
return Theme.tempDanger
|
||||
}
|
||||
if (root.diskUsagePercent > 75) {
|
||||
return Theme.tempWarning
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!root.selectedMount) {
|
||||
return "--"
|
||||
}
|
||||
return root.selectedMount.mount
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
|
||||
return "--%"
|
||||
}
|
||||
return root.diskUsagePercent.toFixed(0) + "%"
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: diskBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100%"
|
||||
}
|
||||
|
||||
width: Math.max(diskBaseline.width, paintedWidth)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tooltipLoader
|
||||
active: false
|
||||
@@ -111,11 +197,11 @@ Rectangle {
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: diskArea
|
||||
z: 1
|
||||
anchors.fill: parent
|
||||
hoverEnabled: root.isVertical
|
||||
hoverEnabled: root.isVerticalOrientation
|
||||
onEntered: {
|
||||
if (root.isVertical && root.selectedMount) {
|
||||
if (root.isVerticalOrientation && root.selectedMount) {
|
||||
tooltipLoader.active = true
|
||||
if (tooltipLoader.item) {
|
||||
const globalPos = mapToGlobal(width / 2, height / 2)
|
||||
@@ -136,107 +222,4 @@ Rectangle {
|
||||
tooltipLoader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: diskColumn
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "storage"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (root.diskUsagePercent > 90) {
|
||||
return Theme.tempDanger
|
||||
}
|
||||
if (root.diskUsagePercent > 75) {
|
||||
return Theme.tempWarning
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
|
||||
return "--"
|
||||
}
|
||||
return root.diskUsagePercent.toFixed(0)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: diskContent
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "storage"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (root.diskUsagePercent > 90) {
|
||||
return Theme.tempDanger
|
||||
}
|
||||
if (root.diskUsagePercent > 75) {
|
||||
return Theme.tempWarning
|
||||
}
|
||||
return Theme.surfaceText
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!root.selectedMount) {
|
||||
return "--"
|
||||
}
|
||||
return root.selectedMount.mount
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.diskUsagePercent === undefined || root.diskUsagePercent === null || root.diskUsagePercent === 0) {
|
||||
return "--%"
|
||||
}
|
||||
return root.diskUsagePercent.toFixed(0) + "%"
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: diskBaseline
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100%"
|
||||
}
|
||||
|
||||
width: Math.max(diskBaseline.width, paintedWidth)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,21 +4,15 @@ import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property var parentScreen
|
||||
property bool compactMode: SettingsData.focusedWindowCompactMode
|
||||
property int availableWidth: 400
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
|
||||
readonly property int baseWidth: contentRow.implicitWidth + horizontalPadding * 2
|
||||
readonly property int maxNormalWidth: 456
|
||||
readonly property int maxCompactWidth: 288
|
||||
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
|
||||
@@ -74,161 +68,160 @@ Rectangle {
|
||||
return false
|
||||
}
|
||||
|
||||
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
|
||||
const activeHyprToplevel = hyprlandToplevels.find(t => t.wayland === activeWindow)
|
||||
try {
|
||||
if (!Hyprland.toplevels) return false
|
||||
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
|
||||
const activeHyprToplevel = hyprlandToplevels.find(t => t?.wayland === activeWindow)
|
||||
|
||||
if (!activeHyprToplevel || !activeHyprToplevel.workspace) {
|
||||
if (!activeHyprToplevel || !activeHyprToplevel.workspace) {
|
||||
return false
|
||||
}
|
||||
|
||||
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id
|
||||
} catch (e) {
|
||||
console.error("FocusedApp: hasWindowsOnCurrentWorkspace error:", e)
|
||||
return false
|
||||
}
|
||||
|
||||
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id
|
||||
}
|
||||
|
||||
return activeWindow && activeWindow.title
|
||||
}
|
||||
|
||||
width: !hasWindowsOnCurrentWorkspace ? 0 : (isVertical ? widgetThickness : (compactMode ? Math.min(baseWidth, maxCompactWidth) : Math.min(baseWidth, maxNormalWidth)))
|
||||
height: !hasWindowsOnCurrentWorkspace ? 0 : (isVertical ? widgetThickness : widgetThickness)
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (!activeWindow || !activeWindow.title) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
clip: true
|
||||
visible: hasWindowsOnCurrentWorkspace
|
||||
|
||||
IconImage {
|
||||
id: appIcon
|
||||
anchors.centerIn: parent
|
||||
width: 18
|
||||
height: 18
|
||||
visible: root.isVertical && activeWindow && status === Image.Ready
|
||||
source: {
|
||||
if (!activeWindow || !activeWindow.appId) return ""
|
||||
const moddedId = Paths.moddedAppId(activeWindow.appId)
|
||||
if (moddedId.toLowerCase().includes("steam_app")) return ""
|
||||
return Quickshell.iconPath(activeDesktopEntry?.icon, true)
|
||||
}
|
||||
smooth: true
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: 18
|
||||
name: "sports_esports"
|
||||
color: Theme.surfaceText
|
||||
visible: {
|
||||
if (!root.isVertical || !activeWindow || !activeWindow.appId) return false
|
||||
const moddedId = Paths.moddedAppId(activeWindow.appId)
|
||||
return moddedId.toLowerCase().includes("steam_app")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: {
|
||||
if (!root.isVertical || !activeWindow || !activeWindow.appId) return false
|
||||
if (appIcon.status === Image.Ready) return false
|
||||
const moddedId = Paths.moddedAppId(activeWindow.appId)
|
||||
return !moddedId.toLowerCase().includes("steam_app")
|
||||
}
|
||||
text: {
|
||||
if (!activeWindow || !activeWindow.appId) return "?"
|
||||
if (activeDesktopEntry && activeDesktopEntry.name) {
|
||||
return activeDesktopEntry.name.charAt(0).toUpperCase()
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: {
|
||||
if (!root.hasWindowsOnCurrentWorkspace) return 0
|
||||
if (root.isVerticalOrientation) return root.widgetThickness - root.horizontalPadding * 2
|
||||
const baseWidth = contentRow.implicitWidth
|
||||
return compactMode ? Math.min(baseWidth, maxCompactWidth - root.horizontalPadding * 2) : Math.min(baseWidth, maxNormalWidth - root.horizontalPadding * 2)
|
||||
}
|
||||
return activeWindow.appId.charAt(0).toUpperCase()
|
||||
}
|
||||
font.pixelSize: 10
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: !root.isVertical
|
||||
|
||||
StyledText {
|
||||
id: appText
|
||||
|
||||
text: {
|
||||
if (!activeWindow || !activeWindow.appId) {
|
||||
return "";
|
||||
IconImage {
|
||||
id: appIcon
|
||||
anchors.centerIn: parent
|
||||
width: 18
|
||||
height: 18
|
||||
visible: root.isVerticalOrientation && activeWindow && status === Image.Ready
|
||||
source: {
|
||||
if (!activeWindow || !activeWindow.appId) return ""
|
||||
const moddedId = Paths.moddedAppId(activeWindow.appId)
|
||||
if (moddedId.toLowerCase().includes("steam_app")) return ""
|
||||
return Quickshell.iconPath(activeDesktopEntry?.icon, true)
|
||||
}
|
||||
|
||||
const desktopEntry = DesktopEntries.heuristicLookup(activeWindow.appId);
|
||||
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
|
||||
smooth: true
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
width: Math.min(implicitWidth, compactMode ? 80 : 180)
|
||||
visible: !compactMode && text.length > 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.outlineButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !compactMode && appText.text && titleText.text
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: titleText
|
||||
|
||||
text: {
|
||||
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
|
||||
const appName = appText.text;
|
||||
if (!title || !appName) {
|
||||
return title;
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: 18
|
||||
name: "sports_esports"
|
||||
color: Theme.surfaceText
|
||||
visible: {
|
||||
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId) return false
|
||||
const moddedId = Paths.moddedAppId(activeWindow.appId)
|
||||
return moddedId.toLowerCase().includes("steam_app")
|
||||
}
|
||||
|
||||
if (title.endsWith(" - " + appName)) {
|
||||
return title.substring(0, title.length - (" - " + appName).length);
|
||||
}
|
||||
|
||||
if (title.endsWith(appName)) {
|
||||
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
width: Math.min(implicitWidth, compactMode ? 280 : 250)
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: {
|
||||
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId) return false
|
||||
if (appIcon.status === Image.Ready) return false
|
||||
const moddedId = Paths.moddedAppId(activeWindow.appId)
|
||||
return !moddedId.toLowerCase().includes("steam_app")
|
||||
}
|
||||
text: {
|
||||
if (!activeWindow || !activeWindow.appId) return "?"
|
||||
if (activeDesktopEntry && activeDesktopEntry.name) {
|
||||
return activeDesktopEntry.name.charAt(0).toUpperCase()
|
||||
}
|
||||
return activeWindow.appId.charAt(0).toUpperCase()
|
||||
}
|
||||
font.pixelSize: 10
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: !root.isVerticalOrientation
|
||||
|
||||
StyledText {
|
||||
id: appText
|
||||
text: {
|
||||
if (!activeWindow || !activeWindow.appId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const desktopEntry = DesktopEntries.heuristicLookup(activeWindow.appId);
|
||||
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
width: Math.min(implicitWidth, compactMode ? 80 : 180)
|
||||
visible: !compactMode && text.length > 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.outlineButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !compactMode && appText.text && titleText.text
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: titleText
|
||||
text: {
|
||||
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
|
||||
const appName = appText.text;
|
||||
if (!title || !appName) {
|
||||
return title;
|
||||
}
|
||||
|
||||
if (title.endsWith(" - " + appName)) {
|
||||
return title.substring(0, title.length - (" - " + appName).length);
|
||||
}
|
||||
|
||||
if (title.endsWith(appName)) {
|
||||
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
width: Math.min(implicitWidth, compactMode ? 280 : 250)
|
||||
visible: text.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: root.isVertical
|
||||
hoverEnabled: root.isVerticalOrientation
|
||||
acceptedButtons: Qt.NoButton
|
||||
onEntered: {
|
||||
if (root.isVertical && activeWindow && activeWindow.appId && root.parentScreen) {
|
||||
if (root.isVerticalOrientation && activeWindow && activeWindow.appId && root.parentScreen) {
|
||||
tooltipLoader.active = true
|
||||
if (tooltipLoader.item) {
|
||||
const globalPos = mapToGlobal(width / 2, height / 2)
|
||||
@@ -260,14 +253,4 @@ Rectangle {
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property var popoutTarget: null
|
||||
property var widgetData: null
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex !== undefined) ? widgetData.selectedGpuIndex : 0
|
||||
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
property real displayTemp: {
|
||||
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
|
||||
return 0;
|
||||
@@ -34,7 +28,6 @@ Rectangle {
|
||||
}
|
||||
|
||||
function updateWidgetPciId(pciId) {
|
||||
// Find and update this widget's pciId in the settings
|
||||
const sections = ["left", "center", "right"];
|
||||
for (let s = 0; s < sections.length; s++) {
|
||||
const sectionId = sections[s];
|
||||
@@ -68,17 +61,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
width: isVertical ? widgetThickness : (gpuTempContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (gpuTempColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = gpuArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["gpu"]);
|
||||
if (widgetData && widgetData.pciId) {
|
||||
@@ -92,12 +74,10 @@ Rectangle {
|
||||
if (widgetData && widgetData.pciId) {
|
||||
DgopService.removeGpuPciId(widgetData.pciId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onWidgetDataChanged() {
|
||||
// Force property re-evaluation by triggering change detection
|
||||
root.selectedGpuIndex = Qt.binding(() => {
|
||||
return (root.widgetData && root.widgetData.selectedGpuIndex !== undefined) ? root.widgetData.selectedGpuIndex : 0;
|
||||
});
|
||||
@@ -106,122 +86,126 @@ Rectangle {
|
||||
target: SettingsData
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: gpuArea
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : gpuTempContent.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? gpuTempColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
Column {
|
||||
id: gpuTempColumn
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "auto_awesome_mosaic"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (root.displayTemp > 80) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (root.displayTemp > 65) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return Math.round(root.displayTemp).toString();
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: gpuTempContent
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "auto_awesome_mosaic"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (root.displayTemp > 80) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (root.displayTemp > 65) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
|
||||
return "--°";
|
||||
}
|
||||
|
||||
return Math.round(root.displayTemp) + "°";
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: gpuTempBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100°"
|
||||
}
|
||||
|
||||
width: root.minimumWidth ? Math.max(gpuTempBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
if (popoutTarget && popoutTarget.setTriggerPosition) {
|
||||
const globalPos = root.visualContent.mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
|
||||
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
}
|
||||
DgopService.setSortBy("cpu");
|
||||
if (root.toggleProcessList) {
|
||||
root.toggleProcessList();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: gpuTempColumn
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "auto_awesome_mosaic"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (root.displayTemp > 80) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (root.displayTemp > 65) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return Math.round(root.displayTemp).toString();
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: gpuTempContent
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "auto_awesome_mosaic"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (root.displayTemp > 80) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (root.displayTemp > 65) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
|
||||
return "--°";
|
||||
}
|
||||
|
||||
return Math.round(root.displayTemp) + "°";
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: gpuTempBaseline
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100°"
|
||||
}
|
||||
|
||||
width: root.minimumWidth ? Math.max(gpuTempBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoSaveTimer
|
||||
|
||||
@@ -231,13 +215,10 @@ Rectangle {
|
||||
if (DgopService.availableGpus && DgopService.availableGpus.length > 0) {
|
||||
const firstGpu = DgopService.availableGpus[0];
|
||||
if (firstGpu && firstGpu.pciId) {
|
||||
// Save the first GPU's PCI ID to this widget's settings
|
||||
updateWidgetPciId(firstGpu.pciId);
|
||||
DgopService.addGpuPciId(firstGpu.pciId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,51 +2,34 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
|
||||
width: isVertical ? widgetThickness : (idleIcon.width + horizontalPadding * 2)
|
||||
height: isVertical ? (idleIcon.height + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: idleIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
name: SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
z: 1
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
SessionService.toggleIdleInhibit();
|
||||
SessionService.toggleIdleInhibit()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,36 +3,69 @@ import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Modules.ProcessList
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
property string currentLayout: ""
|
||||
property string hyprlandKeyboard: ""
|
||||
|
||||
width: isVertical ? widgetThickness : (contentRow.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (contentColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : contentRow.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? contentColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
Column {
|
||||
id: contentColumn
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "keyboard"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!root.currentLayout) return ""
|
||||
const parts = root.currentLayout.split(" ")
|
||||
if (parts.length > 0) {
|
||||
return parts[0].substring(0, 2).toUpperCase()
|
||||
}
|
||||
return root.currentLayout.substring(0, 2).toUpperCase()
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: root.currentLayout
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
z: 1
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
@@ -51,76 +84,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
visible: root.isVertical
|
||||
|
||||
DankIcon {
|
||||
name: "keyboard"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!currentLayout) return ""
|
||||
const parts = currentLayout.split(" ")
|
||||
if (parts.length > 0) {
|
||||
return parts[0].substring(0, 2).toUpperCase()
|
||||
}
|
||||
return currentLayout.substring(0, 2).toUpperCase()
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: !root.isVertical
|
||||
|
||||
StyledText {
|
||||
text: currentLayout
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Process {
|
||||
id: hyprlandLayoutProcess
|
||||
running: false
|
||||
command: ["hyprctl", "-j", "devices"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const data = JSON.parse(text)
|
||||
// Find the main keyboard and get its active keymap
|
||||
const mainKeyboard = data.keyboards.find(kb => kb.main === true)
|
||||
root.hyprlandKeyboard = mainKeyboard.name
|
||||
if (mainKeyboard && mainKeyboard.active_keymap) {
|
||||
root.currentLayout = mainKeyboard.active_keymap
|
||||
} else {
|
||||
root.currentLayout = "Unknown"
|
||||
}
|
||||
} catch (e) {
|
||||
root.currentLayout = "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: 1000
|
||||
@@ -139,7 +102,24 @@ Rectangle {
|
||||
if (CompositorService.isNiri) {
|
||||
root.currentLayout = NiriService.getCurrentKeyboardLayoutName()
|
||||
} else if (CompositorService.isHyprland) {
|
||||
hyprlandLayoutProcess.running = true
|
||||
Proc.runCommand(null, ["hyprctl", "-j", "devices"], (output, exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
root.currentLayout = "Unknown"
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(output)
|
||||
const mainKeyboard = data.keyboards.find(kb => kb.main === true)
|
||||
root.hyprlandKeyboard = mainKeyboard.name
|
||||
if (mainKeyboard && mainKeyboard.active_keymap) {
|
||||
root.currentLayout = mainKeyboard.active_keymap
|
||||
} else {
|
||||
root.currentLayout = "Unknown"
|
||||
}
|
||||
} catch (e) {
|
||||
root.currentLayout = "Unknown"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,117 +3,95 @@ import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isActive: false
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property string section: "left"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
property var hyprlandOverviewLoader: null
|
||||
|
||||
signal clicked()
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
|
||||
width: widgetThickness
|
||||
height: widgetThickness
|
||||
DankIcon {
|
||||
visible: SettingsData.launcherLogoMode === "apps"
|
||||
anchors.centerIn: parent
|
||||
name: "apps"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: launcherArea
|
||||
SystemLogo {
|
||||
visible: SettingsData.launcherLogoMode === "os"
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
colorOverride: Theme.effectiveLogoColor
|
||||
brightnessOverride: SettingsData.launcherLogoBrightness
|
||||
contrastOverride: SettingsData.launcherLogoContrast
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
root.clicked();
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0);
|
||||
const currentScreen = parentScreen || Screen;
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width);
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen);
|
||||
IconImage {
|
||||
visible: SettingsData.launcherLogoMode === "compositor"
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
source: {
|
||||
if (CompositorService.isNiri) {
|
||||
return "file://" + Theme.shellDir + "/assets/niri.svg"
|
||||
} else if (CompositorService.isHyprland) {
|
||||
return "file://" + Theme.shellDir + "/assets/hyprland.svg"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
layer.enabled: Theme.effectiveLogoColor !== ""
|
||||
layer.effect: MultiEffect {
|
||||
saturation: 0
|
||||
colorization: 1
|
||||
colorizationColor: Theme.effectiveLogoColor
|
||||
brightness: SettingsData.launcherLogoBrightness
|
||||
contrast: SettingsData.launcherLogoContrast
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
visible: SettingsData.launcherLogoMode === "custom" && SettingsData.launcherLogoCustomPath !== ""
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
source: SettingsData.launcherLogoCustomPath ? "file://" + SettingsData.launcherLogoCustomPath.replace("file://", "") : ""
|
||||
layer.enabled: Theme.effectiveLogoColor !== ""
|
||||
layer.effect: MultiEffect {
|
||||
saturation: 0
|
||||
colorization: 1
|
||||
colorizationColor: Theme.effectiveLogoColor
|
||||
brightness: SettingsData.launcherLogoBrightness
|
||||
contrast: SettingsData.launcherLogoContrast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: launcherContent
|
||||
|
||||
MouseArea {
|
||||
id: customMouseArea
|
||||
anchors.fill: parent
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = launcherArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
visible: SettingsData.launcherLogoMode === "apps"
|
||||
anchors.centerIn: parent
|
||||
name: "apps"
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
SystemLogo {
|
||||
visible: SettingsData.launcherLogoMode === "os"
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
colorOverride: Theme.effectiveLogoColor
|
||||
brightnessOverride: SettingsData.launcherLogoBrightness
|
||||
contrastOverride: SettingsData.launcherLogoContrast
|
||||
}
|
||||
|
||||
IconImage {
|
||||
visible: SettingsData.launcherLogoMode === "compositor"
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
source: {
|
||||
if (CompositorService.isNiri) {
|
||||
return "file://" + Theme.shellDir + "/assets/niri.svg"
|
||||
} else if (CompositorService.isHyprland) {
|
||||
return "file://" + Theme.shellDir + "/assets/hyprland.svg"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
layer.enabled: Theme.effectiveLogoColor !== ""
|
||||
layer.effect: MultiEffect {
|
||||
saturation: 0
|
||||
colorization: 1
|
||||
colorizationColor: Theme.effectiveLogoColor
|
||||
brightness: SettingsData.launcherLogoBrightness
|
||||
contrast: SettingsData.launcherLogoContrast
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
visible: SettingsData.launcherLogoMode === "custom" && SettingsData.launcherLogoCustomPath !== ""
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
source: SettingsData.launcherLogoCustomPath ? "file://" + SettingsData.launcherLogoCustomPath.replace("file://", "") : ""
|
||||
layer.enabled: Theme.effectiveLogoColor !== ""
|
||||
layer.effect: MultiEffect {
|
||||
saturation: 0
|
||||
colorization: 1
|
||||
colorizationColor: Theme.effectiveLogoColor
|
||||
brightness: SettingsData.launcherLogoBrightness
|
||||
contrast: SettingsData.launcherLogoContrast
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.RightButton
|
||||
onPressed: function (mouse){
|
||||
if (CompositorService.isNiri) {
|
||||
NiriService.toggleOverview()
|
||||
} else if (root.hyprlandOverviewLoader?.item) {
|
||||
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,452 +1,358 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.Mpris
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
readonly property bool playerAvailable: activePlayer !== null
|
||||
property bool compactMode: false
|
||||
readonly property int textWidth: {
|
||||
switch (SettingsData.mediaSize) {
|
||||
case 0:
|
||||
return 0; // No text in small mode
|
||||
return 0;
|
||||
case 2:
|
||||
return 180; // Large text area
|
||||
return 180;
|
||||
default:
|
||||
return 120; // Medium text area
|
||||
return 120;
|
||||
}
|
||||
}
|
||||
readonly property int currentContentWidth: {
|
||||
if (isVertical) {
|
||||
return widgetThickness;
|
||||
if (isVerticalOrientation) {
|
||||
return widgetThickness - horizontalPadding * 2;
|
||||
}
|
||||
const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20;
|
||||
const audioVizWidth = 20;
|
||||
const contentWidth = audioVizWidth + Theme.spacingXS + controlsWidth;
|
||||
return contentWidth + (textWidth > 0 ? textWidth + Theme.spacingXS : 0) + horizontalPadding * 2;
|
||||
return contentWidth + (textWidth > 0 ? textWidth + Theme.spacingXS : 0);
|
||||
}
|
||||
readonly property int currentContentHeight: {
|
||||
if (!isVertical) {
|
||||
return widgetThickness;
|
||||
if (!isVerticalOrientation) {
|
||||
return widgetThickness - horizontalPadding * 2;
|
||||
}
|
||||
const audioVizHeight = 20;
|
||||
const playButtonHeight = 24;
|
||||
return audioVizHeight + Theme.spacingXS + playButtonHeight + horizontalPadding * 2;
|
||||
}
|
||||
property string section: "center"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: currentContentWidth
|
||||
height: currentContentHeight
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: "shown"
|
||||
when: playerAvailable
|
||||
|
||||
PropertyChanges {
|
||||
target: root
|
||||
opacity: 1
|
||||
width: currentContentWidth
|
||||
height: currentContentHeight
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "hidden"
|
||||
when: !playerAvailable
|
||||
|
||||
PropertyChanges {
|
||||
target: root
|
||||
opacity: 0
|
||||
width: isVertical ? widgetThickness : 0
|
||||
height: isVertical ? 0 : widgetThickness
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "shown"
|
||||
to: "hidden"
|
||||
|
||||
SequentialAnimation {
|
||||
PauseAnimation {
|
||||
duration: 500
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
properties: isVertical ? "opacity,height" : "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
Transition {
|
||||
from: "hidden"
|
||||
to: "shown"
|
||||
|
||||
NumberAnimation {
|
||||
properties: isVertical ? "opacity,height" : "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
Column {
|
||||
id: verticalLayout
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
AudioVisualization {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.popupTarget && root.popupTarget.setTriggerPosition) {
|
||||
const globalPos = parent.mapToGlobal(0, 0)
|
||||
const currentScreen = root.parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, parent.width)
|
||||
root.popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
|
||||
}
|
||||
root.clicked()
|
||||
}
|
||||
onEntered: {
|
||||
tooltipLoader.active = true
|
||||
if (tooltipLoader.item && activePlayer) {
|
||||
const globalPos = parent.mapToGlobal(parent.width / 2, parent.height / 2)
|
||||
const screenX = root.parentScreen ? root.parentScreen.x : 0
|
||||
const screenY = root.parentScreen ? root.parentScreen.y : 0
|
||||
const relativeY = globalPos.y - screenY
|
||||
const tooltipX = root.axis?.edge === "left" ? (Theme.barHeight + SettingsData.dankBarSpacing + Theme.spacingXS) : (root.parentScreen.width - Theme.barHeight - SettingsData.dankBarSpacing - Theme.spacingXS)
|
||||
|
||||
let identity = activePlayer.identity || ""
|
||||
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium")
|
||||
let title = activePlayer.trackTitle || "Unknown Track"
|
||||
let subtitle = ""
|
||||
if (isWebMedia && activePlayer.trackTitle) {
|
||||
subtitle = activePlayer.trackArtist || identity
|
||||
} else {
|
||||
subtitle = activePlayer.trackArtist || ""
|
||||
}
|
||||
let tooltipText = subtitle.length > 0 ? title + " • " + subtitle : title
|
||||
|
||||
const isLeft = root.axis?.edge === "left"
|
||||
tooltipLoader.item.show(tooltipText, screenX + tooltipX, relativeY, root.parentScreen, isLeft, !isLeft)
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hide()
|
||||
}
|
||||
tooltipLoader.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
|
||||
visible: root.playerAvailable
|
||||
opacity: activePlayer ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
|
||||
size: 14
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
if (!activePlayer) return
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
activePlayer.togglePlaying()
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
activePlayer.previous()
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
activePlayer.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return audioVizHeight + Theme.spacingXS + playButtonHeight;
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.playerAvailable ? root.currentContentWidth : 0
|
||||
implicitHeight: root.playerAvailable ? root.currentContentHeight : 0
|
||||
opacity: root.playerAvailable ? 1 : 0
|
||||
|
||||
Row {
|
||||
id: mediaRow
|
||||
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
id: mediaInfo
|
||||
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
AudioVisualization {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: textContainer
|
||||
|
||||
property string displayText: {
|
||||
if (!activePlayer || !activePlayer.trackTitle) {
|
||||
return "";
|
||||
states: [
|
||||
State {
|
||||
name: "shown"
|
||||
when: root.playerAvailable
|
||||
PropertyChanges {
|
||||
target: parent
|
||||
opacity: 1
|
||||
implicitWidth: root.currentContentWidth
|
||||
implicitHeight: root.currentContentHeight
|
||||
}
|
||||
|
||||
let identity = activePlayer.identity || "";
|
||||
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari");
|
||||
let title = "";
|
||||
let subtitle = "";
|
||||
if (isWebMedia && activePlayer.trackTitle) {
|
||||
title = activePlayer.trackTitle;
|
||||
subtitle = activePlayer.trackArtist || identity;
|
||||
} else {
|
||||
title = activePlayer.trackTitle || "Unknown Track";
|
||||
subtitle = activePlayer.trackArtist || "";
|
||||
},
|
||||
State {
|
||||
name: "hidden"
|
||||
when: !root.playerAvailable
|
||||
PropertyChanges {
|
||||
target: parent
|
||||
opacity: 0
|
||||
implicitWidth: 0
|
||||
implicitHeight: 0
|
||||
}
|
||||
return subtitle.length > 0 ? title + " • " + subtitle : title;
|
||||
}
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: textWidth
|
||||
height: 20
|
||||
visible: SettingsData.mediaSize > 0
|
||||
clip: true
|
||||
color: "transparent"
|
||||
|
||||
StyledText {
|
||||
id: mediaText
|
||||
|
||||
property bool needsScrolling: implicitWidth > textContainer.width
|
||||
property real scrollOffset: 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: textContainer.displayText
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
wrapMode: Text.NoWrap
|
||||
x: needsScrolling ? -scrollOffset : 0
|
||||
onTextChanged: {
|
||||
scrollOffset = 0;
|
||||
scrollAnimation.restart();
|
||||
}
|
||||
|
||||
]
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "shown"
|
||||
to: "hidden"
|
||||
SequentialAnimation {
|
||||
id: scrollAnimation
|
||||
|
||||
running: mediaText.needsScrolling && textContainer.visible
|
||||
loops: Animation.Infinite
|
||||
|
||||
PauseAnimation {
|
||||
duration: 2000
|
||||
duration: 500
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "scrollOffset"
|
||||
from: 0
|
||||
to: mediaText.implicitWidth - textContainer.width + 5
|
||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
||||
easing.type: Easing.Linear
|
||||
properties: "opacity,implicitWidth,implicitHeight"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
PauseAnimation {
|
||||
duration: 2000
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "scrollOffset"
|
||||
to: 0
|
||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
Transition {
|
||||
from: "hidden"
|
||||
to: "shown"
|
||||
NumberAnimation {
|
||||
properties: "opacity,implicitWidth,implicitHeight"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable && root.opacity > 0 && root.width > 0 && textContainer.visible
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onPressed: {
|
||||
if (root.popupTarget && root.popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
const currentScreen = root.parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.width)
|
||||
root.popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
|
||||
Column {
|
||||
id: verticalLayout
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
AudioVisualization {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
|
||||
const globalPos = parent.mapToGlobal(0, 0)
|
||||
const currentScreen = root.parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, parent.width)
|
||||
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
|
||||
}
|
||||
root.clicked()
|
||||
}
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
|
||||
visible: root.playerAvailable
|
||||
opacity: activePlayer ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
|
||||
size: 14
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
if (!activePlayer) return
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
activePlayer.togglePlaying()
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
activePlayer.previous()
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
activePlayer.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Row {
|
||||
id: mediaRow
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Row {
|
||||
id: mediaInfo
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: prevArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||
visible: root.playerAvailable
|
||||
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
|
||||
AudioVisualization {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_previous"
|
||||
size: 12
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
Rectangle {
|
||||
id: textContainer
|
||||
property string displayText: {
|
||||
if (!activePlayer || !activePlayer.trackTitle) {
|
||||
return "";
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: prevArea
|
||||
let identity = activePlayer.identity || "";
|
||||
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari");
|
||||
let title = "";
|
||||
let subtitle = "";
|
||||
if (isWebMedia && activePlayer.trackTitle) {
|
||||
title = activePlayer.trackTitle;
|
||||
subtitle = activePlayer.trackArtist || identity;
|
||||
} else {
|
||||
title = activePlayer.trackTitle || "Unknown Track";
|
||||
subtitle = activePlayer.trackArtist || "";
|
||||
}
|
||||
return subtitle.length > 0 ? title + " • " + subtitle : title;
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable && root.width > 0
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (activePlayer) {
|
||||
activePlayer.previous();
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: textWidth
|
||||
height: 20
|
||||
visible: SettingsData.mediaSize > 0
|
||||
clip: true
|
||||
color: "transparent"
|
||||
|
||||
StyledText {
|
||||
id: mediaText
|
||||
property bool needsScrolling: implicitWidth > textContainer.width
|
||||
property real scrollOffset: 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: textContainer.displayText
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
wrapMode: Text.NoWrap
|
||||
x: needsScrolling ? -scrollOffset : 0
|
||||
onTextChanged: {
|
||||
scrollOffset = 0;
|
||||
scrollAnimation.restart();
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: scrollAnimation
|
||||
running: mediaText.needsScrolling && textContainer.visible
|
||||
loops: Animation.Infinite
|
||||
|
||||
PauseAnimation {
|
||||
duration: 2000
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "scrollOffset"
|
||||
from: 0
|
||||
to: mediaText.implicitWidth - textContainer.width + 5
|
||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
|
||||
PauseAnimation {
|
||||
duration: 2000
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: mediaText
|
||||
property: "scrollOffset"
|
||||
to: 0
|
||||
duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60)
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
const currentScreen = root.parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, root.barThickness, root.width)
|
||||
root.popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, root.section, currentScreen)
|
||||
}
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
|
||||
visible: root.playerAvailable
|
||||
opacity: activePlayer ? 1 : 0.3
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: prevArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||
visible: root.playerAvailable
|
||||
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
|
||||
size: 14
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
|
||||
}
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_previous"
|
||||
size: 12
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable && root.width > 0
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (activePlayer) {
|
||||
activePlayer.togglePlaying();
|
||||
MouseArea {
|
||||
id: prevArea
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (activePlayer) {
|
||||
activePlayer.previous();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover
|
||||
visible: root.playerAvailable
|
||||
opacity: activePlayer ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
|
||||
size: 14
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (activePlayer) {
|
||||
activePlayer.togglePlaying();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: nextArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||
visible: playerAvailable
|
||||
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_next"
|
||||
size: 12
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextArea
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (activePlayer) {
|
||||
activePlayer.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: nextArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||
visible: playerAvailable
|
||||
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_next"
|
||||
size: 12
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextArea
|
||||
|
||||
anchors.fill: parent
|
||||
enabled: root.playerAvailable && root.width > 0
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (activePlayer) {
|
||||
activePlayer.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,194 +1,167 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Modules.ProcessList
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property int availableWidth: 400
|
||||
readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2
|
||||
readonly property int maxNormalWidth: 456
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
function formatNetworkSpeed(bytesPerSec) {
|
||||
if (bytesPerSec < 1024) {
|
||||
return bytesPerSec.toFixed(0) + " B/s";
|
||||
return bytesPerSec.toFixed(0) + " B/s"
|
||||
} else if (bytesPerSec < 1024 * 1024) {
|
||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s";
|
||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
|
||||
} else if (bytesPerSec < 1024 * 1024 * 1024) {
|
||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
|
||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"
|
||||
} else {
|
||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s";
|
||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"
|
||||
}
|
||||
}
|
||||
|
||||
width: isVertical ? widgetThickness : (contentRow.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (contentColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = networkArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["network"]);
|
||||
DgopService.addRef(["network"])
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["network"]);
|
||||
DgopService.removeRef(["network"])
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: networkArea
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : contentRow.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? contentColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
Column {
|
||||
id: contentColumn
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
visible: root.isVerticalOrientation
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
visible: root.isVertical
|
||||
|
||||
DankIcon {
|
||||
name: "network_check"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const rate = DgopService.networkRxRate
|
||||
if (rate < 1024) return rate.toFixed(0)
|
||||
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
|
||||
return (rate / (1024 * 1024)).toFixed(0) + "M"
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.info
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const rate = DgopService.networkTxRate
|
||||
if (rate < 1024) return rate.toFixed(0)
|
||||
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
|
||||
return (rate / (1024 * 1024)).toFixed(0) + "M"
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.error
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: !root.isVertical
|
||||
|
||||
DankIcon {
|
||||
name: "network_check"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: "↓"
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.info
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.networkRxRate > 0 ? formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
wrapMode: Text.NoWrap
|
||||
|
||||
StyledTextMetrics {
|
||||
id: rxBaseline
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "88.8 MB/s"
|
||||
DankIcon {
|
||||
name: "network_check"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
width: Math.max(rxBaseline.width, paintedWidth)
|
||||
StyledText {
|
||||
text: {
|
||||
const rate = DgopService.networkRxRate
|
||||
if (rate < 1024) return rate.toFixed(0)
|
||||
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
|
||||
return (rate / (1024 * 1024)).toFixed(0) + "M"
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.info
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
StyledText {
|
||||
text: {
|
||||
const rate = DgopService.networkTxRate
|
||||
if (rate < 1024) return rate.toFixed(0)
|
||||
if (rate < 1024 * 1024) return (rate / 1024).toFixed(0) + "K"
|
||||
return (rate / (1024 * 1024)).toFixed(0) + "M"
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.error
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: !root.isVerticalOrientation
|
||||
|
||||
DankIcon {
|
||||
name: "network_check"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: "↓"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.info
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.networkRxRate > 0 ? root.formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
wrapMode: Text.NoWrap
|
||||
|
||||
StyledTextMetrics {
|
||||
id: rxBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "88.8 MB/s"
|
||||
}
|
||||
|
||||
width: Math.max(rxBaseline.width, paintedWidth)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: "↑"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.error
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.networkTxRate > 0 ? root.formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
wrapMode: Text.NoWrap
|
||||
|
||||
StyledTextMetrics {
|
||||
id: txBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "88.8 MB/s"
|
||||
}
|
||||
|
||||
width: Math.max(txBaseline.width, paintedWidth)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: "↑"
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.error
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.networkTxRate > 0 ? formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
wrapMode: Text.NoWrap
|
||||
|
||||
StyledTextMetrics {
|
||||
id: txBaseline
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "88.8 MB/s"
|
||||
}
|
||||
|
||||
width: Math.max(txBaseline.width, paintedWidth)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property string section: "right"
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
signal clicked()
|
||||
|
||||
readonly property string focusedScreenName: (
|
||||
CompositorService.isHyprland && typeof Hyprland !== "undefined" && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor ? (Hyprland.focusedWorkspace.monitor.name || "") :
|
||||
CompositorService.isNiri && typeof NiriService !== "undefined" && NiriService.currentOutput ? NiriService.currentOutput : ""
|
||||
@@ -43,54 +34,43 @@ Rectangle {
|
||||
readonly property var notepadInstance: resolveNotepadInstance()
|
||||
readonly property bool isActive: notepadInstance?.isVisible ?? false
|
||||
|
||||
width: isVertical ? widgetThickness : (notepadIcon.width + horizontalPadding * 2)
|
||||
height: isVertical ? (notepadIcon.height + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
|
||||
DankIcon {
|
||||
id: notepadIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
name: "assignment"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
color: root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.primary
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 4
|
||||
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 4
|
||||
visible: NotepadStorageService.tabs && NotepadStorageService.tabs.length > 0
|
||||
opacity: 0.8
|
||||
}
|
||||
}
|
||||
|
||||
const baseColor = notepadArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: notepadIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
name: "assignment"
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: notepadArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.primary
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 4
|
||||
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 4
|
||||
visible: NotepadStorageService.tabs && NotepadStorageService.tabs.length > 0
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: notepadArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
const inst = root.notepadInstance
|
||||
if (inst) {
|
||||
inst.toggle()
|
||||
}
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,76 +1,36 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool hasUnread: false
|
||||
property bool isActive: false
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
signal clicked()
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
|
||||
width: widgetThickness
|
||||
height: widgetThickness
|
||||
|
||||
MouseArea {
|
||||
id: notificationArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
}
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: notificationContent
|
||||
|
||||
anchors.fill: parent
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent"
|
||||
DankIcon {
|
||||
id: notifIcon
|
||||
anchors.centerIn: parent
|
||||
name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
color: SessionData.doNotDisturb ? Theme.error : (root.isActive ? Theme.primary : Theme.surfaceText)
|
||||
}
|
||||
|
||||
const baseColor = notificationArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: SessionData.doNotDisturb ? Theme.error : (notificationArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 8
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 6
|
||||
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 6
|
||||
visible: root.hasUnread
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.error
|
||||
anchors.right: notifIcon.right
|
||||
anchors.top: notifIcon.top
|
||||
visible: root.hasUnread
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
@@ -19,26 +19,167 @@ Rectangle {
|
||||
readonly property int activeCount: PrivacyService.microphoneActive + PrivacyService.cameraActive + PrivacyService.screensharingActive
|
||||
readonly property real contentWidth: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0
|
||||
readonly property real contentHeight: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0
|
||||
readonly property real visualWidth: isVertical ? widgetThickness : (hasActivePrivacy ? (contentWidth + horizontalPadding * 2) : 0)
|
||||
readonly property real visualHeight: isVertical ? (hasActivePrivacy ? (contentHeight + horizontalPadding * 2) : 0) : widgetThickness
|
||||
|
||||
width: isVertical ? widgetThickness : (hasActivePrivacy ? (contentWidth + horizontalPadding * 2) : 0)
|
||||
height: isVertical ? (hasActivePrivacy ? (contentHeight + horizontalPadding * 2) : 0) : (hasActivePrivacy ? widgetThickness : 0)
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? barThickness : visualWidth
|
||||
height: isVertical ? visualHeight : barThickness
|
||||
visible: hasActivePrivacy
|
||||
opacity: hasActivePrivacy ? 1 : 0
|
||||
enabled: hasActivePrivacy
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
|
||||
Rectangle {
|
||||
id: visualContent
|
||||
width: root.visualWidth
|
||||
height: root.visualHeight
|
||||
anchors.centerIn: parent
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
return Qt.rgba(privacyArea.containsMouse ? Theme.errorPressed.r : Theme.errorHover.r, privacyArea.containsMouse ? Theme.errorPressed.g : Theme.errorHover.g, privacyArea.containsMouse ? Theme.errorPressed.b : Theme.errorHover.b, (privacyArea.containsMouse ? Theme.errorPressed.a : Theme.errorHover.a) * Theme.widgetTransparency)
|
||||
}
|
||||
|
||||
return Qt.rgba(privacyArea.containsMouse ? Theme.errorPressed.r : Theme.errorHover.r, privacyArea.containsMouse ? Theme.errorPressed.g : Theme.errorHover.g, privacyArea.containsMouse ? Theme.errorPressed.b : Theme.errorHover.b, (privacyArea.containsMouse ? Theme.errorPressed.a : Theme.errorHover.a) * Theme.widgetTransparency);
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
visible: root.isVertical && root.hasActivePrivacy
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.microphoneActive
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
const sourceAudio = AudioService.source?.audio
|
||||
const muted = !sourceAudio || sourceAudio.muted || sourceAudio.volume === 0.0
|
||||
if (muted) return "mic_off"
|
||||
return "mic"
|
||||
}
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.error
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.cameraActive
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "camera_video"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.surfaceText
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: -2
|
||||
anchors.topMargin: -1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.screensharingActive
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "screen_share"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.warning
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
visible: !root.isVertical && root.hasActivePrivacy
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.microphoneActive
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
const sourceAudio = AudioService.source?.audio
|
||||
const muted = !sourceAudio || sourceAudio.muted || sourceAudio.volume === 0.0
|
||||
if (muted) return "mic_off"
|
||||
return "mic"
|
||||
}
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.error
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.cameraActive
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "camera_video"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.surfaceText
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: -2
|
||||
anchors.topMargin: -1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.screensharingActive
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "screen_share"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.warning
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Privacy indicator click handler
|
||||
|
||||
id: privacyArea
|
||||
|
||||
z: -1
|
||||
anchors.fill: parent
|
||||
hoverEnabled: hasActivePrivacy
|
||||
enabled: hasActivePrivacy
|
||||
@@ -47,141 +188,8 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
visible: root.isVertical && hasActivePrivacy
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.microphoneActive
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "mic"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.error
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.cameraActive
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "camera_video"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.surfaceText
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: -2
|
||||
anchors.topMargin: -1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.screensharingActive
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "screen_share"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.warning
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
visible: !root.isVertical && hasActivePrivacy
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.microphoneActive
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "mic"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.error
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.cameraActive
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "camera_video"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.surfaceText
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: -2
|
||||
anchors.topMargin: -1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
height: 18
|
||||
visible: PrivacyService.screensharingActive
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
name: "screen_share"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.warning
|
||||
filled: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: tooltip
|
||||
|
||||
width: tooltipText.contentWidth + Theme.spacingM * 2
|
||||
height: tooltipText.contentHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
@@ -196,7 +204,6 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
id: tooltipText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: PrivacyService.getPrivacySummary()
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
@@ -222,9 +229,7 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
@@ -234,7 +239,6 @@ Rectangle {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
@@ -244,7 +248,5 @@ Rectangle {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,37 +1,19 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
property var popoutTarget: null
|
||||
property var widgetData: null
|
||||
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
width: isVertical ? widgetThickness : (ramContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (ramColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = ramArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["memory"]);
|
||||
@@ -40,120 +22,123 @@ Rectangle {
|
||||
DgopService.removeRef(["memory"]);
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ramArea
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : ramContent.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? ramColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
Column {
|
||||
id: ramColumn
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "developer_board"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (DgopService.memoryUsage > 90) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.memoryUsage > 75) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return DgopService.memoryUsage.toFixed(0);
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: ramContent
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "developer_board"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (DgopService.memoryUsage > 90) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.memoryUsage > 75) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
|
||||
return "--%";
|
||||
}
|
||||
|
||||
return DgopService.memoryUsage.toFixed(0) + "%";
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: ramBaseline
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100%"
|
||||
}
|
||||
|
||||
width: root.minimumWidth ? Math.max(ramBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
if (popoutTarget && popoutTarget.setTriggerPosition) {
|
||||
const globalPos = root.visualContent.mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
|
||||
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
}
|
||||
DgopService.setSortBy("memory");
|
||||
if (root.toggleProcessList) {
|
||||
root.toggleProcessList();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: ramColumn
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: "developer_board"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (DgopService.memoryUsage > 90) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.memoryUsage > 75) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return DgopService.memoryUsage.toFixed(0);
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: ramContent
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: "developer_board"
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (DgopService.memoryUsage > 90) {
|
||||
return Theme.tempDanger;
|
||||
}
|
||||
|
||||
if (DgopService.memoryUsage > 75) {
|
||||
return Theme.tempWarning;
|
||||
}
|
||||
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
|
||||
return "--%";
|
||||
}
|
||||
|
||||
return DgopService.memoryUsage.toFixed(0) + "%";
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideNone
|
||||
|
||||
StyledTextMetrics {
|
||||
id: ramBaseline
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100%"
|
||||
}
|
||||
|
||||
width: root.minimumWidth ? Math.max(ramBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
@@ -15,26 +15,49 @@ Rectangle {
|
||||
property var parentWindow: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
property bool isAtBottom: false
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
|
||||
readonly property int calculatedSize: SystemTray.items.values.length > 0 ? SystemTray.items.values.length * 24 + horizontalPadding * 2 : 0
|
||||
|
||||
width: isVertical ? widgetThickness : calculatedSize
|
||||
height: isVertical ? calculatedSize : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SystemTray.items.values.length === 0) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
readonly property var hiddenTrayIds: {
|
||||
const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || ""
|
||||
return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : []
|
||||
}
|
||||
readonly property var visibleTrayItems: {
|
||||
if (!hiddenTrayIds.length) {
|
||||
return SystemTray.items.values
|
||||
}
|
||||
return SystemTray.items.values.filter(item => {
|
||||
const itemId = item?.id || ""
|
||||
return !hiddenTrayIds.includes(itemId.toLowerCase())
|
||||
})
|
||||
}
|
||||
readonly property int calculatedSize: visibleTrayItems.length > 0 ? visibleTrayItems.length * 24 + horizontalPadding * 2 : 0
|
||||
readonly property real visualWidth: isVertical ? widgetThickness : calculatedSize
|
||||
readonly property real visualHeight: isVertical ? calculatedSize : widgetThickness
|
||||
|
||||
width: isVertical ? barThickness : visualWidth
|
||||
height: isVertical ? visualHeight : barThickness
|
||||
visible: visibleTrayItems.length > 0
|
||||
|
||||
Rectangle {
|
||||
id: visualBackground
|
||||
width: root.visualWidth
|
||||
height: root.visualHeight
|
||||
anchors.centerIn: parent
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (visibleTrayItems.length === 0) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
}
|
||||
visible: SystemTray.items.values.length > 0
|
||||
|
||||
Loader {
|
||||
id: layoutLoader
|
||||
@@ -48,84 +71,84 @@ Rectangle {
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items.values
|
||||
model: root.visibleTrayItems
|
||||
|
||||
delegate: Item {
|
||||
property var trayItem: modelData
|
||||
property string iconSource: {
|
||||
let icon = trayItem && trayItem.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon === "") {
|
||||
return "";
|
||||
}
|
||||
if (icon.includes("?path=")) {
|
||||
const split = icon.split("?path=");
|
||||
if (split.length !== 2) {
|
||||
return icon;
|
||||
id: delegateRoot
|
||||
property var trayItem: modelData
|
||||
property string iconSource: {
|
||||
let icon = trayItem && trayItem.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon === "") {
|
||||
return "";
|
||||
}
|
||||
if (icon.includes("?path=")) {
|
||||
const split = icon.split("?path=");
|
||||
if (split.length !== 2) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
const name = split[0];
|
||||
const path = split[1];
|
||||
let fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
if (fileName.startsWith("dropboxstatus")) {
|
||||
fileName = `hicolor/16x16/status/${fileName}`;
|
||||
const name = split[0];
|
||||
const path = split[1];
|
||||
let fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
if (fileName.startsWith("dropboxstatus")) {
|
||||
fileName = `hicolor/16x16/status/${fileName}`;
|
||||
}
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
return `file://${path}/${fileName}`;
|
||||
if (icon.startsWith("/") && !icon.startsWith("file://")) {
|
||||
return `file://${icon}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
if (icon.startsWith("/") && !icon.startsWith("file://")) {
|
||||
return `file://${icon}`;
|
||||
}
|
||||
return icon;
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
width: 24
|
||||
height: root.barThickness
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||
Rectangle {
|
||||
id: visualContent
|
||||
width: 24
|
||||
height: 24
|
||||
anchors.centerIn: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||
|
||||
|
||||
}
|
||||
|
||||
IconImage {
|
||||
anchors.centerIn: parent
|
||||
width: 16
|
||||
height: 16
|
||||
source: parent.iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: (mouse) => {
|
||||
if (!trayItem) {
|
||||
return;
|
||||
IconImage {
|
||||
anchors.centerIn: parent
|
||||
width: 16
|
||||
height: 16
|
||||
source: delegateRoot.iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) {
|
||||
trayItem.activate();
|
||||
return ;
|
||||
}
|
||||
if (trayItem.hasMenu) {
|
||||
root.showForTrayItem(trayItem, parent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: (mouse) => {
|
||||
if (!delegateRoot.trayItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
|
||||
delegateRoot.trayItem.activate();
|
||||
return ;
|
||||
}
|
||||
if (delegateRoot.trayItem.hasMenu) {
|
||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,79 +158,84 @@ Rectangle {
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items.values
|
||||
model: root.visibleTrayItems
|
||||
|
||||
delegate: Item {
|
||||
property var trayItem: modelData
|
||||
property string iconSource: {
|
||||
let icon = trayItem && trayItem.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon === "") {
|
||||
return "";
|
||||
id: delegateRoot
|
||||
property var trayItem: modelData
|
||||
property string iconSource: {
|
||||
let icon = trayItem && trayItem.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon === "") {
|
||||
return "";
|
||||
}
|
||||
if (icon.includes("?path=")) {
|
||||
const split = icon.split("?path=");
|
||||
if (split.length !== 2) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
const name = split[0];
|
||||
const path = split[1];
|
||||
let fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
if (fileName.startsWith("dropboxstatus")) {
|
||||
fileName = `hicolor/16x16/status/${fileName}`;
|
||||
}
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
if (icon.startsWith("/") && !icon.startsWith("file://")) {
|
||||
return `file://${icon}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
if (icon.includes("?path=")) {
|
||||
const split = icon.split("?path=");
|
||||
if (split.length !== 2) {
|
||||
return icon;
|
||||
return "";
|
||||
}
|
||||
|
||||
width: root.barThickness
|
||||
height: 24
|
||||
|
||||
Rectangle {
|
||||
id: visualContent
|
||||
width: 24
|
||||
height: 24
|
||||
anchors.centerIn: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||
|
||||
IconImage {
|
||||
anchors.centerIn: parent
|
||||
width: 16
|
||||
height: 16
|
||||
source: delegateRoot.iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: (mouse) => {
|
||||
if (!delegateRoot.trayItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = split[0];
|
||||
const path = split[1];
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
if (icon.startsWith("/") && !icon.startsWith("file://")) {
|
||||
return `file://${icon}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
|
||||
}
|
||||
|
||||
IconImage {
|
||||
anchors.centerIn: parent
|
||||
width: 16
|
||||
height: 16
|
||||
source: parent.iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: (mouse) => {
|
||||
if (!trayItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) {
|
||||
trayItem.activate();
|
||||
return ;
|
||||
}
|
||||
if (trayItem.hasMenu) {
|
||||
root.showForTrayItem(trayItem, parent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
|
||||
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
|
||||
delegateRoot.trayItem.activate();
|
||||
return ;
|
||||
}
|
||||
if (delegateRoot.trayItem.hasMenu) {
|
||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,154 +1,138 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool isActive: false
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
|
||||
readonly property bool isChecking: SystemUpdateService.isChecking
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: isVertical ? widgetThickness : (updaterIcon.width + horizontalPadding * 2)
|
||||
height: isVertical ? widgetThickness : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = updaterArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
Ref {
|
||||
service: SystemUpdateService
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: statusIcon
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : updaterIcon.implicitWidth
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
|
||||
anchors.centerIn: parent
|
||||
visible: root.isVertical
|
||||
name: {
|
||||
if (isChecking) return "refresh";
|
||||
if (SystemUpdateService.hasError) return "error";
|
||||
if (hasUpdates) return "system_update_alt";
|
||||
return "check_circle";
|
||||
}
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: {
|
||||
if (SystemUpdateService.hasError) return Theme.error;
|
||||
if (hasUpdates) return Theme.primary;
|
||||
return (updaterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText);
|
||||
}
|
||||
|
||||
RotationAnimation {
|
||||
id: rotationAnimation
|
||||
target: statusIcon
|
||||
property: "rotation"
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
running: isChecking
|
||||
loops: Animation.Infinite
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
statusIcon.rotation = 0
|
||||
DankIcon {
|
||||
id: statusIcon
|
||||
anchors.centerIn: parent
|
||||
visible: root.isVerticalOrientation
|
||||
name: {
|
||||
if (root.isChecking) return "refresh"
|
||||
if (SystemUpdateService.hasError) return "error"
|
||||
if (root.hasUpdates) return "system_update_alt"
|
||||
return "check_circle"
|
||||
}
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
color: {
|
||||
if (SystemUpdateService.hasError) return Theme.error
|
||||
if (root.hasUpdates) return Theme.primary
|
||||
return root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 8
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 6
|
||||
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 6
|
||||
visible: root.isVertical && root.hasUpdates && !root.isChecking
|
||||
}
|
||||
RotationAnimation {
|
||||
id: rotationAnimation
|
||||
target: statusIcon
|
||||
property: "rotation"
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
running: root.isChecking
|
||||
loops: Animation.Infinite
|
||||
|
||||
Row {
|
||||
id: updaterIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
visible: !root.isVertical
|
||||
|
||||
DankIcon {
|
||||
id: statusIconHorizontal
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: {
|
||||
if (isChecking) return "refresh";
|
||||
if (SystemUpdateService.hasError) return "error";
|
||||
if (hasUpdates) return "system_update_alt";
|
||||
return "check_circle";
|
||||
}
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: {
|
||||
if (SystemUpdateService.hasError) return Theme.error;
|
||||
if (hasUpdates) return Theme.primary;
|
||||
return (updaterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText);
|
||||
}
|
||||
|
||||
RotationAnimation {
|
||||
id: rotationAnimationHorizontal
|
||||
target: statusIconHorizontal
|
||||
property: "rotation"
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
running: isChecking
|
||||
loops: Animation.Infinite
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
statusIconHorizontal.rotation = 0
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
statusIcon.rotation = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: countText
|
||||
Rectangle {
|
||||
width: 8
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: SettingsData.dankBarNoBackground ? 0 : 6
|
||||
anchors.topMargin: SettingsData.dankBarNoBackground ? 0 : 6
|
||||
visible: root.isVerticalOrientation && root.hasUpdates && !root.isChecking
|
||||
}
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: SystemUpdateService.updateCount.toString()
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
visible: hasUpdates && !isChecking
|
||||
Row {
|
||||
id: updaterIcon
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
visible: !root.isVerticalOrientation
|
||||
|
||||
DankIcon {
|
||||
id: statusIconHorizontal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: {
|
||||
if (root.isChecking) return "refresh"
|
||||
if (SystemUpdateService.hasError) return "error"
|
||||
if (root.hasUpdates) return "system_update_alt"
|
||||
return "check_circle"
|
||||
}
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
color: {
|
||||
if (SystemUpdateService.hasError) return Theme.error
|
||||
if (root.hasUpdates) return Theme.primary
|
||||
return root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
RotationAnimation {
|
||||
id: rotationAnimationHorizontal
|
||||
target: statusIconHorizontal
|
||||
property: "rotation"
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
running: root.isChecking
|
||||
loops: Animation.Infinite
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
statusIconHorizontal.rotation = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: countText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: SystemUpdateService.updateCount.toString()
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
visible: root.hasUpdates && !root.isChecking
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: updaterArea
|
||||
|
||||
z: 1
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
if (popoutTarget && popoutTarget.setTriggerPosition) {
|
||||
const globalPos = root.visualContent.mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
|
||||
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
}
|
||||
root.clicked();
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,35 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
Ref {
|
||||
service: VpnService
|
||||
}
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property int widgetThickness: 28
|
||||
property int barThickness: 32
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
property var popoutTarget: null
|
||||
|
||||
signal toggleVpnPopup()
|
||||
|
||||
width: isVertical ? widgetThickness : (Theme.iconSize + horizontalPadding * 2)
|
||||
height: isVertical ? (Theme.iconSize + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.widgetThickness - root.horizontalPadding * 2
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
|
||||
DankIcon {
|
||||
id: icon
|
||||
|
||||
name: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
color: VpnService.connected ? Theme.primary : Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
const baseColor = clickArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: icon
|
||||
|
||||
name: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: VpnService.connected ? Theme.primary : Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -55,17 +44,18 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
if (popoutTarget && popoutTarget.setTriggerPosition) {
|
||||
const globalPos = root.visualContent.mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth)
|
||||
popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
}
|
||||
root.toggleVpnPopup();
|
||||
}
|
||||
onEntered: {
|
||||
if (root.parentScreen && !(popupTarget && popupTarget.shouldBeVisible)) {
|
||||
if (root.parentScreen && !(popoutTarget && popoutTarget.shouldBeVisible)) {
|
||||
tooltipLoader.active = true
|
||||
if (tooltipLoader.item) {
|
||||
let tooltipText = ""
|
||||
@@ -80,7 +70,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
if (root.isVertical) {
|
||||
if (root.isVerticalOrientation) {
|
||||
const globalPos = mapToGlobal(width / 2, height / 2)
|
||||
const screenX = root.parentScreen ? root.parentScreen.x : 0
|
||||
const screenY = root.parentScreen ? root.parentScreen.y : 0
|
||||
@@ -103,5 +93,4 @@ Rectangle {
|
||||
tooltipLoader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,120 +1,81 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property string section: "center"
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
|
||||
|
||||
signal clicked()
|
||||
|
||||
visible: SettingsData.weatherEnabled
|
||||
width: isVertical ? widgetThickness : (visible ? Math.min(100, weatherRow.implicitWidth + horizontalPadding * 2) : 0)
|
||||
height: isVertical ? (weatherColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const baseColor = weatherArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
|
||||
Ref {
|
||||
service: WeatherService
|
||||
}
|
||||
|
||||
Column {
|
||||
id: weatherColumn
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||
size: Theme.barIconSize(barThickness, -6)
|
||||
color: Theme.primary
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
|
||||
if (temp === undefined || temp === null || temp === 0) {
|
||||
return "--";
|
||||
}
|
||||
return temp;
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: {
|
||||
if (!SettingsData.weatherEnabled) return 0
|
||||
if (root.isVerticalOrientation) return root.widgetThickness - root.horizontalPadding * 2
|
||||
return Math.min(100 - root.horizontalPadding * 2, weatherRow.implicitWidth)
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
implicitHeight: root.isVerticalOrientation ? weatherColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
Row {
|
||||
id: weatherRow
|
||||
Column {
|
||||
id: weatherColumn
|
||||
visible: root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||
size: Theme.barIconSize(barThickness, -6)
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
|
||||
if (temp === undefined || temp === null || temp === 0) {
|
||||
return "--°" + (SettingsData.useFahrenheit ? "F" : "C");
|
||||
DankIcon {
|
||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||
size: Theme.barIconSize(root.barThickness, -6)
|
||||
color: Theme.primary
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C");
|
||||
StyledText {
|
||||
text: {
|
||||
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
|
||||
if (temp === undefined || temp === null || temp === 0) {
|
||||
return "--";
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
Row {
|
||||
id: weatherRow
|
||||
visible: !root.isVerticalOrientation
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
MouseArea {
|
||||
id: weatherArea
|
||||
DankIcon {
|
||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||
size: Theme.barIconSize(root.barThickness, -6)
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0)
|
||||
const currentScreen = parentScreen || Screen
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
|
||||
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
|
||||
StyledText {
|
||||
text: {
|
||||
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
|
||||
if (temp === undefined || temp === null || temp === 0) {
|
||||
return "--°" + (SettingsData.useFahrenheit ? "F" : "C");
|
||||
}
|
||||
|
||||
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C");
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
@@ -15,6 +15,11 @@ Rectangle {
|
||||
property string screenName: ""
|
||||
property real widgetHeight: 30
|
||||
property real barThickness: 48
|
||||
property var hyprlandOverviewLoader: null
|
||||
property var parentScreen: null
|
||||
readonly property var sortedToplevels: {
|
||||
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
|
||||
}
|
||||
property int currentWorkspace: {
|
||||
if (CompositorService.isNiri) {
|
||||
return getNiriActiveWorkspace()
|
||||
@@ -30,9 +35,9 @@ Rectangle {
|
||||
}
|
||||
if (CompositorService.isHyprland) {
|
||||
const baseList = getHyprlandWorkspaces()
|
||||
// Filter out special:scratch_term
|
||||
const filteredList = baseList.filter(ws => ws.name !== "special:scratch_term" && ws.id !== -98)
|
||||
return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList
|
||||
// Filter out special workspaces
|
||||
const filteredList = baseList.filter(ws => ws.id > -1)
|
||||
return SettingsData.showWorkspacePadding ? padWorkspaces(filteredList) : filteredList
|
||||
}
|
||||
return [1]
|
||||
}
|
||||
@@ -197,9 +202,9 @@ Rectangle {
|
||||
return currentMonitor.activeWorkspace?.id ?? 1
|
||||
}
|
||||
|
||||
readonly property real padding: isVertical
|
||||
? Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
: (widgetHeight - workspaceRow.implicitHeight) / 2
|
||||
readonly property real padding: Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
|
||||
readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
|
||||
|
||||
function getRealWorkspaces() {
|
||||
return root.workspaceList.filter(ws => {
|
||||
@@ -228,39 +233,121 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
width: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
|
||||
height: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground)
|
||||
return "transparent"
|
||||
const baseColor = Theme.widgetBaseBackgroundColor
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
width: isVertical ? barThickness : visualWidth
|
||||
height: isVertical ? visualHeight : barThickness
|
||||
visible: CompositorService.isNiri || CompositorService.isHyprland
|
||||
|
||||
Rectangle {
|
||||
id: visualBackground
|
||||
width: root.visualWidth
|
||||
height: root.visualHeight
|
||||
anchors.centerIn: parent
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.dankBarNoBackground)
|
||||
return "transparent"
|
||||
const baseColor = Theme.widgetBaseBackgroundColor
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
acceptedButtons: Qt.RightButton
|
||||
|
||||
property real scrollAccumulator: 0
|
||||
property real touchpadThreshold: 500
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton && CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
|
||||
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen
|
||||
}
|
||||
}
|
||||
|
||||
onWheel: wheel => {
|
||||
const deltaY = wheel.angleDelta.y
|
||||
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0
|
||||
const direction = deltaY < 0 ? 1 : -1
|
||||
|
||||
if (isMouseWheel) {
|
||||
switchWorkspace(direction)
|
||||
if (!SettingsData.workspaceScrolling || !CompositorService.isNiri) {
|
||||
switchWorkspace(direction)
|
||||
}
|
||||
else {
|
||||
const windows = root.sortedToplevels;
|
||||
if (windows.length < 2) {
|
||||
return;
|
||||
}
|
||||
let currentIndex = -1;
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
if (windows[i].activated) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
let nextIndex;
|
||||
if (deltaY < 0) {
|
||||
if (currentIndex === -1) {
|
||||
nextIndex = 0;
|
||||
} else {
|
||||
nextIndex = currentIndex +1;
|
||||
}
|
||||
} else {
|
||||
if (currentIndex === -1) {
|
||||
nextIndex = windows.length -1;
|
||||
} else {
|
||||
nextIndex = currentIndex - 1
|
||||
}
|
||||
}
|
||||
const nextWindow = windows[nextIndex];
|
||||
if (nextWindow) {
|
||||
nextWindow.activate();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
scrollAccumulator += deltaY
|
||||
|
||||
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
|
||||
const touchDirection = scrollAccumulator < 0 ? 1 : -1
|
||||
switchWorkspace(touchDirection)
|
||||
scrollAccumulator = 0
|
||||
if (!SettingsData.workspaceScrolling || !CompositorService.isNiri) {
|
||||
switchWorkspace(touchDirection)
|
||||
}
|
||||
else {
|
||||
const windows = root.sortedToplevels;
|
||||
if (windows.length < 2) {
|
||||
return;
|
||||
}
|
||||
let currentIndex = -1;
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
if (windows[i].activated) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
let nextIndex;
|
||||
if (deltaY < 0) {
|
||||
if (currentIndex === -1) {
|
||||
nextIndex = 0;
|
||||
} else {
|
||||
nextIndex = currentIndex +1;
|
||||
}
|
||||
} else {
|
||||
if (currentIndex === -1) {
|
||||
nextIndex = windows.length -1;
|
||||
} else {
|
||||
nextIndex = currentIndex - 1
|
||||
}
|
||||
}
|
||||
const nextWindow = windows[nextIndex];
|
||||
if (nextWindow) {
|
||||
nextWindow.activate();
|
||||
}
|
||||
}
|
||||
|
||||
scrollAccumulator = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +365,7 @@ Rectangle {
|
||||
Repeater {
|
||||
model: root.workspaceList
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: delegateRoot
|
||||
|
||||
property bool isActive: {
|
||||
@@ -310,6 +397,33 @@ Rectangle {
|
||||
property bool loadedHasIcon: false
|
||||
property var loadedIcons: []
|
||||
|
||||
readonly property real visualWidth: {
|
||||
if (root.isVertical) {
|
||||
return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5
|
||||
} else {
|
||||
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
||||
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons)
|
||||
const iconsWidth = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0)
|
||||
const baseWidth = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7
|
||||
return baseWidth + iconsWidth
|
||||
}
|
||||
return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7
|
||||
}
|
||||
}
|
||||
readonly property real visualHeight: {
|
||||
if (root.isVertical) {
|
||||
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
||||
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons)
|
||||
const iconsHeight = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0)
|
||||
const baseHeight = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7
|
||||
return baseHeight + iconsHeight
|
||||
}
|
||||
return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7
|
||||
} else {
|
||||
return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: dataUpdateTimer
|
||||
interval: 50
|
||||
@@ -351,94 +465,54 @@ Rectangle {
|
||||
dataUpdateTimer.restart()
|
||||
}
|
||||
|
||||
width: {
|
||||
if (root.isVertical) {
|
||||
return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5;
|
||||
} else {
|
||||
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
||||
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
|
||||
const iconsWidth = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0);
|
||||
const baseWidth = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7;
|
||||
return baseWidth + iconsWidth;
|
||||
}
|
||||
return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7;
|
||||
}
|
||||
}
|
||||
height: {
|
||||
if (root.isVertical) {
|
||||
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
||||
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
|
||||
const iconsHeight = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0);
|
||||
const baseHeight = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7;
|
||||
return baseHeight + iconsHeight;
|
||||
}
|
||||
return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7;
|
||||
} else {
|
||||
return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5;
|
||||
}
|
||||
}
|
||||
radius: Math.min(width, height) / 2
|
||||
color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
|
||||
|
||||
border.width: isUrgent && !isActive ? 2 : 0
|
||||
border.color: isUrgent && !isActive ? Theme.error : Theme.withAlpha(Theme.error, 0)
|
||||
|
||||
Behavior on width {
|
||||
enabled: (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
enabled: root.isVertical && (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
width: root.isVertical ? root.barThickness : visualWidth
|
||||
height: root.isVertical ? visualHeight : root.barThickness
|
||||
|
||||
Rectangle {
|
||||
id: visualContent
|
||||
width: delegateRoot.visualWidth
|
||||
height: delegateRoot.visualHeight
|
||||
anchors.centerIn: parent
|
||||
width: root.isVertical ? parent.width + Theme.spacingXL : parent.width
|
||||
height: root.isVertical ? parent.height : parent.height + Theme.spacingXL
|
||||
hoverEnabled: !isPlaceholder
|
||||
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||
enabled: !isPlaceholder
|
||||
onClicked: {
|
||||
if (isPlaceholder) {
|
||||
return
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
|
||||
|
||||
if (CompositorService.isNiri) {
|
||||
NiriService.switchToWorkspace(modelData - 1)
|
||||
} else if (CompositorService.isHyprland && modelData?.id) {
|
||||
Hyprland.dispatch(`workspace ${modelData.id}`)
|
||||
border.width: isUrgent && !isActive ? 2 : 0
|
||||
border.color: isUrgent && !isActive ? Theme.error : Theme.withAlpha(Theme.error, 0)
|
||||
|
||||
Behavior on width {
|
||||
enabled: (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: appIconsLoader
|
||||
anchors.fill: parent
|
||||
active: SettingsData.showWorkspaceApps
|
||||
Behavior on height {
|
||||
enabled: root.isVertical && (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: appIconsLoader
|
||||
anchors.fill: parent
|
||||
active: SettingsData.showWorkspaceApps
|
||||
sourceComponent: Item {
|
||||
Loader {
|
||||
id: contentRow
|
||||
@@ -639,8 +713,27 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: !isPlaceholder
|
||||
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||
enabled: !isPlaceholder
|
||||
onClicked: {
|
||||
if (isPlaceholder) {
|
||||
return
|
||||
}
|
||||
|
||||
if (CompositorService.isNiri) {
|
||||
NiriService.switchToWorkspace(modelData - 1)
|
||||
} else if (CompositorService.isHyprland && modelData?.id) {
|
||||
Hyprland.dispatch(`workspace ${modelData.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- LOGIC / TRIGGERS ---
|
||||
Component.onCompleted: updateAllData()
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -16,6 +16,8 @@ DankPopout {
|
||||
property var triggerScreen: null
|
||||
property int currentTabIndex: 0
|
||||
|
||||
keyboardFocusMode: WlrKeyboardFocus.Exclusive
|
||||
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
triggerSection = section
|
||||
triggerScreen = screen
|
||||
@@ -43,15 +45,49 @@ DankPopout {
|
||||
shouldBeVisible: dashVisible
|
||||
visible: shouldBeVisible
|
||||
|
||||
property bool __focusArmed: false
|
||||
property bool __contentReady: false
|
||||
|
||||
function __tryFocusOnce() {
|
||||
if (!__focusArmed) return
|
||||
const win = root.window
|
||||
if (!win || !win.visible) return
|
||||
if (!contentLoader.item) return
|
||||
|
||||
if (win.requestActivate) win.requestActivate()
|
||||
contentLoader.item.forceActiveFocus(Qt.TabFocusReason)
|
||||
|
||||
if (contentLoader.item.activeFocus)
|
||||
__focusArmed = false
|
||||
}
|
||||
|
||||
onDashVisibleChanged: {
|
||||
if (dashVisible) {
|
||||
__focusArmed = true
|
||||
__contentReady = !!contentLoader.item
|
||||
open()
|
||||
__tryFocusOnce()
|
||||
} else {
|
||||
__focusArmed = false
|
||||
__contentReady = false
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: contentLoader
|
||||
function onLoaded() {
|
||||
__contentReady = true
|
||||
if (__focusArmed) __tryFocusOnce()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.window ? root.window : null
|
||||
enabled: !!root.window
|
||||
function onVisibleChanged() { if (__focusArmed) __tryFocusOnce() }
|
||||
}
|
||||
|
||||
onBackgroundClicked: {
|
||||
dashVisible = false
|
||||
}
|
||||
@@ -67,18 +103,12 @@ DankPopout {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.shouldBeVisible) {
|
||||
forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
root.dashVisible = false
|
||||
event.accepted = true
|
||||
mainContainer.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (root.shouldBeVisible) {
|
||||
Qt.callLater(function() {
|
||||
@@ -86,7 +116,52 @@ DankPopout {
|
||||
})
|
||||
}
|
||||
}
|
||||
target: root
|
||||
}
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
root.dashVisible = false
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Tab && !(event.modifiers & Qt.ShiftModifier)) {
|
||||
let nextIndex = root.currentTabIndex + 1
|
||||
while (nextIndex < tabBar.model.length && tabBar.model[nextIndex] && tabBar.model[nextIndex].isAction) {
|
||||
nextIndex++
|
||||
}
|
||||
if (nextIndex >= tabBar.model.length) {
|
||||
nextIndex = 0
|
||||
}
|
||||
root.currentTabIndex = nextIndex
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
|
||||
let prevIndex = root.currentTabIndex - 1
|
||||
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
|
||||
prevIndex--
|
||||
}
|
||||
if (prevIndex < 0) {
|
||||
prevIndex = tabBar.model.length - 1
|
||||
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
|
||||
prevIndex--
|
||||
}
|
||||
}
|
||||
if (prevIndex >= 0) {
|
||||
root.currentTabIndex = prevIndex
|
||||
}
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
if (root.currentTabIndex === 2 && wallpaperTab.handleKeyEvent) {
|
||||
if (wallpaperTab.handleKeyEvent(event)) {
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -128,17 +203,29 @@ DankPopout {
|
||||
currentIndex: root.currentTabIndex
|
||||
spacing: Theme.spacingS
|
||||
equalWidthTabs: true
|
||||
enableArrowNavigation: false
|
||||
focus: false
|
||||
activeFocusOnTab: false
|
||||
nextFocusTarget: {
|
||||
const item = pages.currentItem
|
||||
if (!item)
|
||||
return null
|
||||
if (item.focusTarget)
|
||||
return item.focusTarget
|
||||
return item
|
||||
}
|
||||
|
||||
model: {
|
||||
let tabs = [
|
||||
{ icon: "dashboard", text: I18n.tr("Overview") },
|
||||
{ icon: "music_note", text: I18n.tr("Media") }
|
||||
{ icon: "music_note", text: I18n.tr("Media") },
|
||||
{ icon: "wallpaper", text: I18n.tr("Wallpapers") }
|
||||
]
|
||||
|
||||
|
||||
if (SettingsData.weatherEnabled) {
|
||||
tabs.push({ icon: "wb_sunny", text: I18n.tr("Weather") })
|
||||
}
|
||||
|
||||
|
||||
tabs.push({ icon: "settings", text: I18n.tr("Settings"), isAction: true })
|
||||
return tabs
|
||||
}
|
||||
@@ -148,7 +235,7 @@ DankPopout {
|
||||
}
|
||||
|
||||
onActionTriggered: function(index) {
|
||||
let settingsIndex = SettingsData.weatherEnabled ? 3 : 2
|
||||
let settingsIndex = SettingsData.weatherEnabled ? 4 : 3
|
||||
if (index === settingsIndex) {
|
||||
dashVisible = false
|
||||
settingsModal.show()
|
||||
@@ -168,7 +255,8 @@ DankPopout {
|
||||
implicitHeight: {
|
||||
if (currentIndex === 0) return overviewTab.implicitHeight
|
||||
if (currentIndex === 1) return mediaTab.implicitHeight
|
||||
if (SettingsData.weatherEnabled && currentIndex === 2) return weatherTab.implicitHeight
|
||||
if (currentIndex === 2) return wallpaperTab.implicitHeight
|
||||
if (SettingsData.weatherEnabled && currentIndex === 3) return weatherTab.implicitHeight
|
||||
return overviewTab.implicitHeight
|
||||
}
|
||||
currentIndex: root.currentTabIndex
|
||||
@@ -178,8 +266,8 @@ DankPopout {
|
||||
|
||||
onSwitchToWeatherTab: {
|
||||
if (SettingsData.weatherEnabled) {
|
||||
tabBar.currentIndex = 2
|
||||
tabBar.tabClicked(2)
|
||||
tabBar.currentIndex = 3
|
||||
tabBar.tabClicked(3)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,9 +281,16 @@ DankPopout {
|
||||
id: mediaTab
|
||||
}
|
||||
|
||||
WallpaperTab {
|
||||
id: wallpaperTab
|
||||
active: root.currentTabIndex === 2
|
||||
tabBarItem: tabBar
|
||||
keyForwardTarget: mainContainer
|
||||
}
|
||||
|
||||
WeatherTab {
|
||||
id: weatherTab
|
||||
visible: SettingsData.weatherEnabled && root.currentTabIndex === 2
|
||||
visible: SettingsData.weatherEnabled && root.currentTabIndex === 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,9 +128,9 @@ Item {
|
||||
|
||||
function getAudioDeviceIcon(device) {
|
||||
if (!device || !device.name) return "speaker"
|
||||
|
||||
|
||||
const name = device.name.toLowerCase()
|
||||
|
||||
|
||||
if (name.includes("bluez") || name.includes("bluetooth"))
|
||||
return "headset"
|
||||
if (name.includes("hdmi"))
|
||||
@@ -139,10 +139,10 @@ Item {
|
||||
return "headset"
|
||||
if (name.includes("analog") || name.includes("built-in"))
|
||||
return "speaker"
|
||||
|
||||
|
||||
return "speaker"
|
||||
}
|
||||
|
||||
|
||||
function getVolumeIcon(sink) {
|
||||
if (!sink || !sink.audio) return "volume_off"
|
||||
|
||||
@@ -262,8 +262,8 @@ Item {
|
||||
maybeFinishSwitch()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
property bool isSeeking: false
|
||||
|
||||
@@ -325,7 +325,7 @@ Item {
|
||||
return mouse.x < item.x || mouse.x > item.x + item.width ||
|
||||
mouse.y < item.y || mouse.y > item.y + item.height
|
||||
}
|
||||
|
||||
|
||||
if (playerSelectorButton.playersExpanded && clickOutside(playerSelectorDropdown)) {
|
||||
playerSelectorButton.playersExpanded = false
|
||||
}
|
||||
@@ -400,11 +400,11 @@ Item {
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Audio Output Devices (") + audioDevicesDropdown.availableDevices.length + ")"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -414,49 +414,49 @@ Item {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
bottomPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
|
||||
DankFlickable {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
contentHeight: deviceColumn.height
|
||||
clip: true
|
||||
|
||||
|
||||
Column {
|
||||
id: deviceColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
Repeater {
|
||||
model: audioDevicesDropdown.availableDevices
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseAreaLeft.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.surfaceContainerHigh
|
||||
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: modelData === AudioService.sink ? 2 : 1
|
||||
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: getAudioDeviceIcon(modelData)
|
||||
size: 20
|
||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 20 - Theme.spacingM * 2
|
||||
|
||||
|
||||
StyledText {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -466,7 +466,7 @@ Item {
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: modelData === AudioService.sink ? "Active" : "Available"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -477,7 +477,7 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseAreaLeft
|
||||
anchors.fill: parent
|
||||
@@ -490,7 +490,7 @@ Item {
|
||||
audioDevicesButton.devicesExpanded = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Behavior on border.color { ColorAnimation { duration: Anims.durShort } }
|
||||
}
|
||||
}
|
||||
@@ -793,7 +793,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -812,7 +812,7 @@ Item {
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 50
|
||||
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
@@ -92,12 +92,12 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
visible: showEventDetails
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: 32
|
||||
height: 32
|
||||
@@ -122,7 +122,7 @@ Rectangle {
|
||||
onClicked: root.showEventDetails = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -130,7 +130,14 @@ Rectangle {
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
height: 40
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: hasEvents ? (Qt.formatDate(selectedDate, "MMM d") + " • " + (selectedDateEvents.length === 1 ? "1 event" : selectedDateEvents.length + " events")) : Qt.formatDate(selectedDate, "MMM d")
|
||||
text: {
|
||||
const dateStr = Qt.formatDate(selectedDate, "MMM d")
|
||||
if (selectedDateEvents && selectedDateEvents.length > 0) {
|
||||
const eventCount = selectedDateEvents.length === 1 ? I18n.tr("1 event") : selectedDateEvents.length + " " + I18n.tr("events")
|
||||
return dateStr + " • " + eventCount
|
||||
}
|
||||
return dateStr
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -142,7 +149,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
height: 28
|
||||
visible: !showEventDetails
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
@@ -208,7 +215,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 18
|
||||
@@ -241,14 +248,14 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Grid {
|
||||
id: calendarGrid
|
||||
visible: !showEventDetails
|
||||
|
||||
|
||||
property date displayDate: systemClock.date
|
||||
property date selectedDate: systemClock.date
|
||||
|
||||
|
||||
readonly property date firstDay: {
|
||||
const firstOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
|
||||
return startOfWeek(firstOfMonth)
|
||||
@@ -334,7 +341,7 @@ Rectangle {
|
||||
visible: showEventDetails
|
||||
clip: true
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
|
||||
delegate: Rectangle {
|
||||
width: parent ? parent.width : 0
|
||||
height: eventContent.implicitHeight + Theme.spacingS
|
||||
@@ -370,7 +377,7 @@ Rectangle {
|
||||
|
||||
Column {
|
||||
id: eventContent
|
||||
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -392,7 +399,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (!modelData || modelData.allDay) {
|
||||
return "All day"
|
||||
return I18n.tr("All day")
|
||||
} else if (modelData.start && modelData.end) {
|
||||
const timeFormat = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP"
|
||||
const startTime = Qt.formatTime(modelData.start, timeFormat)
|
||||
@@ -412,7 +419,7 @@ Rectangle {
|
||||
|
||||
MouseArea {
|
||||
id: eventMouseArea
|
||||
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: modelData.url ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
@@ -4,9 +4,9 @@ import qs.Common
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
|
||||
|
||||
property int pad: Theme.spacingM
|
||||
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
|
||||
@@ -35,7 +35,7 @@ Card {
|
||||
width: 28
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (SettingsData.use24HourClock) {
|
||||
@@ -53,7 +53,7 @@ Card {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -66,7 +66,7 @@ Card {
|
||||
width: 28
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
|
||||
font.pixelSize: 48
|
||||
@@ -76,8 +76,33 @@ Card {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: SettingsData.showSeconds
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
|
||||
font.pixelSize: 48
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
width: 28
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
|
||||
font.pixelSize: 48
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
width: 28
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: systemClock?.date?.toLocaleDateString(Qt.locale(), "MMM dd")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
|
||||
@@ -92,7 +92,7 @@ Card {
|
||||
}
|
||||
// Just using truncated is always true initially idk
|
||||
property bool shouldUseShort: longTextWidth > availableWidth
|
||||
|
||||
|
||||
text: shouldUseShort ? UserInfoService.shortUptime : UserInfoService.uptime || "up 1h 23m"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
|
||||
@@ -47,18 +47,18 @@ Card {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingL
|
||||
visible: WeatherService.weather.available && WeatherService.weather.temp !== 0
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||
size: 48
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
|
||||
@@ -71,7 +71,7 @@ Card {
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Light
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
text: WeatherService.getWeatherCondition(WeatherService.weather.wCode)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
|
||||
523
Modules/DankDash/WallpaperTab.qml
Normal file
523
Modules/DankDash/WallpaperTab.qml
Normal file
@@ -0,0 +1,523 @@
|
||||
import Qt.labs.folderlistmodel
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.FileBrowser
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: 700
|
||||
implicitHeight: 410
|
||||
|
||||
property var wallpaperList: []
|
||||
property string wallpaperDir: ""
|
||||
property int currentPage: 0
|
||||
property int itemsPerPage: 16
|
||||
property int totalPages: Math.max(1, Math.ceil(wallpaperList.length / itemsPerPage))
|
||||
property bool active: false
|
||||
property Item focusTarget: wallpaperGrid
|
||||
property Item tabBarItem: null
|
||||
property int gridIndex: 0
|
||||
property Item keyForwardTarget: null
|
||||
property int lastPage: 0
|
||||
property bool enableAnimation: false
|
||||
|
||||
signal requestTabChange(int newIndex)
|
||||
|
||||
onCurrentPageChanged: {
|
||||
if (currentPage !== lastPage) {
|
||||
enableAnimation = false
|
||||
lastPage = currentPage
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible && active) {
|
||||
setInitialSelection()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadWallpapers()
|
||||
if (visible && active) {
|
||||
setInitialSelection()
|
||||
}
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && visible) {
|
||||
setInitialSelection()
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyEvent(event) {
|
||||
const columns = 4
|
||||
const rows = 4
|
||||
const currentRow = Math.floor(gridIndex / columns)
|
||||
const currentCol = gridIndex % columns
|
||||
const visibleCount = wallpaperGrid.model.length
|
||||
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
if (gridIndex >= 0) {
|
||||
const item = wallpaperGrid.currentItem
|
||||
if (item && item.wallpaperPath) {
|
||||
SessionData.setWallpaper(item.wallpaperPath)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Right) {
|
||||
if (gridIndex + 1 < visibleCount) {
|
||||
// Move right within current page
|
||||
gridIndex++
|
||||
} else if (gridIndex === visibleCount - 1 && currentPage < totalPages - 1) {
|
||||
// At last item in page, go to next page
|
||||
gridIndex = 0
|
||||
currentPage++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Left) {
|
||||
if (gridIndex > 0) {
|
||||
// Move left within current page
|
||||
gridIndex--
|
||||
} else if (gridIndex === 0 && currentPage > 0) {
|
||||
// At first item in page, go to previous page (last item)
|
||||
currentPage--
|
||||
gridIndex = Math.min(itemsPerPage - 1, wallpaperList.length - currentPage * itemsPerPage - 1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Down) {
|
||||
if (gridIndex + columns < visibleCount) {
|
||||
// Move down within current page
|
||||
gridIndex += columns
|
||||
} else if (gridIndex >= visibleCount - columns && currentPage < totalPages - 1) {
|
||||
// In last row, go to next page
|
||||
gridIndex = currentCol
|
||||
currentPage++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Up) {
|
||||
if (gridIndex >= columns) {
|
||||
// Move up within current page
|
||||
gridIndex -= columns
|
||||
} else if (gridIndex < columns && currentPage > 0) {
|
||||
// In first row, go to previous page (last row)
|
||||
currentPage--
|
||||
const prevPageCount = Math.min(itemsPerPage, wallpaperList.length - currentPage * itemsPerPage)
|
||||
const prevPageRows = Math.ceil(prevPageCount / columns)
|
||||
gridIndex = (prevPageRows - 1) * columns + currentCol
|
||||
gridIndex = Math.min(gridIndex, prevPageCount - 1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_PageUp && currentPage > 0) {
|
||||
gridIndex = 0
|
||||
currentPage--
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_PageDown && currentPage < totalPages - 1) {
|
||||
gridIndex = 0
|
||||
currentPage++
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Home && event.modifiers & Qt.ControlModifier) {
|
||||
gridIndex = 0
|
||||
currentPage = 0
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_End && event.modifiers & Qt.ControlModifier) {
|
||||
gridIndex = 0
|
||||
currentPage = totalPages - 1
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function setInitialSelection() {
|
||||
if (!SessionData.wallpaperPath) {
|
||||
gridIndex = 0
|
||||
return
|
||||
}
|
||||
|
||||
const startIndex = currentPage * itemsPerPage
|
||||
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length)
|
||||
const pageWallpapers = wallpaperList.slice(startIndex, endIndex)
|
||||
|
||||
for (let i = 0; i < pageWallpapers.length; i++) {
|
||||
if (pageWallpapers[i] === SessionData.wallpaperPath) {
|
||||
gridIndex = i
|
||||
return
|
||||
}
|
||||
}
|
||||
gridIndex = 0
|
||||
}
|
||||
|
||||
onWallpaperListChanged: {
|
||||
if (visible && active) {
|
||||
setInitialSelection()
|
||||
}
|
||||
}
|
||||
|
||||
function loadWallpapers() {
|
||||
const currentWallpaper = SessionData.wallpaperPath
|
||||
|
||||
// Try current wallpaper path / fallback to wallpaperLastPath
|
||||
if (!currentWallpaper || currentWallpaper.startsWith("#") || currentWallpaper.startsWith("we:")) {
|
||||
if (CacheData.wallpaperLastPath && CacheData.wallpaperLastPath !== "") {
|
||||
wallpaperDir = CacheData.wallpaperLastPath
|
||||
} else {
|
||||
wallpaperDir = ""
|
||||
wallpaperList = []
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
|
||||
}
|
||||
|
||||
function updateWallpaperList() {
|
||||
if (!wallpaperFolderModel || wallpaperFolderModel.count === 0) {
|
||||
wallpaperList = []
|
||||
currentPage = 0
|
||||
gridIndex = 0
|
||||
return
|
||||
}
|
||||
|
||||
// Build list from FolderListModel
|
||||
const files = []
|
||||
for (let i = 0; i < wallpaperFolderModel.count; i++) {
|
||||
const filePath = wallpaperFolderModel.get(i, "filePath")
|
||||
if (filePath) {
|
||||
// Remove file:// prefix if present
|
||||
const cleanPath = filePath.toString().replace(/^file:\/\//, '')
|
||||
files.push(cleanPath)
|
||||
}
|
||||
}
|
||||
|
||||
wallpaperList = files
|
||||
|
||||
const currentPath = SessionData.wallpaperPath
|
||||
const selectedIndex = currentPath ? wallpaperList.indexOf(currentPath) : -1
|
||||
|
||||
if (selectedIndex >= 0) {
|
||||
currentPage = Math.floor(selectedIndex / itemsPerPage)
|
||||
gridIndex = selectedIndex % itemsPerPage
|
||||
} else {
|
||||
const maxPage = Math.max(0, Math.ceil(files.length / itemsPerPage) - 1)
|
||||
currentPage = Math.min(Math.max(0, currentPage), maxPage)
|
||||
gridIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onWallpaperPathChanged() {
|
||||
loadWallpapers()
|
||||
}
|
||||
}
|
||||
|
||||
FolderListModel {
|
||||
id: wallpaperFolderModel
|
||||
|
||||
showDirsFirst: false
|
||||
showDotAndDotDot: false
|
||||
showHidden: false
|
||||
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
|
||||
showFiles: true
|
||||
showDirs: false
|
||||
sortField: FolderListModel.Name
|
||||
folder: wallpaperDir ? "file://" + wallpaperDir : ""
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === FolderListModel.Ready) {
|
||||
updateWallpaperList()
|
||||
}
|
||||
}
|
||||
|
||||
onCountChanged: {
|
||||
if (status === FolderListModel.Ready) {
|
||||
updateWallpaperList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: wallpaperBrowserLoader
|
||||
active: false
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: FileBrowserModal {
|
||||
Component.onCompleted: {
|
||||
open()
|
||||
}
|
||||
browserTitle: "Select Wallpaper Directory"
|
||||
browserIcon: "folder_open"
|
||||
browserType: "wallpaper"
|
||||
showHiddenFiles: false
|
||||
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
|
||||
allowStacking: true
|
||||
|
||||
onFileSelected: (path) => {
|
||||
// Set the selected wallpaper
|
||||
const cleanPath = path.replace(/^file:\/\//, '')
|
||||
SessionData.setWallpaper(cleanPath)
|
||||
|
||||
// Extract directory from the selected file and load all wallpapers
|
||||
const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/'))
|
||||
if (dirPath) {
|
||||
wallpaperDir = dirPath
|
||||
CacheData.wallpaperLastPath = dirPath
|
||||
CacheData.saveCache()
|
||||
}
|
||||
close()
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
Qt.callLater(() => wallpaperBrowserLoader.active = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height - 50
|
||||
|
||||
GridView {
|
||||
id: wallpaperGrid
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Theme.spacingS
|
||||
height: parent.height - Theme.spacingS
|
||||
cellWidth: width / 4
|
||||
cellHeight: height / 4
|
||||
clip: true
|
||||
enabled: root.active
|
||||
interactive: root.active
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
keyNavigationEnabled: false
|
||||
activeFocusOnTab: false
|
||||
highlightFollowsCurrentItem: true
|
||||
highlightMoveDuration: enableAnimation ? Theme.shortDuration : 0
|
||||
focus: false
|
||||
|
||||
highlight: Item {
|
||||
z: 1000
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
color: "transparent"
|
||||
border.width: 3
|
||||
border.color: Theme.primary
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
model: {
|
||||
const startIndex = currentPage * itemsPerPage
|
||||
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length)
|
||||
return wallpaperList.slice(startIndex, endIndex)
|
||||
}
|
||||
|
||||
onModelChanged: {
|
||||
const clampedIndex = model.length > 0 ? Math.min(Math.max(0, gridIndex), model.length - 1) : 0
|
||||
if (gridIndex !== clampedIndex) {
|
||||
gridIndex = clampedIndex
|
||||
}
|
||||
}
|
||||
|
||||
onCountChanged: {
|
||||
if (count > 0) {
|
||||
const clampedIndex = Math.min(gridIndex, count - 1)
|
||||
currentIndex = clampedIndex
|
||||
positionViewAtIndex(clampedIndex, GridView.Contain)
|
||||
}
|
||||
enableAnimation = true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onGridIndexChanged() {
|
||||
if (enableAnimation && wallpaperGrid.count > 0) {
|
||||
wallpaperGrid.currentIndex = gridIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
width: wallpaperGrid.cellWidth
|
||||
height: wallpaperGrid.cellHeight
|
||||
|
||||
property string wallpaperPath: modelData || ""
|
||||
property bool isSelected: SessionData.wallpaperPath === modelData
|
||||
|
||||
Rectangle {
|
||||
id: wallpaperCard
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
color: Theme.surfaceContainerHighest
|
||||
radius: Theme.cornerRadius
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent"
|
||||
radius: parent.radius
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: thumbnailImage
|
||||
anchors.fill: parent
|
||||
source: modelData ? `file://${modelData}` : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: true
|
||||
smooth: true
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1.0
|
||||
maskSource: ShaderEffectSource {
|
||||
sourceItem: Rectangle {
|
||||
width: thumbnailImage.width
|
||||
height: thumbnailImage.height
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: thumbnailImage.status === Image.Loading
|
||||
visible: running
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
cornerRadius: parent.radius
|
||||
stateColor: Theme.primary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wallpaperMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
gridIndex = index
|
||||
if (modelData) {
|
||||
SessionData.setWallpaper(modelData)
|
||||
}
|
||||
// Don't steal focus - let mainContainer keep it for keyboard nav
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
visible: wallpaperList.length === 0
|
||||
text: "No wallpapers found\n\nClick the folder icon below to browse"
|
||||
font.pixelSize: 14
|
||||
color: Theme.outline
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 50
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Row {
|
||||
id: controlsRow
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "skip_previous"
|
||||
iconSize: 20
|
||||
buttonSize: 32
|
||||
enabled: currentPage > 0
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
onClicked: {
|
||||
if (currentPage > 0) {
|
||||
currentPage--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: wallpaperList.length > 0 ? `${wallpaperList.length} wallpapers • ${currentPage + 1} / ${totalPages}` : "No wallpapers"
|
||||
font.pixelSize: 14
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "skip_next"
|
||||
iconSize: 20
|
||||
buttonSize: 32
|
||||
enabled: currentPage < totalPages - 1
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
onClicked: {
|
||||
if (currentPage < totalPages - 1) {
|
||||
currentPage++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: browseButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "folder_open"
|
||||
iconSize: 20
|
||||
buttonSize: 32
|
||||
opacity: 0.7
|
||||
onClicked: wallpaperBrowserLoader.active = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ Item {
|
||||
anchors.left: tempText.right
|
||||
anchors.leftMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
@@ -540,7 +540,7 @@ Item {
|
||||
width: (parent.width - Theme.spacingXS * 6) / 7
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
|
||||
property var dayDate: {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() + index)
|
||||
|
||||
@@ -34,8 +34,8 @@ Variants {
|
||||
property real backgroundTransparency: SettingsData.dockTransparency
|
||||
property bool groupByApp: SettingsData.dockGroupByApp
|
||||
|
||||
readonly property real widgetHeight: Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
|
||||
readonly property real effectiveBarHeight: Math.max(widgetHeight + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
|
||||
readonly property real widgetHeight: SettingsData.dockIconSize
|
||||
readonly property real effectiveBarHeight: widgetHeight + SettingsData.dockSpacing * 2 + 10
|
||||
readonly property real barSpacing: {
|
||||
const barIsHorizontal = (SettingsData.dankBarPosition === SettingsData.Position.Top || SettingsData.dankBarPosition === SettingsData.Position.Bottom)
|
||||
const barIsVertical = (SettingsData.dankBarPosition === SettingsData.Position.Left || SettingsData.dankBarPosition === SettingsData.Position.Right)
|
||||
@@ -60,14 +60,6 @@ Variants {
|
||||
|
||||
|
||||
property bool contextMenuOpen: (dockVariants.contextMenu && dockVariants.contextMenu.visible && dockVariants.contextMenu.screen === modelData)
|
||||
property bool windowIsFullscreen: {
|
||||
if (!ToplevelManager.activeToplevel) {
|
||||
return false
|
||||
}
|
||||
const activeWindow = ToplevelManager.activeToplevel
|
||||
const fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"]
|
||||
return fullscreenApps.some(app => activeWindow.appId && activeWindow.appId.toLowerCase().includes(app))
|
||||
}
|
||||
property bool revealSticky: false
|
||||
|
||||
Timer {
|
||||
@@ -81,7 +73,7 @@ Variants {
|
||||
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
|
||||
return true
|
||||
}
|
||||
return (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky) && !windowIsFullscreen
|
||||
return !autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky
|
||||
}
|
||||
|
||||
onContextMenuOpenChanged: {
|
||||
@@ -99,100 +91,147 @@ Variants {
|
||||
}
|
||||
|
||||
screen: modelData
|
||||
visible: SettingsData.showDock || (CompositorService.isNiri && SettingsData.dockOpenOnOverview && NiriService.inOverview)
|
||||
visible: {
|
||||
if (CompositorService.isNiri && NiriService.inOverview) {
|
||||
return SettingsData.dockOpenOnOverview
|
||||
}
|
||||
return SettingsData.showDock
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
|
||||
exclusiveZone: {
|
||||
if (!SettingsData.showDock || autoHide) return -1
|
||||
if (barSpacing > 0) return -1
|
||||
return px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap)
|
||||
return px(effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap)
|
||||
}
|
||||
|
||||
property real animationHeadroom: Math.ceil(SettingsData.dockIconSize * 0.35)
|
||||
|
||||
implicitWidth: isVertical ? (px(effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
|
||||
implicitHeight: !isVertical ? (px(effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
|
||||
|
||||
Item {
|
||||
id: maskItem
|
||||
parent: dock.contentItem
|
||||
visible: false
|
||||
x: {
|
||||
const baseX = dockCore.x + dockMouseArea.x
|
||||
if (isVertical && SettingsData.dockPosition === SettingsData.Position.Right) {
|
||||
return baseX - animationHeadroom
|
||||
}
|
||||
return baseX
|
||||
}
|
||||
y: {
|
||||
const baseY = dockCore.y + dockMouseArea.y
|
||||
if (!isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom) {
|
||||
return baseY - animationHeadroom
|
||||
}
|
||||
return baseY
|
||||
}
|
||||
width: dockMouseArea.width + (isVertical ? animationHeadroom : 0)
|
||||
height: dockMouseArea.height + (!isVertical ? animationHeadroom : 0)
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: dockMouseArea
|
||||
item: maskItem
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: appTooltip
|
||||
z: 1000
|
||||
|
||||
property var hoveredButton: {
|
||||
if (!dockApps.children[0]) {
|
||||
return null
|
||||
}
|
||||
const layoutItem = dockApps.children[0]
|
||||
const flowLayout = layoutItem.children[0]
|
||||
let repeater = null
|
||||
for (var i = 0; i < flowLayout.children.length; i++) {
|
||||
const child = flowLayout.children[i]
|
||||
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
|
||||
repeater = child
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!repeater || !repeater.itemAt) {
|
||||
return null
|
||||
}
|
||||
for (var i = 0; i < repeater.count; i++) {
|
||||
const item = repeater.itemAt(i)
|
||||
if (item && item.dockButton && item.dockButton.showTooltip) {
|
||||
return item.dockButton
|
||||
}
|
||||
}
|
||||
property var hoveredButton: {
|
||||
if (!dockApps.children[0]) {
|
||||
return null
|
||||
}
|
||||
const layoutItem = dockApps.children[0]
|
||||
const flowLayout = layoutItem.children[0]
|
||||
let repeater = null
|
||||
for (var i = 0; i < flowLayout.children.length; i++) {
|
||||
const child = flowLayout.children[i]
|
||||
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
|
||||
repeater = child
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!repeater || !repeater.itemAt) {
|
||||
return null
|
||||
}
|
||||
for (var i = 0; i < repeater.count; i++) {
|
||||
const item = repeater.itemAt(i)
|
||||
if (item && item.dockButton && item.dockButton.showTooltip) {
|
||||
return item.dockButton
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
property string tooltipText: hoveredButton ? hoveredButton.tooltipText : ""
|
||||
DankTooltip {
|
||||
id: dockTooltip
|
||||
targetScreen: dock.screen
|
||||
}
|
||||
|
||||
visible: hoveredButton !== null && tooltipText !== ""
|
||||
width: px(tooltipLabel.implicitWidth + 24)
|
||||
height: px(tooltipLabel.implicitHeight + 12)
|
||||
Timer {
|
||||
id: tooltipRevealDelay
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: dock.showTooltipForHoveredButton()
|
||||
}
|
||||
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
x: {
|
||||
if (!hoveredButton) return 0
|
||||
const buttonPos = hoveredButton.mapToItem(dock.contentItem, 0, 0)
|
||||
if (!dock.isVertical) {
|
||||
return buttonPos.x + hoveredButton.width / 2 - width / 2
|
||||
} else {
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Right) {
|
||||
return buttonPos.x - width - Theme.spacingS
|
||||
function showTooltipForHoveredButton() {
|
||||
dockTooltip.hide()
|
||||
if (dock.hoveredButton && dock.reveal && !slideXAnimation.running && !slideYAnimation.running) {
|
||||
const buttonGlobalPos = dock.hoveredButton.mapToGlobal(0, 0)
|
||||
const tooltipText = dock.hoveredButton.tooltipText || ""
|
||||
if (tooltipText) {
|
||||
const screenX = dock.screen ? (dock.screen.x || 0) : 0
|
||||
const screenY = dock.screen ? (dock.screen.y || 0) : 0
|
||||
const screenHeight = dock.screen ? dock.screen.height : 0
|
||||
if (!dock.isVertical) {
|
||||
const isBottom = SettingsData.dockPosition === SettingsData.Position.Bottom
|
||||
const globalX = buttonGlobalPos.x + dock.hoveredButton.width / 2
|
||||
const screenRelativeY = isBottom
|
||||
? (screenHeight - dock.effectiveBarHeight - SettingsData.dockSpacing - SettingsData.dockBottomGap - 35)
|
||||
: (buttonGlobalPos.y - screenY + dock.hoveredButton.height + Theme.spacingS)
|
||||
dockTooltip.show(tooltipText,
|
||||
globalX,
|
||||
screenRelativeY,
|
||||
dock.screen,
|
||||
false, false)
|
||||
} else {
|
||||
return buttonPos.x + hoveredButton.width + Theme.spacingS
|
||||
const isLeft = SettingsData.dockPosition === SettingsData.Position.Left
|
||||
const tooltipOffset = dock.effectiveBarHeight + SettingsData.dockSpacing + Theme.spacingXS
|
||||
const tooltipX = isLeft ? tooltipOffset : (dock.screen.width - tooltipOffset)
|
||||
const screenRelativeY = buttonGlobalPos.y - screenY + dock.hoveredButton.height / 2
|
||||
dockTooltip.show(tooltipText,
|
||||
screenX + tooltipX,
|
||||
screenRelativeY,
|
||||
dock.screen,
|
||||
isLeft,
|
||||
!isLeft)
|
||||
}
|
||||
}
|
||||
}
|
||||
y: {
|
||||
if (!hoveredButton) return 0
|
||||
const buttonPos = hoveredButton.mapToItem(dock.contentItem, 0, 0)
|
||||
if (!dock.isVertical) {
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
|
||||
return buttonPos.y - height - Theme.spacingS
|
||||
} else {
|
||||
return buttonPos.y + hoveredButton.height + Theme.spacingS
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: dock
|
||||
function onRevealChanged() {
|
||||
if (!dock.reveal) {
|
||||
tooltipRevealDelay.stop()
|
||||
dockTooltip.hide()
|
||||
} else {
|
||||
return buttonPos.y + hoveredButton.height / 2 - height / 2
|
||||
tooltipRevealDelay.restart()
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: tooltipLabel
|
||||
anchors.centerIn: parent
|
||||
text: appTooltip.tooltipText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
function onHoveredButtonChanged() {
|
||||
dock.showTooltipForHoveredButton()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockCore
|
||||
anchors.fill: parent
|
||||
x: isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? animationHeadroom : 0
|
||||
y: !isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? animationHeadroom : 0
|
||||
|
||||
Connections {
|
||||
target: dockMouseArea
|
||||
@@ -213,21 +252,21 @@ Variants {
|
||||
property real currentScreen: modelData ? modelData : dock.screen
|
||||
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920
|
||||
property real screenHeight: currentScreen ? currentScreen.geometry.height : 1080
|
||||
property real maxDockWidth: Math.min(screenWidth * 0.8, 1200)
|
||||
property real maxDockHeight: Math.min(screenHeight * 0.8, 1200)
|
||||
property real maxDockWidth: screenWidth * 0.98
|
||||
property real maxDockHeight: screenHeight * 0.98
|
||||
|
||||
height: {
|
||||
if (dock.isVertical) {
|
||||
return dock.reveal ? Math.min(dockBackground.implicitHeight + 32, maxDockHeight) : Math.min(Math.max(dockBackground.implicitHeight + 64, 200), screenHeight * 0.5)
|
||||
return dock.reveal ? Math.min(dockBackground.implicitHeight + 4, maxDockHeight) : Math.min(Math.max(dockBackground.implicitHeight + 64, 200), screenHeight * 0.5)
|
||||
} else {
|
||||
return dock.reveal ? px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1
|
||||
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1
|
||||
}
|
||||
}
|
||||
width: {
|
||||
if (dock.isVertical) {
|
||||
return dock.reveal ? px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1
|
||||
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1
|
||||
} else {
|
||||
return dock.reveal ? Math.min(dockBackground.implicitWidth + 32, maxDockWidth) : Math.min(Math.max(dockBackground.implicitWidth + 64, 200), screenWidth * 0.5)
|
||||
return dock.reveal ? Math.min(dockBackground.implicitWidth + 4, maxDockWidth) : Math.min(Math.max(dockBackground.implicitWidth + 64, 200), screenWidth * 0.5)
|
||||
}
|
||||
}
|
||||
anchors {
|
||||
@@ -243,14 +282,14 @@ Variants {
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
@@ -258,13 +297,14 @@ Variants {
|
||||
Item {
|
||||
id: dockContainer
|
||||
anchors.fill: parent
|
||||
clip: false
|
||||
|
||||
transform: Translate {
|
||||
id: dockSlide
|
||||
x: {
|
||||
if (!dock.isVertical) return 0
|
||||
if (dock.reveal) return 0
|
||||
const hideDistance = 58 + SettingsData.dockSpacing + SettingsData.dockBottomGap + 10
|
||||
const hideDistance = dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + 10
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Right) {
|
||||
return hideDistance
|
||||
} else {
|
||||
@@ -274,7 +314,7 @@ Variants {
|
||||
y: {
|
||||
if (dock.isVertical) return 0
|
||||
if (dock.reveal) return 0
|
||||
const hideDistance = 58 + SettingsData.dockSpacing + SettingsData.dockBottomGap + 10
|
||||
const hideDistance = dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + 10
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
|
||||
return hideDistance
|
||||
} else {
|
||||
@@ -284,14 +324,16 @@ Variants {
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
id: slideXAnimation
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
id: slideYAnimation
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
@@ -301,17 +343,17 @@ Variants {
|
||||
id: dockBackground
|
||||
objectName: "dockBackground"
|
||||
anchors {
|
||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined
|
||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined) : undefined
|
||||
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
|
||||
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
|
||||
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.left) : undefined
|
||||
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined) : undefined
|
||||
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
|
||||
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
|
||||
}
|
||||
anchors.topMargin: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? 0 : barSpacing + 4) : 0
|
||||
anchors.bottomMargin: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? barSpacing + 1 : 0) : 0
|
||||
anchors.leftMargin: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? 0 : barSpacing + 4) : 0
|
||||
anchors.rightMargin: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? barSpacing + 1 : 0) : 0
|
||||
anchors.topMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Top ? barSpacing + 1 : 0
|
||||
anchors.bottomMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? barSpacing + 1 : 0
|
||||
anchors.leftMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Left ? barSpacing + 1 : 0
|
||||
anchors.rightMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? barSpacing + 1 : 0
|
||||
|
||||
implicitWidth: dock.isVertical ? (dockApps.implicitHeight + SettingsData.dockSpacing * 2) : (dockApps.implicitWidth + SettingsData.dockSpacing * 2)
|
||||
implicitHeight: dock.isVertical ? (dockApps.implicitWidth + SettingsData.dockSpacing * 2) : (dockApps.implicitHeight + SettingsData.dockSpacing * 2)
|
||||
@@ -322,32 +364,34 @@ Variants {
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
layer.enabled: true
|
||||
clip: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
|
||||
radius: parent.radius
|
||||
}
|
||||
}
|
||||
|
||||
DockApps {
|
||||
id: dockApps
|
||||
DockApps {
|
||||
id: dockApps
|
||||
|
||||
anchors.top: !dock.isVertical ? parent.top : undefined
|
||||
anchors.bottom: !dock.isVertical ? parent.bottom : undefined
|
||||
anchors.horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
|
||||
anchors.left: dock.isVertical ? parent.left : undefined
|
||||
anchors.right: dock.isVertical ? parent.right : undefined
|
||||
anchors.verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
|
||||
anchors.topMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.bottomMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.leftMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.rightMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top ? dockBackground.top : undefined) : undefined
|
||||
anchors.bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? dockBackground.bottom : undefined) : undefined
|
||||
anchors.horizontalCenter: !dock.isVertical ? dockBackground.horizontalCenter : undefined
|
||||
anchors.left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Left ? dockBackground.left : undefined) : undefined
|
||||
anchors.right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? dockBackground.right : undefined) : undefined
|
||||
anchors.verticalCenter: dock.isVertical ? dockBackground.verticalCenter : undefined
|
||||
anchors.topMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.bottomMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.leftMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.rightMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
|
||||
contextMenu: dockVariants.contextMenu
|
||||
groupByApp: dock.groupByApp
|
||||
isVertical: dock.isVertical
|
||||
}
|
||||
contextMenu: dockVariants.contextMenu
|
||||
groupByApp: dock.groupByApp
|
||||
isVertical: dock.isVertical
|
||||
dockScreen: dock.screen
|
||||
iconSize: dock.widgetHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ Item {
|
||||
property var contextMenu: null
|
||||
property var dockApps: null
|
||||
property int index: -1
|
||||
property var parentDockScreen: null
|
||||
property bool longPressing: false
|
||||
property bool dragging: false
|
||||
property point dragStartPos: Qt.point(0, 0)
|
||||
@@ -26,6 +27,7 @@ Item {
|
||||
property bool isHovered: mouseArea.containsMouse && !dragging
|
||||
property bool showTooltip: mouseArea.containsMouse && !dragging
|
||||
property var cachedDesktopEntry: null
|
||||
property real actualIconSize: 40
|
||||
|
||||
function updateDesktopEntry() {
|
||||
if (!appData || appData.appId === "__SEPARATOR__") {
|
||||
@@ -88,9 +90,6 @@ Item {
|
||||
return cachedDesktopEntry && cachedDesktopEntry.name ? cachedDesktopEntry.name : appData.appId
|
||||
}
|
||||
|
||||
width: 40
|
||||
height: 40
|
||||
|
||||
function getToplevelObject() {
|
||||
if (!appData) {
|
||||
return null
|
||||
@@ -144,34 +143,47 @@ Item {
|
||||
return toplevels
|
||||
}
|
||||
onIsHoveredChanged: {
|
||||
if (mouseArea.pressed) return
|
||||
|
||||
if (isHovered) {
|
||||
exitAnimation.stop()
|
||||
if (!bounceAnimation.running)
|
||||
if (!bounceAnimation.running) {
|
||||
bounceAnimation.restart()
|
||||
}
|
||||
} else {
|
||||
bounceAnimation.stop()
|
||||
exitAnimation.restart()
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool animateX: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
||||
readonly property real animationDistance: actualIconSize
|
||||
readonly property real animationDirection: {
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom) return -1
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Top) return 1
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Right) return -1
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Left) return 1
|
||||
return -1
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: bounceAnimation
|
||||
|
||||
running: false
|
||||
|
||||
NumberAnimation {
|
||||
target: translateY
|
||||
property: "y"
|
||||
to: -10
|
||||
target: iconTransform
|
||||
property: animateX ? "x" : "y"
|
||||
to: animationDirection * animationDistance * 0.25
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedAccel
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: translateY
|
||||
property: "y"
|
||||
to: -8
|
||||
target: iconTransform
|
||||
property: animateX ? "x" : "y"
|
||||
to: animationDirection * animationDistance * 0.2
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedDecel
|
||||
@@ -182,24 +194,14 @@ Item {
|
||||
id: exitAnimation
|
||||
|
||||
running: false
|
||||
target: translateY
|
||||
property: "y"
|
||||
target: iconTransform
|
||||
property: animateX ? "x" : "y"
|
||||
to: 0
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedDecel
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||
border.width: 2
|
||||
border.color: Theme.primary
|
||||
visible: dragging
|
||||
z: -1
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: longPressTimer
|
||||
|
||||
@@ -216,7 +218,6 @@ Item {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: -20
|
||||
hoverEnabled: true
|
||||
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
@@ -252,7 +253,7 @@ Item {
|
||||
if (dragging) {
|
||||
dragOffset = Qt.point(mouse.x - dragStartPos.x, mouse.y - dragStartPos.y)
|
||||
if (dockApps) {
|
||||
const threshold = 40
|
||||
const threshold = actualIconSize
|
||||
let newTargetIndex = targetIndex
|
||||
if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1) {
|
||||
newTargetIndex = targetIndex + 1
|
||||
@@ -317,7 +318,7 @@ Item {
|
||||
}
|
||||
} else {
|
||||
if (contextMenu) {
|
||||
contextMenu.showForButton(root, appData, 65, true, cachedDesktopEntry)
|
||||
contextMenu.showForButton(root, appData, root.height + 25, true, cachedDesktopEntry, parentDockScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,7 +335,7 @@ Item {
|
||||
}
|
||||
} else if (appData && appData.type === "grouped") {
|
||||
if (contextMenu) {
|
||||
contextMenu.showForButton(root, appData, 40, false, cachedDesktopEntry)
|
||||
contextMenu.showForButton(root, appData, root.height, false, cachedDesktopEntry, parentDockScreen)
|
||||
}
|
||||
} else if (appData && appData.appId) {
|
||||
const desktopEntry = cachedDesktopEntry
|
||||
@@ -351,7 +352,7 @@ Item {
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (contextMenu && appData) {
|
||||
contextMenu.showForButton(root, appData, 40, false, cachedDesktopEntry)
|
||||
contextMenu.showForButton(root, appData, root.height, false, cachedDesktopEntry, parentDockScreen)
|
||||
} else {
|
||||
console.warn("No context menu or appData available")
|
||||
}
|
||||
@@ -359,123 +360,205 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
Item {
|
||||
id: visualContent
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.centerIn: parent
|
||||
implicitSize: 40
|
||||
source: {
|
||||
if (appData.appId === "__SEPARATOR__") {
|
||||
return ""
|
||||
}
|
||||
const moddedId = Paths.moddedAppId(appData.appId)
|
||||
if (moddedId.toLowerCase().includes("steam_app")) {
|
||||
return ""
|
||||
}
|
||||
return cachedDesktopEntry && cachedDesktopEntry.icon ? Quickshell.iconPath(cachedDesktopEntry.icon, true) : ""
|
||||
transform: Translate {
|
||||
id: iconTransform
|
||||
x: 0
|
||||
y: 0
|
||||
}
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: 40
|
||||
name: "sports_esports"
|
||||
color: Theme.surfaceText
|
||||
visible: {
|
||||
if (!appData || !appData.appId || appData.appId === "__SEPARATOR__") {
|
||||
return false
|
||||
}
|
||||
const moddedId = Paths.moddedAppId(appData.appId)
|
||||
return moddedId.toLowerCase().includes("steam_app")
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||
border.width: 2
|
||||
border.color: Theme.primary
|
||||
visible: dragging
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 40
|
||||
anchors.centerIn: parent
|
||||
visible: iconImg.status !== Image.Ready
|
||||
color: Theme.surfaceLight
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.primarySelected
|
||||
IconImage {
|
||||
id: iconImg
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!appData || !appData.appId) {
|
||||
return "?"
|
||||
implicitSize: actualIconSize
|
||||
source: {
|
||||
if (appData.appId === "__SEPARATOR__") {
|
||||
return ""
|
||||
}
|
||||
|
||||
const desktopEntry = cachedDesktopEntry
|
||||
if (desktopEntry && desktopEntry.name) {
|
||||
return desktopEntry.name.charAt(0).toUpperCase()
|
||||
const moddedId = Paths.moddedAppId(appData.appId)
|
||||
if (moddedId.toLowerCase().includes("steam_app")) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return appData.appId.charAt(0).toUpperCase()
|
||||
return cachedDesktopEntry && cachedDesktopEntry.icon ? Quickshell.iconPath(cachedDesktopEntry.icon, true) : ""
|
||||
}
|
||||
font.pixelSize: 14
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
}
|
||||
|
||||
// Indicator for running/focused state
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -2
|
||||
spacing: 2
|
||||
visible: appData && (appData.isRunning || appData.type === "window" || (appData.type === "grouped" && appData.windowCount > 0))
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
if (!appData) return 0
|
||||
if (appData.type === "grouped") {
|
||||
return Math.min(appData.windowCount, 4)
|
||||
} else if (appData.type === "window" || appData.isRunning) {
|
||||
return 1
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: actualIconSize
|
||||
name: "sports_esports"
|
||||
color: Theme.surfaceText
|
||||
visible: {
|
||||
if (!appData || !appData.appId || appData.appId === "__SEPARATOR__") {
|
||||
return false
|
||||
}
|
||||
return 0
|
||||
const moddedId = Paths.moddedAppId(appData.appId)
|
||||
return moddedId.toLowerCase().includes("steam_app")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: appData && appData.type === "grouped" && appData.windowCount > 1 ? 4 : 8
|
||||
height: 2
|
||||
radius: 1
|
||||
color: {
|
||||
if (!appData) {
|
||||
return "transparent"
|
||||
Rectangle {
|
||||
width: actualIconSize
|
||||
height: actualIconSize
|
||||
anchors.centerIn: parent
|
||||
visible: iconImg.status !== Image.Ready
|
||||
color: Theme.surfaceLight
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.primarySelected
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!appData || !appData.appId) {
|
||||
return "?"
|
||||
}
|
||||
|
||||
if (appData.type !== "grouped" || appData.windowCount === 1) {
|
||||
if (isWindowFocused) {
|
||||
return Theme.primary
|
||||
const desktopEntry = cachedDesktopEntry
|
||||
if (desktopEntry && desktopEntry.name) {
|
||||
return desktopEntry.name.charAt(0).toUpperCase()
|
||||
}
|
||||
|
||||
return appData.appId.charAt(0).toUpperCase()
|
||||
}
|
||||
font.pixelSize: Math.max(8, parent.width * 0.35)
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.horizontalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.horizontalCenter
|
||||
anchors.verticalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? parent.verticalCenter : undefined
|
||||
anchors.bottom: SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined
|
||||
anchors.top: SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined
|
||||
anchors.left: SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined
|
||||
anchors.right: SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined
|
||||
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? -2 : 0
|
||||
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Top ? -2 : 0
|
||||
anchors.leftMargin: SettingsData.dockPosition === SettingsData.Position.Left ? -2 : 0
|
||||
anchors.rightMargin: SettingsData.dockPosition === SettingsData.Position.Right ? -2 : 0
|
||||
|
||||
sourceComponent: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? columnIndicator : rowIndicator
|
||||
|
||||
visible: {
|
||||
if (!appData) return false
|
||||
if (appData.type === "window") return true
|
||||
if (appData.type === "grouped") return appData.windowCount > 0
|
||||
return appData.isRunning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: rowIndicator
|
||||
|
||||
Row {
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
if (!appData) return 0
|
||||
if (appData.type === "grouped") {
|
||||
return Math.min(appData.windowCount, 4)
|
||||
} else if (appData.type === "window" || appData.isRunning) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
|
||||
height: Math.max(2, actualIconSize * 0.05)
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (!appData) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
if (appData.type !== "grouped" || appData.windowCount === 1) {
|
||||
if (isWindowFocused) {
|
||||
return Theme.primary
|
||||
}
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
}
|
||||
|
||||
if (appData.type === "grouped" && appData.windowCount > 1) {
|
||||
const groupToplevels = getGroupedToplevels()
|
||||
if (index < groupToplevels.length && groupToplevels[index].activated) {
|
||||
return Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
}
|
||||
|
||||
if (appData.type === "grouped" && appData.windowCount > 1) {
|
||||
const groupToplevels = getGroupedToplevels()
|
||||
if (index < groupToplevels.length && groupToplevels[index].activated) {
|
||||
return Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: columnIndicator
|
||||
|
||||
transform: Translate {
|
||||
id: translateY
|
||||
Column {
|
||||
spacing: 2
|
||||
|
||||
y: 0
|
||||
Repeater {
|
||||
model: {
|
||||
if (!appData) return 0
|
||||
if (appData.type === "grouped") {
|
||||
return Math.min(appData.windowCount, 4)
|
||||
} else if (appData.type === "window" || appData.isRunning) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(2, actualIconSize * 0.05)
|
||||
height: appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (!appData) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
if (appData.type !== "grouped" || appData.windowCount === 1) {
|
||||
if (isWindowFocused) {
|
||||
return Theme.primary
|
||||
}
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
}
|
||||
|
||||
if (appData.type === "grouped" && appData.windowCount > 1) {
|
||||
const groupToplevels = getGroupedToplevels()
|
||||
if (index < groupToplevels.length && groupToplevels[index].activated) {
|
||||
return Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ Item {
|
||||
property int pinnedAppCount: 0
|
||||
property bool groupByApp: false
|
||||
property bool isVertical: false
|
||||
property var dockScreen: null
|
||||
property real iconSize: 40
|
||||
|
||||
clip: false
|
||||
implicitWidth: isVertical ? appLayout.height : appLayout.width
|
||||
implicitHeight: isVertical ? appLayout.width : appLayout.height
|
||||
|
||||
@@ -36,14 +39,18 @@ Item {
|
||||
|
||||
Item {
|
||||
id: appLayout
|
||||
anchors.centerIn: parent
|
||||
width: layoutFlow.width
|
||||
height: layoutFlow.height
|
||||
anchors.horizontalCenter: root.isVertical ? undefined : parent.horizontalCenter
|
||||
anchors.verticalCenter: root.isVertical ? parent.verticalCenter : undefined
|
||||
anchors.left: root.isVertical && SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined
|
||||
anchors.right: root.isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined
|
||||
anchors.top: root.isVertical ? undefined : parent.top
|
||||
|
||||
Flow {
|
||||
id: layoutFlow
|
||||
flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
||||
spacing: 8
|
||||
spacing: Math.min(8, Math.max(4, root.iconSize * 0.08))
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
@@ -192,33 +199,42 @@ Item {
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
property alias dockButton: button
|
||||
clip: false
|
||||
|
||||
width: model.type === "separator" ? 16 : 40
|
||||
height: 40
|
||||
width: model.type === "separator" ? (root.isVertical ? root.iconSize : 8) : (root.isVertical ? root.iconSize : root.iconSize * 1.2)
|
||||
height: model.type === "separator" ? (root.isVertical ? 8 : root.iconSize) : (root.isVertical ? root.iconSize * 1.2 : root.iconSize)
|
||||
|
||||
Rectangle {
|
||||
visible: model.type === "separator"
|
||||
width: 2
|
||||
height: 20
|
||||
width: root.isVertical ? root.iconSize * 0.5 : 2
|
||||
height: root.isVertical ? 2 : root.iconSize * 0.5
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
radius: 1
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
visible: model.type === "separator"
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
DockAppButton {
|
||||
id: button
|
||||
visible: model.type !== "separator"
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: 40
|
||||
height: 40
|
||||
width: delegateItem.width
|
||||
height: delegateItem.height
|
||||
actualIconSize: root.iconSize
|
||||
|
||||
appData: model
|
||||
contextMenu: root.contextMenu
|
||||
dockApps: root
|
||||
index: model.index
|
||||
parentDockScreen: root.dockScreen
|
||||
|
||||
// Override tooltip for windows to show window title
|
||||
showWindowTitle: model.type === "window" || model.type === "grouped"
|
||||
windowTitle: model.windowTitle || ""
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import qs.Widgets
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool showContextMenu: false
|
||||
property var appData: null
|
||||
property var anchorItem: null
|
||||
property real dockVisibleHeight: 40
|
||||
@@ -17,33 +16,25 @@ PanelWindow {
|
||||
property bool hidePin: false
|
||||
property var desktopEntry: null
|
||||
|
||||
function showForButton(button, data, dockHeight, hidePinOption, entry) {
|
||||
function showForButton(button, data, dockHeight, hidePinOption, entry, dockScreen) {
|
||||
if (dockScreen) {
|
||||
root.screen = dockScreen
|
||||
}
|
||||
|
||||
anchorItem = button
|
||||
appData = data
|
||||
dockVisibleHeight = dockHeight || 40
|
||||
hidePin = hidePinOption || false
|
||||
desktopEntry = entry || null
|
||||
|
||||
const dockWindow = button.Window.window
|
||||
if (dockWindow) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
const s = Quickshell.screens[i]
|
||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
||||
root.screen = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showContextMenu = true
|
||||
visible = true
|
||||
}
|
||||
function close() {
|
||||
showContextMenu = false
|
||||
visible = false
|
||||
}
|
||||
|
||||
screen: Quickshell.screens[0]
|
||||
|
||||
visible: showContextMenu
|
||||
screen: null
|
||||
visible: false
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
@@ -175,7 +166,7 @@ PanelWindow {
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
opacity: root.showContextMenu ? 1 : 0
|
||||
opacity: root.visible ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
|
||||
@@ -7,7 +7,6 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Greetd
|
||||
import Quickshell.Services.Pam
|
||||
import Quickshell.Services.Mpris
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -16,8 +15,6 @@ import qs.Modules.Lock
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var sessionLock
|
||||
|
||||
readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS")
|
||||
property string screenName: ""
|
||||
property string randomFact: ""
|
||||
@@ -34,30 +31,10 @@ Item {
|
||||
|
||||
signal launchRequested
|
||||
|
||||
property bool powerDialogVisible: false
|
||||
property string powerDialogTitle: ""
|
||||
property string powerDialogMessage: ""
|
||||
property string powerDialogConfirmText: ""
|
||||
property color powerDialogConfirmColor: Theme.primary
|
||||
property var powerDialogOnConfirm: function () {}
|
||||
|
||||
function pickRandomFact() {
|
||||
randomFact = Facts.getRandomFact()
|
||||
}
|
||||
|
||||
function showPowerDialog(title, message, confirmText, confirmColor, onConfirm) {
|
||||
powerDialogTitle = title
|
||||
powerDialogMessage = message
|
||||
powerDialogConfirmText = confirmText
|
||||
powerDialogConfirmColor = confirmColor
|
||||
powerDialogOnConfirm = onConfirm
|
||||
powerDialogVisible = true
|
||||
}
|
||||
|
||||
function hidePowerDialog() {
|
||||
powerDialogVisible = false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
pickRandomFact()
|
||||
WeatherService.addRef()
|
||||
@@ -137,21 +114,6 @@ Item {
|
||||
onTriggered: updateHyprlandLayout()
|
||||
}
|
||||
|
||||
// ! This was for development and testing, just leaving so people can see how I did it.
|
||||
// Timer {
|
||||
// id: autoUnlockTimer
|
||||
// interval: 10000
|
||||
// running: true
|
||||
// onTriggered: {
|
||||
// root.sessionLock.locked = false
|
||||
// GreeterState.unlocking = true
|
||||
// const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]
|
||||
// if (sessionCmd) {
|
||||
// GreetdMemory.setLastSessionId(sessionCmd.split(" ")[0])
|
||||
// Greetd.launch(sessionCmd.split(" "), [], true)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Connections {
|
||||
target: GreetdMemory
|
||||
@@ -186,14 +148,14 @@ Item {
|
||||
source: {
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
if (screenName && currentWallpaper && currentWallpaper.startsWith("we:")) {
|
||||
const cacheHome = StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()
|
||||
const cacheHome = StandardPaths.writableLocation(StandardPaths.GenericCacheLocation).toString()
|
||||
const baseDir = Paths.strip(cacheHome)
|
||||
const screenshotPath = baseDir + "/dankshell/we_screenshots" + "/" + currentWallpaper.substring(3) + ".jpg"
|
||||
const screenshotPath = baseDir + "/DankMaterialShell/we_screenshots" + "/" + currentWallpaper.substring(3) + ".jpg"
|
||||
return screenshotPath
|
||||
}
|
||||
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : ""
|
||||
}
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
fillMode: Theme.getFillMode(SettingsData.wallpaperFillMode)
|
||||
smooth: true
|
||||
asynchronous: false
|
||||
cache: true
|
||||
@@ -373,11 +335,11 @@ Item {
|
||||
syncingFromState = true
|
||||
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput
|
||||
syncingFromState = false
|
||||
if (isPrimaryScreen)
|
||||
if (isPrimaryScreen && !powerMenu.isVisible)
|
||||
forceActiveFocus()
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (visible && isPrimaryScreen)
|
||||
if (visible && isPrimaryScreen && !powerMenu.isVisible)
|
||||
forceActiveFocus()
|
||||
}
|
||||
}
|
||||
@@ -693,180 +655,11 @@ Item {
|
||||
height: 24
|
||||
color: Qt.rgba(255, 255, 255, 0.2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: MprisController.activePlayer
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
visible: MprisController.activePlayer
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Item {
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Loader {
|
||||
active: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
|
||||
sourceComponent: Component {
|
||||
Ref {
|
||||
service: CavaService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: !CavaService.cavaAvailable && MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
interval: 256
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
CavaService.values = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20, Math.random() * 45 + 15, Math.random() * 55 + 25]
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 1.5
|
||||
|
||||
Repeater {
|
||||
model: 6
|
||||
|
||||
Rectangle {
|
||||
width: 2
|
||||
height: {
|
||||
if (MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing && CavaService.values.length > index) {
|
||||
const rawLevel = CavaService.values[index] || 0
|
||||
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100
|
||||
const maxHeight = Theme.iconSize - 2
|
||||
const minHeight = 3
|
||||
return minHeight + (scaledLevel / 100) * (maxHeight - minHeight)
|
||||
}
|
||||
return 3
|
||||
}
|
||||
radius: 1.5
|
||||
color: "white"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standardDecel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visible: {
|
||||
const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) ||
|
||||
(CompositorService.isHyprland && hyprlandLayoutCount > 1)
|
||||
return keyboardVisible && WeatherService.weather.available
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const player = MprisController.activePlayer
|
||||
if (!player?.trackTitle)
|
||||
return ""
|
||||
const title = player.trackTitle
|
||||
const artist = player.trackArtist || ""
|
||||
return artist ? title + " • " + artist : title
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: "white"
|
||||
opacity: 0.9
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
width: Math.min(implicitWidth, 400)
|
||||
wrapMode: Text.NoWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: prevArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent"
|
||||
visible: MprisController.activePlayer
|
||||
opacity: (MprisController.activePlayer?.canGoPrevious ?? false) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_previous"
|
||||
size: 12
|
||||
color: "white"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: prevArea
|
||||
anchors.fill: parent
|
||||
enabled: MprisController.activePlayer?.canGoPrevious ?? false
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: MprisController.activePlayer?.previous()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? Qt.rgba(255, 255, 255, 0.9) : Qt.rgba(255, 255, 255, 0.2)
|
||||
visible: MprisController.activePlayer
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||
size: 14
|
||||
color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "black" : "white"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: MprisController.activePlayer
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: MprisController.activePlayer?.togglePlaying()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: nextArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent"
|
||||
visible: MprisController.activePlayer
|
||||
opacity: (MprisController.activePlayer?.canGoNext ?? false) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_next"
|
||||
size: 12
|
||||
color: "white"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextArea
|
||||
anchors.fill: parent
|
||||
enabled: MprisController.activePlayer?.canGoNext ?? false
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: MprisController.activePlayer?.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 24
|
||||
color: Qt.rgba(255, 255, 255, 0.2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: MprisController.activePlayer && WeatherService.weather.available
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -1073,33 +866,15 @@ Item {
|
||||
visible: root.randomFact !== ""
|
||||
}
|
||||
|
||||
Row {
|
||||
DankActionButton {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Theme.spacingXL
|
||||
spacing: Theme.spacingL
|
||||
visible: GreetdSettings.lockScreenShowPowerActions
|
||||
|
||||
DankActionButton {
|
||||
iconName: "power_settings_new"
|
||||
iconColor: Theme.error
|
||||
buttonSize: 40
|
||||
onClicked: {
|
||||
showPowerDialog("Power Off", "Power off this computer?", "Power Off", Theme.error, function () {
|
||||
SessionService.poweroff()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "refresh"
|
||||
buttonSize: 40
|
||||
onClicked: {
|
||||
showPowerDialog("Restart", "Restart this computer?", "Restart", Theme.primary, function () {
|
||||
SessionService.reboot()
|
||||
})
|
||||
}
|
||||
}
|
||||
iconName: "power_settings_new"
|
||||
iconColor: Theme.error
|
||||
buttonSize: 40
|
||||
onClicked: powerMenu.show()
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -1285,7 +1060,6 @@ Item {
|
||||
}
|
||||
|
||||
function onReadyToLaunch() {
|
||||
root.sessionLock.locked = false
|
||||
GreeterState.unlocking = true
|
||||
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]
|
||||
if (sessionCmd) {
|
||||
@@ -1314,90 +1088,12 @@ Item {
|
||||
onTriggered: GreeterState.pamState = ""
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.8)
|
||||
visible: powerDialogVisible
|
||||
z: 1000
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 320
|
||||
height: 180
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "power_settings_new"
|
||||
size: 32
|
||||
color: powerDialogConfirmColor
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: powerDialogMessage
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceVariant
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Cancel")
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: hidePowerDialog()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: powerDialogConfirmColor
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: powerDialogConfirmText
|
||||
color: Theme.primaryText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
hidePowerDialog()
|
||||
powerDialogOnConfirm()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LockPowerMenu {
|
||||
id: powerMenu
|
||||
showLogout: false
|
||||
onClosed: {
|
||||
if (isPrimaryScreen && inputField && inputField.forceActiveFocus) {
|
||||
Qt.callLater(() => inputField.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Greetd
|
||||
import qs.Common
|
||||
|
||||
WlSessionLockSurface {
|
||||
id: root
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
required property WlSessionLock lock
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
color: "transparent"
|
||||
property var modelData
|
||||
|
||||
GreeterContent {
|
||||
anchors.fill: parent
|
||||
screenName: root.screen?.name ?? ""
|
||||
sessionLock: root.lock
|
||||
screen: modelData
|
||||
anchors {
|
||||
left: true
|
||||
right: true
|
||||
top: true
|
||||
bottom: true
|
||||
}
|
||||
exclusionMode: ExclusionMode.Normal
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
color: "transparent"
|
||||
|
||||
GreeterContent {
|
||||
anchors.fill: parent
|
||||
screenName: root.screen?.name ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,105 @@ A greeter for [greetd](https://github.com/kennylevinsen/greetd) that follows the
|
||||
|
||||
## Installation
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Arch linux users can install [greetd-dms-greeter-git](https://aur.archlinux.org/packages/greetd-dms-greeter-git) from the AUR.
|
||||
|
||||
```bash
|
||||
paru -S greetd-dms-greeter-git
|
||||
# Or with yay
|
||||
yay -S greetd-dms-greeter-git
|
||||
```
|
||||
|
||||
Once installed, disable any existing display manager and enable greetd:
|
||||
|
||||
```bash
|
||||
sudo systemctl disable gdm sddm lightdm
|
||||
sudo systemctl enable greetd
|
||||
```
|
||||
|
||||
#### Syncing themes (Optional)
|
||||
|
||||
To sync your wallpaper and theme with the greeter login screen, follow the manual setup below:
|
||||
|
||||
<details>
|
||||
<summary>Manual theme syncing</summary>
|
||||
|
||||
```bash
|
||||
# Add yourself to greeter group
|
||||
sudo usermod -aG greeter <username>
|
||||
|
||||
# Set ACLs to allow greeter to traverse your directories
|
||||
setfacl -m u:greeter:x ~ ~/.config ~/.local ~/.cache ~/.local/state
|
||||
|
||||
# Set group ownership on config directories
|
||||
sudo chgrp -R greeter ~/.config/DankMaterialShell
|
||||
sudo chgrp -R greeter ~/.local/state/DankMaterialShell
|
||||
sudo chgrp -R greeter ~/.cache/quickshell
|
||||
sudo chmod -R g+rX ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell ~/.cache/quickshell
|
||||
|
||||
# Create symlinks
|
||||
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
|
||||
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
|
||||
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/colors.json
|
||||
|
||||
# Logout and login for group membership to take effect
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Fedora / RHEL / Rocky / Alma
|
||||
|
||||
Install from COPR or build the RPM:
|
||||
|
||||
```bash
|
||||
# From COPR (when available)
|
||||
sudo dnf copr enable avenge/dms
|
||||
sudo dnf install dms-greeter
|
||||
|
||||
# Or build locally
|
||||
cd /path/to/DankMaterialShell
|
||||
rpkg local
|
||||
sudo rpm -ivh x86_64/dms-greeter-*.rpm
|
||||
```
|
||||
|
||||
The package automatically:
|
||||
- Creates the greeter user
|
||||
- Sets up directories and permissions
|
||||
- Configures greetd with auto-detected compositor
|
||||
- Applies SELinux contexts
|
||||
- Installs the `dms-greeter-sync` helper script
|
||||
|
||||
Then disable existing display manager and enable greetd:
|
||||
|
||||
```bash
|
||||
sudo systemctl disable gdm sddm lightdm
|
||||
sudo systemctl enable greetd
|
||||
```
|
||||
|
||||
#### Syncing themes (Optional)
|
||||
|
||||
The RPM package includes the `dms-greeter-sync` helper for easy theme syncing:
|
||||
|
||||
```bash
|
||||
dms-greeter-sync
|
||||
```
|
||||
|
||||
Then logout/login to see your wallpaper on the greeter!
|
||||
|
||||
<details>
|
||||
<summary>What does dms-greeter-sync do?</summary>
|
||||
|
||||
The `dms-greeter-sync` helper automatically:
|
||||
- Adds you to the greeter group
|
||||
- Sets minimal ACL permissions on parent directories (traverse only)
|
||||
- Sets group ownership on your DMS config directories
|
||||
- Creates symlinks to share your theme files with the greeter
|
||||
|
||||
This uses standard Linux ACLs (Access Control Lists) - the same security model used by GNOME, KDE, and systemd. The greeter user only gets traverse permission through your directories and can only read the specific theme files you share.
|
||||
|
||||
</details>
|
||||
|
||||
### Automatic
|
||||
|
||||
The easiest thing is to run `dms greeter install` or `dms` for interactive installation.
|
||||
@@ -19,21 +118,33 @@ The easiest thing is to run `dms greeter install` or `dms` for interactive insta
|
||||
### Manual
|
||||
|
||||
1. Install `greetd` (in most distro's standard repositories) and `quickshell`
|
||||
2. Clone the dms project to `/etc/xdg/quickshell/dms-greeter`
|
||||
|
||||
2. Create the greeter user (if not already created by greetd):
|
||||
```bash
|
||||
sudo groupadd -r greeter
|
||||
sudo useradd -r -g greeter -d /var/lib/greeter -s /bin/bash -c "System Greeter" greeter
|
||||
sudo mkdir -p /var/lib/greeter
|
||||
sudo chown greeter:greeter /var/lib/greeter
|
||||
```
|
||||
|
||||
3. Clone the dms project to `/etc/xdg/quickshell/dms-greeter`:
|
||||
```bash
|
||||
sudo git clone https://github.com/AvengeMedia/DankMaterialShell.git /etc/xdg/quickshell/dms-greeter
|
||||
```
|
||||
3. Copy `assets/dms-greeter` to `/usr/local/bin/dms-greeter`:
|
||||
|
||||
4. Copy `Modules/Greetd/assets/dms-greeter` to `/usr/local/bin/dms-greeter`:
|
||||
```bash
|
||||
sudo cp assets/dms-greeter /usr/local/bin/dms-greeter
|
||||
sudo cp /etc/xdg/quickshell/dms-greeter/Modules/Greetd/assets/dms-greeter /usr/local/bin/dms-greeter
|
||||
sudo chmod +x /usr/local/bin/dms-greeter
|
||||
```
|
||||
4. Create greeter cache directory with proper permissions:
|
||||
|
||||
5. Create greeter cache directory with proper permissions:
|
||||
```bash
|
||||
sudo mkdir -p /var/cache/dms-greeter
|
||||
sudo chown greeter:greeter /var/cache/dms-greeter
|
||||
sudo chmod 770 /var/cache/dms-greeter
|
||||
sudo chmod 750 /var/cache/dms-greeter
|
||||
```
|
||||
|
||||
6. Edit or create `/etc/greetd/config.toml`:
|
||||
```toml
|
||||
[terminal]
|
||||
@@ -45,7 +156,13 @@ user = "greeter"
|
||||
command = "/usr/local/bin/dms-greeter --command niri"
|
||||
```
|
||||
|
||||
Enable the greeter with `sudo systemctl enable greetd`
|
||||
7. Disable existing display manager and enable greetd:
|
||||
```bash
|
||||
sudo systemctl disable gdm sddm lightdm
|
||||
sudo systemctl enable greetd
|
||||
```
|
||||
|
||||
8. (Optional) Set up theme syncing using the manual ACL method described in the Configuration → Personalization section below
|
||||
|
||||
#### Legacy installation (deprecated)
|
||||
|
||||
@@ -114,21 +231,31 @@ Simply edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` to change
|
||||
|
||||
#### Personalization
|
||||
|
||||
Wallpapers and themes and weather and clock formats and things are a TODO on the documentation, but it's configured exactly the same as dms.
|
||||
The greeter can be personalized with wallpapers, themes, weather, clock formats, and more - configured exactly the same as dms.
|
||||
|
||||
You can synchronize those configurations with a specific user if you want greeter settings to always mirror the shell.
|
||||
**Easiest method:** Run `dms-greeter-sync` to automatically sync your DMS theme with the greeter.
|
||||
|
||||
The greeter uses the `dms-greeter` group for file access permissions, so ensure your user and the greeter user are both members of this group.
|
||||
**Manual method:** You can manually synchronize configurations if you want greeter settings to always mirror your shell:
|
||||
|
||||
```bash
|
||||
# For core settings (theme, clock formats, etc)
|
||||
# Add yourself to the greeter group
|
||||
sudo usermod -aG greeter $USER
|
||||
|
||||
# Set ACLs to allow greeter user to traverse your home directory
|
||||
setfacl -m u:greeter:x ~ ~/.config ~/.local ~/.cache ~/.local/state
|
||||
|
||||
# Set group permissions on DMS directories
|
||||
sudo chgrp -R greeter ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell ~/.cache/quickshell
|
||||
sudo chmod -R g+rX ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell ~/.cache/quickshell
|
||||
|
||||
# Create symlinks for theme files
|
||||
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
|
||||
# For state (mainly you would configure wallpaper in this file)
|
||||
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
|
||||
# For wallpaper based theming
|
||||
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/dms-colors.json
|
||||
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /var/cache/dms-greeter/colors.json
|
||||
|
||||
# Logout and login for group membership to take effect
|
||||
```
|
||||
|
||||
You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable or the `--cache-dir` flag when using `dms-greeter`. The default is `/var/cache/dms-greeter`.
|
||||
**Advanced:** You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable or the `--cache-dir` flag when using `dms-greeter`. The default is `/var/cache/dms-greeter`.
|
||||
|
||||
The cache directory should be owned by `greeter:greeter` with `770` permissions.
|
||||
284
Modules/HyprWorkspaces/HyprlandOverview.qml
Normal file
284
Modules/HyprWorkspaces/HyprlandOverview.qml
Normal file
@@ -0,0 +1,284 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Scope {
|
||||
id: overviewScope
|
||||
|
||||
property bool overviewOpen: false
|
||||
|
||||
Loader {
|
||||
id: hyprlandLoader
|
||||
active: overviewScope.overviewOpen
|
||||
asynchronous: false
|
||||
|
||||
sourceComponent: Variants {
|
||||
id: overviewVariants
|
||||
model: Quickshell.screens
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
required property var modelData
|
||||
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
|
||||
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
|
||||
|
||||
screen: modelData
|
||||
visible: overviewScope.overviewOpen
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "quickshell:overview"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: grab
|
||||
windows: [root]
|
||||
active: false
|
||||
property bool hasBeenActivated: false
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
hasBeenActivated = true
|
||||
}
|
||||
}
|
||||
onCleared: () => {
|
||||
if (hasBeenActivated && overviewScope.overviewOpen) {
|
||||
overviewScope.overviewOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: overviewScope
|
||||
function onOverviewOpenChanged() {
|
||||
if (overviewScope.overviewOpen) {
|
||||
grab.hasBeenActivated = false
|
||||
delayedGrabTimer.start()
|
||||
} else {
|
||||
delayedGrabTimer.stop()
|
||||
grab.active = false
|
||||
grab.hasBeenActivated = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onMonitorIsFocusedChanged() {
|
||||
if (overviewScope.overviewOpen && root.monitorIsFocused && !grab.active) {
|
||||
grab.hasBeenActivated = false
|
||||
grab.active = true
|
||||
} else if (overviewScope.overviewOpen && !root.monitorIsFocused && grab.active) {
|
||||
grab.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delayedGrabTimer
|
||||
interval: 150
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (overviewScope.overviewOpen && root.monitorIsFocused) {
|
||||
grab.active = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeTimer
|
||||
interval: Theme.expressiveDurations.expressiveDefaultSpatial + 120
|
||||
onTriggered: {
|
||||
root.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: overviewScope.overviewOpen ? 0.5 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: mouse => {
|
||||
const localPos = mapToItem(contentContainer, mouse.x, mouse.y)
|
||||
if (localPos.x < 0 || localPos.x > contentContainer.width || localPos.y < 0 || localPos.y > contentContainer.height) {
|
||||
overviewScope.overviewOpen = false
|
||||
closeTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 100
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
|
||||
opacity: overviewScope.overviewOpen ? 1 : 0
|
||||
transform: [scaleTransform, motionTransform]
|
||||
|
||||
Scale {
|
||||
id: scaleTransform
|
||||
origin.x: contentContainer.width / 2
|
||||
origin.y: contentContainer.height / 2
|
||||
xScale: overviewScope.overviewOpen ? 1 : 0.96
|
||||
yScale: overviewScope.overviewOpen ? 1 : 0.96
|
||||
|
||||
Behavior on xScale {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on yScale {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Translate {
|
||||
id: motionTransform
|
||||
x: 0
|
||||
y: overviewScope.overviewOpen ? 0 : Theme.spacingL
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: overviewLoader
|
||||
active: overviewScope.overviewOpen
|
||||
asynchronous: false
|
||||
|
||||
sourceComponent: OverviewWidget {
|
||||
panelWindow: root
|
||||
overviewOpen: overviewScope.overviewOpen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: focusScope
|
||||
anchors.fill: parent
|
||||
visible: overviewScope.overviewOpen
|
||||
focus: overviewScope.overviewOpen && root.monitorIsFocused
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
if (!root.monitorIsFocused) return
|
||||
overviewScope.overviewOpen = false
|
||||
closeTimer.restart()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (!root.monitorIsFocused) return
|
||||
|
||||
if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) {
|
||||
if (!overviewLoader.item) return
|
||||
|
||||
const thisMonitorWorkspaceIds = overviewLoader.item.thisMonitorWorkspaceIds
|
||||
if (thisMonitorWorkspaceIds.length === 0) return
|
||||
|
||||
const currentId = root.monitor.activeWorkspace?.id ?? thisMonitorWorkspaceIds[0]
|
||||
const currentIndex = thisMonitorWorkspaceIds.indexOf(currentId)
|
||||
|
||||
let targetIndex
|
||||
if (event.key === Qt.Key_Left) {
|
||||
targetIndex = currentIndex - 1
|
||||
if (targetIndex < 0) targetIndex = thisMonitorWorkspaceIds.length - 1
|
||||
} else {
|
||||
targetIndex = currentIndex + 1
|
||||
if (targetIndex >= thisMonitorWorkspaceIds.length) targetIndex = 0
|
||||
}
|
||||
|
||||
const targetId = thisMonitorWorkspaceIds[targetIndex]
|
||||
Hyprland.dispatch("workspace " + targetId)
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible && overviewScope.overviewOpen && root.monitorIsFocused) {
|
||||
Qt.callLater(() => focusScope.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onMonitorIsFocusedChanged() {
|
||||
if (root.monitorIsFocused && overviewScope.overviewOpen) {
|
||||
Qt.callLater(() => focusScope.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible && overviewScope.overviewOpen) {
|
||||
Qt.callLater(() => focusScope.forceActiveFocus())
|
||||
} else if (!visible) {
|
||||
grab.active = false
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: overviewScope
|
||||
function onOverviewOpenChanged() {
|
||||
if (overviewScope.overviewOpen) {
|
||||
closeTimer.stop()
|
||||
root.visible = true
|
||||
Qt.callLater(() => focusScope.forceActiveFocus())
|
||||
} else {
|
||||
closeTimer.restart()
|
||||
grab.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
428
Modules/HyprWorkspaces/OverviewWidget.qml
Normal file
428
Modules/HyprWorkspaces/OverviewWidget.qml
Normal file
@@ -0,0 +1,428 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
required property var panelWindow
|
||||
required property bool overviewOpen
|
||||
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
|
||||
readonly property int workspacesShown: SettingsData.overviewRows * SettingsData.overviewColumns
|
||||
|
||||
readonly property var allWorkspaces: Hyprland.workspaces?.values || []
|
||||
readonly property var allWorkspaceIds: {
|
||||
const workspaces = allWorkspaces
|
||||
if (!workspaces || workspaces.length === 0) return []
|
||||
try {
|
||||
const ids = workspaces.map(ws => ws?.id).filter(id => id !== null && id !== undefined)
|
||||
return ids.sort((a, b) => a - b)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var thisMonitorWorkspaceIds: {
|
||||
const workspaces = allWorkspaces
|
||||
const mon = monitor
|
||||
if (!workspaces || workspaces.length === 0 || !mon) return []
|
||||
try {
|
||||
const filtered = workspaces.filter(ws => ws?.monitor?.name === mon.name)
|
||||
return filtered.map(ws => ws?.id).filter(id => id !== null && id !== undefined).sort((a, b) => a - b)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var displayedWorkspaceIds: {
|
||||
if (!allWorkspaceIds || allWorkspaceIds.length === 0) {
|
||||
const result = []
|
||||
for (let i = 1; i <= workspacesShown; i++) {
|
||||
result.push(i)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
try {
|
||||
const maxExisting = Math.max(...allWorkspaceIds)
|
||||
const totalNeeded = Math.max(workspacesShown, allWorkspaceIds.length)
|
||||
const result = []
|
||||
|
||||
for (let i = 1; i <= maxExisting; i++) {
|
||||
result.push(i)
|
||||
}
|
||||
|
||||
let nextId = maxExisting + 1
|
||||
while (result.length < totalNeeded) {
|
||||
result.push(nextId)
|
||||
nextId++
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (e) {
|
||||
const result = []
|
||||
for (let i = 1; i <= workspacesShown; i++) {
|
||||
result.push(i)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int minWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[0] : 1
|
||||
readonly property int maxWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[displayedWorkspaceIds.length - 1] : workspacesShown
|
||||
readonly property int displayWorkspaceCount: displayedWorkspaceIds.length
|
||||
|
||||
function getWorkspaceMonitorName(workspaceId) {
|
||||
if (!allWorkspaces || !workspaceId) return ""
|
||||
try {
|
||||
const ws = allWorkspaces.find(w => w?.id === workspaceId)
|
||||
return ws?.monitor?.name ?? ""
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
function workspaceHasWindows(workspaceId) {
|
||||
if (!workspaceId) return false
|
||||
try {
|
||||
const workspace = allWorkspaces.find(ws => ws?.id === workspaceId)
|
||||
if (!workspace) return false
|
||||
const toplevels = workspace?.toplevels?.values || []
|
||||
return toplevels.length > 0
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
property bool monitorIsFocused: monitor?.focused ?? false
|
||||
property real scale: SettingsData.overviewScale
|
||||
property color activeBorderColor: Theme.primary
|
||||
|
||||
property real workspaceImplicitWidth: ((monitor.width / monitor.scale) * root.scale)
|
||||
property real workspaceImplicitHeight: ((monitor.height / monitor.scale) * root.scale)
|
||||
|
||||
property int workspaceZ: 0
|
||||
property int windowZ: 1
|
||||
property int monitorLabelZ: 2
|
||||
property int windowDraggingZ: 99999
|
||||
property real workspaceSpacing: 5
|
||||
|
||||
property int draggingFromWorkspace: -1
|
||||
property int draggingTargetWorkspace: -1
|
||||
|
||||
implicitWidth: overviewBackground.implicitWidth + Theme.spacingL * 2
|
||||
implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2
|
||||
|
||||
Component.onCompleted: {
|
||||
Hyprland.refreshToplevels()
|
||||
Hyprland.refreshWorkspaces()
|
||||
Hyprland.refreshMonitors()
|
||||
}
|
||||
|
||||
onOverviewOpenChanged: {
|
||||
if (overviewOpen) {
|
||||
Hyprland.refreshToplevels()
|
||||
Hyprland.refreshWorkspaces()
|
||||
Hyprland.refreshMonitors()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: overviewBackground
|
||||
property real padding: 10
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
|
||||
implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2
|
||||
implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowBlur: 0.5
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 4
|
||||
shadowColor: Theme.shadowStrong
|
||||
shadowOpacity: 1
|
||||
blurMax: 32
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: workspaceColumnLayout
|
||||
|
||||
z: root.workspaceZ
|
||||
anchors.centerIn: parent
|
||||
spacing: workspaceSpacing
|
||||
|
||||
Repeater {
|
||||
model: SettingsData.overviewRows
|
||||
delegate: RowLayout {
|
||||
id: row
|
||||
property int rowIndex: index
|
||||
spacing: workspaceSpacing
|
||||
|
||||
Repeater {
|
||||
model: SettingsData.overviewColumns
|
||||
Rectangle {
|
||||
id: workspace
|
||||
property int colIndex: index
|
||||
property int workspaceIndex: rowIndex * SettingsData.overviewColumns + colIndex
|
||||
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
|
||||
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
|
||||
property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null
|
||||
property bool isActive: workspaceObj?.active ?? false
|
||||
property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true
|
||||
property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false
|
||||
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
|
||||
property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3)
|
||||
property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1)
|
||||
property color hoveredBorderColor: Theme.surfaceVariant
|
||||
property bool hoveredWhileDragging: false
|
||||
property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows
|
||||
|
||||
visible: workspaceValue !== -1
|
||||
|
||||
implicitWidth: root.workspaceImplicitWidth
|
||||
implicitHeight: root.workspaceImplicitHeight
|
||||
color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 2
|
||||
border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : "transparent")
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: workspaceValue
|
||||
font.pixelSize: Theme.fontSizeXLarge * 6
|
||||
font.weight: Font.DemiBold
|
||||
color: Theme.withAlpha(Theme.surfaceText, workspaceExists ? 0.2 : 0.1)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: workspaceArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: {
|
||||
if (root.draggingTargetWorkspace === -1) {
|
||||
root.overviewOpen = false
|
||||
Hyprland.dispatch(`workspace ${workspaceValue}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropArea {
|
||||
anchors.fill: parent
|
||||
onEntered: {
|
||||
root.draggingTargetWorkspace = workspaceValue
|
||||
if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return
|
||||
hoveredWhileDragging = true
|
||||
}
|
||||
onExited: {
|
||||
hoveredWhileDragging = false
|
||||
if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: windowSpace
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: workspaceColumnLayout.implicitWidth
|
||||
implicitHeight: workspaceColumnLayout.implicitHeight
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const workspaces = root.allWorkspaces
|
||||
const minId = root.minWorkspaceId
|
||||
const maxId = root.maxWorkspaceId
|
||||
|
||||
if (!workspaces || workspaces.length === 0) return []
|
||||
|
||||
try {
|
||||
const result = []
|
||||
for (const workspace of workspaces) {
|
||||
const wsId = workspace?.id ?? -1
|
||||
if (wsId >= minId && wsId <= maxId) {
|
||||
const toplevels = workspace?.toplevels?.values || []
|
||||
for (const toplevel of toplevels) {
|
||||
result.push(toplevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error("OverviewWidget filter error:", e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate: OverviewWindow {
|
||||
id: window
|
||||
required property var modelData
|
||||
|
||||
overviewOpen: root.overviewOpen
|
||||
readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1
|
||||
|
||||
function getWorkspaceIndex() {
|
||||
if (!root.displayedWorkspaceIds || root.displayedWorkspaceIds.length === 0) return 0
|
||||
if (!windowWorkspaceId || windowWorkspaceId < 0) return 0
|
||||
try {
|
||||
for (let i = 0; i < root.displayedWorkspaceIds.length; i++) {
|
||||
if (root.displayedWorkspaceIds[i] === windowWorkspaceId) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
} catch (e) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int workspaceIndex: getWorkspaceIndex()
|
||||
readonly property int workspaceColIndex: workspaceIndex % SettingsData.overviewColumns
|
||||
readonly property int workspaceRowIndex: Math.floor(workspaceIndex / SettingsData.overviewColumns)
|
||||
|
||||
toplevel: modelData
|
||||
scale: root.scale
|
||||
availableWorkspaceWidth: root.workspaceImplicitWidth
|
||||
availableWorkspaceHeight: root.workspaceImplicitHeight
|
||||
widgetMonitorId: root.monitor.id
|
||||
|
||||
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
|
||||
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
|
||||
|
||||
z: atInitPosition ? root.windowZ : root.windowDraggingZ
|
||||
property bool atInitPosition: (initX == x && initY == y)
|
||||
|
||||
Drag.hotSpot.x: width / 2
|
||||
Drag.hotSpot.y: height / 2
|
||||
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: window.hovered = true
|
||||
onExited: window.hovered = false
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||
drag.target: parent
|
||||
|
||||
onPressed: (mouse) => {
|
||||
root.draggingFromWorkspace = windowData?.workspace.id
|
||||
window.pressed = true
|
||||
window.Drag.active = true
|
||||
window.Drag.source = window
|
||||
window.Drag.hotSpot.x = mouse.x
|
||||
window.Drag.hotSpot.y = mouse.y
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
const targetWorkspace = root.draggingTargetWorkspace
|
||||
window.pressed = false
|
||||
window.Drag.active = false
|
||||
root.draggingFromWorkspace = -1
|
||||
root.draggingTargetWorkspace = -1
|
||||
|
||||
if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) {
|
||||
Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace},address:${windowData?.address}`)
|
||||
Qt.callLater(() => {
|
||||
Hyprland.refreshToplevels()
|
||||
Hyprland.refreshWorkspaces()
|
||||
Qt.callLater(() => {
|
||||
window.x = window.initX
|
||||
window.y = window.initY
|
||||
})
|
||||
})
|
||||
} else {
|
||||
window.x = window.initX
|
||||
window.y = window.initY
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: (event) => {
|
||||
if (!windowData) return
|
||||
|
||||
if (event.button === Qt.LeftButton) {
|
||||
root.overviewOpen = false
|
||||
Hyprland.dispatch(`focuswindow address:${windowData.address}`)
|
||||
event.accepted = true
|
||||
} else if (event.button === Qt.MiddleButton) {
|
||||
Hyprland.dispatch(`closewindow address:${windowData.address}`)
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: monitorLabelSpace
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: workspaceColumnLayout.implicitWidth
|
||||
implicitHeight: workspaceColumnLayout.implicitHeight
|
||||
z: root.monitorLabelZ
|
||||
|
||||
Repeater {
|
||||
model: SettingsData.overviewRows
|
||||
delegate: Item {
|
||||
id: labelRow
|
||||
property int rowIndex: index
|
||||
y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex
|
||||
width: parent.width
|
||||
height: root.workspaceImplicitHeight
|
||||
|
||||
Repeater {
|
||||
model: SettingsData.overviewColumns
|
||||
delegate: Item {
|
||||
id: labelItem
|
||||
property int colIndex: index
|
||||
property int workspaceIndex: labelRow.rowIndex * SettingsData.overviewColumns + colIndex
|
||||
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
|
||||
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
|
||||
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
|
||||
|
||||
x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex
|
||||
width: root.workspaceImplicitWidth
|
||||
height: root.workspaceImplicitHeight
|
||||
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacingS
|
||||
width: monitorNameText.contentWidth + Theme.spacingS * 2
|
||||
height: monitorNameText.contentHeight + Theme.spacingXS * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surface
|
||||
visible: labelItem.workspaceExists && labelItem.workspaceMonitorName !== ""
|
||||
|
||||
StyledText {
|
||||
id: monitorNameText
|
||||
anchors.centerIn: parent
|
||||
text: labelItem.workspaceMonitorName
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Modules/HyprWorkspaces/OverviewWindow.qml
Normal file
140
Modules/HyprWorkspaces/OverviewWindow.qml
Normal file
@@ -0,0 +1,140 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property var toplevel
|
||||
property var scale
|
||||
required property bool overviewOpen
|
||||
property var availableWorkspaceWidth
|
||||
property var availableWorkspaceHeight
|
||||
property bool restrictToWorkspace: true
|
||||
|
||||
readonly property var windowData: toplevel?.lastIpcObject || null
|
||||
readonly property var monitorObj: toplevel?.monitor
|
||||
readonly property var monitorData: monitorObj?.lastIpcObject || null
|
||||
|
||||
property real initX: Math.max(((windowData?.at?.[0] ?? 0) - (monitorData?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset
|
||||
property real initY: Math.max(((windowData?.at?.[1] ?? 0) - (monitorData?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset
|
||||
property real xOffset: 0
|
||||
property real yOffset: 0
|
||||
property int widgetMonitorId: 0
|
||||
|
||||
property var targetWindowWidth: (windowData?.size?.[0] ?? 100) * scale
|
||||
property var targetWindowHeight: (windowData?.size?.[1] ?? 100) * scale
|
||||
property bool hovered: false
|
||||
property bool pressed: false
|
||||
|
||||
property var iconToWindowRatio: 0.25
|
||||
property var iconToWindowRatioCompact: 0.45
|
||||
property var entry: DesktopEntries.heuristicLookup(windowData?.class)
|
||||
property var iconPath: Quickshell.iconPath(entry?.icon ?? windowData?.class ?? "application-x-executable", "image-missing")
|
||||
property bool compactMode: Theme.fontSizeSmall * 4 > targetWindowHeight || Theme.fontSizeSmall * 4 > targetWindowWidth
|
||||
|
||||
x: initX
|
||||
y: initY
|
||||
width: Math.min((windowData?.size?.[0] ?? 100) * root.scale, availableWorkspaceWidth)
|
||||
height: Math.min((windowData?.size?.[1] ?? 100) * root.scale, availableWorkspaceHeight)
|
||||
opacity: (monitorObj?.id ?? -1) == widgetMonitorId ? 1 : 0.4
|
||||
|
||||
Rectangle {
|
||||
id: maskRect
|
||||
width: root.width
|
||||
height: root.height
|
||||
radius: Theme.cornerRadius
|
||||
visible: false
|
||||
layer.enabled: true
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskSource: maskRect
|
||||
maskSpreadAtMin: 1
|
||||
maskThresholdMin: 0.5
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
|
||||
ScreencopyView {
|
||||
id: windowPreview
|
||||
anchors.fill: parent
|
||||
captureSource: root.overviewOpen ? root.toplevel?.wayland : null
|
||||
live: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: pressed ? Theme.withAlpha(Theme.surfaceContainerHigh, 0.5) :
|
||||
hovered ? Theme.withAlpha(Theme.surfaceVariant, 0.3) :
|
||||
Theme.withAlpha(Theme.surfaceContainer, 0.1)
|
||||
border.color: Theme.withAlpha(Theme.outline, 0.3)
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Theme.fontSizeSmall * 0.5
|
||||
|
||||
Image {
|
||||
id: windowIcon
|
||||
property var iconSize: {
|
||||
return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1)
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: root.iconPath
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
sourceSize: Qt.size(iconSize, iconSize)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user