mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Compare commits
154 Commits
cc-editmod
...
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af8ee5af0f | ||
|
|
32d9aa0cf2 | ||
|
|
7e49631912 | ||
|
|
43970d34aa | ||
|
|
abb3c40697 | ||
|
|
2757a41102 | ||
|
|
1f8bddaa5e | ||
|
|
4c3b7ca60f | ||
|
|
d9d83e5767 | ||
|
|
b4ebde47be | ||
|
|
ef9f76190d | ||
|
|
71b96efca0 | ||
|
|
7158e09b0e | ||
|
|
8ef125bed2 | ||
|
|
0b11fb2fd5 | ||
|
|
3871f3cf3d | ||
|
|
7c5d1ec0f6 | ||
|
|
b507b08e34 | ||
|
|
1b06090f72 | ||
|
|
5460c20ac3 | ||
|
|
2ccec607a0 | ||
|
|
8a99fcf188 | ||
|
|
1e2489ca76 | ||
|
|
89793d2d62 | ||
|
|
11a1af89f4 | ||
|
|
e24ddb804d | ||
|
|
3524d365be | ||
|
|
2b3b9d037c | ||
|
|
5140cd9d7f | ||
|
|
2df9437b39 | ||
|
|
db440b8a14 | ||
|
|
c3dd70bc99 | ||
|
|
223e783bbc | ||
|
|
ca086dbf16 | ||
|
|
523422cf6c | ||
|
|
2dc310dcbc | ||
|
|
c092cd2921 | ||
|
|
2b14ef76c9 | ||
|
|
fbbf10078f | ||
|
|
2315d423c4 | ||
|
|
9a43465ebf | ||
|
|
fc1444763d | ||
|
|
804bf879ed | ||
|
|
ad44f09421 | ||
|
|
df2469468b | ||
|
|
f8f4fe11eb | ||
|
|
039a370add | ||
|
|
6feebc086d | ||
|
|
bc335c7d72 | ||
|
|
a6dd7254b2 | ||
|
|
7b1026c624 | ||
|
|
4758393cc1 | ||
|
|
52373a3a7d | ||
|
|
c30f9a2841 | ||
|
|
44d6f8f15c | ||
|
|
5ada12f989 | ||
|
|
d213045168 | ||
|
|
d83478239e | ||
|
|
3869955357 | ||
|
|
345d37edf8 | ||
|
|
2788ef28cf | ||
|
|
0d5c1bb3df | ||
|
|
c3d505cdad | ||
|
|
90854e1dd4 | ||
|
|
f96e3b04be | ||
|
|
44449e26a0 | ||
|
|
ddc88fd360 | ||
|
|
fedec450cb | ||
|
|
04ea742830 | ||
|
|
5a5c860cef | ||
|
|
55d06a43f8 | ||
|
|
71eecd6e7b | ||
|
|
6f3019f84b | ||
|
|
e95d3126b2 | ||
|
|
5da265bf0b | ||
|
|
2ce9c43b8c | ||
|
|
740b2f206c | ||
|
|
af622bc7e7 | ||
|
|
7816b50b27 | ||
|
|
4cb7a909f7 | ||
|
|
0fac88e171 | ||
|
|
b4ab9d9650 | ||
|
|
731db13c14 | ||
|
|
414a1ad4d2 | ||
|
|
16055fe96e | ||
|
|
6140c398f0 | ||
|
|
bd02923616 | ||
|
|
6021815fd3 | ||
|
|
8c4aba5479 | ||
|
|
2428b22171 | ||
|
|
a3d30211f6 | ||
|
|
730300d211 | ||
|
|
aaca31276b | ||
|
|
53fb927e36 | ||
|
|
fb5aa0313e | ||
|
|
9b41eecbf1 | ||
|
|
ae461b1caf | ||
|
|
57e36d6710 | ||
|
|
a7c4f09c5b | ||
|
|
554ef16e49 | ||
|
|
082321f860 | ||
|
|
df4f7b8c9e | ||
|
|
3f1742f074 | ||
|
|
4560d5c2d5 | ||
|
|
0ca12d275c | ||
|
|
df9e834309 | ||
|
|
ab1c0bb129 | ||
|
|
5070e4c950 | ||
|
|
c13526ccad | ||
|
|
46e16a6c69 | ||
|
|
53983933dc | ||
|
|
09f3ca39a1 | ||
|
|
42b4c91f35 | ||
|
|
37120776be | ||
|
|
d67bcb66c9 | ||
|
|
acf6e72f35 | ||
|
|
0964b271f5 | ||
|
|
1f7998fc44 | ||
|
|
7ac59f7bd0 | ||
|
|
bf238640af | ||
|
|
6e638cadb7 | ||
|
|
ab0759f441 | ||
|
|
123ec5c78a | ||
|
|
d262c67832 | ||
|
|
fcb9a2838d | ||
|
|
82d1672741 | ||
|
|
657a8b6094 | ||
|
|
165f7e0720 | ||
|
|
91aba84c29 | ||
|
|
02c4ac1bc7 | ||
|
|
c529959027 | ||
|
|
e875d1a5d7 | ||
|
|
d280505b9f | ||
|
|
fdd9d0000b | ||
|
|
74c793eedf | ||
|
|
bd8976c620 | ||
|
|
a3e2563f9b | ||
|
|
a310e3d8fa | ||
|
|
ae9da35af1 | ||
|
|
b45837ff5c | ||
|
|
3dd9ab8a29 | ||
|
|
5ebd2f6b8e | ||
|
|
977043ac92 | ||
|
|
4c6182b79c | ||
|
|
01125e092c | ||
|
|
99ef447a52 | ||
|
|
4722ff9b97 | ||
|
|
23a19df79a | ||
|
|
97b86c6faa | ||
|
|
e6296f20b9 | ||
|
|
0ee9dcc255 | ||
|
|
c7b4e2c49d | ||
|
|
066d1847e4 | ||
|
|
3155bf24c1 |
90
.github/workflows/release.yml
vendored
90
.github/workflows/release.yml
vendored
@@ -1,59 +1,97 @@
|
||||
name: Create Release
|
||||
# Release from a dispatch event from the danklinux repo
|
||||
name: Create Release from DMS
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
repository_dispatch:
|
||||
types: [dms_release]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref_name }}
|
||||
group: release-${{ github.event.client_payload.tag }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
create_release:
|
||||
name: 📦 Create GitHub Release
|
||||
create_release_from_dms:
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
TAG: ${{ github.event.client_payload.tag }}
|
||||
DMS_REPO: ${{ github.event.client_payload.dms_repo }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch full history for changelog generation
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Ensure VERSION and tag
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
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)"
|
||||
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
|
||||
|
||||
# Push commit (if any) and tag
|
||||
git push --follow-tags origin HEAD
|
||||
|
||||
# Generate changelog
|
||||
- name: Generate Changelog
|
||||
id: changelog
|
||||
run: |
|
||||
# Get the previous tag
|
||||
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||
|
||||
set -e
|
||||
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "")
|
||||
if [ -z "$PREVIOUS_TAG" ]; then
|
||||
echo "No previous tag found, using all commits"
|
||||
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50)
|
||||
else
|
||||
echo "Generating changelog from $PREVIOUS_TAG to HEAD"
|
||||
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" $PREVIOUS_TAG..HEAD)
|
||||
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}")
|
||||
fi
|
||||
|
||||
# Create the changelog with proper formatting
|
||||
cat > CHANGELOG.md << EOF
|
||||
## What's Changed
|
||||
|
||||
$CHANGELOG
|
||||
|
||||
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${{ github.ref_name }}
|
||||
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${TAG}
|
||||
EOF
|
||||
|
||||
# Set output for use in release step
|
||||
echo "changelog<<EOF" >> $GITHUB_OUTPUT
|
||||
cat CHANGELOG.md >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
# Create GitHub Release
|
||||
- name: Create GitHub Release
|
||||
uses: comnoco/create-release-action@v2.0.5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create/Update DankMaterialShell Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
release_name: Release ${{ github.ref_name }}
|
||||
tag_name: ${{ env.TAG }}
|
||||
name: Release ${{ env.TAG }}
|
||||
body: ${{ steps.changelog.outputs.changelog }}
|
||||
draft: false
|
||||
prerelease: ${{ contains(github.ref_name, '-') }}
|
||||
prerelease: ${{ contains(env.TAG, '-') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Download DMS release assets
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
# gh is preinstalled on ubuntu-24.04; auth via GH_TOKEN env
|
||||
gh release download "${TAG}" -R "${DMS_REPO}" --dir ./_dms_assets
|
||||
|
||||
- name: Attach DMS assets to DankMaterialShell release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ env.TAG }}
|
||||
files: _dms_assets/**
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
233
CLAUDE.md
233
CLAUDE.md
@@ -63,6 +63,9 @@ quickshell -p shell.qml
|
||||
# Or use the shorthand
|
||||
qs -p .
|
||||
|
||||
# Run with verbose output for debugging
|
||||
qs -v -p shell.qml
|
||||
|
||||
# Code formatting and linting
|
||||
qmlfmt -t 4 -i 4 -b 250 -w /path/to/file.qml # Format a QML file (requires qmlfmt, do not use qmlformat)
|
||||
qmllint **/*.qml # Lint all QML files for syntax errors
|
||||
@@ -89,6 +92,7 @@ shell.qml # Main entry point (minimal orchestration)
|
||||
│ ├── DisplayService.qml
|
||||
│ ├── NotificationService.qml
|
||||
│ ├── WeatherService.qml
|
||||
│ ├── PluginService.qml
|
||||
│ └── [14 more services]
|
||||
├── Modules/ # UI components (93 files)
|
||||
│ ├── TopBar/ # Panel components (13 files)
|
||||
@@ -104,15 +108,21 @@ shell.qml # Main entry point (minimal orchestration)
|
||||
│ ├── SettingsModal.qml
|
||||
│ ├── ClipboardHistoryModal.qml
|
||||
│ ├── ProcessListModal.qml
|
||||
│ ├── PluginSettingsModal.qml
|
||||
│ └── [7 more modals]
|
||||
└── Widgets/ # Reusable UI controls (19 files)
|
||||
├── DankIcon.qml
|
||||
├── DankSlider.qml
|
||||
├── DankToggle.qml
|
||||
├── DankTabBar.qml
|
||||
├── DankGridView.qml
|
||||
├── DankListView.qml
|
||||
└── [13 more widgets]
|
||||
├── Widgets/ # Reusable UI controls (19 files)
|
||||
│ ├── DankIcon.qml
|
||||
│ ├── DankSlider.qml
|
||||
│ ├── DankToggle.qml
|
||||
│ ├── DankTabBar.qml
|
||||
│ ├── DankGridView.qml
|
||||
│ ├── DankListView.qml
|
||||
│ └── [13 more widgets]
|
||||
└── plugins/ # External plugins directory ($CONFIGPATH/DankMaterialShell/plugins/)
|
||||
└── PluginName/ # Example Plugin structure
|
||||
├── plugin.json # Plugin manifest
|
||||
├── PluginNameWidget.qml # Widget component
|
||||
└── PluginNameSettings.qml # Settings UI
|
||||
```
|
||||
|
||||
### Component Organization
|
||||
@@ -163,6 +173,12 @@ shell.qml # Main entry point (minimal orchestration)
|
||||
- **DankLocationSearch**: Location picker with search
|
||||
- **SystemLogo**: Animated system branding component
|
||||
|
||||
7. **Plugins/** - External plugin system (`$CONFIGPATH/DankMaterialShell/plugins/`)
|
||||
- **PluginService**: Discovers, loads, and manages plugin lifecycle
|
||||
- **Dynamic Loading**: Plugins loaded at runtime from external directory
|
||||
- **DankBar Integration**: Plugin widgets rendered alongside built-in widgets
|
||||
- **Settings System**: Per-plugin settings with persistence
|
||||
|
||||
### Key Architectural Patterns
|
||||
|
||||
1. **Singleton Services Pattern**:
|
||||
@@ -408,10 +424,10 @@ When modifying the shell:
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
|
||||
property bool featureAvailable: false
|
||||
property type currentValue: defaultValue
|
||||
|
||||
|
||||
function performAction(param) {
|
||||
// Implementation
|
||||
}
|
||||
@@ -422,7 +438,7 @@ When modifying the shell:
|
||||
```qml
|
||||
// In module files
|
||||
property alias serviceValue: NewService.currentValue
|
||||
|
||||
|
||||
SomeControl {
|
||||
visible: NewService.featureAvailable
|
||||
enabled: NewService.featureAvailable
|
||||
@@ -430,6 +446,200 @@ When modifying the shell:
|
||||
}
|
||||
```
|
||||
|
||||
### Creating Plugins
|
||||
|
||||
Plugins are external, dynamically-loaded components that extend DankMaterialShell functionality. Plugins are stored in `~/.config/DankMaterialShell/plugins/` and have their settings isolated from core DMS settings.
|
||||
|
||||
**Plugin Types:**
|
||||
- **Widget plugins** (`"type": "widget"` or omit type field): Display UI components in DankBar
|
||||
- **Daemon plugins** (`"type": "daemon"`): Run invisibly in the background without UI
|
||||
|
||||
#### Widget Plugins
|
||||
|
||||
1. **Create plugin directory**:
|
||||
```bash
|
||||
mkdir -p ~/.config/DankMaterialShell/plugins/YourPlugin
|
||||
```
|
||||
|
||||
2. **Create manifest** (`plugin.json`):
|
||||
```json
|
||||
{
|
||||
"id": "yourPlugin",
|
||||
"name": "Your Plugin",
|
||||
"description": "Widget description",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"icon": "extension",
|
||||
"type": "widget",
|
||||
"component": "./YourWidget.qml",
|
||||
"settings": "./YourSettings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
```
|
||||
|
||||
3. **Create widget component** (`YourWidget.qml`):
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Services
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool compactMode: false
|
||||
property string section: "center"
|
||||
property real widgetHeight: 30
|
||||
property var pluginService: null
|
||||
|
||||
width: content.implicitWidth + 16
|
||||
height: widgetHeight
|
||||
radius: 8
|
||||
color: "#20FFFFFF"
|
||||
|
||||
Component.onCompleted: {
|
||||
if (pluginService) {
|
||||
var data = pluginService.loadPluginData("yourPlugin", "key", defaultValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **Create settings component** (`YourSettings.qml`):
|
||||
```qml
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
property var pluginService: null
|
||||
|
||||
implicitHeight: settingsColumn.implicitHeight
|
||||
height: implicitHeight
|
||||
|
||||
Column {
|
||||
id: settingsColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
text: "Your Plugin Settings"
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
// Your settings UI here
|
||||
}
|
||||
|
||||
function saveSettings(key, value) {
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("yourPlugin", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
function loadSettings(key, defaultValue) {
|
||||
if (pluginService) {
|
||||
return pluginService.loadPluginData("yourPlugin", key, defaultValue)
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. **Enable plugin**:
|
||||
- Open Settings → Plugins
|
||||
- Click "Scan for Plugins"
|
||||
- Toggle plugin to enable
|
||||
- Add plugin ID to DankBar widget list
|
||||
|
||||
#### Daemon Plugins
|
||||
|
||||
Daemon plugins run invisibly in the background without any UI components. They're useful for monitoring system events, background tasks, or data synchronization.
|
||||
|
||||
1. **Create plugin directory**:
|
||||
```bash
|
||||
mkdir -p ~/.config/DankMaterialShell/plugins/YourDaemon
|
||||
```
|
||||
|
||||
2. **Create manifest** (`plugin.json`):
|
||||
```json
|
||||
{
|
||||
"id": "yourDaemon",
|
||||
"name": "Your Daemon",
|
||||
"description": "Background daemon description",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"icon": "settings_applications",
|
||||
"type": "daemon",
|
||||
"component": "./YourDaemon.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
```
|
||||
|
||||
3. **Create daemon component** (`YourDaemon.qml`):
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var pluginService: null
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onWallpaperPathChanged() {
|
||||
console.log("Wallpaper changed:", SessionData.wallpaperPath)
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("yourDaemon", "lastEvent", Date.now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("Daemon started")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **Enable daemon**:
|
||||
- Open Settings → Plugins
|
||||
- Click "Scan for Plugins"
|
||||
- Toggle daemon to enable
|
||||
- Daemon runs automatically in background
|
||||
|
||||
**Example**: See `PLUGINS/WallpaperWatcherDaemon/` for a complete daemon plugin that monitors wallpaper changes
|
||||
|
||||
**Plugin Directory Structure:**
|
||||
```
|
||||
~/.config/DankMaterialShell/
|
||||
├── settings.json # Core DMS settings + plugin settings
|
||||
│ └── pluginSettings: {
|
||||
│ └── yourPlugin: {
|
||||
│ ├── enabled: true,
|
||||
│ └── customData: {...}
|
||||
│ }
|
||||
│ }
|
||||
└── plugins/ # Plugin files directory
|
||||
└── YourPlugin/ # Plugin directory (matches manifest ID)
|
||||
├── plugin.json # Plugin manifest
|
||||
├── YourWidget.qml # Widget component
|
||||
└── YourSettings.qml # Settings UI (optional)
|
||||
```
|
||||
|
||||
**Key Plugin APIs:**
|
||||
- `pluginService.loadPluginData(pluginId, key, default)` - Load persistent data
|
||||
- `pluginService.savePluginData(pluginId, key, value)` - Save persistent data
|
||||
- `PluginService.enablePlugin(pluginId)` - Load plugin
|
||||
- `PluginService.disablePlugin(pluginId)` - Unload plugin
|
||||
|
||||
**Important Notes:**
|
||||
- Plugin settings are automatically injected by the PluginService via `item.pluginService = PluginService`
|
||||
- Settings are stored in the main settings.json but namespaced under `pluginSettings.{pluginId}`
|
||||
- Plugin directories must match the plugin ID in the manifest
|
||||
- Use the injected `pluginService` property in both widget and settings components
|
||||
|
||||
### Debugging Common Issues
|
||||
|
||||
1. **Import errors**: Check import paths
|
||||
@@ -454,6 +664,7 @@ When modifying the shell:
|
||||
- **Function Discovery**: Use grep/search tools to find existing utility functions before implementing new ones
|
||||
- **Modern QML Patterns**: Leverage new widgets like DankTextField, DankDropdown, CachingImage
|
||||
- **Structured Organization**: Follow the established Services/Modules/Widgets/Modals separation
|
||||
- **Plugin System**: For user extensions, create plugins instead of modifying core modules - see docs/PLUGINS.md
|
||||
|
||||
### Common Widget Patterns
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contributing
|
||||
|
||||
Contributions are welcome and encourages.
|
||||
Contributions are welcome and encouraged.
|
||||
|
||||
## Formatting
|
||||
|
||||
@@ -27,4 +27,4 @@ Sometimes it just breaks code though. Like turning `"_\""` into `"_""`, so you m
|
||||
|
||||
## Pull request
|
||||
|
||||
Include screenshots/video if applicable in your pull request if applicable, to visualize what your change is affecting.
|
||||
Include screenshots/video if applicable in your pull request if applicable, to visualize what your change is affecting.
|
||||
|
||||
53
Common/Facts.qml
Normal file
53
Common/Facts.qml
Normal file
@@ -0,0 +1,53 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property var facts: [
|
||||
"A photon takes 100,000 to 200,000 years bouncing through the Sun's dense core, then races to Earth in just 8 minutes 20 seconds.",
|
||||
"A teaspoon of neutron star matter would weigh a billion metric tons here on Earth.",
|
||||
"Right now, 100 trillion solar neutrinos are passing through your body every second.",
|
||||
"The Sun converts 4 million metric tons of matter into pure energy every second—enough to power Earth for 500,000 years.",
|
||||
"The universe still glows with leftover heat from the Big Bang—just 2.7 degrees above absolute zero.",
|
||||
"There's a nebula out there that's actually colder than empty space itself.",
|
||||
"We've detected black holes crashing together by measuring spacetime stretch by less than 1/10,000th the width of a proton.",
|
||||
"Fast radio bursts can release more energy in 5 milliseconds than our Sun produces in 3 days.",
|
||||
"Our galaxy might be crawling with billions of rogue planets drifting alone in the dark.",
|
||||
"Distant galaxies can move away from us faster than light because space itself is stretching.",
|
||||
"The edge of what we can see is 46.5 billion light-years away, even though the universe is only 13.8 billion years old.",
|
||||
"The universe is mostly invisible: 5% regular matter, 27% dark matter, 68% dark energy.",
|
||||
"A day on Venus lasts longer than its entire year around the Sun.",
|
||||
"On Mercury, the time between sunrises is 176 Earth days long.",
|
||||
"In about 4.5 billion years, our galaxy will smash into Andromeda.",
|
||||
"Most of the gold in your jewelry was forged when neutron stars collided somewhere in space.",
|
||||
"PSR J1748-2446ad, the fastest spinning star, rotates 716 times per second—its equator moves at 24% the speed of light.",
|
||||
"Cosmic rays create particles that shouldn't make it to Earth's surface, but time dilation lets them sneak through.",
|
||||
"Jupiter's magnetic field is so huge that if we could see it, it would look bigger than the Moon in our sky.",
|
||||
"Interstellar space is so empty it's like a cube 32 kilometers wide containing just a single grain of sand.",
|
||||
"Voyager 1 is 24 billion kilometers away but won't leave the Sun's gravitational influence for another 30,000 years.",
|
||||
"Counting to a billion at one number per second would take over 31 years.",
|
||||
"Space is so vast, even speeding at light-speed, you'd never return past the cosmic horizon.",
|
||||
"Astronauts on the ISS age about 0.01 seconds less each year than people on Earth.",
|
||||
"Sagittarius B2, a dust cloud near our galaxy's center, contains ethyl formate—the compound that gives raspberries their flavor and rum its smell.",
|
||||
"Beyond 16 billion light-years, the cosmic event horizon marks where space expands too fast for light to ever reach us again.",
|
||||
"Even at light-speed, you'd never catch up to most galaxies—space expands faster.",
|
||||
"Only around 5% of galaxies are ever reachable—even at light-speed.",
|
||||
"If the Sun vanished, we'd still orbit it for 8 minutes before drifting away.",
|
||||
"If a planet 65 million light-years away looked at Earth now, it'd see dinosaurs.",
|
||||
"Our oldest radio signals will reach the Milky Way's center in 26,000 years.",
|
||||
"Every atom in your body heavier than hydrogen was forged in the nuclear furnace of a dying star.",
|
||||
"The Moon moves 3.8 centimeters farther from Earth every year.",
|
||||
"The universe creates 275 million new stars every single day.",
|
||||
"Jupiter's Great Red Spot is a storm twice the size of Earth that has been raging for at least 350 years.",
|
||||
"If you watched someone fall into a black hole, they'd appear frozen at the event horizon forever—time effectively stops from your perspective.",
|
||||
"The Boötes Supervoid is a cosmic desert 1.8 billion light-years across with 60% fewer galaxies than it should have."
|
||||
]
|
||||
|
||||
function getRandomFact() {
|
||||
return facts[Math.floor(Math.random() * facts.length)]
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,19 @@ Singleton {
|
||||
|
||||
id: root
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
property bool isLightMode: false
|
||||
property string wallpaperPath: ""
|
||||
property string wallpaperLastPath: ""
|
||||
property string profileLastPath: ""
|
||||
property bool perMonitorWallpaper: false
|
||||
property var monitorWallpapers: ({})
|
||||
property bool perModeWallpaper: false
|
||||
property string wallpaperPathLight: ""
|
||||
property string wallpaperPathDark: ""
|
||||
property var monitorWallpapersLight: ({})
|
||||
property var monitorWallpapersDark: ({})
|
||||
property bool doNotDisturb: false
|
||||
property bool nightModeEnabled: false
|
||||
property int nightModeTemperature: 4500
|
||||
@@ -63,14 +70,22 @@ Singleton {
|
||||
property int batteryHibernateTimeout: 0 // Never
|
||||
|
||||
property bool lockBeforeSuspend: false
|
||||
property var recentColors: []
|
||||
property bool showThirdPartyPlugins: false
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
loadSettings()
|
||||
if (!isGreeterMode) {
|
||||
loadSettings()
|
||||
}
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
parseSettings(settingsFile.text())
|
||||
if (isGreeterMode) {
|
||||
parseSettings(greeterSessionFile.text())
|
||||
} else {
|
||||
parseSettings(settingsFile.text())
|
||||
}
|
||||
}
|
||||
|
||||
function parseSettings(content) {
|
||||
@@ -83,6 +98,11 @@ Singleton {
|
||||
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
|
||||
wallpaperPathLight = settings.wallpaperPathLight !== undefined ? settings.wallpaperPathLight : ""
|
||||
wallpaperPathDark = settings.wallpaperPathDark !== undefined ? settings.wallpaperPathDark : ""
|
||||
monitorWallpapersLight = settings.monitorWallpapersLight !== undefined ? settings.monitorWallpapersLight : {}
|
||||
monitorWallpapersDark = settings.monitorWallpapersDark !== undefined ? settings.monitorWallpapersDark : {}
|
||||
doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false
|
||||
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
|
||||
nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500
|
||||
@@ -132,10 +152,13 @@ Singleton {
|
||||
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
|
||||
batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
|
||||
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
|
||||
|
||||
// Generate system themes but don't override user's theme choice
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
recentColors = settings.recentColors !== undefined ? settings.recentColors : []
|
||||
showThirdPartyPlugins = settings.showThirdPartyPlugins !== undefined ? settings.showThirdPartyPlugins : false
|
||||
|
||||
if (!isGreeterMode) {
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -144,6 +167,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
if (isGreeterMode) return
|
||||
settingsFile.setText(JSON.stringify({
|
||||
"isLightMode": isLightMode,
|
||||
"wallpaperPath": wallpaperPath,
|
||||
@@ -151,6 +175,11 @@ Singleton {
|
||||
"profileLastPath": profileLastPath,
|
||||
"perMonitorWallpaper": perMonitorWallpaper,
|
||||
"monitorWallpapers": monitorWallpapers,
|
||||
"perModeWallpaper": perModeWallpaper,
|
||||
"wallpaperPathLight": wallpaperPathLight,
|
||||
"wallpaperPathDark": wallpaperPathDark,
|
||||
"monitorWallpapersLight": monitorWallpapersLight,
|
||||
"monitorWallpapersDark": monitorWallpapersDark,
|
||||
"doNotDisturb": doNotDisturb,
|
||||
"nightModeEnabled": nightModeEnabled,
|
||||
"nightModeTemperature": nightModeTemperature,
|
||||
@@ -185,15 +214,29 @@ Singleton {
|
||||
"batteryLockTimeout": batteryLockTimeout,
|
||||
"batterySuspendTimeout": batterySuspendTimeout,
|
||||
"batteryHibernateTimeout": batteryHibernateTimeout,
|
||||
"lockBeforeSuspend": lockBeforeSuspend
|
||||
"lockBeforeSuspend": lockBeforeSuspend,
|
||||
"recentColors": recentColors,
|
||||
"showThirdPartyPlugins": showThirdPartyPlugins
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -264,6 +307,13 @@ Singleton {
|
||||
|
||||
function setWallpaper(imagePath) {
|
||||
wallpaperPath = imagePath
|
||||
if (perModeWallpaper) {
|
||||
if (isLightMode) {
|
||||
wallpaperPathLight = imagePath
|
||||
} else {
|
||||
wallpaperPathDark = imagePath
|
||||
}
|
||||
}
|
||||
saveSettings()
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
@@ -273,6 +323,13 @@ Singleton {
|
||||
|
||||
function setWallpaperColor(color) {
|
||||
wallpaperPath = color
|
||||
if (perModeWallpaper) {
|
||||
if (isLightMode) {
|
||||
wallpaperPathLight = color
|
||||
} else {
|
||||
wallpaperPathDark = color
|
||||
}
|
||||
}
|
||||
saveSettings()
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
@@ -308,6 +365,16 @@ Singleton {
|
||||
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
|
||||
@@ -420,9 +487,51 @@ Singleton {
|
||||
|
||||
function setPerMonitorWallpaper(enabled) {
|
||||
perMonitorWallpaper = enabled
|
||||
if (enabled && perModeWallpaper) {
|
||||
syncWallpaperForCurrentMode()
|
||||
}
|
||||
saveSettings()
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
|
||||
function setPerModeWallpaper(enabled) {
|
||||
if (enabled && wallpaperCyclingEnabled) {
|
||||
setWallpaperCyclingEnabled(false)
|
||||
}
|
||||
if (enabled && perMonitorWallpaper) {
|
||||
var monitorCyclingAny = false
|
||||
for (var key in monitorCyclingSettings) {
|
||||
if (monitorCyclingSettings[key].enabled) {
|
||||
monitorCyclingAny = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (monitorCyclingAny) {
|
||||
var newSettings = Object.assign({}, monitorCyclingSettings)
|
||||
for (var screenName in newSettings) {
|
||||
newSettings[screenName].enabled = false
|
||||
}
|
||||
monitorCyclingSettings = newSettings
|
||||
}
|
||||
}
|
||||
|
||||
perModeWallpaper = enabled
|
||||
if (enabled) {
|
||||
if (perMonitorWallpaper) {
|
||||
monitorWallpapersLight = Object.assign({}, monitorWallpapers)
|
||||
monitorWallpapersDark = Object.assign({}, monitorWallpapers)
|
||||
} else {
|
||||
wallpaperPathLight = wallpaperPath
|
||||
wallpaperPathDark = wallpaperPath
|
||||
}
|
||||
} else {
|
||||
syncWallpaperForCurrentMode()
|
||||
}
|
||||
saveSettings()
|
||||
|
||||
// Refresh dynamic theming when per-monitor mode changes
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
@@ -436,9 +545,29 @@ Singleton {
|
||||
delete newMonitorWallpapers[screenName]
|
||||
}
|
||||
monitorWallpapers = newMonitorWallpapers
|
||||
|
||||
if (perModeWallpaper) {
|
||||
if (isLightMode) {
|
||||
var newLight = Object.assign({}, monitorWallpapersLight)
|
||||
if (path && path !== "") {
|
||||
newLight[screenName] = path
|
||||
} else {
|
||||
delete newLight[screenName]
|
||||
}
|
||||
monitorWallpapersLight = newLight
|
||||
} else {
|
||||
var newDark = Object.assign({}, monitorWallpapersDark)
|
||||
if (path && path !== "") {
|
||||
newDark[screenName] = path
|
||||
} else {
|
||||
delete newDark[screenName]
|
||||
}
|
||||
monitorWallpapersDark = newDark
|
||||
}
|
||||
}
|
||||
|
||||
saveSettings()
|
||||
|
||||
// Trigger dynamic theming if this is the first monitor and dynamic theming is enabled
|
||||
if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined") {
|
||||
var screens = Quickshell.screens
|
||||
if (screens.length > 0 && screenName === screens[0].name) {
|
||||
@@ -514,25 +643,51 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setShowThirdPartyPlugins(enabled) {
|
||||
showThirdPartyPlugins = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: settingsFile
|
||||
|
||||
path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
|
||||
blockLoading: true
|
||||
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
|
||||
blockLoading: isGreeterMode
|
||||
blockWrites: true
|
||||
watchChanges: true
|
||||
watchChanges: !isGreeterMode
|
||||
onLoaded: {
|
||||
parseSettings(settingsFile.text())
|
||||
hasTriedDefaultSession = false
|
||||
if (!isGreeterMode) {
|
||||
parseSettings(settingsFile.text())
|
||||
hasTriedDefaultSession = false
|
||||
}
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
if (!hasTriedDefaultSession) {
|
||||
hasTriedDefaultSession = true
|
||||
if (!isGreeterMode && !hasTriedDefaultSettings) {
|
||||
hasTriedDefaultSettings = true
|
||||
defaultSessionCheckProcess.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: greeterSessionFile
|
||||
|
||||
path: {
|
||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
|
||||
return greetCfgDir + "/session.json"
|
||||
}
|
||||
preload: isGreeterMode
|
||||
blockLoading: false
|
||||
blockWrites: true
|
||||
watchChanges: false
|
||||
printErrors: true
|
||||
onLoaded: {
|
||||
if (isGreeterMode) {
|
||||
parseSettings(greeterSessionFile.text())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: defaultSessionCheckProcess
|
||||
|
||||
|
||||
@@ -12,12 +12,29 @@ import qs.Services
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
enum Position {
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
enum AnimationSpeed {
|
||||
None,
|
||||
Shortest,
|
||||
Short,
|
||||
Medium,
|
||||
Long
|
||||
}
|
||||
|
||||
// Theme settings
|
||||
property string currentThemeName: "blue"
|
||||
property string customThemeFile: ""
|
||||
property string matugenScheme: "scheme-tonal-spot"
|
||||
property real topBarTransparency: 0.75
|
||||
property real topBarWidgetTransparency: 0.85
|
||||
property real dankBarTransparency: 1.0
|
||||
property real dankBarWidgetTransparency: 1.0
|
||||
property real popupTransparency: 1.0
|
||||
property real dockTransparency: 1
|
||||
property bool use24HourClock: true
|
||||
@@ -70,12 +87,13 @@ Singleton {
|
||||
property string clockDateFormat: ""
|
||||
property string lockDateFormat: ""
|
||||
property int mediaSize: 1
|
||||
property var topBarLeftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"]
|
||||
property var topBarCenterWidgets: ["music", "clock", "weather"]
|
||||
property var topBarRightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"]
|
||||
property alias topBarLeftWidgetsModel: leftWidgetsModel
|
||||
property alias topBarCenterWidgetsModel: centerWidgetsModel
|
||||
property alias topBarRightWidgetsModel: rightWidgetsModel
|
||||
property var dankBarLeftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"]
|
||||
property var dankBarCenterWidgets: ["music", "clock", "weather"]
|
||||
property var dankBarRightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"]
|
||||
property var dankBarWidgetOrder: []
|
||||
property alias dankBarLeftWidgetsModel: leftWidgetsModel
|
||||
property alias dankBarCenterWidgetsModel: centerWidgetsModel
|
||||
property alias dankBarRightWidgetsModel: rightWidgetsModel
|
||||
property string appLauncherViewMode: "list"
|
||||
property string spotlightModalViewMode: "list"
|
||||
property string networkPreference: "auto"
|
||||
@@ -85,15 +103,19 @@ Singleton {
|
||||
property bool qt5ctAvailable: false
|
||||
property bool qt6ctAvailable: false
|
||||
property bool gtkAvailable: false
|
||||
property bool useOSLogo: false
|
||||
property string osLogoColorOverride: ""
|
||||
property real osLogoBrightness: 0.5
|
||||
property real osLogoContrast: 1
|
||||
property string launcherLogoMode: "apps"
|
||||
property string launcherLogoCustomPath: ""
|
||||
property string launcherLogoColorOverride: ""
|
||||
property bool launcherLogoColorInvertOnMode: false
|
||||
property real launcherLogoBrightness: 0.5
|
||||
property real launcherLogoContrast: 1
|
||||
property int launcherLogoSizeOffset: 0
|
||||
property bool weatherEnabled: true
|
||||
property string fontFamily: "Inter Variable"
|
||||
property string monoFontFamily: "Fira Code"
|
||||
property int fontWeight: Font.Normal
|
||||
property real fontScale: 1.0
|
||||
property real dankBarFontScale: 1.0
|
||||
property bool notepadUseMonospace: true
|
||||
property string notepadFontFamily: ""
|
||||
property real notepadFontSize: 14
|
||||
@@ -117,17 +139,24 @@ Singleton {
|
||||
property bool showDock: false
|
||||
property bool dockAutoHide: false
|
||||
property bool dockGroupByApp: false
|
||||
property bool dockOpenOnOverview: false
|
||||
property int dockPosition: SettingsData.Position.Bottom
|
||||
property real dockSpacing: 4
|
||||
property real dockBottomGap: 0
|
||||
property real cornerRadius: 12
|
||||
property bool notificationOverlayEnabled: false
|
||||
property bool topBarAutoHide: false
|
||||
property bool topBarOpenOnOverview: false
|
||||
property bool topBarVisible: true
|
||||
property real topBarSpacing: 4
|
||||
property real topBarBottomGap: 0
|
||||
property real topBarInnerPadding: 8
|
||||
property bool topBarSquareCorners: false
|
||||
property bool topBarNoBackground: false
|
||||
property bool topBarGothCornersEnabled: false
|
||||
property bool dankBarAutoHide: false
|
||||
property bool dankBarOpenOnOverview: false
|
||||
property bool dankBarVisible: true
|
||||
property real dankBarSpacing: 4
|
||||
property real dankBarBottomGap: 0
|
||||
property real dankBarInnerPadding: 4
|
||||
property bool dankBarSquareCorners: false
|
||||
property bool dankBarNoBackground: false
|
||||
property bool dankBarGothCornersEnabled: false
|
||||
property bool dankBarBorderEnabled: false
|
||||
property int dankBarPosition: SettingsData.Position.Top
|
||||
property bool dankBarIsVertical: dankBarPosition === SettingsData.Position.Left || dankBarPosition === SettingsData.Position.Right
|
||||
property bool lockScreenShowPowerActions: true
|
||||
property bool hideBrightnessSlider: false
|
||||
property string widgetBackgroundColor: "sch"
|
||||
@@ -135,18 +164,26 @@ Singleton {
|
||||
property int notificationTimeoutLow: 5000
|
||||
property int notificationTimeoutNormal: 5000
|
||||
property int notificationTimeoutCritical: 0
|
||||
property int notificationPopupPosition: SettingsData.Position.Top
|
||||
property bool osdAlwaysShowValue: false
|
||||
property var screenPreferences: ({})
|
||||
property int animationSpeed: SettingsData.AnimationSpeed.Short
|
||||
readonly property string defaultFontFamily: "Inter Variable"
|
||||
readonly property string defaultMonoFontFamily: "Fira Code"
|
||||
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
|
||||
readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation)
|
||||
readonly property string _configDir: Paths.strip(_configUrl)
|
||||
readonly property string pluginSettingsPath: _configDir + "/DankMaterialShell/plugin_settings.json"
|
||||
|
||||
signal forceTopBarLayoutRefresh
|
||||
signal forceDankBarLayoutRefresh
|
||||
signal forceDockLayoutRefresh
|
||||
signal widgetDataChanged
|
||||
signal workspaceIconsUpdated
|
||||
|
||||
property bool _loading: false
|
||||
property bool _pluginSettingsLoading: false
|
||||
|
||||
property var pluginSettings: ({})
|
||||
|
||||
function getEffectiveTimeFormat() {
|
||||
if (use24HourClock) {
|
||||
@@ -173,28 +210,57 @@ Singleton {
|
||||
"size": 20,
|
||||
"selectedGpuIndex": 0,
|
||||
"pciId": "",
|
||||
"mountPath": "/"
|
||||
"mountPath": "/",
|
||||
"minimumWidth": true
|
||||
}
|
||||
leftWidgetsModel.append(dummyItem)
|
||||
centerWidgetsModel.append(dummyItem)
|
||||
rightWidgetsModel.append(dummyItem)
|
||||
|
||||
updateListModel(leftWidgetsModel, topBarLeftWidgets)
|
||||
updateListModel(centerWidgetsModel, topBarCenterWidgets)
|
||||
updateListModel(rightWidgetsModel, topBarRightWidgets)
|
||||
updateListModel(leftWidgetsModel, dankBarLeftWidgets)
|
||||
updateListModel(centerWidgetsModel, dankBarCenterWidgets)
|
||||
updateListModel(rightWidgetsModel, dankBarRightWidgets)
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
_loading = true
|
||||
parseSettings(settingsFile.text())
|
||||
_loading = false
|
||||
loadPluginSettings()
|
||||
}
|
||||
|
||||
function loadPluginSettings() {
|
||||
_pluginSettingsLoading = true
|
||||
parsePluginSettings(pluginSettingsFile.text())
|
||||
_pluginSettingsLoading = false
|
||||
}
|
||||
|
||||
function parsePluginSettings(content) {
|
||||
_pluginSettingsLoading = true
|
||||
try {
|
||||
if (content && content.trim()) {
|
||||
pluginSettings = JSON.parse(content)
|
||||
} else {
|
||||
pluginSettings = {}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("SettingsData: Failed to parse plugin settings:", e.message)
|
||||
pluginSettings = {}
|
||||
} finally {
|
||||
_pluginSettingsLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
function parseSettings(content) {
|
||||
_loading = true
|
||||
var shouldMigrate = false
|
||||
try {
|
||||
if (content && content.trim()) {
|
||||
var settings = JSON.parse(content)
|
||||
if (settings.pluginSettings !== undefined) {
|
||||
pluginSettings = settings.pluginSettings
|
||||
shouldMigrate = true
|
||||
}
|
||||
// Auto-migrate from old theme system
|
||||
if (settings.themeIndex !== undefined || settings.themeIsDynamic !== undefined) {
|
||||
const themeNames = ["blue", "deepBlue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral"]
|
||||
@@ -209,8 +275,8 @@ Singleton {
|
||||
}
|
||||
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
|
||||
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot"
|
||||
topBarTransparency = settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 0.75
|
||||
topBarWidgetTransparency = settings.topBarWidgetTransparency !== undefined ? (settings.topBarWidgetTransparency > 1 ? settings.topBarWidgetTransparency / 100 : settings.topBarWidgetTransparency) : 0.85
|
||||
dankBarTransparency = settings.dankBarTransparency !== undefined ? (settings.dankBarTransparency > 1 ? settings.dankBarTransparency / 100 : settings.dankBarTransparency) : (settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 1.0)
|
||||
dankBarWidgetTransparency = settings.dankBarWidgetTransparency !== undefined ? (settings.dankBarWidgetTransparency > 1 ? settings.dankBarWidgetTransparency / 100 : settings.dankBarWidgetTransparency) : (settings.topBarWidgetTransparency !== undefined ? (settings.topBarWidgetTransparency > 1 ? settings.topBarWidgetTransparency / 100 : settings.topBarWidgetTransparency) : 1.0)
|
||||
popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 1.0
|
||||
dockTransparency = settings.dockTransparency !== undefined ? (settings.dockTransparency > 1 ? settings.dockTransparency / 100 : settings.dockTransparency) : 1
|
||||
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true
|
||||
@@ -264,23 +330,24 @@ Singleton {
|
||||
clockDateFormat = settings.clockDateFormat !== undefined ? settings.clockDateFormat : ""
|
||||
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : ""
|
||||
mediaSize = settings.mediaSize !== undefined ? settings.mediaSize : (settings.mediaCompactMode !== undefined ? (settings.mediaCompactMode ? 0 : 1) : 1)
|
||||
if (settings.topBarWidgetOrder) {
|
||||
topBarLeftWidgets = settings.topBarWidgetOrder.filter(w => {
|
||||
if (settings.dankBarWidgetOrder || settings.topBarWidgetOrder) {
|
||||
var widgetOrder = settings.dankBarWidgetOrder || settings.topBarWidgetOrder
|
||||
dankBarLeftWidgets = widgetOrder.filter(w => {
|
||||
return ["launcherButton", "workspaceSwitcher", "focusedWindow"].includes(w)
|
||||
})
|
||||
topBarCenterWidgets = settings.topBarWidgetOrder.filter(w => {
|
||||
dankBarCenterWidgets = widgetOrder.filter(w => {
|
||||
return ["clock", "music", "weather"].includes(w)
|
||||
})
|
||||
topBarRightWidgets = settings.topBarWidgetOrder.filter(w => {
|
||||
dankBarRightWidgets = widgetOrder.filter(w => {
|
||||
return ["systemTray", "clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"].includes(w)
|
||||
})
|
||||
} else {
|
||||
var leftWidgets = settings.topBarLeftWidgets !== undefined ? settings.topBarLeftWidgets : ["launcherButton", "workspaceSwitcher", "focusedWindow"]
|
||||
var centerWidgets = settings.topBarCenterWidgets !== undefined ? settings.topBarCenterWidgets : ["music", "clock", "weather"]
|
||||
var rightWidgets = settings.topBarRightWidgets !== undefined ? settings.topBarRightWidgets : ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"]
|
||||
topBarLeftWidgets = leftWidgets
|
||||
topBarCenterWidgets = centerWidgets
|
||||
topBarRightWidgets = rightWidgets
|
||||
var leftWidgets = settings.dankBarLeftWidgets !== undefined ? settings.dankBarLeftWidgets : (settings.topBarLeftWidgets !== undefined ? settings.topBarLeftWidgets : ["launcherButton", "workspaceSwitcher", "focusedWindow"])
|
||||
var centerWidgets = settings.dankBarCenterWidgets !== undefined ? settings.dankBarCenterWidgets : (settings.topBarCenterWidgets !== undefined ? settings.topBarCenterWidgets : ["music", "clock", "weather"])
|
||||
var rightWidgets = settings.dankBarRightWidgets !== undefined ? settings.dankBarRightWidgets : (settings.topBarRightWidgets !== undefined ? settings.topBarRightWidgets : ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"])
|
||||
dankBarLeftWidgets = leftWidgets
|
||||
dankBarCenterWidgets = centerWidgets
|
||||
dankBarRightWidgets = rightWidgets
|
||||
updateListModel(leftWidgetsModel, leftWidgets)
|
||||
updateListModel(centerWidgetsModel, centerWidgets)
|
||||
updateListModel(rightWidgetsModel, rightWidgets)
|
||||
@@ -289,14 +356,25 @@ Singleton {
|
||||
spotlightModalViewMode = settings.spotlightModalViewMode !== undefined ? settings.spotlightModalViewMode : "list"
|
||||
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto"
|
||||
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default"
|
||||
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false
|
||||
osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : ""
|
||||
osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5
|
||||
osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1
|
||||
if (settings.useOSLogo !== undefined) {
|
||||
launcherLogoMode = settings.useOSLogo ? "os" : "apps"
|
||||
launcherLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : ""
|
||||
launcherLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5
|
||||
launcherLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1
|
||||
} else {
|
||||
launcherLogoMode = settings.launcherLogoMode !== undefined ? settings.launcherLogoMode : "apps"
|
||||
launcherLogoCustomPath = settings.launcherLogoCustomPath !== undefined ? settings.launcherLogoCustomPath : ""
|
||||
launcherLogoColorOverride = settings.launcherLogoColorOverride !== undefined ? settings.launcherLogoColorOverride : ""
|
||||
launcherLogoColorInvertOnMode = settings.launcherLogoColorInvertOnMode !== undefined ? settings.launcherLogoColorInvertOnMode : false
|
||||
launcherLogoBrightness = settings.launcherLogoBrightness !== undefined ? settings.launcherLogoBrightness : 0.5
|
||||
launcherLogoContrast = settings.launcherLogoContrast !== undefined ? settings.launcherLogoContrast : 1
|
||||
launcherLogoSizeOffset = settings.launcherLogoSizeOffset !== undefined ? settings.launcherLogoSizeOffset : 0
|
||||
}
|
||||
fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : defaultFontFamily
|
||||
monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : defaultMonoFontFamily
|
||||
fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal
|
||||
fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0
|
||||
dankBarFontScale = settings.dankBarFontScale !== undefined ? settings.dankBarFontScale : 1.0
|
||||
notepadUseMonospace = settings.notepadUseMonospace !== undefined ? settings.notepadUseMonospace : true
|
||||
notepadFontFamily = settings.notepadFontFamily !== undefined ? settings.notepadFontFamily : ""
|
||||
notepadFontSize = settings.notepadFontSize !== undefined ? settings.notepadFontSize : 14
|
||||
@@ -308,25 +386,34 @@ Singleton {
|
||||
showDock = settings.showDock !== undefined ? settings.showDock : false
|
||||
dockAutoHide = settings.dockAutoHide !== undefined ? settings.dockAutoHide : false
|
||||
dockGroupByApp = settings.dockGroupByApp !== undefined ? settings.dockGroupByApp : false
|
||||
dockPosition = settings.dockPosition !== undefined ? settings.dockPosition : SettingsData.Position.Bottom
|
||||
dockSpacing = settings.dockSpacing !== undefined ? settings.dockSpacing : 4
|
||||
dockBottomGap = settings.dockBottomGap !== undefined ? settings.dockBottomGap : 0
|
||||
cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12
|
||||
notificationOverlayEnabled = settings.notificationOverlayEnabled !== undefined ? settings.notificationOverlayEnabled : false
|
||||
topBarAutoHide = settings.topBarAutoHide !== undefined ? settings.topBarAutoHide : false
|
||||
topBarOpenOnOverview = settings.topBarOpenOnOverview !== undefined ? settings.topBarOpenOnOverview : false
|
||||
topBarVisible = settings.topBarVisible !== undefined ? settings.topBarVisible : true
|
||||
dankBarAutoHide = settings.dankBarAutoHide !== undefined ? settings.dankBarAutoHide : (settings.topBarAutoHide !== undefined ? settings.topBarAutoHide : false)
|
||||
dankBarOpenOnOverview = settings.dankBarOpenOnOverview !== undefined ? settings.dankBarOpenOnOverview : (settings.topBarOpenOnOverview !== undefined ? settings.topBarOpenOnOverview : false)
|
||||
dankBarVisible = settings.dankBarVisible !== undefined ? settings.dankBarVisible : (settings.topBarVisible !== undefined ? settings.topBarVisible : true)
|
||||
dockOpenOnOverview = settings.dockOpenOnOverview !== undefined ? settings.dockOpenOnOverview : false
|
||||
notificationTimeoutLow = settings.notificationTimeoutLow !== undefined ? settings.notificationTimeoutLow : 5000
|
||||
notificationTimeoutNormal = settings.notificationTimeoutNormal !== undefined ? settings.notificationTimeoutNormal : 5000
|
||||
notificationTimeoutCritical = settings.notificationTimeoutCritical !== undefined ? settings.notificationTimeoutCritical : 0
|
||||
topBarSpacing = settings.topBarSpacing !== undefined ? settings.topBarSpacing : 4
|
||||
topBarBottomGap = settings.topBarBottomGap !== undefined ? settings.topBarBottomGap : 0
|
||||
topBarInnerPadding = settings.topBarInnerPadding !== undefined ? settings.topBarInnerPadding : 8
|
||||
topBarSquareCorners = settings.topBarSquareCorners !== undefined ? settings.topBarSquareCorners : false
|
||||
topBarNoBackground = settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false
|
||||
topBarGothCornersEnabled = settings.topBarGothCornersEnabled !== undefined ? settings.topBarGothCornersEnabled : false
|
||||
notificationPopupPosition = settings.notificationPopupPosition !== undefined ? settings.notificationPopupPosition : SettingsData.Position.Top
|
||||
osdAlwaysShowValue = settings.osdAlwaysShowValue !== undefined ? settings.osdAlwaysShowValue : false
|
||||
dankBarSpacing = settings.dankBarSpacing !== undefined ? settings.dankBarSpacing : (settings.topBarSpacing !== undefined ? settings.topBarSpacing : 4)
|
||||
dankBarBottomGap = settings.dankBarBottomGap !== undefined ? settings.dankBarBottomGap : (settings.topBarBottomGap !== undefined ? settings.topBarBottomGap : 0)
|
||||
dankBarInnerPadding = settings.dankBarInnerPadding !== undefined ? settings.dankBarInnerPadding : (settings.topBarInnerPadding !== undefined ? settings.topBarInnerPadding : 4)
|
||||
dankBarSquareCorners = settings.dankBarSquareCorners !== undefined ? settings.dankBarSquareCorners : (settings.topBarSquareCorners !== undefined ? settings.topBarSquareCorners : false)
|
||||
dankBarNoBackground = settings.dankBarNoBackground !== undefined ? settings.dankBarNoBackground : (settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false)
|
||||
dankBarGothCornersEnabled = settings.dankBarGothCornersEnabled !== undefined ? settings.dankBarGothCornersEnabled : (settings.topBarGothCornersEnabled !== undefined ? settings.topBarGothCornersEnabled : false)
|
||||
dankBarBorderEnabled = settings.dankBarBorderEnabled !== undefined ? settings.dankBarBorderEnabled : false
|
||||
dankBarPosition = settings.dankBarPosition !== undefined ? settings.dankBarPosition : (settings.dankBarAtBottom !== undefined ? (settings.dankBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : (settings.topBarAtBottom !== undefined ? (settings.topBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : SettingsData.Position.Top))
|
||||
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true
|
||||
hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false
|
||||
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch"
|
||||
surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s"
|
||||
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
|
||||
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : SettingsData.AnimationSpeed.Short
|
||||
applyStoredTheme()
|
||||
detectAvailableIconThemes()
|
||||
detectQtTools()
|
||||
@@ -340,6 +427,11 @@ Singleton {
|
||||
} finally {
|
||||
_loading = false
|
||||
}
|
||||
|
||||
if (shouldMigrate) {
|
||||
savePluginSettings()
|
||||
saveSettings()
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
@@ -349,8 +441,8 @@ Singleton {
|
||||
"currentThemeName": currentThemeName,
|
||||
"customThemeFile": customThemeFile,
|
||||
"matugenScheme": matugenScheme,
|
||||
"topBarTransparency": topBarTransparency,
|
||||
"topBarWidgetTransparency": topBarWidgetTransparency,
|
||||
"dankBarTransparency": dankBarTransparency,
|
||||
"dankBarWidgetTransparency": dankBarWidgetTransparency,
|
||||
"popupTransparency": popupTransparency,
|
||||
"dockTransparency": dockTransparency,
|
||||
"use24HourClock": use24HourClock,
|
||||
@@ -395,21 +487,25 @@ Singleton {
|
||||
"clockDateFormat": clockDateFormat,
|
||||
"lockDateFormat": lockDateFormat,
|
||||
"mediaSize": mediaSize,
|
||||
"topBarLeftWidgets": topBarLeftWidgets,
|
||||
"topBarCenterWidgets": topBarCenterWidgets,
|
||||
"topBarRightWidgets": topBarRightWidgets,
|
||||
"dankBarLeftWidgets": dankBarLeftWidgets,
|
||||
"dankBarCenterWidgets": dankBarCenterWidgets,
|
||||
"dankBarRightWidgets": dankBarRightWidgets,
|
||||
"appLauncherViewMode": appLauncherViewMode,
|
||||
"spotlightModalViewMode": spotlightModalViewMode,
|
||||
"networkPreference": networkPreference,
|
||||
"iconTheme": iconTheme,
|
||||
"useOSLogo": useOSLogo,
|
||||
"osLogoColorOverride": osLogoColorOverride,
|
||||
"osLogoBrightness": osLogoBrightness,
|
||||
"osLogoContrast": osLogoContrast,
|
||||
"launcherLogoMode": launcherLogoMode,
|
||||
"launcherLogoCustomPath": launcherLogoCustomPath,
|
||||
"launcherLogoColorOverride": launcherLogoColorOverride,
|
||||
"launcherLogoColorInvertOnMode": launcherLogoColorInvertOnMode,
|
||||
"launcherLogoBrightness": launcherLogoBrightness,
|
||||
"launcherLogoContrast": launcherLogoContrast,
|
||||
"launcherLogoSizeOffset": launcherLogoSizeOffset,
|
||||
"fontFamily": fontFamily,
|
||||
"monoFontFamily": monoFontFamily,
|
||||
"fontWeight": fontWeight,
|
||||
"fontScale": fontScale,
|
||||
"dankBarFontScale": dankBarFontScale,
|
||||
"notepadUseMonospace": notepadUseMonospace,
|
||||
"notepadFontFamily": notepadFontFamily,
|
||||
"notepadFontSize": notepadFontSize,
|
||||
@@ -421,17 +517,23 @@ Singleton {
|
||||
"showDock": showDock,
|
||||
"dockAutoHide": dockAutoHide,
|
||||
"dockGroupByApp": dockGroupByApp,
|
||||
"dockOpenOnOverview": dockOpenOnOverview,
|
||||
"dockPosition": dockPosition,
|
||||
"dockSpacing": dockSpacing,
|
||||
"dockBottomGap": dockBottomGap,
|
||||
"cornerRadius": cornerRadius,
|
||||
"notificationOverlayEnabled": notificationOverlayEnabled,
|
||||
"topBarAutoHide": topBarAutoHide,
|
||||
"topBarOpenOnOverview": topBarOpenOnOverview,
|
||||
"topBarVisible": topBarVisible,
|
||||
"topBarSpacing": topBarSpacing,
|
||||
"topBarBottomGap": topBarBottomGap,
|
||||
"topBarInnerPadding": topBarInnerPadding,
|
||||
"topBarSquareCorners": topBarSquareCorners,
|
||||
"topBarNoBackground": topBarNoBackground,
|
||||
"topBarGothCornersEnabled": topBarGothCornersEnabled,
|
||||
"dankBarAutoHide": dankBarAutoHide,
|
||||
"dankBarOpenOnOverview": dankBarOpenOnOverview,
|
||||
"dankBarVisible": dankBarVisible,
|
||||
"dankBarSpacing": dankBarSpacing,
|
||||
"dankBarBottomGap": dankBarBottomGap,
|
||||
"dankBarInnerPadding": dankBarInnerPadding,
|
||||
"dankBarSquareCorners": dankBarSquareCorners,
|
||||
"dankBarNoBackground": dankBarNoBackground,
|
||||
"dankBarGothCornersEnabled": dankBarGothCornersEnabled,
|
||||
"dankBarBorderEnabled": dankBarBorderEnabled,
|
||||
"dankBarPosition": dankBarPosition,
|
||||
"lockScreenShowPowerActions": lockScreenShowPowerActions,
|
||||
"hideBrightnessSlider": hideBrightnessSlider,
|
||||
"widgetBackgroundColor": widgetBackgroundColor,
|
||||
@@ -439,10 +541,19 @@ Singleton {
|
||||
"notificationTimeoutLow": notificationTimeoutLow,
|
||||
"notificationTimeoutNormal": notificationTimeoutNormal,
|
||||
"notificationTimeoutCritical": notificationTimeoutCritical,
|
||||
"screenPreferences": screenPreferences
|
||||
"notificationPopupPosition": notificationPopupPosition,
|
||||
"osdAlwaysShowValue": osdAlwaysShowValue,
|
||||
"screenPreferences": screenPreferences,
|
||||
"animationSpeed": animationSpeed
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
function savePluginSettings() {
|
||||
if (_pluginSettingsLoading)
|
||||
return
|
||||
pluginSettingsFile.setText(JSON.stringify(pluginSettings, null, 2))
|
||||
}
|
||||
|
||||
function setShowWorkspaceIndex(enabled) {
|
||||
showWorkspaceIndex = enabled
|
||||
saveSettings()
|
||||
@@ -555,11 +666,11 @@ Singleton {
|
||||
|
||||
function applyStoredTheme() {
|
||||
if (typeof Theme !== "undefined")
|
||||
Theme.switchTheme(currentThemeName, false)
|
||||
Theme.switchTheme(currentThemeName, false, false)
|
||||
else
|
||||
Qt.callLater(() => {
|
||||
if (typeof Theme !== "undefined")
|
||||
Theme.switchTheme(currentThemeName, false)
|
||||
Theme.switchTheme(currentThemeName, false, false)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -586,13 +697,13 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function setTopBarTransparency(transparency) {
|
||||
topBarTransparency = transparency
|
||||
function setDankBarTransparency(transparency) {
|
||||
dankBarTransparency = transparency
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarWidgetTransparency(transparency) {
|
||||
topBarWidgetTransparency = transparency
|
||||
function setDankBarWidgetTransparency(transparency) {
|
||||
dankBarWidgetTransparency = transparency
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
@@ -727,25 +838,25 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarWidgetOrder(order) {
|
||||
topBarWidgetOrder = order
|
||||
function setDankBarWidgetOrder(order) {
|
||||
dankBarWidgetOrder = order
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarLeftWidgets(order) {
|
||||
topBarLeftWidgets = order
|
||||
function setDankBarLeftWidgets(order) {
|
||||
dankBarLeftWidgets = order
|
||||
updateListModel(leftWidgetsModel, order)
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarCenterWidgets(order) {
|
||||
topBarCenterWidgets = order
|
||||
function setDankBarCenterWidgets(order) {
|
||||
dankBarCenterWidgets = order
|
||||
updateListModel(centerWidgetsModel, order)
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarRightWidgets(order) {
|
||||
topBarRightWidgets = order
|
||||
function setDankBarRightWidgets(order) {
|
||||
dankBarRightWidgets = order
|
||||
updateListModel(rightWidgetsModel, order)
|
||||
saveSettings()
|
||||
}
|
||||
@@ -759,6 +870,7 @@ Singleton {
|
||||
var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex
|
||||
var pciId = typeof order[i] === "string" ? undefined : order[i].pciId
|
||||
var mountPath = typeof order[i] === "string" ? undefined : order[i].mountPath
|
||||
var minimumWidth = typeof order[i] === "string" ? undefined : order[i].minimumWidth
|
||||
var item = {
|
||||
"widgetId": widgetId,
|
||||
"enabled": enabled
|
||||
@@ -771,6 +883,8 @@ Singleton {
|
||||
item.pciId = pciId
|
||||
if (mountPath !== undefined)
|
||||
item.mountPath = mountPath
|
||||
if (minimumWidth !== undefined)
|
||||
item.minimumWidth = minimumWidth
|
||||
|
||||
listModel.append(item)
|
||||
}
|
||||
@@ -778,13 +892,13 @@ Singleton {
|
||||
widgetDataChanged()
|
||||
}
|
||||
|
||||
function resetTopBarWidgetsToDefault() {
|
||||
function resetDankBarWidgetsToDefault() {
|
||||
var defaultLeft = ["launcherButton", "workspaceSwitcher", "focusedWindow"]
|
||||
var defaultCenter = ["music", "clock", "weather"]
|
||||
var defaultRight = ["systemTray", "clipboard", "notificationButton", "battery", "controlCenterButton"]
|
||||
topBarLeftWidgets = defaultLeft
|
||||
topBarCenterWidgets = defaultCenter
|
||||
topBarRightWidgets = defaultRight
|
||||
dankBarLeftWidgets = defaultLeft
|
||||
dankBarCenterWidgets = defaultCenter
|
||||
dankBarRightWidgets = defaultRight
|
||||
updateListModel(leftWidgetsModel, defaultLeft)
|
||||
updateListModel(centerWidgetsModel, defaultCenter)
|
||||
updateListModel(rightWidgetsModel, defaultRight)
|
||||
@@ -893,23 +1007,38 @@ Singleton {
|
||||
updateQtIconTheme(iconTheme)
|
||||
}
|
||||
|
||||
function setUseOSLogo(enabled) {
|
||||
useOSLogo = enabled
|
||||
function setLauncherLogoMode(mode) {
|
||||
launcherLogoMode = mode
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setOSLogoColorOverride(color) {
|
||||
osLogoColorOverride = color
|
||||
function setLauncherLogoCustomPath(path) {
|
||||
launcherLogoCustomPath = path
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setOSLogoBrightness(brightness) {
|
||||
osLogoBrightness = brightness
|
||||
function setLauncherLogoColorOverride(color) {
|
||||
launcherLogoColorOverride = color
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setOSLogoContrast(contrast) {
|
||||
osLogoContrast = contrast
|
||||
function setLauncherLogoColorInvertOnMode(invert) {
|
||||
launcherLogoColorInvertOnMode = invert
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLauncherLogoBrightness(brightness) {
|
||||
launcherLogoBrightness = brightness
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLauncherLogoContrast(contrast) {
|
||||
launcherLogoContrast = contrast
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setLauncherLogoSizeOffset(offset) {
|
||||
launcherLogoSizeOffset = offset
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
@@ -933,6 +1062,11 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setDankBarFontScale(scale) {
|
||||
dankBarFontScale = scale
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setGtkThemingEnabled(enabled) {
|
||||
gtkThemingEnabled = enabled
|
||||
saveSettings()
|
||||
@@ -951,6 +1085,24 @@ Singleton {
|
||||
|
||||
function setShowDock(enabled) {
|
||||
showDock = enabled
|
||||
if (enabled && dockPosition === dankBarPosition) {
|
||||
if (dankBarPosition === SettingsData.Position.Top) {
|
||||
setDockPosition(SettingsData.Position.Bottom)
|
||||
return
|
||||
}
|
||||
if (dankBarPosition === SettingsData.Position.Bottom) {
|
||||
setDockPosition(SettingsData.Position.Top)
|
||||
return
|
||||
}
|
||||
if (dankBarPosition === SettingsData.Position.Left) {
|
||||
setDockPosition(SettingsData.Position.Right)
|
||||
return
|
||||
}
|
||||
if (dankBarPosition === SettingsData.Position.Right) {
|
||||
setDockPosition(SettingsData.Position.Left)
|
||||
return
|
||||
}
|
||||
}
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
@@ -964,6 +1116,11 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setdockOpenOnOverview(enabled) {
|
||||
dockOpenOnOverview = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setCornerRadius(radius) {
|
||||
cornerRadius = radius
|
||||
saveSettings()
|
||||
@@ -974,23 +1131,23 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarAutoHide(enabled) {
|
||||
topBarAutoHide = enabled
|
||||
function setDankBarAutoHide(enabled) {
|
||||
dankBarAutoHide = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarOpenOnOverview(enabled) {
|
||||
topBarOpenOnOverview = enabled
|
||||
function setDankBarOpenOnOverview(enabled) {
|
||||
dankBarOpenOnOverview = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarVisible(visible) {
|
||||
topBarVisible = visible
|
||||
function setDankBarVisible(visible) {
|
||||
dankBarVisible = visible
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function toggleTopBarVisible() {
|
||||
topBarVisible = !topBarVisible
|
||||
function toggleDankBarVisible() {
|
||||
dankBarVisible = !dankBarVisible
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
@@ -1009,36 +1166,172 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarSpacing(spacing) {
|
||||
topBarSpacing = spacing
|
||||
function setNotificationPopupPosition(position) {
|
||||
notificationPopupPosition = position
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarBottomGap(gap) {
|
||||
topBarBottomGap = gap
|
||||
function setOsdAlwaysShowValue(enabled) {
|
||||
osdAlwaysShowValue = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarInnerPadding(padding) {
|
||||
topBarInnerPadding = padding
|
||||
function sendTestNotifications() {
|
||||
sendTestNotification(0)
|
||||
testNotifTimer1.start()
|
||||
testNotifTimer2.start()
|
||||
}
|
||||
|
||||
function sendTestNotification(index) {
|
||||
const notifications = [
|
||||
["Notification Position Test", "DMS test notification 1 of 3 ~ Hi there!", "dialog-information"],
|
||||
["Second Test", "DMS Notification 2 of 3 ~ Check it out!", "emblem-default"],
|
||||
["Third Test", "DMS notification 3 of 3 ~ Enjoy!", "emblem-favorite"]
|
||||
]
|
||||
|
||||
if (index < 0 || index >= notifications.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const notif = notifications[index]
|
||||
testNotificationProcess.command = ["notify-send", "-h", "int:transient:1", "-a", "DMS", "-i", notif[2], notif[0], notif[1]]
|
||||
testNotificationProcess.running = true
|
||||
}
|
||||
|
||||
property Process testNotificationProcess
|
||||
|
||||
testNotificationProcess: Process {
|
||||
command: []
|
||||
running: false
|
||||
}
|
||||
|
||||
property Timer testNotifTimer1
|
||||
|
||||
testNotifTimer1: Timer {
|
||||
interval: 400
|
||||
repeat: false
|
||||
onTriggered: sendTestNotification(1)
|
||||
}
|
||||
|
||||
property Timer testNotifTimer2
|
||||
|
||||
testNotifTimer2: Timer {
|
||||
interval: 800
|
||||
repeat: false
|
||||
onTriggered: sendTestNotification(2)
|
||||
}
|
||||
|
||||
function setDankBarSpacing(spacing) {
|
||||
dankBarSpacing = spacing
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarSquareCorners(enabled) {
|
||||
topBarSquareCorners = enabled
|
||||
function setDankBarBottomGap(gap) {
|
||||
dankBarBottomGap = gap
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarNoBackground(enabled) {
|
||||
topBarNoBackground = enabled
|
||||
function setDankBarInnerPadding(padding) {
|
||||
dankBarInnerPadding = padding
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setTopBarGothCornersEnabled(enabled) {
|
||||
topBarGothCornersEnabled = enabled
|
||||
function setDankBarSquareCorners(enabled) {
|
||||
dankBarSquareCorners = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setDankBarNoBackground(enabled) {
|
||||
dankBarNoBackground = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setDankBarGothCornersEnabled(enabled) {
|
||||
dankBarGothCornersEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setDankBarBorderEnabled(enabled) {
|
||||
dankBarBorderEnabled = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setDankBarPosition(position) {
|
||||
dankBarPosition = position
|
||||
if (position === SettingsData.Position.Bottom && dockPosition === SettingsData.Position.Bottom && showDock) {
|
||||
setDockPosition(SettingsData.Position.Top)
|
||||
return
|
||||
}
|
||||
if (position === SettingsData.Position.Top && dockPosition === SettingsData.Position.Top && showDock) {
|
||||
setDockPosition(SettingsData.Position.Bottom)
|
||||
return
|
||||
}
|
||||
if (position === SettingsData.Position.Left && dockPosition === SettingsData.Position.Left && showDock) {
|
||||
setDockPosition(SettingsData.Position.Right)
|
||||
return
|
||||
}
|
||||
if (position === SettingsData.Position.Right && dockPosition === SettingsData.Position.Right && showDock) {
|
||||
setDockPosition(SettingsData.Position.Left)
|
||||
return
|
||||
}
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setDockPosition(position) {
|
||||
dockPosition = position
|
||||
if (position === SettingsData.Position.Bottom && dankBarPosition === SettingsData.Position.Bottom && showDock) {
|
||||
setDankBarPosition(SettingsData.Position.Top)
|
||||
}
|
||||
if (position === SettingsData.Position.Top && dankBarPosition === SettingsData.Position.Top && showDock) {
|
||||
setDankBarPosition(SettingsData.Position.Bottom)
|
||||
}
|
||||
if (position === SettingsData.Position.Left && dankBarPosition === SettingsData.Position.Left && showDock) {
|
||||
setDankBarPosition(SettingsData.Position.Right)
|
||||
}
|
||||
if (position === SettingsData.Position.Right && dankBarPosition === SettingsData.Position.Right && showDock) {
|
||||
setDankBarPosition(SettingsData.Position.Left)
|
||||
}
|
||||
saveSettings()
|
||||
Qt.callLater(() => forceDockLayoutRefresh())
|
||||
}
|
||||
function setDockSpacing(spacing) {
|
||||
dockSpacing = spacing
|
||||
saveSettings()
|
||||
}
|
||||
function setDockBottomGap(gap) {
|
||||
dockBottomGap = gap
|
||||
saveSettings()
|
||||
}
|
||||
function setDockOpenOnOverview(enabled) {
|
||||
dockOpenOnOverview = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function getPopupYPosition(barHeight) {
|
||||
const gothOffset = dankBarGothCornersEnabled ? Theme.cornerRadius : 0
|
||||
return barHeight + dankBarSpacing + dankBarBottomGap - gothOffset + Theme.popupDistance
|
||||
}
|
||||
|
||||
function getPopupTriggerPosition(globalPos, screen, barThickness, widgetWidth) {
|
||||
const screenX = screen ? screen.x : 0
|
||||
const screenY = screen ? screen.y : 0
|
||||
const relativeX = globalPos.x - screenX
|
||||
const relativeY = globalPos.y - screenY
|
||||
|
||||
if (dankBarPosition === SettingsData.Position.Left || dankBarPosition === SettingsData.Position.Right) {
|
||||
return {
|
||||
x: relativeY,
|
||||
y: barThickness + dankBarSpacing + Theme.popupDistance,
|
||||
width: widgetWidth
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: relativeX,
|
||||
y: barThickness + dankBarSpacing + dankBarBottomGap + Theme.popupDistance,
|
||||
width: widgetWidth
|
||||
}
|
||||
}
|
||||
|
||||
function setLockScreenShowPowerActions(enabled) {
|
||||
lockScreenShowPowerActions = enabled
|
||||
saveSettings()
|
||||
@@ -1075,14 +1368,48 @@ Singleton {
|
||||
return Quickshell.screens.filter(screen => prefs.includes(screen.name))
|
||||
}
|
||||
|
||||
// Plugin settings functions
|
||||
function getPluginSetting(pluginId, key, defaultValue) {
|
||||
if (!pluginSettings[pluginId]) {
|
||||
return defaultValue
|
||||
}
|
||||
return pluginSettings[pluginId][key] !== undefined ? pluginSettings[pluginId][key] : defaultValue
|
||||
}
|
||||
|
||||
function setPluginSetting(pluginId, key, value) {
|
||||
if (!pluginSettings[pluginId]) {
|
||||
pluginSettings[pluginId] = {}
|
||||
}
|
||||
pluginSettings[pluginId][key] = value
|
||||
savePluginSettings()
|
||||
}
|
||||
|
||||
function removePluginSettings(pluginId) {
|
||||
if (pluginSettings[pluginId]) {
|
||||
delete pluginSettings[pluginId]
|
||||
savePluginSettings()
|
||||
}
|
||||
}
|
||||
|
||||
function getPluginSettingsForPlugin(pluginId) {
|
||||
return pluginSettings[pluginId] || {}
|
||||
}
|
||||
|
||||
function setAnimationSpeed(speed) {
|
||||
animationSpeed = speed
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function _shq(s) {
|
||||
return "'" + String(s).replace(/'/g, "'\\''") + "'"
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadSettings()
|
||||
fontCheckTimer.start()
|
||||
initializeListModels()
|
||||
if (!isGreeterMode) {
|
||||
loadSettings()
|
||||
fontCheckTimer.start()
|
||||
initializeListModels()
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
@@ -1123,25 +1450,47 @@ Singleton {
|
||||
FileView {
|
||||
id: settingsFile
|
||||
|
||||
path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
|
||||
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
watchChanges: true
|
||||
watchChanges: !isGreeterMode
|
||||
onLoaded: {
|
||||
parseSettings(settingsFile.text())
|
||||
hasTriedDefaultSettings = false
|
||||
if (!isGreeterMode) {
|
||||
parseSettings(settingsFile.text())
|
||||
hasTriedDefaultSettings = false
|
||||
}
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
if (!hasTriedDefaultSettings) {
|
||||
if (!isGreeterMode && !hasTriedDefaultSettings) {
|
||||
hasTriedDefaultSettings = true
|
||||
defaultSettingsCheckProcess.running = true
|
||||
} else {
|
||||
} else if (!isGreeterMode) {
|
||||
applyStoredTheme()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: pluginSettingsFile
|
||||
|
||||
path: isGreeterMode ? "" : pluginSettingsPath
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
watchChanges: !isGreeterMode
|
||||
onLoaded: {
|
||||
if (!isGreeterMode) {
|
||||
parsePluginSettings(pluginSettingsFile.text())
|
||||
}
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
if (!isGreeterMode) {
|
||||
pluginSettings = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: systemDefaultDetectionProcess
|
||||
|
||||
@@ -1221,22 +1570,22 @@ Singleton {
|
||||
|
||||
IpcHandler {
|
||||
function reveal(): string {
|
||||
root.setTopBarVisible(true)
|
||||
root.setDankBarVisible(true)
|
||||
return "BAR_SHOW_SUCCESS"
|
||||
}
|
||||
|
||||
function hide(): string {
|
||||
root.setTopBarVisible(false)
|
||||
root.setDankBarVisible(false)
|
||||
return "BAR_HIDE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
root.toggleTopBarVisible()
|
||||
return topBarVisible ? "BAR_SHOW_SUCCESS" : "BAR_HIDE_SUCCESS"
|
||||
root.toggleDankBarVisible()
|
||||
return root.dankBarVisible ? "BAR_SHOW_SUCCESS" : "BAR_HIDE_SUCCESS"
|
||||
}
|
||||
|
||||
function status(): string {
|
||||
return topBarVisible ? "visible" : "hidden"
|
||||
return root.dankBarVisible ? "visible" : "hidden"
|
||||
}
|
||||
|
||||
target: "bar"
|
||||
|
||||
@@ -2,101 +2,101 @@
|
||||
// Separated from Theme.qml to keep that file clean
|
||||
|
||||
const CatppuccinMocha = {
|
||||
surface: "#45475a",
|
||||
surface: "#313244",
|
||||
surfaceText: "#cdd6f4",
|
||||
surfaceVariant: "#45475a",
|
||||
surfaceVariant: "#313244",
|
||||
surfaceVariantText: "#a6adc8",
|
||||
background: "#1e1e2e",
|
||||
backgroundText: "#cdd6f4",
|
||||
outline: "#6c7086",
|
||||
surfaceContainer: "#313244",
|
||||
surfaceContainer: "#45475a",
|
||||
surfaceContainerHigh: "#585b70",
|
||||
surfaceContainerHighest: "#7f849c"
|
||||
surfaceContainerHighest: "#6c7086"
|
||||
}
|
||||
|
||||
const CatppuccinLatte = {
|
||||
surface: "#bcc0cc",
|
||||
surface: "#e6e9ef",
|
||||
surfaceText: "#4c4f69",
|
||||
surfaceVariant: "#bcc0cc",
|
||||
surfaceVariant: "#e6e9ef",
|
||||
surfaceVariantText: "#6c6f85",
|
||||
background: "#eff1f5",
|
||||
backgroundText: "#4c4f69",
|
||||
outline: "#9ca0b0",
|
||||
surfaceContainer: "#ccd0da",
|
||||
surfaceContainerHigh: "#acb0be",
|
||||
surfaceContainerHighest: "#8c8fa1"
|
||||
surfaceContainer: "#dce0e8",
|
||||
surfaceContainerHigh: "#ccd0da",
|
||||
surfaceContainerHighest: "#bcc0cc"
|
||||
}
|
||||
|
||||
const CatppuccinVariants = {
|
||||
"cat-rosewater": {
|
||||
name: "Rosewater",
|
||||
dark: { primary: "#f5e0dc", secondary: "#f2cdcd", primaryText: "#1e1e2e", primaryContainer: "#8b6b5e", surfaceTint: "#f5e0dc" },
|
||||
light: { primary: "#dc8a78", secondary: "#dd7878", primaryText: "#ffffff", primaryContainer: "#f4d2ca", surfaceTint: "#dc8a78" }
|
||||
dark: { primary: "#f5e0dc", secondary: "#f2cdcd", primaryText: "#1e1e2e", primaryContainer: "#7d5d56", surfaceTint: "#f5e0dc" },
|
||||
light: { primary: "#dc8a78", secondary: "#dd7878", primaryText: "#ffffff", primaryContainer: "#f6e7e3", surfaceTint: "#dc8a78" }
|
||||
},
|
||||
"cat-flamingo": {
|
||||
name: "Flamingo",
|
||||
dark: { primary: "#f2cdcd", secondary: "#f5e0dc", primaryText: "#1e1e2e", primaryContainer: "#885d62", surfaceTint: "#f2cdcd" },
|
||||
light: { primary: "#dd7878", secondary: "#dc8a78", primaryText: "#ffffff", primaryContainer: "#f4caca", surfaceTint: "#dd7878" }
|
||||
dark: { primary: "#f2cdcd", secondary: "#f5e0dc", primaryText: "#1e1e2e", primaryContainer: "#7a555a", surfaceTint: "#f2cdcd" },
|
||||
light: { primary: "#dd7878", secondary: "#dc8a78", primaryText: "#ffffff", primaryContainer: "#f6e5e5", surfaceTint: "#dd7878" }
|
||||
},
|
||||
"cat-pink": {
|
||||
name: "Pink",
|
||||
dark: { primary: "#f5c2e7", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#8b537a", surfaceTint: "#f5c2e7" },
|
||||
light: { primary: "#ea76cb", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#f7c9e7", surfaceTint: "#ea76cb" }
|
||||
dark: { primary: "#f5c2e7", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#7a3f69", surfaceTint: "#f5c2e7" },
|
||||
light: { primary: "#ea76cb", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#f7d7ee", surfaceTint: "#ea76cb" }
|
||||
},
|
||||
"cat-mauve": {
|
||||
name: "Mauve",
|
||||
dark: { primary: "#cba6f7", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#61378a", surfaceTint: "#cba6f7" },
|
||||
light: { primary: "#8839ef", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#e4d3ff", surfaceTint: "#8839ef" }
|
||||
dark: { primary: "#cba6f7", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#55307f", surfaceTint: "#cba6f7" },
|
||||
light: { primary: "#8839ef", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#eadcff", surfaceTint: "#8839ef" }
|
||||
},
|
||||
"cat-red": {
|
||||
name: "Red",
|
||||
dark: { primary: "#f38ba8", secondary: "#eba0ac", primaryText: "#1e1e2e", primaryContainer: "#891c3b", surfaceTint: "#f38ba8" },
|
||||
light: { primary: "#d20f39", secondary: "#e64553", primaryText: "#ffffff", primaryContainer: "#f1b8c4", surfaceTint: "#d20f39" }
|
||||
dark: { primary: "#f38ba8", secondary: "#eba0ac", primaryText: "#1e1e2e", primaryContainer: "#6f2438", surfaceTint: "#f38ba8" },
|
||||
light: { primary: "#d20f39", secondary: "#e64553", primaryText: "#ffffff", primaryContainer: "#f6d0d6", surfaceTint: "#d20f39" }
|
||||
},
|
||||
"cat-maroon": {
|
||||
name: "Maroon",
|
||||
dark: { primary: "#eba0ac", secondary: "#f38ba8", primaryText: "#1e1e2e", primaryContainer: "#81313f", surfaceTint: "#eba0ac" },
|
||||
light: { primary: "#e64553", secondary: "#d20f39", primaryText: "#ffffff", primaryContainer: "#f4c3c8", surfaceTint: "#e64553" }
|
||||
dark: { primary: "#eba0ac", secondary: "#f38ba8", primaryText: "#1e1e2e", primaryContainer: "#6d3641", surfaceTint: "#eba0ac" },
|
||||
light: { primary: "#e64553", secondary: "#d20f39", primaryText: "#ffffff", primaryContainer: "#f7d8dc", surfaceTint: "#e64553" }
|
||||
},
|
||||
"cat-peach": {
|
||||
name: "Peach",
|
||||
dark: { primary: "#fab387", secondary: "#f9e2af", primaryText: "#1e1e2e", primaryContainer: "#90441a", surfaceTint: "#fab387" },
|
||||
light: { primary: "#fe640b", secondary: "#df8e1d", primaryText: "#ffffff", primaryContainer: "#ffddcc", surfaceTint: "#fe640b" }
|
||||
dark: { primary: "#fab387", secondary: "#f9e2af", primaryText: "#1e1e2e", primaryContainer: "#734226", surfaceTint: "#fab387" },
|
||||
light: { primary: "#fe640b", secondary: "#df8e1d", primaryText: "#ffffff", primaryContainer: "#ffe4d5", surfaceTint: "#fe640b" }
|
||||
},
|
||||
"cat-yellow": {
|
||||
name: "Yellow",
|
||||
dark: { primary: "#f9e2af", secondary: "#a6e3a1", primaryText: "#1e1e2e", primaryContainer: "#8f7342", surfaceTint: "#f9e2af" },
|
||||
light: { primary: "#df8e1d", secondary: "#40a02b", primaryText: "#ffffff", primaryContainer: "#fff3cc", surfaceTint: "#df8e1d" }
|
||||
dark: { primary: "#f9e2af", secondary: "#a6e3a1", primaryText: "#1e1e2e", primaryContainer: "#6e5a2f", surfaceTint: "#f9e2af" },
|
||||
light: { primary: "#df8e1d", secondary: "#40a02b", primaryText: "#ffffff", primaryContainer: "#fff6d6", surfaceTint: "#df8e1d" }
|
||||
},
|
||||
"cat-green": {
|
||||
name: "Green",
|
||||
dark: { primary: "#a6e3a1", secondary: "#94e2d5", primaryText: "#1e1e2e", primaryContainer: "#3c7534", surfaceTint: "#a6e3a1" },
|
||||
light: { primary: "#40a02b", secondary: "#179299", primaryText: "#ffffff", primaryContainer: "#d4f5d4", surfaceTint: "#40a02b" }
|
||||
dark: { primary: "#a6e3a1", secondary: "#94e2d5", primaryText: "#1e1e2e", primaryContainer: "#2f5f36", surfaceTint: "#a6e3a1" },
|
||||
light: { primary: "#40a02b", secondary: "#179299", primaryText: "#ffffff", primaryContainer: "#dff4e0", surfaceTint: "#40a02b" }
|
||||
},
|
||||
"cat-teal": {
|
||||
name: "Teal",
|
||||
dark: { primary: "#94e2d5", secondary: "#89dceb", primaryText: "#1e1e2e", primaryContainer: "#2a7468", surfaceTint: "#94e2d5" },
|
||||
light: { primary: "#179299", secondary: "#04a5e5", primaryText: "#ffffff", primaryContainer: "#ccf2f2", surfaceTint: "#179299" }
|
||||
dark: { primary: "#94e2d5", secondary: "#89dceb", primaryText: "#1e1e2e", primaryContainer: "#2e5e59", surfaceTint: "#94e2d5" },
|
||||
light: { primary: "#179299", secondary: "#04a5e5", primaryText: "#ffffff", primaryContainer: "#daf3f1", surfaceTint: "#179299" }
|
||||
},
|
||||
"cat-sky": {
|
||||
name: "Sky",
|
||||
dark: { primary: "#89dceb", secondary: "#74c7ec", primaryText: "#1e1e2e", primaryContainer: "#196e7e", surfaceTint: "#89dceb" },
|
||||
light: { primary: "#04a5e5", secondary: "#209fb5", primaryText: "#ffffff", primaryContainer: "#ccebff", surfaceTint: "#04a5e5" }
|
||||
dark: { primary: "#89dceb", secondary: "#74c7ec", primaryText: "#1e1e2e", primaryContainer: "#24586a", surfaceTint: "#89dceb" },
|
||||
light: { primary: "#04a5e5", secondary: "#209fb5", primaryText: "#ffffff", primaryContainer: "#dbf1fb", surfaceTint: "#04a5e5" }
|
||||
},
|
||||
"cat-sapphire": {
|
||||
name: "Sapphire",
|
||||
dark: { primary: "#74c7ec", secondary: "#89b4fa", primaryText: "#1e1e2e", primaryContainer: "#0a597f", surfaceTint: "#74c7ec" },
|
||||
light: { primary: "#209fb5", secondary: "#1e66f5", primaryText: "#ffffff", primaryContainer: "#d0f0f5", surfaceTint: "#209fb5" }
|
||||
dark: { primary: "#74c7ec", secondary: "#89b4fa", primaryText: "#1e1e2e", primaryContainer: "#1f4d6f", surfaceTint: "#74c7ec" },
|
||||
light: { primary: "#209fb5", secondary: "#1e66f5", primaryText: "#ffffff", primaryContainer: "#def3f8", surfaceTint: "#209fb5" }
|
||||
},
|
||||
"cat-blue": {
|
||||
name: "Blue",
|
||||
dark: { primary: "#89b4fa", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#19468d", surfaceTint: "#89b4fa" },
|
||||
light: { primary: "#1e66f5", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#ccd9ff", surfaceTint: "#1e66f5" }
|
||||
dark: { primary: "#89b4fa", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#243f75", surfaceTint: "#89b4fa" },
|
||||
light: { primary: "#1e66f5", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#e0e9ff", surfaceTint: "#1e66f5" }
|
||||
},
|
||||
"cat-lavender": {
|
||||
name: "Lavender",
|
||||
dark: { primary: "#b4befe", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#4a5091", surfaceTint: "#b4befe" },
|
||||
light: { primary: "#7287fd", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#dde1ff", surfaceTint: "#7287fd" }
|
||||
dark: { primary: "#b4befe", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#3f4481", surfaceTint: "#b4befe" },
|
||||
light: { primary: "#7287fd", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#e5e8ff", surfaceTint: "#7287fd" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,9 +290,9 @@ const StockThemes = {
|
||||
background: "#131315",
|
||||
backgroundText: "#e4e2e3",
|
||||
outline: "#929092",
|
||||
surfaceContainer: "#2a2a2a",
|
||||
surfaceContainerHigh: "#2a2a2b",
|
||||
surfaceContainerHighest: "#353535",
|
||||
surfaceContainer: "#353535",
|
||||
surfaceContainerHigh: "#424242",
|
||||
surfaceContainerHighest: "#505050",
|
||||
error: "#ffb4ab",
|
||||
warning: "#3f4759",
|
||||
info: "#595e6c",
|
||||
@@ -476,8 +476,9 @@ const StockThemes = {
|
||||
background: "#ffffff",
|
||||
backgroundText: "#1a1a1a",
|
||||
outline: "#757577",
|
||||
surfaceContainer: "#f5f5f6",
|
||||
surfaceContainerHigh: "#eaeaeb",
|
||||
surfaceContainer: "#e8e8ea",
|
||||
surfaceContainerHigh: "#dcdcde",
|
||||
surfaceContainerHighest: "#d0d0d2",
|
||||
error: "#ba1a1a",
|
||||
warning: "#f9e79f",
|
||||
info: "#5d6475",
|
||||
|
||||
214
Common/Theme.qml
214
Common/Theme.qml
@@ -16,11 +16,12 @@ Singleton {
|
||||
|
||||
readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true"
|
||||
|
||||
readonly property real popupDistance: 4
|
||||
// ! TODO - Synchronize with niri/hyprland gaps?
|
||||
readonly property real popupDistance: 2
|
||||
|
||||
property string currentTheme: "blue"
|
||||
property string currentThemeCategory: "generic"
|
||||
property bool isLightMode: false
|
||||
property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false
|
||||
|
||||
readonly property string dynamic: "dynamic"
|
||||
readonly property string custom : "custom"
|
||||
@@ -30,9 +31,8 @@ Singleton {
|
||||
readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "")
|
||||
readonly property string wallpaperPath: {
|
||||
if (typeof SessionData === "undefined") return ""
|
||||
|
||||
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
// Use first monitor's wallpaper for dynamic theming
|
||||
var screens = Quickshell.screens
|
||||
if (screens.length > 0) {
|
||||
var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name)
|
||||
@@ -82,11 +82,20 @@ Singleton {
|
||||
Component.onCompleted: {
|
||||
Quickshell.execDetached(["mkdir", "-p", stateDir])
|
||||
matugenCheck.running = true
|
||||
if (typeof SessionData !== "undefined")
|
||||
if (typeof SessionData !== "undefined") {
|
||||
SessionData.isLightModeChanged.connect(root.onLightModeChanged)
|
||||
|
||||
isLightMode = SessionData.isLightMode
|
||||
}
|
||||
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
|
||||
switchTheme(SettingsData.currentThemeName, false)
|
||||
switchTheme(SettingsData.currentThemeName, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
function applyGreeterTheme(themeName) {
|
||||
switchTheme(themeName, false, false)
|
||||
if (themeName === dynamic && dynamicColorsFileView.path) {
|
||||
dynamicColorsFileView.reload()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,11 +241,22 @@ Singleton {
|
||||
property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
|
||||
property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
|
||||
|
||||
property int shorterDuration: 100
|
||||
property int shortDuration: 150
|
||||
property int mediumDuration: 300
|
||||
property int longDuration: 500
|
||||
property int extraLongDuration: 1000
|
||||
readonly property var animationDurations: [
|
||||
{ shorter: 0, short: 0, medium: 0, long: 0, extraLong: 0 },
|
||||
{ shorter: 50, short: 75, medium: 150, long: 250, extraLong: 500 },
|
||||
{ shorter: 100, short: 150, medium: 300, long: 500, extraLong: 1000 },
|
||||
{ shorter: 150, short: 225, medium: 450, long: 750, extraLong: 1500 },
|
||||
{ shorter: 200, short: 300, medium: 600, long: 1000, extraLong: 2000 }
|
||||
]
|
||||
|
||||
readonly property int currentAnimationSpeed: typeof SettingsData !== "undefined" ? SettingsData.animationSpeed : SettingsData.AnimationSpeed.Short
|
||||
readonly property var currentDurations: animationDurations[currentAnimationSpeed] || animationDurations[SettingsData.AnimationSpeed.Short]
|
||||
|
||||
property int shorterDuration: currentDurations.shorter
|
||||
property int shortDuration: currentDurations.short
|
||||
property int mediumDuration: currentDurations.medium
|
||||
property int longDuration: currentDurations.long
|
||||
property int extraLongDuration: currentDurations.extraLong
|
||||
property int standardEasing: Easing.OutCubic
|
||||
property int emphasizedEasing: Easing.OutQuart
|
||||
|
||||
@@ -256,8 +276,8 @@ Singleton {
|
||||
property real iconSizeLarge: 32
|
||||
|
||||
property real panelTransparency: 0.85
|
||||
property real widgetTransparency: typeof SettingsData !== "undefined" && SettingsData.topBarWidgetTransparency !== undefined ? SettingsData.topBarWidgetTransparency : 0.85
|
||||
property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 0.92
|
||||
property real widgetTransparency: typeof SettingsData !== "undefined" && SettingsData.dankBarWidgetTransparency !== undefined ? SettingsData.dankBarWidgetTransparency : 1.0
|
||||
property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 1.0
|
||||
|
||||
function screenTransition() {
|
||||
CompositorService.isNiri && NiriService.doScreenTransition()
|
||||
@@ -266,7 +286,12 @@ Singleton {
|
||||
function switchTheme(themeName, savePrefs = true, enableTransition = true) {
|
||||
if (enableTransition) {
|
||||
screenTransition()
|
||||
themeTransitionTimer.themeName = themeName
|
||||
themeTransitionTimer.savePrefs = savePrefs
|
||||
themeTransitionTimer.restart()
|
||||
return
|
||||
}
|
||||
|
||||
if (themeName === dynamic) {
|
||||
currentTheme = dynamic
|
||||
currentThemeCategory = dynamic
|
||||
@@ -278,34 +303,45 @@ Singleton {
|
||||
}
|
||||
} else {
|
||||
currentTheme = themeName
|
||||
// Determine category based on theme name
|
||||
if (StockThemes.isCatppuccinVariant(themeName)) {
|
||||
currentThemeCategory = "catppuccin"
|
||||
} else {
|
||||
currentThemeCategory = "generic"
|
||||
}
|
||||
}
|
||||
if (savePrefs && typeof SettingsData !== "undefined")
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
|
||||
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode)
|
||||
SettingsData.setTheme(currentTheme)
|
||||
|
||||
generateSystemThemesFromCurrentTheme()
|
||||
if (!isGreeterMode) {
|
||||
generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
|
||||
function setLightMode(light, savePrefs = true) {
|
||||
screenTransition()
|
||||
function setLightMode(light, savePrefs = true, enableTransition = false) {
|
||||
if (enableTransition) {
|
||||
screenTransition()
|
||||
lightModeTransitionTimer.lightMode = light
|
||||
lightModeTransitionTimer.savePrefs = savePrefs
|
||||
lightModeTransitionTimer.restart()
|
||||
return
|
||||
}
|
||||
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
|
||||
isLightMode = light
|
||||
if (savePrefs && typeof SessionData !== "undefined")
|
||||
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
|
||||
SessionData.setLightMode(isLightMode)
|
||||
PortalService.setLightMode(isLightMode)
|
||||
generateSystemThemesFromCurrentTheme()
|
||||
if (!isGreeterMode) {
|
||||
PortalService.setLightMode(isLightMode)
|
||||
generateSystemThemesFromCurrentTheme()
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLightMode(savePrefs = true) {
|
||||
setLightMode(!isLightMode, savePrefs)
|
||||
setLightMode(!isLightMode, savePrefs, true)
|
||||
}
|
||||
|
||||
function forceGenerateSystemThemes() {
|
||||
screenTransition()
|
||||
if (!matugenAvailable) {
|
||||
return
|
||||
}
|
||||
@@ -329,8 +365,10 @@ Singleton {
|
||||
}
|
||||
|
||||
function switchThemeCategory(category, defaultTheme) {
|
||||
currentThemeCategory = category
|
||||
switchTheme(defaultTheme, true, false)
|
||||
screenTransition()
|
||||
themeCategoryTransitionTimer.category = category
|
||||
themeCategoryTransitionTimer.defaultTheme = defaultTheme
|
||||
themeCategoryTransitionTimer.restart()
|
||||
}
|
||||
|
||||
function getCatppuccinColor(variantName) {
|
||||
@@ -354,7 +392,6 @@ Singleton {
|
||||
}
|
||||
|
||||
function loadCustomTheme(themeData) {
|
||||
screenTransition()
|
||||
if (themeData.dark || themeData.light) {
|
||||
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
|
||||
const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light
|
||||
@@ -436,6 +473,19 @@ Singleton {
|
||||
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5
|
||||
}
|
||||
|
||||
function barIconSize(barThickness, offset) {
|
||||
const defaultOffset = offset !== undefined ? offset : -6
|
||||
return Math.round((barThickness / 48) * (iconSize + defaultOffset))
|
||||
}
|
||||
|
||||
function barTextSize(barThickness) {
|
||||
const scale = barThickness / 48
|
||||
const dankBarScale = (typeof SettingsData !== "undefined" ? SettingsData.dankBarFontScale : 1.0)
|
||||
if (scale <= 0.75) return fontSizeSmall * 0.9 * dankBarScale
|
||||
if (scale >= 1.25) return fontSizeMedium * dankBarScale
|
||||
return fontSizeSmall * dankBarScale
|
||||
}
|
||||
|
||||
function getBatteryIcon(level, isCharging, batteryAvailable) {
|
||||
if (!batteryAvailable)
|
||||
return _getBatteryPowerProfileIcon()
|
||||
@@ -574,7 +624,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function generateSystemThemesFromCurrentTheme() {
|
||||
if (!matugenAvailable)
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
|
||||
if (!matugenAvailable || isGreeterMode)
|
||||
return
|
||||
|
||||
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
|
||||
@@ -638,14 +689,64 @@ Singleton {
|
||||
qtApplier.running = true
|
||||
}
|
||||
|
||||
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
|
||||
|
||||
function snap(value, dpr) {
|
||||
return Math.round(value * dpr) / dpr
|
||||
}
|
||||
|
||||
function invertHex(hex) {
|
||||
hex = hex.replace('#', '');
|
||||
|
||||
if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
|
||||
return hex;
|
||||
}
|
||||
|
||||
const r = parseInt(hex.substr(0, 2), 16);
|
||||
const g = parseInt(hex.substr(2, 2), 16);
|
||||
const b = parseInt(hex.substr(4, 2), 16);
|
||||
|
||||
const invR = (255 - r).toString(16).padStart(2, '0');
|
||||
const invG = (255 - g).toString(16).padStart(2, '0');
|
||||
const invB = (255 - b).toString(16).padStart(2, '0');
|
||||
|
||||
return `#${invR}${invG}${invB}`;
|
||||
}
|
||||
|
||||
property string baseLogoColor: {
|
||||
if (typeof SettingsData === "undefined") return ""
|
||||
const colorOverride = SettingsData.launcherLogoColorOverride
|
||||
if (!colorOverride || colorOverride === "") return ""
|
||||
return colorOverride
|
||||
}
|
||||
|
||||
property string effectiveLogoColor: {
|
||||
if (typeof SettingsData === "undefined") return ""
|
||||
|
||||
const colorOverride = SettingsData.launcherLogoColorOverride
|
||||
if (!colorOverride || colorOverride === "") return ""
|
||||
|
||||
if (!SettingsData.launcherLogoColorInvertOnMode) {
|
||||
return colorOverride
|
||||
}
|
||||
|
||||
if (typeof SessionData !== "undefined" && SessionData.isLightMode) {
|
||||
return invertHex(baseLogoColor)
|
||||
}
|
||||
|
||||
return baseLogoColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
Process {
|
||||
id: matugenCheck
|
||||
command: ["which", "matugen"]
|
||||
onExited: code => {
|
||||
matugenAvailable = (code === 0) && !envDisableMatugen
|
||||
if (!matugenAvailable) {
|
||||
console.log("matugen not not available in path or disabled via DMS_DISABLE_MATUGEN")
|
||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
|
||||
|
||||
if (!matugenAvailable || isGreeterMode) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -696,10 +797,7 @@ Singleton {
|
||||
onExited: exitCode => {
|
||||
workerRunning = false
|
||||
|
||||
if (exitCode === 2) {
|
||||
// Exit code 2 means wallpaper/color not found - this is expected on first run
|
||||
console.log("Theme worker: wallpaper/color not found, skipping theme generation")
|
||||
} else if (exitCode !== 0) {
|
||||
if (exitCode !== 0 && exitCode !== 2) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showError("Theme worker failed (" + exitCode + ")")
|
||||
}
|
||||
@@ -788,8 +886,14 @@ Singleton {
|
||||
|
||||
FileView {
|
||||
id: dynamicColorsFileView
|
||||
path: stateDir + "/dms-colors.json"
|
||||
watchChanges: currentTheme === dynamic
|
||||
path: {
|
||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
|
||||
const colorsPath = SessionData.isGreeterMode
|
||||
? greetCfgDir + "/colors.json"
|
||||
: stateDir + "/dms-colors.json"
|
||||
return colorsPath
|
||||
}
|
||||
watchChanges: currentTheme === dynamic && !SessionData.isGreeterMode
|
||||
|
||||
function parseAndLoadColors() {
|
||||
try {
|
||||
@@ -802,6 +906,7 @@ Singleton {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Theme: Failed to parse dynamic colors:", e)
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.wallpaperErrorStatus = "error"
|
||||
ToastService.showError("Dynamic colors parse error: " + e.message)
|
||||
@@ -833,16 +938,16 @@ Singleton {
|
||||
|
||||
function toggle(): string {
|
||||
root.toggleLightMode()
|
||||
return root.isLightMode ? "light" : "dark"
|
||||
return root.isLightMode ? "dark" : "light"
|
||||
}
|
||||
|
||||
function light(): string {
|
||||
root.setLightMode(true)
|
||||
root.setLightMode(true, true, true)
|
||||
return "light"
|
||||
}
|
||||
|
||||
function dark(): string {
|
||||
root.setLightMode(false)
|
||||
root.setLightMode(false, true, true)
|
||||
return "dark"
|
||||
}
|
||||
|
||||
@@ -850,4 +955,35 @@ Singleton {
|
||||
return root.isLightMode ? "light" : "dark"
|
||||
}
|
||||
}
|
||||
|
||||
// These timers are for screen transitions, since sometimes QML still beats the niri call
|
||||
Timer {
|
||||
id: themeTransitionTimer
|
||||
interval: 50
|
||||
repeat: false
|
||||
property string themeName: ""
|
||||
property bool savePrefs: true
|
||||
onTriggered: root.switchTheme(themeName, savePrefs, false)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: lightModeTransitionTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
property bool lightMode: false
|
||||
property bool savePrefs: true
|
||||
onTriggered: root.setLightMode(lightMode, savePrefs, false)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: themeCategoryTransitionTimer
|
||||
interval: 50
|
||||
repeat: false
|
||||
property string category: ""
|
||||
property string defaultTheme: ""
|
||||
onTriggered: {
|
||||
root.currentThemeCategory = category
|
||||
root.switchTheme(defaultTheme, true, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
DMSGreeter.qml
Normal file
29
DMSGreeter.qml
Normal file
@@ -0,0 +1,29 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Greetd
|
||||
import qs.Common
|
||||
import qs.Modules.Greetd
|
||||
|
||||
ShellRoot {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
763
DMSShell.qml
Normal file
763
DMSShell.qml
Normal file
@@ -0,0 +1,763 @@
|
||||
//@ pragma Env QSG_RENDER_LOOP=threaded
|
||||
//@ pragma UseQApplication
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals
|
||||
import qs.Modals.Clipboard
|
||||
import qs.Modals.Common
|
||||
import qs.Modals.Settings
|
||||
import qs.Modals.Spotlight
|
||||
import qs.Modules
|
||||
import qs.Modules.AppDrawer
|
||||
import qs.Modules.DankDash
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Modules.Dock
|
||||
import qs.Modules.Lock
|
||||
import qs.Modules.Notepad
|
||||
import qs.Modules.Notifications.Center
|
||||
import qs.Widgets
|
||||
import qs.Modules.Notifications.Popup
|
||||
import qs.Modules.OSD
|
||||
import qs.Modules.ProcessList
|
||||
import qs.Modules.Settings
|
||||
import qs.Modules.DankBar
|
||||
import qs.Modules.DankBar.Popouts
|
||||
import qs.Modules.Plugins
|
||||
import qs.Services
|
||||
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
property bool servicesReady: false
|
||||
|
||||
Component.onCompleted: {
|
||||
PortalService.init()
|
||||
DisplayService.nightModeEnabled
|
||||
WallpaperCyclingService.cyclingActive
|
||||
PluginService.pluginDirectory
|
||||
Qt.callLater(() => {
|
||||
servicesReady = true
|
||||
})
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
id: daemonPluginInstantiator
|
||||
model: servicesReady ? Object.keys(PluginService.pluginDaemonComponents) : []
|
||||
|
||||
delegate: Loader {
|
||||
id: daemonLoader
|
||||
property string pluginId: modelData
|
||||
sourceComponent: PluginService.pluginDaemonComponents[pluginId]
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.pluginService = PluginService
|
||||
if (item.popoutService !== undefined) {
|
||||
item.popoutService = PopoutService
|
||||
}
|
||||
item.pluginId = pluginId
|
||||
console.log("Daemon plugin loaded:", pluginId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WallpaperBackground {}
|
||||
|
||||
Lock {
|
||||
id: lock
|
||||
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: dankBarLoader
|
||||
asynchronous: false
|
||||
|
||||
property var currentPosition: SettingsData.dankBarPosition
|
||||
property bool initialized: false
|
||||
|
||||
sourceComponent: DankBar {
|
||||
onColorPickerRequested: colorPickerModal.show()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
initialized = true
|
||||
}
|
||||
|
||||
onCurrentPositionChanged: {
|
||||
if (!initialized) return
|
||||
|
||||
const component = sourceComponent
|
||||
sourceComponent = null
|
||||
sourceComponent = component
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: dockLoader
|
||||
active: true
|
||||
asynchronous: false
|
||||
|
||||
property var currentPosition: SettingsData.dockPosition
|
||||
property bool initialized: false
|
||||
|
||||
sourceComponent: Dock {
|
||||
contextMenu: dockContextMenuLoader.item ? dockContextMenuLoader.item : null
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
dockContextMenuLoader.active = true
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
initialized = true
|
||||
}
|
||||
|
||||
onCurrentPositionChanged: {
|
||||
if (!initialized) return
|
||||
|
||||
console.log("DEBUG: Dock position changed to:", currentPosition, "- recreating dock")
|
||||
const comp = sourceComponent
|
||||
sourceComponent = null
|
||||
sourceComponent = comp
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: dankDashPopoutLoader
|
||||
|
||||
active: false
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Component {
|
||||
DankDashPopout {
|
||||
id: dankDashPopout
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.dankDashPopout = dankDashPopout
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: dockContextMenuLoader
|
||||
|
||||
active: false
|
||||
|
||||
DockContextMenu {
|
||||
id: dockContextMenu
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: notificationCenterLoader
|
||||
|
||||
active: false
|
||||
|
||||
NotificationCenterPopout {
|
||||
id: notificationCenter
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.notificationCenterPopout = notificationCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("notifications")
|
||||
|
||||
delegate: NotificationPopupManager {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: controlCenterLoader
|
||||
|
||||
active: false
|
||||
|
||||
property var modalRef: colorPickerModal
|
||||
property LazyLoader powerModalLoaderRef: powerMenuModalLoader
|
||||
|
||||
ControlCenterPopout {
|
||||
id: controlCenterPopout
|
||||
colorPickerModal: controlCenterLoader.modalRef
|
||||
powerMenuModalLoader: controlCenterLoader.powerModalLoaderRef
|
||||
|
||||
onLockRequested: {
|
||||
lock.activate()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.controlCenterPopout = controlCenterPopout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: wifiPasswordModalLoader
|
||||
|
||||
active: false
|
||||
|
||||
WifiPasswordModal {
|
||||
id: wifiPasswordModal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.wifiPasswordModal = wifiPasswordModal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: networkInfoModalLoader
|
||||
|
||||
active: false
|
||||
|
||||
NetworkInfoModal {
|
||||
id: networkInfoModal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.networkInfoModal = networkInfoModal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: batteryPopoutLoader
|
||||
|
||||
active: false
|
||||
|
||||
BatteryPopout {
|
||||
id: batteryPopout
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.batteryPopout = batteryPopout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: vpnPopoutLoader
|
||||
|
||||
active: false
|
||||
|
||||
VpnPopout {
|
||||
id: vpnPopout
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.vpnPopout = vpnPopout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: powerMenuLoader
|
||||
|
||||
active: false
|
||||
|
||||
PowerMenu {
|
||||
id: powerMenu
|
||||
|
||||
onPowerActionRequested: (action, title, message) => {
|
||||
powerConfirmModalLoader.active = true
|
||||
if (powerConfirmModalLoader.item) {
|
||||
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
|
||||
powerConfirmModalLoader.item.show(title, message, function () {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
SessionService.logout()
|
||||
break
|
||||
case "suspend":
|
||||
SessionService.suspend()
|
||||
break
|
||||
case "hibernate":
|
||||
SessionService.hibernate()
|
||||
break
|
||||
case "reboot":
|
||||
SessionService.reboot()
|
||||
break
|
||||
case "poweroff":
|
||||
SessionService.poweroff()
|
||||
break
|
||||
}
|
||||
}, function () {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: powerConfirmModalLoader
|
||||
|
||||
active: false
|
||||
|
||||
ConfirmModal {
|
||||
id: powerConfirmModal
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: processListPopoutLoader
|
||||
|
||||
active: false
|
||||
|
||||
ProcessListPopout {
|
||||
id: processListPopout
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.processListPopout = processListPopout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsModal {
|
||||
id: settingsModal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.settingsModal = settingsModal
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: appDrawerLoader
|
||||
|
||||
active: false
|
||||
|
||||
AppDrawerPopout {
|
||||
id: appDrawerPopout
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.appDrawerPopout = appDrawerPopout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SpotlightModal {
|
||||
id: spotlightModal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.spotlightModal = spotlightModal
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardHistoryModal {
|
||||
id: clipboardHistoryModalPopup
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.clipboardHistoryModal = clipboardHistoryModalPopup
|
||||
}
|
||||
}
|
||||
|
||||
NotificationModal {
|
||||
id: notificationModal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.notificationModal = notificationModal
|
||||
}
|
||||
}
|
||||
|
||||
DankColorPickerModal {
|
||||
id: colorPickerModal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.colorPickerModal = colorPickerModal
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: processListModalLoader
|
||||
|
||||
active: false
|
||||
|
||||
ProcessListModal {
|
||||
id: processListModal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.processListModal = processListModal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: systemUpdateLoader
|
||||
|
||||
active: false
|
||||
|
||||
SystemUpdatePopout {
|
||||
id: systemUpdatePopout
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.systemUpdatePopout = systemUpdatePopout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
id: notepadSlideoutVariants
|
||||
model: SettingsData.getFilteredScreens("notepad")
|
||||
|
||||
delegate: DankSlideout {
|
||||
id: notepadSlideout
|
||||
modelData: item
|
||||
title: qsTr("Notepad")
|
||||
slideoutWidth: 480
|
||||
expandable: true
|
||||
expandedWidthValue: 960
|
||||
customTransparency: SettingsData.notepadTransparencyOverride
|
||||
|
||||
content: Component {
|
||||
Notepad {
|
||||
onHideRequested: {
|
||||
notepadSlideout.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (isVisible) {
|
||||
hide()
|
||||
} else {
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: powerMenuModalLoader
|
||||
|
||||
active: false
|
||||
|
||||
PowerMenuModal {
|
||||
id: powerMenuModal
|
||||
|
||||
onPowerActionRequested: (action, title, message) => {
|
||||
powerConfirmModalLoader.active = true
|
||||
if (powerConfirmModalLoader.item) {
|
||||
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
|
||||
powerConfirmModalLoader.item.show(title, message, function () {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
SessionService.logout()
|
||||
break
|
||||
case "suspend":
|
||||
SessionService.suspend()
|
||||
break
|
||||
case "hibernate":
|
||||
SessionService.hibernate()
|
||||
break
|
||||
case "reboot":
|
||||
SessionService.reboot()
|
||||
break
|
||||
case "poweroff":
|
||||
SessionService.poweroff()
|
||||
break
|
||||
}
|
||||
}, function () {})
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.powerMenuModal = powerMenuModal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
powerMenuModalLoader.active = true
|
||||
if (powerMenuModalLoader.item)
|
||||
powerMenuModalLoader.item.openCentered()
|
||||
|
||||
return "POWERMENU_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (powerMenuModalLoader.item)
|
||||
powerMenuModalLoader.item.close()
|
||||
|
||||
return "POWERMENU_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
powerMenuModalLoader.active = true
|
||||
if (powerMenuModalLoader.item) {
|
||||
if (powerMenuModalLoader.item.shouldBeVisible) {
|
||||
powerMenuModalLoader.item.close()
|
||||
} else {
|
||||
powerMenuModalLoader.item.openCentered()
|
||||
}
|
||||
}
|
||||
|
||||
return "POWERMENU_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "powermenu"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
processListModalLoader.active = true
|
||||
if (processListModalLoader.item)
|
||||
processListModalLoader.item.show()
|
||||
|
||||
return "PROCESSLIST_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (processListModalLoader.item)
|
||||
processListModalLoader.item.hide()
|
||||
|
||||
return "PROCESSLIST_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
processListModalLoader.active = true
|
||||
if (processListModalLoader.item)
|
||||
processListModalLoader.item.toggle()
|
||||
|
||||
return "PROCESSLIST_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "processlist"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
controlCenterLoader.active = true
|
||||
if (controlCenterLoader.item) {
|
||||
controlCenterLoader.item.open()
|
||||
return "CONTROL_CENTER_OPEN_SUCCESS"
|
||||
}
|
||||
return "CONTROL_CENTER_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (controlCenterLoader.item) {
|
||||
controlCenterLoader.item.close()
|
||||
return "CONTROL_CENTER_CLOSE_SUCCESS"
|
||||
}
|
||||
return "CONTROL_CENTER_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
controlCenterLoader.active = true
|
||||
if (controlCenterLoader.item) {
|
||||
controlCenterLoader.item.toggle()
|
||||
return "CONTROL_CENTER_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "CONTROL_CENTER_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "control-center"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(tab: string): string {
|
||||
dankDashPopoutLoader.active = true
|
||||
if (dankDashPopoutLoader.item) {
|
||||
switch (tab.toLowerCase()) {
|
||||
case "media":
|
||||
dankDashPopoutLoader.item.currentTabIndex = 1
|
||||
break
|
||||
case "weather":
|
||||
dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
|
||||
break
|
||||
default:
|
||||
dankDashPopoutLoader.item.currentTabIndex = 0
|
||||
break
|
||||
}
|
||||
dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
|
||||
dankDashPopoutLoader.item.dashVisible = true
|
||||
return "DASH_OPEN_SUCCESS"
|
||||
}
|
||||
return "DASH_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
if (dankDashPopoutLoader.item) {
|
||||
dankDashPopoutLoader.item.dashVisible = false
|
||||
return "DASH_CLOSE_SUCCESS"
|
||||
}
|
||||
return "DASH_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(tab: string): string {
|
||||
dankDashPopoutLoader.active = true
|
||||
if (dankDashPopoutLoader.item) {
|
||||
if (dankDashPopoutLoader.item.dashVisible) {
|
||||
dankDashPopoutLoader.item.dashVisible = false
|
||||
} else {
|
||||
switch (tab.toLowerCase()) {
|
||||
case "media":
|
||||
dankDashPopoutLoader.item.currentTabIndex = 1
|
||||
break
|
||||
case "weather":
|
||||
dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
|
||||
break
|
||||
default:
|
||||
dankDashPopoutLoader.item.currentTabIndex = 0
|
||||
break
|
||||
}
|
||||
dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
|
||||
dankDashPopoutLoader.item.dashVisible = true
|
||||
}
|
||||
return "DASH_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "DASH_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "dash"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function getFocusedScreenName() {
|
||||
if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) {
|
||||
return Hyprland.focusedWorkspace.monitor.name
|
||||
}
|
||||
if (CompositorService.isNiri && NiriService.currentOutput) {
|
||||
return NiriService.currentOutput
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function getActiveNotepadInstance() {
|
||||
if (notepadSlideoutVariants.instances.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (notepadSlideoutVariants.instances.length === 1) {
|
||||
return notepadSlideoutVariants.instances[0]
|
||||
}
|
||||
|
||||
var focusedScreen = getFocusedScreenName()
|
||||
if (focusedScreen && notepadSlideoutVariants.instances.length > 0) {
|
||||
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
|
||||
var slideout = notepadSlideoutVariants.instances[i]
|
||||
if (slideout.modelData && slideout.modelData.name === focusedScreen) {
|
||||
return slideout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
|
||||
var slideout = notepadSlideoutVariants.instances[i]
|
||||
if (slideout.isVisible) {
|
||||
return slideout
|
||||
}
|
||||
}
|
||||
|
||||
return notepadSlideoutVariants.instances[0]
|
||||
}
|
||||
|
||||
function open(): string {
|
||||
var instance = getActiveNotepadInstance()
|
||||
if (instance) {
|
||||
instance.show()
|
||||
return "NOTEPAD_OPEN_SUCCESS"
|
||||
}
|
||||
return "NOTEPAD_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
var instance = getActiveNotepadInstance()
|
||||
if (instance) {
|
||||
instance.hide()
|
||||
return "NOTEPAD_CLOSE_SUCCESS"
|
||||
}
|
||||
return "NOTEPAD_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
var instance = getActiveNotepadInstance()
|
||||
if (instance) {
|
||||
instance.toggle()
|
||||
return "NOTEPAD_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "NOTEPAD_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "notepad"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function toggle(): string {
|
||||
SessionService.toggleIdleInhibit()
|
||||
return SessionService.idleInhibited ? "Idle inhibit enabled" : "Idle inhibit disabled"
|
||||
}
|
||||
|
||||
function enable(): string {
|
||||
SessionService.enableIdleInhibit()
|
||||
return "Idle inhibit enabled"
|
||||
}
|
||||
|
||||
function disable(): string {
|
||||
SessionService.disableIdleInhibit()
|
||||
return "Idle inhibit disabled"
|
||||
}
|
||||
|
||||
function status(): string {
|
||||
return SessionService.idleInhibited ? "Idle inhibit is enabled" : "Idle inhibit is disabled"
|
||||
}
|
||||
|
||||
function reason(newReason: string): string {
|
||||
if (!newReason) {
|
||||
return `Current reason: ${SessionService.inhibitReason}`
|
||||
}
|
||||
|
||||
SessionService.setInhibitReason(newReason)
|
||||
return `Inhibit reason set to: ${newReason}`
|
||||
}
|
||||
|
||||
target: "inhibit"
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("toast")
|
||||
|
||||
delegate: Toast {
|
||||
modelData: item
|
||||
visible: ToastService.toastVisible
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
delegate: VolumeOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
delegate: MicMuteOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
delegate: BrightnessOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
delegate: IdleInhibitorOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "No clipboard entries found"
|
||||
text: qsTr("No clipboard entries found")
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
|
||||
@@ -53,7 +53,7 @@ QtObject {
|
||||
modal.hide()
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) {
|
||||
if (!modal.keyboardNavigationActive) {
|
||||
modal.keyboardNavigationActive = true
|
||||
modal.selectedIndex = 0
|
||||
@@ -62,7 +62,7 @@ QtObject {
|
||||
selectNext()
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
} else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) {
|
||||
if (!modal.keyboardNavigationActive) {
|
||||
modal.keyboardNavigationActive = true
|
||||
modal.selectedIndex = 0
|
||||
@@ -74,6 +74,42 @@ QtObject {
|
||||
selectPrevious()
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
||||
if (!modal.keyboardNavigationActive) {
|
||||
modal.keyboardNavigationActive = true
|
||||
modal.selectedIndex = 0
|
||||
} else {
|
||||
selectNext()
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
||||
if (!modal.keyboardNavigationActive) {
|
||||
modal.keyboardNavigationActive = true
|
||||
modal.selectedIndex = 0
|
||||
} else if (modal.selectedIndex === 0) {
|
||||
modal.keyboardNavigationActive = false
|
||||
} else {
|
||||
selectPrevious()
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||
if (!modal.keyboardNavigationActive) {
|
||||
modal.keyboardNavigationActive = true
|
||||
modal.selectedIndex = 0
|
||||
} else {
|
||||
selectNext()
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
||||
if (!modal.keyboardNavigationActive) {
|
||||
modal.keyboardNavigationActive = true
|
||||
modal.selectedIndex = 0
|
||||
} else if (modal.selectedIndex === 0) {
|
||||
modal.keyboardNavigationActive = false
|
||||
} else {
|
||||
selectPrevious()
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Delete && (event.modifiers & Qt.ShiftModifier)) {
|
||||
modal.clearAll()
|
||||
modal.hide()
|
||||
|
||||
@@ -26,7 +26,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Shift+Del: Clear All • Esc: Close"
|
||||
text: qsTr("Shift+Del: Clear All • Esc: Close")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import QtQuick
|
||||
import Qt.labs.platform
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: colorPickerModal
|
||||
|
||||
signal colorSelected(color selectedColor)
|
||||
|
||||
function show() {
|
||||
colorDialog.open()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
colorDialog.close()
|
||||
}
|
||||
|
||||
function copyColorToClipboard(colorValue) {
|
||||
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
|
||||
ToastService.showInfo(`Color ${colorValue} copied to clipboard`)
|
||||
console.log("Copied color to clipboard:", colorValue)
|
||||
}
|
||||
|
||||
ColorDialog {
|
||||
id: colorDialog
|
||||
title: "Color Picker - Select and copy color"
|
||||
color: Theme.primary
|
||||
|
||||
onAccepted: {
|
||||
const colorString = color.toString()
|
||||
copyColorToClipboard(colorString)
|
||||
colorSelected(color)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,48 @@ DankModal {
|
||||
selectedButton = 1
|
||||
event.accepted = true
|
||||
break
|
||||
case Qt.Key_N:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
keyboardNavigation = true
|
||||
selectedButton = (selectedButton + 1) % 2
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_P:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
keyboardNavigation = true
|
||||
selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_J:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
keyboardNavigation = true
|
||||
selectedButton = 1
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_K:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
keyboardNavigation = true
|
||||
selectedButton = 0
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_H:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
keyboardNavigation = true
|
||||
selectedButton = 0
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_L:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
keyboardNavigation = true
|
||||
selectedButton = 1
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_Tab:
|
||||
keyboardNavigation = true
|
||||
selectedButton = selectedButton === -1 ? 0 : (selectedButton + 1) % 2
|
||||
|
||||
@@ -22,7 +22,7 @@ PanelWindow {
|
||||
property bool closeOnEscapeKey: true
|
||||
property bool closeOnBackgroundClick: true
|
||||
property string animationType: "scale"
|
||||
property int animationDuration: Theme.shorterDuration
|
||||
property int animationDuration: Theme.shortDuration
|
||||
property var animationEasing: Theme.emphasizedEasing
|
||||
property color backgroundColor: Theme.surfaceContainer
|
||||
property color borderColor: Theme.outlineMedium
|
||||
@@ -34,6 +34,7 @@ PanelWindow {
|
||||
property bool shouldHaveFocus: shouldBeVisible
|
||||
property bool allowFocusOverride: false
|
||||
property bool allowStacking: false
|
||||
property bool keepContentLoaded: false
|
||||
|
||||
signal opened
|
||||
signal dialogClosed
|
||||
@@ -90,7 +91,7 @@ PanelWindow {
|
||||
Timer {
|
||||
id: closeTimer
|
||||
|
||||
interval: animationDuration + 50
|
||||
interval: animationDuration + 100
|
||||
onTriggered: {
|
||||
visible = false
|
||||
}
|
||||
@@ -156,9 +157,8 @@ PanelWindow {
|
||||
radius: root.cornerRadius
|
||||
border.color: root.borderColor
|
||||
border.width: root.borderWidth
|
||||
layer.enabled: root.enableShadow
|
||||
opacity: root.shouldBeVisible ? 1 : 0
|
||||
scale: root.animationType === "scale" ? (root.shouldBeVisible ? 1 : 0.9) : 1
|
||||
clip: false
|
||||
layer.enabled: true
|
||||
transform: root.animationType === "slide" ? slideTransform : null
|
||||
|
||||
Translate {
|
||||
@@ -168,27 +168,25 @@ PanelWindow {
|
||||
y: root.shouldBeVisible ? 0 : -30
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
active: root.visible
|
||||
asynchronous: false
|
||||
}
|
||||
focus: root.shouldBeVisible
|
||||
clip: false
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.animationEasing
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
id: contentLoader
|
||||
|
||||
Behavior on scale {
|
||||
enabled: root.animationType === "scale"
|
||||
anchors.fill: parent
|
||||
active: root.keepContentLoaded || root.shouldBeVisible || root.visible
|
||||
asynchronous: false
|
||||
focus: true
|
||||
clip: false
|
||||
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.animationEasing
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
Qt.callLater(() => item.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +197,15 @@ PanelWindow {
|
||||
shadowBlur: 1
|
||||
shadowColor: Theme.shadowStrong
|
||||
shadowOpacity: 0.3
|
||||
source: contentContainer
|
||||
opacity: root.shouldBeVisible ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: animationDuration
|
||||
easing.type: animationEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,8 +214,8 @@ PanelWindow {
|
||||
|
||||
objectName: "modalFocusScope"
|
||||
anchors.fill: parent
|
||||
visible: root.visible // Only active when the modal is visible
|
||||
focus: root.visible
|
||||
visible: root.shouldBeVisible || root.visible
|
||||
focus: root.shouldBeVisible
|
||||
Keys.onEscapePressed: event => {
|
||||
if (root.closeOnEscapeKey && shouldHaveFocus) {
|
||||
root.close()
|
||||
@@ -223,7 +230,7 @@ PanelWindow {
|
||||
|
||||
Connections {
|
||||
function onShouldHaveFocusChanged() {
|
||||
if (shouldHaveFocus && visible) {
|
||||
if (shouldHaveFocus && shouldBeVisible) {
|
||||
Qt.callLater(() => focusScope.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
|
||||
580
Modals/DankColorPickerModal.qml
Normal file
580
Modals/DankColorPickerModal.qml
Normal file
@@ -0,0 +1,580 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property string pickerTitle: "Choose Color"
|
||||
property color selectedColor: Theme.primary
|
||||
property bool shouldBeVisible: false
|
||||
property var onColorSelectedCallback: null
|
||||
|
||||
signal colorSelected(color selectedColor)
|
||||
|
||||
property color currentColor: Theme.primary
|
||||
property real hue: 0
|
||||
property real saturation: 1
|
||||
property real value: 1
|
||||
property real alpha: 1
|
||||
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
|
||||
}
|
||||
|
||||
function show() {
|
||||
open()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
close()
|
||||
}
|
||||
|
||||
onColorSelected: (color) => {
|
||||
if (onColorSelectedCallback) {
|
||||
onColorSelectedCallback(color)
|
||||
}
|
||||
}
|
||||
|
||||
function copyColorToClipboard(colorValue) {
|
||||
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
|
||||
ToastService.showInfo(`Color ${colorValue} copied`)
|
||||
SessionData.addRecentColor(currentColor)
|
||||
}
|
||||
|
||||
function updateFromColor(color) {
|
||||
hue = color.hsvHue
|
||||
saturation = color.hsvSaturation
|
||||
value = color.hsvValue
|
||||
alpha = color.a
|
||||
gradientX = saturation
|
||||
gradientY = 1 - value
|
||||
}
|
||||
|
||||
function updateColor() {
|
||||
currentColor = Qt.hsva(hue, saturation, value, alpha)
|
||||
}
|
||||
|
||||
function updateColorFromGradient(x, y) {
|
||||
saturation = Math.max(0, Math.min(1, x))
|
||||
value = Math.max(0, Math.min(1, 1 - y))
|
||||
updateColor()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("hyprpicker exited with code:", exitCode)
|
||||
}
|
||||
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"
|
||||
]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: colorContent
|
||||
|
||||
anchors.fill: parent
|
||||
focus: root.shouldBeVisible
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
root.close()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Column {
|
||||
width: parent.width - 90
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: root.pickerTitle
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Select a color from the palette or use custom sliders")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "colorize"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: () => {
|
||||
pickColorFromScreen()
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: () => {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
id: gradientPicker
|
||||
width: parent.width - 70
|
||||
height: 280
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.hsva(root.hue, 1, 1, 1)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: "#ffffff" }
|
||||
GradientStop { position: 1.0; color: "transparent" }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Vertical
|
||||
GradientStop { position: 0.0; color: "transparent" }
|
||||
GradientStop { position: 1.0; color: "#000000" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: pickerCircle
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 8
|
||||
border.color: "white"
|
||||
border.width: 2
|
||||
color: "transparent"
|
||||
x: root.gradientX * parent.width - width / 2
|
||||
y: root.gradientY * parent.height - height / 2
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
radius: width / 2
|
||||
border.color: "black"
|
||||
border.width: 1
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.CrossCursor
|
||||
onPressed: mouse => {
|
||||
const x = Math.max(0, Math.min(1, mouse.x / width))
|
||||
const y = Math.max(0, Math.min(1, mouse.y / height))
|
||||
root.gradientX = x
|
||||
root.gradientY = y
|
||||
root.updateColorFromGradient(x, y)
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
const x = Math.max(0, Math.min(1, mouse.x / width))
|
||||
const y = Math.max(0, Math.min(1, mouse.y / height))
|
||||
root.gradientX = x
|
||||
root.gradientY = y
|
||||
root.updateColorFromGradient(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: hueSlider
|
||||
width: 50
|
||||
height: 280
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Vertical
|
||||
GradientStop { position: 0.00; color: "#ff0000" }
|
||||
GradientStop { position: 0.17; color: "#ffff00" }
|
||||
GradientStop { position: 0.33; color: "#00ff00" }
|
||||
GradientStop { position: 0.50; color: "#00ffff" }
|
||||
GradientStop { position: 0.67; color: "#0000ff" }
|
||||
GradientStop { position: 0.83; color: "#ff00ff" }
|
||||
GradientStop { position: 1.00; color: "#ff0000" }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: hueIndicator
|
||||
width: parent.width
|
||||
height: 4
|
||||
color: "white"
|
||||
border.color: "black"
|
||||
border.width: 1
|
||||
y: root.hue * parent.height - height / 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
onPressed: mouse => {
|
||||
const h = Math.max(0, Math.min(1, mouse.y / height))
|
||||
root.hue = h
|
||||
root.updateColor()
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
const h = Math.max(0, Math.min(1, mouse.y / height))
|
||||
root.hue = h
|
||||
root.updateColor()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Material Colors")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
GridView {
|
||||
width: parent.width
|
||||
height: 140
|
||||
cellWidth: 38
|
||||
cellHeight: 38
|
||||
clip: true
|
||||
interactive: false
|
||||
model: root.standardColors
|
||||
|
||||
delegate: Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
color: modelData
|
||||
radius: 4
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
root.currentColor = modelData
|
||||
root.updateFromColor(root.currentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Column {
|
||||
width: 210
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Recent Colors")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: 5
|
||||
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 4
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
|
||||
color: {
|
||||
if (index < SessionData.recentColors.length) {
|
||||
return SessionData.recentColors[index]
|
||||
}
|
||||
return Theme.surfaceContainerHigh
|
||||
}
|
||||
|
||||
opacity: index < SessionData.recentColors.length ? 1.0 : 0.3
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: index < SessionData.recentColors.length ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: index < SessionData.recentColors.length
|
||||
onClicked: () => {
|
||||
if (index < SessionData.recentColors.length) {
|
||||
root.currentColor = SessionData.recentColors[index]
|
||||
root.updateFromColor(root.currentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - 330
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Opacity")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
width: parent.width
|
||||
value: Math.round(root.alpha * 100)
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
showValue: false
|
||||
onSliderValueChanged: (newValue) => {
|
||||
root.alpha = newValue / 100
|
||||
root.updateColor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: root.currentColor
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Hex:")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: hexInput
|
||||
width: 120
|
||||
height: 38
|
||||
text: root.currentColor.toString()
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
textColor: {
|
||||
if (text.length === 0) return Theme.surfaceText
|
||||
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
|
||||
return hexPattern.test(text) ? Theme.surfaceText : Theme.error
|
||||
}
|
||||
placeholderText: "#000000"
|
||||
backgroundColor: Theme.surfaceHover
|
||||
borderWidth: 1
|
||||
focusedBorderWidth: 2
|
||||
topPadding: Theme.spacingS
|
||||
bottomPadding: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onAccepted: () => {
|
||||
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
|
||||
if (!hexPattern.test(text)) return
|
||||
const color = Qt.color(text)
|
||||
if (color) {
|
||||
root.currentColor = color
|
||||
root.updateFromColor(color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
width: 80
|
||||
buttonHeight: 36
|
||||
text: qsTr("Apply")
|
||||
backgroundColor: Theme.primary
|
||||
textColor: Theme.background
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
|
||||
if (!hexPattern.test(hexInput.text)) return
|
||||
const color = Qt.color(hexInput.text)
|
||||
if (color) {
|
||||
root.currentColor = color
|
||||
root.updateFromColor(color)
|
||||
root.selectedColor = root.currentColor
|
||||
colorSelected(root.currentColor)
|
||||
SessionData.addRecentColor(root.currentColor)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 460
|
||||
height: 1
|
||||
}
|
||||
|
||||
DankButton {
|
||||
width: 70
|
||||
buttonHeight: 36
|
||||
text: qsTr("Cancel")
|
||||
backgroundColor: "transparent"
|
||||
textColor: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.close()
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
border.color: Theme.surfaceVariantAlpha
|
||||
border.width: 1
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
width: 70
|
||||
buttonHeight: 36
|
||||
text: qsTr("Copy")
|
||||
backgroundColor: Theme.primary
|
||||
textColor: Theme.background
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
const colorString = root.currentColor.toString()
|
||||
copyColorToClipboard(colorString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ DankModal {
|
||||
property bool weAvailable: false
|
||||
property string wePath: ""
|
||||
property bool weMode: false
|
||||
property var parentModal: null
|
||||
|
||||
signal fileSelected(string path)
|
||||
|
||||
@@ -131,6 +132,8 @@ DankModal {
|
||||
|
||||
objectName: "fileBrowserModal"
|
||||
allowStacking: true
|
||||
closeOnEscapeKey: false
|
||||
shouldHaveFocus: shouldBeVisible
|
||||
Component.onCompleted: {
|
||||
currentPath = getLastPath()
|
||||
}
|
||||
@@ -165,10 +168,23 @@ DankModal {
|
||||
visible: false
|
||||
onBackgroundClicked: close()
|
||||
onOpened: {
|
||||
modalFocusScope.forceActiveFocus()
|
||||
if (parentModal) {
|
||||
parentModal.shouldHaveFocus = false
|
||||
parentModal.allowFocusOverride = true
|
||||
}
|
||||
Qt.callLater(() => {
|
||||
if (contentLoader && contentLoader.item) {
|
||||
contentLoader.item.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
modalFocusScope.Keys.onPressed: function (event) {
|
||||
keyboardController.handleKey(event)
|
||||
onDialogClosed: {
|
||||
if (parentModal) {
|
||||
parentModal.allowFocusOverride = false
|
||||
parentModal.shouldHaveFocus = Qt.binding(() => {
|
||||
return parentModal.shouldBeVisible
|
||||
})
|
||||
}
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
@@ -239,7 +255,12 @@ DankModal {
|
||||
return
|
||||
}
|
||||
if (!keyboardNavigationActive) {
|
||||
if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right) {
|
||||
const isInitKey = event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right ||
|
||||
(event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) ||
|
||||
(event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) ||
|
||||
(event.key === Qt.Key_L && event.modifiers & Qt.ControlModifier)
|
||||
|
||||
if (isInitKey) {
|
||||
keyboardNavigationActive = true
|
||||
if (currentPath !== homeDir) {
|
||||
backButtonFocused = true
|
||||
@@ -281,6 +302,69 @@ DankModal {
|
||||
}
|
||||
event.accepted = true
|
||||
break
|
||||
case Qt.Key_N:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
if (backButtonFocused) {
|
||||
backButtonFocused = false
|
||||
selectedIndex = 0
|
||||
} else if (selectedIndex < totalItems - 1) {
|
||||
selectedIndex++
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_P:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
if (selectedIndex > 0) {
|
||||
selectedIndex--
|
||||
} else if (currentPath !== homeDir) {
|
||||
backButtonFocused = true
|
||||
selectedIndex = -1
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_J:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
if (selectedIndex < totalItems - 1) {
|
||||
selectedIndex++
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_K:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
if (selectedIndex > 0) {
|
||||
selectedIndex--
|
||||
} else if (currentPath !== homeDir) {
|
||||
backButtonFocused = true
|
||||
selectedIndex = -1
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_H:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
if (!backButtonFocused && selectedIndex > 0) {
|
||||
selectedIndex--
|
||||
} else if (currentPath !== homeDir) {
|
||||
backButtonFocused = true
|
||||
selectedIndex = -1
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_L:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
if (backButtonFocused) {
|
||||
backButtonFocused = false
|
||||
selectedIndex = 0
|
||||
} else if (selectedIndex < totalItems - 1) {
|
||||
selectedIndex++
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
break
|
||||
case Qt.Key_Left:
|
||||
if (backButtonFocused)
|
||||
return
|
||||
@@ -387,6 +471,16 @@ DankModal {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Keys.onPressed: event => {
|
||||
keyboardController.handleKey(event)
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
@@ -687,7 +781,7 @@ DankModal {
|
||||
width: parent.width - saveButton.width - Theme.spacingM
|
||||
height: 40
|
||||
text: defaultFileName
|
||||
placeholderText: "Enter filename..."
|
||||
placeholderText: qsTr("Enter filename...")
|
||||
ignoreLeftRightKeys: false
|
||||
focus: saveMode
|
||||
topPadding: Theme.spacingS
|
||||
@@ -720,7 +814,7 @@ DankModal {
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "Save"
|
||||
text: qsTr("Save")
|
||||
color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "File Information"
|
||||
text: qsTr("File Information")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -56,7 +56,7 @@ DankModal {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Network Information"
|
||||
text: qsTr("Network Information")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -126,7 +126,7 @@ DankModal {
|
||||
id: closeText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "Close"
|
||||
text: qsTr("Close")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -9,31 +9,47 @@ DankModal {
|
||||
|
||||
property int selectedIndex: 0
|
||||
property int optionCount: SessionService.hibernateSupported ? 5 : 4
|
||||
property rect parentBounds: Qt.rect(0, 0, 0, 0)
|
||||
property var parentScreen: null
|
||||
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
|
||||
function openCentered() {
|
||||
parentBounds = Qt.rect(0, 0, 0, 0)
|
||||
parentScreen = null
|
||||
backgroundOpacity = 0.5
|
||||
open()
|
||||
}
|
||||
|
||||
function openFromControlCenter(bounds, targetScreen) {
|
||||
parentBounds = bounds
|
||||
parentScreen = targetScreen
|
||||
backgroundOpacity = 0
|
||||
open()
|
||||
}
|
||||
|
||||
function selectOption(action) {
|
||||
close();
|
||||
const actions = {
|
||||
"logout": {
|
||||
"title": "Log Out",
|
||||
"message": "Are you sure you want to log out?"
|
||||
"title": qsTr("Log Out"),
|
||||
"message": qsTr("Are you sure you want to log out?")
|
||||
},
|
||||
"suspend": {
|
||||
"title": "Suspend",
|
||||
"message": "Are you sure you want to suspend the system?"
|
||||
"title": qsTr("Suspend"),
|
||||
"message": qsTr("Are you sure you want to suspend the system?")
|
||||
},
|
||||
"hibernate": {
|
||||
"title": "Hibernate",
|
||||
"message": "Are you sure you want to hibernate the system?"
|
||||
"title": qsTr("Hibernate"),
|
||||
"message": qsTr("Are you sure you want to hibernate the system?")
|
||||
},
|
||||
"reboot": {
|
||||
"title": "Reboot",
|
||||
"message": "Are you sure you want to reboot the system?"
|
||||
"title": qsTr("Reboot"),
|
||||
"message": qsTr("Are you sure you want to reboot the system?")
|
||||
},
|
||||
"poweroff": {
|
||||
"title": "Power Off",
|
||||
"message": "Are you sure you want to power off the system?"
|
||||
"title": qsTr("Power Off"),
|
||||
"message": qsTr("Are you sure you want to power off the system?")
|
||||
}
|
||||
}
|
||||
const selected = actions[action]
|
||||
@@ -47,6 +63,16 @@ DankModal {
|
||||
width: 320
|
||||
height: contentLoader.item ? contentLoader.item.implicitHeight : 300
|
||||
enableShadow: true
|
||||
screen: parentScreen
|
||||
positioning: parentBounds.width > 0 ? "custom" : "center"
|
||||
customPosition: {
|
||||
if (parentBounds.width > 0) {
|
||||
const centerX = parentBounds.x + (parentBounds.width - width) / 2
|
||||
const centerY = parentBounds.y + (parentBounds.height - height) / 2
|
||||
return Qt.point(centerX, centerY)
|
||||
}
|
||||
return Qt.point(0, 0)
|
||||
}
|
||||
onBackgroundClicked: () => {
|
||||
return close();
|
||||
}
|
||||
@@ -57,13 +83,11 @@ DankModal {
|
||||
modalFocusScope.Keys.onPressed: (event) => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Up:
|
||||
case Qt.Key_Backtab:
|
||||
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount;
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Down:
|
||||
selectedIndex = (selectedIndex + 1) % optionCount;
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Tab:
|
||||
selectedIndex = (selectedIndex + 1) % optionCount;
|
||||
event.accepted = true;
|
||||
@@ -78,6 +102,30 @@ DankModal {
|
||||
}
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_N:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
selectedIndex = (selectedIndex + 1) % optionCount;
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
case Qt.Key_P:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount;
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
case Qt.Key_J:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
selectedIndex = (selectedIndex + 1) % optionCount;
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
case Qt.Key_K:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount;
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +144,7 @@ DankModal {
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
text: "Power Options"
|
||||
text: qsTr("Power Options")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -153,7 +201,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Log Out"
|
||||
text: qsTr("Log Out")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -206,7 +254,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Suspend"
|
||||
text: qsTr("Suspend")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -260,7 +308,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Hibernate"
|
||||
text: qsTr("Hibernate")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -314,7 +362,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Reboot"
|
||||
text: qsTr("Reboot")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -368,7 +416,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Power Off"
|
||||
text: qsTr("Power Off")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -123,7 +123,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "System Monitor Unavailable"
|
||||
text: qsTr("System Monitor Unavailable")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.error
|
||||
@@ -154,7 +154,7 @@ DankModal {
|
||||
height: 40
|
||||
|
||||
StyledText {
|
||||
text: "System Monitor"
|
||||
text: qsTr("System Monitor")
|
||||
font.pixelSize: Theme.fontSizeLarge + 4
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
|
||||
@@ -19,7 +19,7 @@ Item {
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
StyledText {
|
||||
text: "Battery not detected - only AC power settings available"
|
||||
text: qsTr("Battery not detected - only AC power settings available")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
visible: !BatteryService.batteryAvailable
|
||||
@@ -51,7 +51,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Idle Settings"
|
||||
text: qsTr("Idle Settings")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
@@ -79,8 +79,7 @@ Item {
|
||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||
|
||||
width: parent.width
|
||||
text: "Automatically lock after"
|
||||
text: qsTr("Automatically lock after")
|
||||
options: timeoutOptions
|
||||
|
||||
Connections {
|
||||
@@ -116,8 +115,7 @@ Item {
|
||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||
|
||||
width: parent.width
|
||||
text: "Turn off monitors after"
|
||||
text: qsTr("Turn off monitors after")
|
||||
options: timeoutOptions
|
||||
|
||||
Connections {
|
||||
@@ -153,8 +151,7 @@ Item {
|
||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||
|
||||
width: parent.width
|
||||
text: "Suspend system after"
|
||||
text: qsTr("Suspend system after")
|
||||
options: timeoutOptions
|
||||
|
||||
Connections {
|
||||
@@ -190,8 +187,7 @@ Item {
|
||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||
|
||||
width: parent.width
|
||||
text: "Hibernate system after"
|
||||
text: qsTr("Hibernate system after")
|
||||
options: timeoutOptions
|
||||
visible: SessionService.hibernateSupported
|
||||
|
||||
@@ -225,14 +221,14 @@ Item {
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: "Lock before suspend"
|
||||
text: qsTr("Lock before suspend")
|
||||
description: "Automatically lock the screen when the system prepares to suspend"
|
||||
checked: SessionData.lockBeforeSuspend
|
||||
onToggled: checked => SessionData.setLockBeforeSuspend(checked)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Idle monitoring not supported - requires newer Quickshell version"
|
||||
text: qsTr("Idle monitoring not supported - requires newer Quickshell version")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
@@ -2,11 +2,12 @@ import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Settings
|
||||
|
||||
Item {
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
property int currentIndex: 0
|
||||
property var parentModal: null
|
||||
focus: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -67,7 +68,8 @@ Item {
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: TopBarTab {
|
||||
sourceComponent: DankBarTab {
|
||||
parentModal: root.parentModal
|
||||
}
|
||||
|
||||
}
|
||||
@@ -154,13 +156,27 @@ Item {
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: aboutLoader
|
||||
id: pluginsLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 10
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: PluginsTab {
|
||||
parentModal: root.parentModal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: aboutLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 11
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: AboutTab {
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ DankModal {
|
||||
|
||||
objectName: "settingsModal"
|
||||
width: 800
|
||||
height: 750
|
||||
height: 800
|
||||
visible: false
|
||||
onBackgroundClicked: () => {
|
||||
return hide();
|
||||
@@ -78,6 +78,7 @@ DankModal {
|
||||
id: profileBrowser
|
||||
|
||||
allowStacking: true
|
||||
parentModal: settingsModal
|
||||
browserTitle: "Select Profile Image"
|
||||
browserIcon: "person"
|
||||
browserType: "profile"
|
||||
@@ -87,12 +88,6 @@ DankModal {
|
||||
close();
|
||||
}
|
||||
onDialogClosed: () => {
|
||||
if (settingsModal) {
|
||||
settingsModal.allowFocusOverride = false;
|
||||
settingsModal.shouldHaveFocus = Qt.binding(() => {
|
||||
return settingsModal.shouldBeVisible;
|
||||
});
|
||||
}
|
||||
allowStacking = true;
|
||||
}
|
||||
}
|
||||
@@ -101,6 +96,7 @@ DankModal {
|
||||
id: wallpaperBrowser
|
||||
|
||||
allowStacking: true
|
||||
parentModal: settingsModal
|
||||
browserTitle: "Select Wallpaper"
|
||||
browserIcon: "wallpaper"
|
||||
browserType: "wallpaper"
|
||||
@@ -115,7 +111,7 @@ DankModal {
|
||||
}
|
||||
|
||||
settingsContent: Component {
|
||||
Item {
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
@@ -144,7 +140,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Settings"
|
||||
text: qsTr("Settings")
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -18,7 +18,7 @@ Rectangle {
|
||||
"text": "Weather",
|
||||
"icon": "cloud"
|
||||
}, {
|
||||
"text": "Top Bar",
|
||||
"text": "Dank Bar",
|
||||
"icon": "toolbar"
|
||||
}, {
|
||||
"text": "Widgets",
|
||||
@@ -38,6 +38,9 @@ Rectangle {
|
||||
}, {
|
||||
"text": "Power",
|
||||
"icon": "power_settings_new"
|
||||
}, {
|
||||
"text": "Plugins",
|
||||
"icon": "extension"
|
||||
}, {
|
||||
"text": "About",
|
||||
"icon": "info"
|
||||
@@ -83,7 +86,7 @@ Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 44
|
||||
radius: Theme.cornerRadius
|
||||
color: isActive ? Theme.primaryContainer : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
color: isActive ? Theme.primary : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -94,14 +97,14 @@ Rectangle {
|
||||
DankIcon {
|
||||
name: modelData.icon || ""
|
||||
size: Theme.iconSize - 2
|
||||
color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText
|
||||
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.text || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText
|
||||
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText
|
||||
font.weight: parent.parent.isActive ? Font.Medium : Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
@@ -13,8 +13,13 @@ Item {
|
||||
property alias searchField: searchField
|
||||
property var parentModal: null
|
||||
|
||||
function resetScroll() {
|
||||
resultsView.resetScroll()
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
clip: false
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (parentModal)
|
||||
@@ -33,6 +38,46 @@ Item {
|
||||
} else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow()
|
||||
event.accepted = true
|
||||
} else if (event.key == Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||
appLauncher.selectNext()
|
||||
event.accepted = true
|
||||
} else if (event.key == Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
||||
appLauncher.selectPrevious()
|
||||
event.accepted = true
|
||||
} else if (event.key == Qt.Key_L && event.modifiers & Qt.ControlModifier && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow()
|
||||
event.accepted = true
|
||||
} else if (event.key == Qt.Key_H && event.modifiers & Qt.ControlModifier && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow()
|
||||
} else {
|
||||
appLauncher.selectNext()
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow()
|
||||
} else {
|
||||
appLauncher.selectPrevious()
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow()
|
||||
} else {
|
||||
appLauncher.selectNext()
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow()
|
||||
} else {
|
||||
appLauncher.selectPrevious()
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
appLauncher.launchSelected()
|
||||
event.accepted = true
|
||||
@@ -61,27 +106,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: categorySelector.height + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
|
||||
|
||||
CategorySelector {
|
||||
id: categorySelector
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
categories: appLauncher.categories
|
||||
selectedCategory: appLauncher.selectedCategory
|
||||
compact: false
|
||||
onCategorySelected: category => {
|
||||
appLauncher.setCategory(category)
|
||||
}
|
||||
}
|
||||
}
|
||||
clip: false
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
@@ -124,7 +149,7 @@ Item {
|
||||
else if (appLauncher.model.count > 0)
|
||||
appLauncher.launchApp(appLauncher.model.get(0))
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
|
||||
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
|
||||
event.accepted = false
|
||||
}
|
||||
}
|
||||
@@ -188,6 +213,7 @@ Item {
|
||||
}
|
||||
|
||||
SpotlightResults {
|
||||
id: resultsView
|
||||
appLauncher: spotlightKeyHandler.appLauncher
|
||||
contextMenu: contextMenu
|
||||
}
|
||||
@@ -205,7 +231,7 @@ Item {
|
||||
visible: contextMenu.visible
|
||||
z: 999
|
||||
onClicked: () => {
|
||||
contextMenu.close()
|
||||
contextMenu.hide()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -1,65 +1,72 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
Popup {
|
||||
id: contextMenu
|
||||
|
||||
property var currentApp: null
|
||||
property bool menuVisible: false
|
||||
property var appLauncher: null
|
||||
property var parentHandler: null
|
||||
|
||||
function show(x, y, app) {
|
||||
currentApp = app
|
||||
const menuWidth = 180
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
let finalX = x + 8
|
||||
let finalY = y + 8
|
||||
if (parentHandler) {
|
||||
if (finalX + menuWidth > parentHandler.width)
|
||||
finalX = x - menuWidth - 8
|
||||
|
||||
if (finalY + menuHeight > parentHandler.height)
|
||||
finalY = y - menuHeight - 8
|
||||
|
||||
finalX = Math.max(8, Math.min(finalX, parentHandler.width - menuWidth - 8))
|
||||
finalY = Math.max(8, Math.min(finalY, parentHandler.height - menuHeight - 8))
|
||||
}
|
||||
contextMenu.x = finalX
|
||||
contextMenu.y = finalY
|
||||
contextMenu.visible = true
|
||||
contextMenu.menuVisible = true
|
||||
contextMenu.x = x + 4
|
||||
contextMenu.y = y + 4
|
||||
contextMenu.open()
|
||||
}
|
||||
|
||||
function close() {
|
||||
contextMenu.menuVisible = false
|
||||
Qt.callLater(() => {
|
||||
contextMenu.visible = false
|
||||
})
|
||||
function hide() {
|
||||
contextMenu.close()
|
||||
}
|
||||
|
||||
visible: false
|
||||
width: 180
|
||||
width: Math.max(180, Math.min(300, menuColumn.implicitWidth + Theme.spacingS * 2))
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
z: 1000
|
||||
opacity: menuVisible ? 1 : 0
|
||||
scale: menuVisible ? 1 : 0.85
|
||||
padding: 0
|
||||
closePolicy: Popup.CloseOnPressOutside
|
||||
modal: false
|
||||
dim: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: parent.z - 1
|
||||
background: Rectangle {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -125,7 +132,7 @@ Rectangle {
|
||||
SessionData.removePinnedApp(appId)
|
||||
else
|
||||
SessionData.addPinnedApp(appId)
|
||||
contextMenu.close()
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,6 +151,82 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Theme.iconSize - 2
|
||||
height: Theme.iconSize - 2
|
||||
visible: modelData.icon && modelData.icon !== ""
|
||||
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData && contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
|
||||
SessionService.launchDesktopAction(contextMenu.currentApp.desktopEntry, modelData)
|
||||
if (appLauncher) {
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
}
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions && contextMenu.currentApp.desktopEntry.actions.length > 0
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
@@ -165,7 +248,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Launch"
|
||||
text: qsTr("Launch")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
@@ -183,23 +266,72 @@ Rectangle {
|
||||
if (contextMenu.currentApp && appLauncher)
|
||||
appLauncher.launchApp(contextMenu.currentApp)
|
||||
|
||||
contextMenu.close()
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: SessionService.hasPrimeRun
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: SessionService.hasPrimeRun
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Launch on dGPU")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: primeRunMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
if (contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
|
||||
SessionService.launchDesktopEntry(contextMenu.currentApp.desktopEntry, true)
|
||||
if (appLauncher) {
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
}
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,24 +18,32 @@ DankModal {
|
||||
function show() {
|
||||
spotlightOpen = true
|
||||
open()
|
||||
if (contentLoader.item && contentLoader.item.appLauncher) {
|
||||
contentLoader.item.appLauncher.searchQuery = ""
|
||||
}
|
||||
|
||||
Qt.callLater(() => {
|
||||
if (contentLoader.item && contentLoader.item.searchField) {
|
||||
contentLoader.item.searchField.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
if (contentLoader.item && contentLoader.item.searchField) {
|
||||
contentLoader.item.searchField.forceActiveFocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function hide() {
|
||||
spotlightOpen = false
|
||||
close()
|
||||
if (contentLoader.item && contentLoader.item.appLauncher) {
|
||||
contentLoader.item.appLauncher.searchQuery = ""
|
||||
contentLoader.item.appLauncher.selectedIndex = 0
|
||||
contentLoader.item.appLauncher.setCategory("All")
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
if (contentLoader.item) {
|
||||
if (contentLoader.item.appLauncher) {
|
||||
contentLoader.item.appLauncher.searchQuery = ""
|
||||
contentLoader.item.appLauncher.selectedIndex = 0
|
||||
contentLoader.item.appLauncher.setCategory("All")
|
||||
}
|
||||
if (contentLoader.item.resetScroll) {
|
||||
contentLoader.item.resetScroll()
|
||||
}
|
||||
if (contentLoader.item.searchField) {
|
||||
contentLoader.item.searchField.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,12 +57,13 @@ DankModal {
|
||||
|
||||
shouldBeVisible: spotlightOpen
|
||||
width: 550
|
||||
height: 600
|
||||
height: 700
|
||||
backgroundColor: Theme.popupBackground()
|
||||
cornerRadius: Theme.cornerRadius
|
||||
borderColor: Theme.outlineMedium
|
||||
borderWidth: 1
|
||||
enableShadow: true
|
||||
keepContentLoaded: true
|
||||
onVisibleChanged: () => {
|
||||
if (visible && !spotlightOpen) {
|
||||
show()
|
||||
|
||||
@@ -10,10 +10,16 @@ Rectangle {
|
||||
property var appLauncher: null
|
||||
property var contextMenu: null
|
||||
|
||||
function resetScroll() {
|
||||
resultsList.contentY = 0
|
||||
resultsGrid.contentY = 0
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
clip: false
|
||||
|
||||
DankListView {
|
||||
id: resultsList
|
||||
@@ -157,7 +163,8 @@ Rectangle {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
resultsList.itemClicked(index, model)
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -308,7 +315,8 @@ Rectangle {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
resultsGrid.itemClicked(index, model)
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ DankModal {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Connect to Wi-Fi"
|
||||
text: qsTr("Connect to Wi-Fi")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -201,7 +201,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Show password"
|
||||
text: qsTr("Show password")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -229,7 +229,7 @@ DankModal {
|
||||
id: cancelText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "Cancel"
|
||||
text: qsTr("Cancel")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -260,7 +260,7 @@ DankModal {
|
||||
id: connectText
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "Connect"
|
||||
text: qsTr("Connect")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
@@ -12,7 +13,6 @@ import qs.Widgets
|
||||
DankPopout {
|
||||
id: appDrawerPopout
|
||||
|
||||
property string triggerSection: "left"
|
||||
property var triggerScreen: null
|
||||
|
||||
// Setting to Exclusive, so virtual keyboards can send input to app drawer
|
||||
@@ -33,9 +33,9 @@ DankPopout {
|
||||
popupWidth: 520
|
||||
popupHeight: 600
|
||||
triggerX: Theme.spacingL
|
||||
triggerY: Math.max(26 + SettingsData.topBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.topBarInnerPadding)) + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance
|
||||
triggerY: Math.max(26 + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding)) + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap - 2
|
||||
triggerWidth: 40
|
||||
positioning: "center"
|
||||
positioning: ""
|
||||
screen: triggerScreen
|
||||
|
||||
onShouldBeVisibleChanged: {
|
||||
@@ -128,6 +128,44 @@ DankPopout {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
||||
appLauncher.selectNext()
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
||||
appLauncher.selectPrevious()
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||
appLauncher.selectNext()
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
||||
appLauncher.selectPrevious()
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
if (event.key === Qt.Key_L && event.modifiers & Qt.ControlModifier) {
|
||||
appLauncher.selectNextInRow()
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_H && event.modifiers & Qt.ControlModifier) {
|
||||
appLauncher.selectPreviousInRow()
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!searchField.activeFocus && event.text && /[a-zA-Z0-9\s]/.test(event.text)) {
|
||||
searchField.forceActiveFocus()
|
||||
searchField.insertText(event.text)
|
||||
@@ -149,7 +187,7 @@ DankPopout {
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Applications"
|
||||
text: qsTr("Applications")
|
||||
font.pixelSize: Theme.fontSizeLarge + 4
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
@@ -234,18 +272,17 @@ DankPopout {
|
||||
spacing: Theme.spacingM
|
||||
visible: searchField.text.length === 0
|
||||
leftPadding: Theme.spacingS
|
||||
topPadding: Theme.spacingXS
|
||||
|
||||
Item {
|
||||
width: 200
|
||||
height: 36
|
||||
Rectangle {
|
||||
width: 180
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
|
||||
DankDropdown {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
text: ""
|
||||
dropdownWidth: 180
|
||||
currentValue: appLauncher.selectedCategory
|
||||
options: appLauncher.categories
|
||||
optionIcons: appLauncher.categoryIcons
|
||||
@@ -256,7 +293,7 @@ DankPopout {
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 310
|
||||
width: parent.width - 290
|
||||
height: 1
|
||||
}
|
||||
|
||||
@@ -452,7 +489,8 @@ DankPopout {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
appList.itemClicked(index, model)
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y)
|
||||
var globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
|
||||
appList.itemRightClicked(index, model, panelPos.x, panelPos.y)
|
||||
}
|
||||
}
|
||||
@@ -613,7 +651,8 @@ DankPopout {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
appGrid.itemClicked(index, model)
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y)
|
||||
var globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
|
||||
appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y)
|
||||
}
|
||||
}
|
||||
@@ -626,68 +665,68 @@ DankPopout {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Popup {
|
||||
id: contextMenu
|
||||
|
||||
property var currentApp: null
|
||||
property bool menuVisible: false
|
||||
|
||||
readonly property string appId: (currentApp && currentApp.desktopEntry) ? (currentApp.desktopEntry.id || currentApp.desktopEntry.execString || "") : ""
|
||||
readonly property bool isPinned: appId && SessionData.isPinnedApp(appId)
|
||||
|
||||
function show(x, y, app) {
|
||||
currentApp = app
|
||||
|
||||
const menuWidth = 180
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
|
||||
let finalX = x + 8
|
||||
let finalY = y + 8
|
||||
|
||||
if (finalX + menuWidth > appDrawerPopout.popupWidth) {
|
||||
finalX = x - menuWidth - 8
|
||||
}
|
||||
|
||||
if (finalY + menuHeight > appDrawerPopout.popupHeight) {
|
||||
finalY = y - menuHeight - 8
|
||||
}
|
||||
|
||||
finalX = Math.max(8, Math.min(finalX, appDrawerPopout.popupWidth - menuWidth - 8))
|
||||
finalY = Math.max(8, Math.min(finalY, appDrawerPopout.popupHeight - menuHeight - 8))
|
||||
|
||||
contextMenu.x = finalX
|
||||
contextMenu.y = finalY
|
||||
contextMenu.visible = true
|
||||
contextMenu.menuVisible = true
|
||||
contextMenu.x = x + 4
|
||||
contextMenu.y = y + 4
|
||||
contextMenu.open()
|
||||
}
|
||||
|
||||
function close() {
|
||||
contextMenu.menuVisible = false
|
||||
Qt.callLater(() => {
|
||||
contextMenu.visible = false
|
||||
})
|
||||
function hide() {
|
||||
contextMenu.close()
|
||||
}
|
||||
|
||||
visible: false
|
||||
width: 180
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
z: 1000
|
||||
opacity: menuVisible ? 1 : 0
|
||||
scale: menuVisible ? 1 : 0.85
|
||||
padding: 0
|
||||
closePolicy: Popup.CloseOnPressOutside
|
||||
modal: false
|
||||
dim: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: parent.z - 1
|
||||
background: Rectangle {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -742,7 +781,7 @@ DankPopout {
|
||||
} else {
|
||||
SessionData.addPinnedApp(contextMenu.appId)
|
||||
}
|
||||
contextMenu.close()
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -761,6 +800,76 @@ DankPopout {
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Theme.iconSize - 2
|
||||
height: Theme.iconSize - 2
|
||||
visible: modelData.icon && modelData.icon !== ""
|
||||
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData && contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
|
||||
SessionService.launchDesktopAction(contextMenu.currentApp.desktopEntry, modelData)
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions && contextMenu.currentApp.desktopEntry.actions.length > 0
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
@@ -782,7 +891,7 @@ DankPopout {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Launch"
|
||||
text: qsTr("Launch")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
@@ -800,23 +909,70 @@ DankPopout {
|
||||
if (contextMenu.currentApp)
|
||||
appLauncher.launchApp(contextMenu.currentApp)
|
||||
|
||||
contextMenu.close()
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
Rectangle {
|
||||
visible: SessionService.hasPrimeRun
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
Rectangle {
|
||||
visible: SessionService.hasPrimeRun
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Launch on dGPU")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: primeRunMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (contextMenu.currentApp && contextMenu.currentApp.desktopEntry) {
|
||||
SessionService.launchDesktopEntry(contextMenu.currentApp.desktopEntry, true)
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -826,7 +982,7 @@ DankPopout {
|
||||
visible: contextMenu.visible
|
||||
z: 999
|
||||
onClicked: {
|
||||
contextMenu.close()
|
||||
contextMenu.hide()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -18,7 +18,6 @@ Item {
|
||||
readonly property color unselectedBorderColor: "transparent"
|
||||
|
||||
function handleCategoryClick(category) {
|
||||
selectedCategory = category
|
||||
categorySelected(category)
|
||||
}
|
||||
|
||||
|
||||
241
Modules/ControlCenter/BuiltinPlugins/VpnWidget.qml
Normal file
241
Modules/ControlCenter/BuiltinPlugins/VpnWidget.qml
Normal file
@@ -0,0 +1,241 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginComponent {
|
||||
id: root
|
||||
|
||||
ccWidgetIcon: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
|
||||
ccWidgetPrimaryText: "VPN"
|
||||
ccWidgetSecondaryText: {
|
||||
if (!VpnService.connected)
|
||||
return "Disconnected"
|
||||
const names = VpnService.activeNames || []
|
||||
if (names.length <= 1)
|
||||
return names[0] || "Connected"
|
||||
return names[0] + " +" + (names.length - 1)
|
||||
}
|
||||
ccWidgetIsActive: VpnService.connected
|
||||
|
||||
onCcWidgetToggled: {
|
||||
if (VpnService.connected) {
|
||||
VpnService.disconnectAllActive()
|
||||
} else if (VpnService.profiles.length > 0) {
|
||||
VpnService.connect(VpnService.profiles[0].uuid)
|
||||
}
|
||||
}
|
||||
|
||||
ccDetailContent: Component {
|
||||
Rectangle {
|
||||
id: detailRoot
|
||||
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
id: detailColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
RowLayout {
|
||||
spacing: Theme.spacingS
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!VpnService.connected)
|
||||
return "Active: None"
|
||||
const names = VpnService.activeNames || []
|
||||
if (names.length <= 1)
|
||||
return "Active: " + (names[0] || "VPN")
|
||||
return "Active: " + names[0] + " +" + (names.length - 1)
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 28
|
||||
radius: 14
|
||||
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
|
||||
visible: VpnService.connected
|
||||
width: 110
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "link_off"
|
||||
size: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Disconnect")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: discAllArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: VpnService.disconnectAllActive()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1
|
||||
width: parent.width
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
width: parent.width
|
||||
height: 160
|
||||
contentHeight: listCol.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: listCol
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: VpnService.profiles.length === 0 ? 120 : 0
|
||||
visible: height > 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "playlist_remove"
|
||||
size: 36
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("No VPN profiles found")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Add a VPN in NetworkManager")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: VpnService.profiles
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
|
||||
width: parent ? parent.width : 300
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: rowArea.containsMouse ? Theme.primaryHoverLight : (VpnService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
|
||||
border.width: VpnService.isActiveUuid(modelData.uuid) ? 2 : 1
|
||||
border.color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
|
||||
size: Theme.iconSize - 4
|
||||
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
StyledText {
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (modelData.type === "wireguard")
|
||||
return "WireGuard"
|
||||
const svc = modelData.serviceType || ""
|
||||
if (svc.indexOf("openvpn") !== -1)
|
||||
return "OpenVPN"
|
||||
if (svc.indexOf("wireguard") !== -1)
|
||||
return "WireGuard (plugin)"
|
||||
if (svc.indexOf("openconnect") !== -1)
|
||||
return "OpenConnect"
|
||||
if (svc.indexOf("fortissl") !== -1 || svc.indexOf("forti") !== -1)
|
||||
return "Fortinet"
|
||||
if (svc.indexOf("strongswan") !== -1)
|
||||
return "IPsec (strongSwan)"
|
||||
if (svc.indexOf("libreswan") !== -1)
|
||||
return "IPsec (Libreswan)"
|
||||
if (svc.indexOf("l2tp") !== -1)
|
||||
return "L2TP/IPsec"
|
||||
if (svc.indexOf("pptp") !== -1)
|
||||
return "PPTP"
|
||||
if (svc.indexOf("vpnc") !== -1)
|
||||
return "Cisco (vpnc)"
|
||||
if (svc.indexOf("sstp") !== -1)
|
||||
return "SSTP"
|
||||
if (svc)
|
||||
return svc.split('.').pop()
|
||||
return "VPN"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rowArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: VpnService.toggle(modelData.uuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ Rectangle {
|
||||
DankIcon {
|
||||
name: root.iconName
|
||||
size: Theme.iconSize
|
||||
color: isActive ? Theme.primaryContainer : Theme.primary
|
||||
color: isActive ? Theme.primaryText : Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
text: root.text
|
||||
style: Typography.Style.Body
|
||||
color: isActive ? Theme.primaryContainer : Theme.surfaceText
|
||||
color: isActive ? Theme.primaryText : Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
@@ -86,7 +86,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
text: root.secondaryText
|
||||
style: Typography.Style.Caption
|
||||
color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
|
||||
color: isActive ? Theme.primaryText : Theme.surfaceVariantText
|
||||
visible: text.length > 0
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
|
||||
@@ -1,38 +1,104 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.ControlCenter.Details
|
||||
import qs.Modules.ControlCenter.Models
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string expandedSection: ""
|
||||
property var expandedWidgetData: null
|
||||
property var bluetoothCodecSelector: null
|
||||
|
||||
property var pluginDetailInstance: null
|
||||
property var widgetModel: null
|
||||
|
||||
Loader {
|
||||
id: pluginDetailLoader
|
||||
width: parent.width
|
||||
height: 250
|
||||
y: Theme.spacingS
|
||||
active: parent.height > 0
|
||||
property string sectionKey: root.expandedSection
|
||||
sourceComponent: {
|
||||
switch (root.expandedSection) {
|
||||
case "network":
|
||||
case "wifi": return networkDetailComponent
|
||||
case "bluetooth": return bluetoothDetailComponent
|
||||
case "audioOutput": return audioOutputDetailComponent
|
||||
case "audioInput": return audioInputDetailComponent
|
||||
case "battery": return batteryDetailComponent
|
||||
default:
|
||||
if (root.expandedSection.startsWith("diskUsage_")) {
|
||||
return diskUsageDetailComponent
|
||||
}
|
||||
return null
|
||||
active: false
|
||||
sourceComponent: null
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: coreDetailLoader
|
||||
width: parent.width
|
||||
height: 250
|
||||
y: Theme.spacingS
|
||||
active: false
|
||||
sourceComponent: null
|
||||
}
|
||||
|
||||
onExpandedSectionChanged: {
|
||||
if (pluginDetailInstance) {
|
||||
pluginDetailInstance.destroy()
|
||||
pluginDetailInstance = null
|
||||
}
|
||||
pluginDetailLoader.active = false
|
||||
coreDetailLoader.active = false
|
||||
|
||||
if (!root.expandedSection) {
|
||||
return
|
||||
}
|
||||
|
||||
if (root.expandedSection.startsWith("builtin_")) {
|
||||
const builtinId = root.expandedSection
|
||||
let builtinInstance = null
|
||||
|
||||
if (builtinId === "builtin_vpn" && widgetModel?.vpnBuiltinInstance) {
|
||||
builtinInstance = widgetModel.vpnBuiltinInstance
|
||||
}
|
||||
|
||||
if (!builtinInstance || !builtinInstance.ccDetailContent) {
|
||||
return
|
||||
}
|
||||
|
||||
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent
|
||||
pluginDetailLoader.active = parent.height > 0
|
||||
return
|
||||
}
|
||||
onSectionKeyChanged: {
|
||||
active = false
|
||||
active = true
|
||||
|
||||
if (root.expandedSection.startsWith("plugin_")) {
|
||||
const pluginId = root.expandedSection.replace("plugin_", "")
|
||||
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
|
||||
if (!pluginComponent) {
|
||||
return
|
||||
}
|
||||
|
||||
pluginDetailInstance = pluginComponent.createObject(null)
|
||||
if (!pluginDetailInstance || !pluginDetailInstance.ccDetailContent) {
|
||||
if (pluginDetailInstance) {
|
||||
pluginDetailInstance.destroy()
|
||||
pluginDetailInstance = null
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
pluginDetailLoader.sourceComponent = pluginDetailInstance.ccDetailContent
|
||||
pluginDetailLoader.active = parent.height > 0
|
||||
return
|
||||
}
|
||||
|
||||
if (root.expandedSection.startsWith("diskUsage_")) {
|
||||
coreDetailLoader.sourceComponent = diskUsageDetailComponent
|
||||
coreDetailLoader.active = parent.height > 0
|
||||
return
|
||||
}
|
||||
|
||||
switch (root.expandedSection) {
|
||||
case "network":
|
||||
case "wifi": coreDetailLoader.sourceComponent = networkDetailComponent; break
|
||||
case "bluetooth": coreDetailLoader.sourceComponent = bluetoothDetailComponent; break
|
||||
case "audioOutput": coreDetailLoader.sourceComponent = audioOutputDetailComponent; break
|
||||
case "audioInput": coreDetailLoader.sourceComponent = audioInputDetailComponent; break
|
||||
case "battery": coreDetailLoader.sourceComponent = batteryDetailComponent; break
|
||||
default: return
|
||||
}
|
||||
|
||||
coreDetailLoader.active = parent.height > 0
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -42,7 +108,17 @@ Item {
|
||||
|
||||
Component {
|
||||
id: bluetoothDetailComponent
|
||||
BluetoothDetail {}
|
||||
BluetoothDetail {
|
||||
id: bluetoothDetail
|
||||
onShowCodecSelector: function(device) {
|
||||
if (root.bluetoothCodecSelector) {
|
||||
root.bluetoothCodecSelector.show(device)
|
||||
root.bluetoothCodecSelector.codecSelected.connect(function(deviceAddress, codecName) {
|
||||
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
|
||||
@@ -69,8 +69,23 @@ Item {
|
||||
Component {
|
||||
id: diskUsageDetailComponent
|
||||
DiskUsageDetail {
|
||||
currentMountPath: root.expandedWidgetData?.currentMountPath || "/"
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,9 @@ import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
import qs.Modules.ControlCenter.Components
|
||||
import "../utils/layout.js" as LayoutUtils
|
||||
|
||||
Item {
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property bool editMode: false
|
||||
@@ -12,72 +13,32 @@ Item {
|
||||
property int expandedWidgetIndex: -1
|
||||
property var model: null
|
||||
property var expandedWidgetData: null
|
||||
property var bluetoothCodecSelector: null
|
||||
property bool darkModeTransitionPending: false
|
||||
|
||||
signal expandClicked(var widgetData, int globalIndex)
|
||||
signal removeWidget(int index)
|
||||
signal moveWidget(int fromIndex, int toIndex)
|
||||
signal toggleWidgetSize(int index)
|
||||
|
||||
readonly property int gridColumns: 4
|
||||
readonly property real cellWidth: (width - (gridSpacing + 1) * (gridColumns - 1)) / gridColumns
|
||||
readonly property real cellHeight: 60
|
||||
readonly property real gridSpacing: 4
|
||||
spacing: editMode ? Theme.spacingL : Theme.spacingS
|
||||
|
||||
height: {
|
||||
const dummy = [SettingsData.controlCenterWidgets?.length, widgetPositions.length]
|
||||
return calculateGridHeight() + (detailHost.active ? detailHost.height + Theme.spacingL : 0)
|
||||
property var currentRowWidgets: []
|
||||
property real currentRowWidth: 0
|
||||
property int expandedRowIndex: -1
|
||||
property var colorPickerModal: null
|
||||
|
||||
function calculateRowsAndWidgets() {
|
||||
return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex)
|
||||
}
|
||||
|
||||
function calculateGridHeight() {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
if (widgets.length === 0)
|
||||
return 0
|
||||
|
||||
let rows = []
|
||||
let currentRow = []
|
||||
let currentWidth = 0
|
||||
const spacing = gridSpacing
|
||||
const baseWidth = width
|
||||
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
const widget = widgets[i]
|
||||
const widgetWidth = widget.width || 50
|
||||
|
||||
let itemWidth
|
||||
if (widgetWidth <= 25) {
|
||||
itemWidth = (baseWidth - spacing * 3) / 4
|
||||
} else if (widgetWidth <= 50) {
|
||||
itemWidth = (baseWidth - spacing) / 2
|
||||
} else if (widgetWidth <= 75) {
|
||||
itemWidth = (baseWidth - spacing * 2) * 0.75
|
||||
} else {
|
||||
itemWidth = baseWidth
|
||||
}
|
||||
|
||||
if (currentRow.length > 0 && (currentWidth + spacing + itemWidth > baseWidth)) {
|
||||
rows.push([...currentRow])
|
||||
currentRow = [widget]
|
||||
currentWidth = itemWidth
|
||||
} else {
|
||||
currentRow.push(widget)
|
||||
currentWidth += (currentRow.length > 1 ? spacing : 0) + itemWidth
|
||||
}
|
||||
}
|
||||
|
||||
if (currentRow.length > 0) {
|
||||
rows.push(currentRow)
|
||||
}
|
||||
|
||||
return rows.length * cellHeight + (rows.length > 0 ? (rows.length - 1) * spacing : 0)
|
||||
property var layoutResult: {
|
||||
const dummy = [expandedSection, expandedWidgetIndex, model?.controlCenterWidgets]
|
||||
return calculateRowsAndWidgets()
|
||||
}
|
||||
|
||||
DragDropDetailHost {
|
||||
id: detailHost
|
||||
y: calculateGridHeight()
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
expandedSection: root.expandedSection
|
||||
expandedWidgetData: root.expandedWidgetData
|
||||
onLayoutResultChanged: {
|
||||
expandedRowIndex = layoutResult.expandedRowIndex
|
||||
}
|
||||
|
||||
function moveToTop(item) {
|
||||
@@ -92,131 +53,125 @@ Item {
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: widgetRepeater
|
||||
model: SettingsData.controlCenterWidgets || []
|
||||
model: root.layoutResult.rows
|
||||
|
||||
DragDropWidgetWrapper {
|
||||
id: widgetWrapper
|
||||
|
||||
editMode: root.editMode
|
||||
widgetData: modelData
|
||||
widgetIndex: index
|
||||
gridCellWidth: root.cellWidth
|
||||
gridCellHeight: root.cellHeight
|
||||
gridColumns: root.gridColumns
|
||||
gridLayout: root
|
||||
isSlider: {
|
||||
const id = modelData.id || ""
|
||||
return id === "volumeSlider" || id === "brightnessSlider" || id === "inputVolumeSlider"
|
||||
Column {
|
||||
width: root.width
|
||||
spacing: 0
|
||||
property int rowIndex: index
|
||||
property var rowWidgets: modelData
|
||||
property bool isSliderOnlyRow: {
|
||||
const widgets = rowWidgets || []
|
||||
if (widgets.length === 0)
|
||||
return false
|
||||
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
|
||||
}
|
||||
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
|
||||
bottomPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
|
||||
|
||||
widgetComponent: {
|
||||
const id = modelData.id || ""
|
||||
if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
|
||||
return compoundPillComponent
|
||||
} else if (id === "volumeSlider") {
|
||||
return audioSliderComponent
|
||||
} else if (id === "brightnessSlider") {
|
||||
return brightnessSliderComponent
|
||||
} else if (id === "inputVolumeSlider") {
|
||||
return inputAudioSliderComponent
|
||||
} else if (id === "battery") {
|
||||
const widgetWidth = modelData.width || 50
|
||||
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent
|
||||
} else if (id === "diskUsage") {
|
||||
return diskUsagePillComponent
|
||||
} else {
|
||||
const widgetWidth = modelData.width || 50
|
||||
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
|
||||
Flow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: rowWidgets || []
|
||||
|
||||
DragDropWidgetWrapper {
|
||||
widgetData: modelData
|
||||
property int globalWidgetIndex: {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
if (widgets[i].id === modelData.id) {
|
||||
if (modelData.id === "diskUsage") {
|
||||
if (widgets[i].instanceId === modelData.instanceId) {
|
||||
return i
|
||||
}
|
||||
} else {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
property int widgetWidth: modelData.width || 50
|
||||
width: {
|
||||
const baseWidth = root.width
|
||||
const spacing = Theme.spacingS
|
||||
if (widgetWidth <= 25) {
|
||||
return (baseWidth - spacing * 3) / 4
|
||||
} else if (widgetWidth <= 50) {
|
||||
return (baseWidth - spacing) / 2
|
||||
} else if (widgetWidth <= 75) {
|
||||
return (baseWidth - spacing * 2) * 0.75
|
||||
} else {
|
||||
return baseWidth
|
||||
}
|
||||
}
|
||||
height: isSliderOnlyRow ? 48 : 60
|
||||
|
||||
editMode: root.editMode
|
||||
widgetIndex: globalWidgetIndex
|
||||
gridCellWidth: width
|
||||
gridCellHeight: height
|
||||
gridColumns: 4
|
||||
gridLayout: root
|
||||
isSlider: {
|
||||
const id = modelData.id || ""
|
||||
return id === "volumeSlider" || id === "brightnessSlider" || id === "inputVolumeSlider"
|
||||
}
|
||||
|
||||
widgetComponent: {
|
||||
const id = modelData.id || ""
|
||||
if (id.startsWith("builtin_")) {
|
||||
return builtinPluginWidgetComponent
|
||||
} else if (id.startsWith("plugin_")) {
|
||||
return pluginWidgetComponent
|
||||
} else if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
|
||||
return compoundPillComponent
|
||||
} else if (id === "volumeSlider") {
|
||||
return audioSliderComponent
|
||||
} else if (id === "brightnessSlider") {
|
||||
return brightnessSliderComponent
|
||||
} else if (id === "inputVolumeSlider") {
|
||||
return inputAudioSliderComponent
|
||||
} else if (id === "battery") {
|
||||
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent
|
||||
} else if (id === "diskUsage") {
|
||||
return diskUsagePillComponent
|
||||
} else if (id === "colorPicker") {
|
||||
return colorPickerPillComponent
|
||||
} else {
|
||||
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
|
||||
}
|
||||
}
|
||||
|
||||
onWidgetMoved: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
onRemoveWidget: index => root.removeWidget(index)
|
||||
onToggleWidgetSize: index => root.toggleWidgetSize(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x: calculateWidgetX(index)
|
||||
y: calculateWidgetY(index)
|
||||
DetailHost {
|
||||
width: parent.width
|
||||
height: active ? (250 + Theme.spacingS) : 0
|
||||
property bool active: {
|
||||
if (root.expandedSection === "")
|
||||
return false
|
||||
|
||||
onWidgetMoved: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
onRemoveWidget: index => root.removeWidget(index)
|
||||
onToggleWidgetSize: index => root.toggleWidgetSize(index)
|
||||
if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) {
|
||||
const expandedInstanceId = root.expandedWidgetData.instanceId
|
||||
return rowWidgets.some(w => w.id === "diskUsage" && w.instanceId === expandedInstanceId)
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
enabled: !editMode
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Easing.OutCubic
|
||||
return rowIndex === root.expandedRowIndex
|
||||
}
|
||||
visible: active
|
||||
expandedSection: root.expandedSection
|
||||
expandedWidgetData: root.expandedWidgetData
|
||||
bluetoothCodecSelector: root.bluetoothCodecSelector
|
||||
widgetModel: root.model
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: !editMode
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property var widgetPositions: calculateAllWidgetPositions()
|
||||
|
||||
function calculateAllWidgetPositions() {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
let positions = []
|
||||
let currentX = 0
|
||||
let currentY = 0
|
||||
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
const widget = widgets[i]
|
||||
const widgetWidth = widget.width || 50
|
||||
let cellsNeeded = 1
|
||||
|
||||
if (widgetWidth <= 25)
|
||||
cellsNeeded = 1
|
||||
else if (widgetWidth <= 50)
|
||||
cellsNeeded = 2
|
||||
else if (widgetWidth <= 75)
|
||||
cellsNeeded = 3
|
||||
else
|
||||
cellsNeeded = 4
|
||||
|
||||
if (currentX + cellsNeeded > gridColumns) {
|
||||
currentX = 0
|
||||
currentY++
|
||||
}
|
||||
|
||||
const horizontalSpacing = gridSpacing
|
||||
positions[i] = {
|
||||
"x": currentX * cellWidth + (currentX > 0 ? currentX * horizontalSpacing : 0),
|
||||
"y": currentY * cellHeight + (currentY > 0 ? currentY * gridSpacing : 0),
|
||||
"cellsUsed": cellsNeeded
|
||||
}
|
||||
|
||||
currentX += cellsNeeded
|
||||
|
||||
if (currentX >= gridColumns) {
|
||||
currentX = 0
|
||||
currentY++
|
||||
}
|
||||
}
|
||||
|
||||
return positions
|
||||
}
|
||||
|
||||
function calculateWidgetX(widgetIndex) {
|
||||
if (widgetIndex < 0 || widgetIndex >= widgetPositions.length)
|
||||
return 0
|
||||
return widgetPositions[widgetIndex].x
|
||||
}
|
||||
|
||||
function calculateWidgetY(widgetIndex) {
|
||||
if (widgetIndex < 0 || widgetIndex >= widgetPositions.length)
|
||||
return 0
|
||||
return widgetPositions[widgetIndex].y
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onControlCenterWidgetsChanged() {
|
||||
widgetPositions = calculateAllWidgetPositions()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +182,7 @@ Item {
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
width: parent.width
|
||||
height: cellHeight
|
||||
height: 60
|
||||
iconName: {
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi":
|
||||
@@ -395,7 +350,7 @@ Item {
|
||||
return false
|
||||
}
|
||||
}
|
||||
enabled: !root.editMode && (widgetDef?.enabled ?? true)
|
||||
enabled: widgetDef?.enabled ?? true
|
||||
onToggled: {
|
||||
if (root.editMode)
|
||||
return
|
||||
@@ -431,8 +386,9 @@ Item {
|
||||
}
|
||||
}
|
||||
onExpandClicked: {
|
||||
if (!root.editMode)
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
if (root.editMode)
|
||||
return
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
onWheelEvent: function (wheelEvent) {
|
||||
if (root.editMode)
|
||||
@@ -471,43 +427,67 @@ Item {
|
||||
|
||||
Component {
|
||||
id: audioSliderComponent
|
||||
AudioSliderRow {
|
||||
Item {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 14
|
||||
enabled: !root.editMode
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
height: 16
|
||||
|
||||
AudioSliderRow {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: brightnessSliderComponent
|
||||
BrightnessSliderRow {
|
||||
Item {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 14
|
||||
enabled: !root.editMode
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
height: 16
|
||||
|
||||
BrightnessSliderRow {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: inputAudioSliderComponent
|
||||
InputAudioSliderRow {
|
||||
Item {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 14
|
||||
enabled: !root.editMode
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
height: 16
|
||||
|
||||
InputAudioSliderRow {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: batteryPillComponent
|
||||
BatteryPill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: cellHeight
|
||||
enabled: !root.editMode
|
||||
height: 60
|
||||
|
||||
onExpandClicked: {
|
||||
if (!root.editMode)
|
||||
root.expandClicked(parent.widgetData, parent.widgetIndex)
|
||||
if (!root.editMode) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,12 +495,15 @@ Item {
|
||||
Component {
|
||||
id: smallBatteryComponent
|
||||
SmallBatteryButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 48
|
||||
enabled: !root.editMode
|
||||
|
||||
onClicked: {
|
||||
if (!root.editMode)
|
||||
root.expandClicked(parent.widgetData, parent.widgetIndex)
|
||||
if (!root.editMode) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,9 +513,8 @@ Item {
|
||||
ToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
width: parent.width
|
||||
height: cellHeight
|
||||
height: 60
|
||||
|
||||
iconName: {
|
||||
switch (widgetData.id || "") {
|
||||
@@ -545,7 +527,7 @@ Item {
|
||||
case "idleInhibitor":
|
||||
return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
default:
|
||||
return widgetDef?.icon || "help"
|
||||
return "help"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,18 +542,25 @@ Item {
|
||||
case "idleInhibitor":
|
||||
return SessionService.idleInhibited ? "Keeping Awake" : "Keep Awake"
|
||||
default:
|
||||
return widgetDef?.text || "Unknown"
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
|
||||
iconRotation: {
|
||||
if (widgetData.id !== "darkMode")
|
||||
return 0
|
||||
if (darkModeTransitionPending) {
|
||||
return SessionData.isLightMode ? 180 : 0
|
||||
}
|
||||
return SessionData.isLightMode ? 180 : 0
|
||||
}
|
||||
|
||||
isActive: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode":
|
||||
return DisplayService.nightModeEnabled || false
|
||||
case "darkMode":
|
||||
return !SessionData.isLightMode
|
||||
return SessionData.isLightMode
|
||||
case "doNotDisturb":
|
||||
return SessionData.doNotDisturb || false
|
||||
case "idleInhibitor":
|
||||
@@ -581,7 +570,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
enabled: !root.editMode && (widgetDef?.enabled ?? true)
|
||||
enabled: !root.editMode
|
||||
|
||||
onClicked: {
|
||||
if (root.editMode)
|
||||
@@ -595,7 +584,8 @@ Item {
|
||||
}
|
||||
case "darkMode":
|
||||
{
|
||||
Theme.toggleLightMode()
|
||||
Theme.screenTransition()
|
||||
Theme.setLightMode(!SessionData.isLightMode)
|
||||
break
|
||||
}
|
||||
case "doNotDisturb":
|
||||
@@ -618,7 +608,6 @@ Item {
|
||||
SmallToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
width: parent.width
|
||||
height: 48
|
||||
|
||||
@@ -633,18 +622,25 @@ Item {
|
||||
case "idleInhibitor":
|
||||
return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
default:
|
||||
return widgetDef?.icon || "help"
|
||||
return "help"
|
||||
}
|
||||
}
|
||||
|
||||
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
|
||||
iconRotation: {
|
||||
if (widgetData.id !== "darkMode")
|
||||
return 0
|
||||
if (darkModeTransitionPending) {
|
||||
return SessionData.isLightMode ? 180 : 0
|
||||
}
|
||||
return SessionData.isLightMode ? 180 : 0
|
||||
}
|
||||
|
||||
isActive: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode":
|
||||
return DisplayService.nightModeEnabled || false
|
||||
case "darkMode":
|
||||
return !SessionData.isLightMode
|
||||
return SessionData.isLightMode
|
||||
case "doNotDisturb":
|
||||
return SessionData.doNotDisturb || false
|
||||
case "idleInhibitor":
|
||||
@@ -654,7 +650,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
enabled: !root.editMode && (widgetDef?.enabled ?? true)
|
||||
enabled: !root.editMode
|
||||
|
||||
onClicked: {
|
||||
if (root.editMode)
|
||||
@@ -668,7 +664,8 @@ Item {
|
||||
}
|
||||
case "darkMode":
|
||||
{
|
||||
Theme.toggleLightMode()
|
||||
Theme.screenTransition()
|
||||
Theme.setLightMode(!SessionData.isLightMode)
|
||||
break
|
||||
}
|
||||
case "doNotDisturb":
|
||||
@@ -689,14 +686,273 @@ Item {
|
||||
Component {
|
||||
id: diskUsagePillComponent
|
||||
DiskUsagePill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: cellHeight
|
||||
enabled: !root.editMode
|
||||
mountPath: parent.widgetData?.mountPath || "/"
|
||||
instanceId: parent.widgetData?.instanceId || ""
|
||||
height: 60
|
||||
|
||||
mountPath: widgetData.mountPath || "/"
|
||||
instanceId: widgetData.instanceId || ""
|
||||
|
||||
onExpandClicked: {
|
||||
if (!root.editMode)
|
||||
root.expandClicked(parent.widgetData, parent.widgetIndex)
|
||||
if (!root.editMode) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: colorPickerPillComponent
|
||||
ColorPickerPill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
colorPickerModal: root.colorPickerModal
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: builtinPluginWidgetComponent
|
||||
Loader {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property int widgetWidth: widgetData.width || 50
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
property var builtinInstance: {
|
||||
const id = widgetData.id || ""
|
||||
if (id === "builtin_vpn") {
|
||||
return root.model?.vpnBuiltinInstance
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
sourceComponent: {
|
||||
if (!builtinInstance)
|
||||
return null
|
||||
|
||||
const hasDetail = builtinInstance.ccDetailContent !== null
|
||||
|
||||
if (widgetWidth <= 25) {
|
||||
return builtinSmallToggleComponent
|
||||
} else if (hasDetail) {
|
||||
return builtinCompoundPillComponent
|
||||
} else {
|
||||
return builtinToggleComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: builtinCompoundPillComponent
|
||||
CompoundPill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var builtinInstance: parent.builtinInstance
|
||||
|
||||
iconName: builtinInstance?.ccWidgetIcon || "extension"
|
||||
primaryText: builtinInstance?.ccWidgetPrimaryText || "Built-in"
|
||||
secondaryText: builtinInstance?.ccWidgetSecondaryText || ""
|
||||
isActive: builtinInstance?.ccWidgetIsActive || false
|
||||
|
||||
onToggled: {
|
||||
if (root.editMode)
|
||||
return
|
||||
if (builtinInstance) {
|
||||
builtinInstance.ccWidgetToggled()
|
||||
}
|
||||
}
|
||||
|
||||
onExpandClicked: {
|
||||
if (root.editMode)
|
||||
return
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: builtinToggleComponent
|
||||
ToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var builtinInstance: parent.builtinInstance
|
||||
|
||||
iconName: builtinInstance?.ccWidgetIcon || "extension"
|
||||
text: builtinInstance?.ccWidgetPrimaryText || "Built-in"
|
||||
isActive: builtinInstance?.ccWidgetIsActive || false
|
||||
enabled: !root.editMode
|
||||
|
||||
onClicked: {
|
||||
if (root.editMode)
|
||||
return
|
||||
if (builtinInstance) {
|
||||
builtinInstance.ccWidgetToggled()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: builtinSmallToggleComponent
|
||||
SmallToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var builtinInstance: parent.builtinInstance
|
||||
|
||||
iconName: builtinInstance?.ccWidgetIcon || "extension"
|
||||
isActive: builtinInstance?.ccWidgetIsActive || false
|
||||
enabled: !root.editMode
|
||||
|
||||
onClicked: {
|
||||
if (root.editMode)
|
||||
return
|
||||
if (builtinInstance) {
|
||||
builtinInstance.ccWidgetToggled()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pluginWidgetComponent
|
||||
Loader {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property int widgetWidth: widgetData.width || 50
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
property var pluginInstance: null
|
||||
property string pluginId: widgetData.id?.replace("plugin_", "") || ""
|
||||
|
||||
sourceComponent: {
|
||||
if (!pluginInstance)
|
||||
return null
|
||||
|
||||
const hasDetail = pluginInstance.ccDetailContent !== null
|
||||
|
||||
if (widgetWidth <= 25) {
|
||||
return pluginSmallToggleComponent
|
||||
} else if (hasDetail) {
|
||||
return pluginCompoundPillComponent
|
||||
} else {
|
||||
return pluginToggleComponent
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(() => {
|
||||
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
|
||||
if (pluginComponent) {
|
||||
const instance = pluginComponent.createObject(null, {
|
||||
"pluginId": pluginId,
|
||||
"pluginService": PluginService,
|
||||
"visible": false,
|
||||
"width": 0,
|
||||
"height": 0
|
||||
})
|
||||
if (instance) {
|
||||
pluginInstance = instance
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PluginService
|
||||
function onPluginDataChanged(changedPluginId) {
|
||||
if (changedPluginId === pluginId && pluginInstance) {
|
||||
pluginInstance.loadPluginData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (pluginInstance) {
|
||||
pluginInstance.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pluginCompoundPillComponent
|
||||
CompoundPill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var pluginInstance: parent.pluginInstance
|
||||
|
||||
iconName: pluginInstance?.ccWidgetIcon || "extension"
|
||||
primaryText: pluginInstance?.ccWidgetPrimaryText || "Plugin"
|
||||
secondaryText: pluginInstance?.ccWidgetSecondaryText || ""
|
||||
isActive: pluginInstance?.ccWidgetIsActive || false
|
||||
|
||||
onToggled: {
|
||||
if (root.editMode)
|
||||
return
|
||||
if (pluginInstance) {
|
||||
pluginInstance.ccWidgetToggled()
|
||||
}
|
||||
}
|
||||
|
||||
onExpandClicked: {
|
||||
if (root.editMode)
|
||||
return
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pluginToggleComponent
|
||||
ToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var pluginInstance: parent.pluginInstance
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
|
||||
iconName: pluginInstance?.ccWidgetIcon || widgetDef?.icon || "extension"
|
||||
text: pluginInstance?.ccWidgetPrimaryText || widgetDef?.text || "Plugin"
|
||||
secondaryText: pluginInstance?.ccWidgetSecondaryText || ""
|
||||
isActive: pluginInstance?.ccWidgetIsActive || false
|
||||
enabled: !root.editMode
|
||||
|
||||
onClicked: {
|
||||
if (root.editMode)
|
||||
return
|
||||
if (pluginInstance) {
|
||||
pluginInstance.ccWidgetToggled()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pluginSmallToggleComponent
|
||||
SmallToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var pluginInstance: parent.pluginInstance
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
|
||||
iconName: pluginInstance?.ccWidgetIcon || widgetDef?.icon || "extension"
|
||||
isActive: pluginInstance?.ccWidgetIsActive || false
|
||||
enabled: !root.editMode
|
||||
|
||||
onClicked: {
|
||||
if (root.editMode)
|
||||
return
|
||||
if (pluginInstance && pluginInstance.ccDetailContent) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
} else if (pluginInstance) {
|
||||
pluginInstance.ccWidgetToggled()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ Item {
|
||||
property int gridColumns: 4
|
||||
property var gridLayout: null
|
||||
|
||||
z: dragArea.drag.active ? 10000 : 1
|
||||
|
||||
signal widgetMoved(int fromIndex, int toIndex)
|
||||
signal removeWidget(int index)
|
||||
signal toggleWidgetSize(int index)
|
||||
@@ -27,7 +29,7 @@ Item {
|
||||
else if (widgetWidth <= 75) return gridCellWidth * 3
|
||||
else return gridCellWidth * 4
|
||||
}
|
||||
height: gridCellHeight
|
||||
height: isSlider ? 16 : gridCellHeight
|
||||
|
||||
Rectangle {
|
||||
id: dragIndicator
|
||||
@@ -37,7 +39,7 @@ Item {
|
||||
border.width: dragArea.drag.active ? 2 : 0
|
||||
radius: Theme.cornerRadius
|
||||
opacity: dragArea.drag.active ? 0.8 : 1.0
|
||||
z: dragArea.drag.active ? 1000 : 1
|
||||
z: dragArea.drag.active ? 10000 : 1
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation { duration: 150 }
|
||||
@@ -56,13 +58,14 @@ Item {
|
||||
property int globalWidgetIndex: root.widgetIndex
|
||||
property int widgetWidth: root.widgetData?.width || 50
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: editModeBlocker
|
||||
anchors.fill: parent
|
||||
enabled: root.editMode
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onPressed: mouse.accepted = true
|
||||
onWheel: wheel.accepted = true
|
||||
onPressed: function(mouse) { mouse.accepted = true }
|
||||
onWheel: function(wheel) { wheel.accepted = true }
|
||||
z: 100
|
||||
}
|
||||
}
|
||||
@@ -71,23 +74,23 @@ Item {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
enabled: editMode
|
||||
cursorShape: editMode ? Qt.OpenHandCursor : Qt.ArrowCursor
|
||||
cursorShape: editMode ? Qt.OpenHandCursor : Qt.PointingHandCursor
|
||||
drag.target: editMode ? root : null
|
||||
drag.axis: Drag.XAndYAxis
|
||||
drag.smoothed: true
|
||||
|
||||
onPressed: {
|
||||
onPressed: function(mouse) {
|
||||
if (editMode) {
|
||||
cursorShape = Qt.ClosedHandCursor
|
||||
root.z = 1000
|
||||
root.parent.moveToTop(root)
|
||||
if (root.gridLayout && root.gridLayout.moveToTop) {
|
||||
root.gridLayout.moveToTop(root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
onReleased: function(mouse) {
|
||||
if (editMode) {
|
||||
cursorShape = Qt.OpenHandCursor
|
||||
root.z = 1
|
||||
root.snapToGrid()
|
||||
}
|
||||
}
|
||||
@@ -97,6 +100,19 @@ Item {
|
||||
Drag.hotSpot.x: width / 2
|
||||
Drag.hotSpot.y: height / 2
|
||||
|
||||
function swapIndices(i, j) {
|
||||
if (i === j) return;
|
||||
const arr = SettingsData.controlCenterWidgets;
|
||||
if (!arr || i < 0 || j < 0 || i >= arr.length || j >= arr.length) return;
|
||||
|
||||
const copy = arr.slice();
|
||||
const tmp = copy[i];
|
||||
copy[i] = copy[j];
|
||||
copy[j] = tmp;
|
||||
|
||||
SettingsData.setControlCenterWidgets(copy);
|
||||
}
|
||||
|
||||
function snapToGrid() {
|
||||
if (!editMode || !gridLayout) return
|
||||
|
||||
@@ -104,80 +120,90 @@ Item {
|
||||
const cellWidth = gridLayout.width / gridColumns
|
||||
const cellHeight = gridCellHeight + Theme.spacingS
|
||||
|
||||
let targetCol = Math.max(0, Math.round(globalPos.x / cellWidth))
|
||||
let targetRow = Math.max(0, Math.round(globalPos.y / cellHeight))
|
||||
const centerX = globalPos.x + (root.width / 2)
|
||||
const centerY = globalPos.y + (root.height / 2)
|
||||
|
||||
const widgetCells = Math.ceil(root.width / cellWidth)
|
||||
let targetCol = Math.max(0, Math.floor(centerX / cellWidth))
|
||||
let targetRow = Math.max(0, Math.floor(centerY / cellHeight))
|
||||
|
||||
if (targetCol + widgetCells > gridColumns) {
|
||||
targetCol = Math.max(0, gridColumns - widgetCells)
|
||||
}
|
||||
targetCol = Math.min(targetCol, gridColumns - 1)
|
||||
|
||||
const newIndex = findBestInsertionIndex(targetRow, targetCol)
|
||||
|
||||
if (newIndex !== widgetIndex && newIndex >= 0 && newIndex < (SettingsData.controlCenterWidgets?.length || 0)) {
|
||||
widgetMoved(widgetIndex, newIndex)
|
||||
swapIndices(widgetIndex, newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
function findBestInsertionIndex(targetRow, targetCol) {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
if (!widgets.length) return 0
|
||||
const widgets = SettingsData.controlCenterWidgets || [];
|
||||
const n = widgets.length;
|
||||
if (!n || widgetIndex < 0 || widgetIndex >= n) return -1;
|
||||
|
||||
const targetPosition = targetRow * gridColumns + targetCol
|
||||
function spanFor(width) {
|
||||
const w = width ?? 50;
|
||||
if (w <= 25) return 1;
|
||||
if (w <= 50) return 2;
|
||||
if (w <= 75) return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Find the widget position closest to our target
|
||||
let bestIndex = 0
|
||||
let bestDistance = Infinity
|
||||
const cols = gridColumns || 4;
|
||||
|
||||
for (let i = 0; i <= widgets.length; i++) {
|
||||
if (i === widgetIndex) continue
|
||||
let row = 0, col = 0;
|
||||
let draggedOrigKey = null;
|
||||
|
||||
let currentPos = calculatePositionForIndex(i)
|
||||
let distance = Math.abs(currentPos - targetPosition)
|
||||
const pos = [];
|
||||
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance
|
||||
bestIndex = i > widgetIndex ? i - 1 : i
|
||||
for (let i = 0; i < n; i++) {
|
||||
const span = Math.min(spanFor(widgets[i].width), cols);
|
||||
|
||||
if (col + span > cols) {
|
||||
row++;
|
||||
col = 0;
|
||||
}
|
||||
|
||||
const startCol = col;
|
||||
const centerKey = row * cols + (startCol + (span - 1) / 2);
|
||||
|
||||
if (i === widgetIndex) {
|
||||
draggedOrigKey = centerKey;
|
||||
} else {
|
||||
pos.push({ index: i, row, startCol, span, centerKey });
|
||||
}
|
||||
|
||||
col += span;
|
||||
if (col >= cols) {
|
||||
row++;
|
||||
col = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.max(0, Math.min(bestIndex, widgets.length - 1))
|
||||
}
|
||||
if (pos.length === 0) return -1;
|
||||
|
||||
function calculatePositionForIndex(index) {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
let currentX = 0
|
||||
let currentY = 0
|
||||
const centerColCoord = targetCol + 0.5;
|
||||
const targetKey = targetRow * cols + centerColCoord;
|
||||
|
||||
for (let i = 0; i < index && i < widgets.length; i++) {
|
||||
if (i === widgetIndex) continue
|
||||
|
||||
const widget = widgets[i]
|
||||
const widgetWidth = widget.width || 50
|
||||
let cellsNeeded = widgetWidth <= 25 ? 1 : widgetWidth <= 50 ? 2 : widgetWidth <= 75 ? 3 : 4
|
||||
|
||||
if (currentX + cellsNeeded > gridColumns) {
|
||||
currentX = 0
|
||||
currentY++
|
||||
}
|
||||
|
||||
currentX += cellsNeeded
|
||||
if (currentX >= gridColumns) {
|
||||
currentX = 0
|
||||
currentY++
|
||||
for (let k = 0; k < pos.length; k++) {
|
||||
const p = pos[k];
|
||||
if (p.row === targetRow && centerColCoord >= p.startCol && centerColCoord < (p.startCol + p.span)) {
|
||||
return p.index;
|
||||
}
|
||||
}
|
||||
|
||||
return currentY * gridColumns + currentX
|
||||
}
|
||||
let lo = 0, hi = pos.length - 1;
|
||||
if (targetKey <= pos[0].centerKey) return pos[0].index;
|
||||
if (targetKey >= pos[hi].centerKey) return pos[hi].index;
|
||||
|
||||
function getWidgetWidth(widgetWidth) {
|
||||
const cellWidth = gridLayout ? gridLayout.width / gridColumns : gridCellWidth
|
||||
if (widgetWidth <= 25) return cellWidth
|
||||
else if (widgetWidth <= 50) return cellWidth * 2
|
||||
else if (widgetWidth <= 75) return cellWidth * 3
|
||||
else return cellWidth * 4
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
const mk = pos[mid].centerKey;
|
||||
if (targetKey < mk) hi = mid - 1;
|
||||
else if (targetKey > mk) lo = mid + 1;
|
||||
else return pos[mid].index;
|
||||
}
|
||||
const movingUp = (draggedOrigKey != null) ? (targetKey < draggedOrigKey) : false;
|
||||
return (movingUp ? pos[lo].index : pos[hi].index);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -200,11 +226,12 @@ Item {
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: removeWidget(widgetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
PieChartSizeControl {
|
||||
SizeControls {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: -6
|
||||
|
||||
@@ -7,6 +7,7 @@ Row {
|
||||
id: root
|
||||
|
||||
property var availableWidgets: []
|
||||
property Item popoutContent: null
|
||||
|
||||
signal addWidget(string widgetId)
|
||||
signal resetToDefault()
|
||||
@@ -19,7 +20,9 @@ Row {
|
||||
|
||||
Popup {
|
||||
id: addWidgetPopup
|
||||
anchors.centerIn: parent
|
||||
parent: popoutContent
|
||||
x: parent ? Math.round((parent.width - width) / 2) : 0
|
||||
y: parent ? Math.round((parent.height - height) / 2) : 0
|
||||
width: 400
|
||||
height: 300
|
||||
modal: true
|
||||
@@ -52,7 +55,7 @@ Row {
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: "Add Widget"
|
||||
text: qsTr("Add Widget")
|
||||
style: Typography.Style.Subtitle
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -152,7 +155,7 @@ Row {
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: "Add Widget"
|
||||
text: qsTr("Add Widget")
|
||||
style: Typography.Style.Button
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -186,7 +189,7 @@ Row {
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: "Defaults"
|
||||
text: qsTr("Defaults")
|
||||
style: Typography.Style.Button
|
||||
color: Theme.warning
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -220,7 +223,7 @@ Row {
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: "Reset"
|
||||
text: qsTr("Reset")
|
||||
style: Typography.Style.Button
|
||||
color: Theme.error
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -6,10 +6,9 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool powerOptionsExpanded: false
|
||||
property bool editMode: false
|
||||
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
signal powerButtonClicked()
|
||||
signal lockRequested()
|
||||
signal editModeToggled()
|
||||
|
||||
@@ -83,13 +82,11 @@ Rectangle {
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
|
||||
iconName: "power_settings_new"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: root.powerOptionsExpanded ? Theme.primary : Theme.surfaceText
|
||||
iconColor: Theme.surfaceText
|
||||
backgroundColor: "transparent"
|
||||
onClicked: {
|
||||
root.powerOptionsExpanded = !root.powerOptionsExpanded
|
||||
}
|
||||
onClicked: root.powerButtonClicked()
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool expanded: false
|
||||
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
|
||||
implicitHeight: expanded ? 60 : 0
|
||||
height: implicitHeight
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||
Theme.outline.b, 0.08)
|
||||
border.width: root.expanded ? 1 : 0
|
||||
opacity: root.expanded ? 1 : 0
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: SessionService.hibernateSupported ? Theme.spacingS : Theme.spacingL
|
||||
visible: root.expanded
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "logout"
|
||||
text: "Logout"
|
||||
onPressed: root.powerActionRequested("logout", "Logout", "Are you sure you want to logout?")
|
||||
}
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "restart_alt"
|
||||
text: "Restart"
|
||||
onPressed: root.powerActionRequested("reboot", "Restart", "Are you sure you want to restart?")
|
||||
}
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "bedtime"
|
||||
text: "Suspend"
|
||||
onPressed: root.powerActionRequested("suspend", "Suspend", "Are you sure you want to suspend?")
|
||||
}
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "ac_unit"
|
||||
text: "Hibernate"
|
||||
visible: SessionService.hibernateSupported
|
||||
onPressed: root.powerActionRequested("hibernate", "Hibernate", "Are you sure you want to hibernate?")
|
||||
}
|
||||
|
||||
PowerButton {
|
||||
width: SessionService.hibernateSupported ? 85 : 100
|
||||
iconName: "power_settings_new"
|
||||
text: "Shutdown"
|
||||
onPressed: root.powerActionRequested("poweroff", "Shutdown", "Are you sure you want to shutdown?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Modules/ControlCenter/Components/SizeControls.qml
Normal file
52
Modules/ControlCenter/Components/SizeControls.qml
Normal file
@@ -0,0 +1,52 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property int currentSize: 50
|
||||
property bool isSlider: false
|
||||
property int widgetIndex: -1
|
||||
|
||||
signal sizeChanged(int newSize)
|
||||
|
||||
readonly property var availableSizes: isSlider ? [50, 100] : [25, 50, 75, 100]
|
||||
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: root.availableSizes
|
||||
|
||||
Rectangle {
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 3
|
||||
color: modelData === root.currentSize ? Theme.primary : Theme.surfaceContainer
|
||||
border.color: modelData === root.currentSize ? Theme.primary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.toString()
|
||||
font.pixelSize: 8
|
||||
font.weight: Font.Medium
|
||||
color: modelData === root.currentSize ? Theme.primaryText : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.currentSize = modelData
|
||||
root.sizeChanged(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,719 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
import qs.Modules.ControlCenter.Components
|
||||
import "../utils/layout.js" as LayoutUtils
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property bool editMode: false
|
||||
property string expandedSection: ""
|
||||
property int expandedWidgetIndex: -1
|
||||
property var model: null
|
||||
property var expandedWidgetData: null
|
||||
|
||||
signal expandClicked(var widgetData, int globalIndex)
|
||||
signal removeWidget(int index)
|
||||
signal moveWidget(int fromIndex, int toIndex)
|
||||
signal toggleWidgetSize(int index)
|
||||
|
||||
spacing: editMode ? Theme.spacingL : Theme.spacingS
|
||||
|
||||
property var currentRowWidgets: []
|
||||
property real currentRowWidth: 0
|
||||
property int expandedRowIndex: -1
|
||||
|
||||
function calculateRowsAndWidgets() {
|
||||
return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex)
|
||||
}
|
||||
|
||||
property var layoutResult: {
|
||||
const dummy = [expandedSection, expandedWidgetIndex, model?.controlCenterWidgets]
|
||||
return calculateRowsAndWidgets()
|
||||
}
|
||||
|
||||
onLayoutResultChanged: {
|
||||
expandedRowIndex = layoutResult.expandedRowIndex
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.layoutResult.rows
|
||||
|
||||
Column {
|
||||
width: root.width
|
||||
spacing: 0
|
||||
property int rowIndex: index
|
||||
property var rowWidgets: modelData
|
||||
property bool isSliderOnlyRow: {
|
||||
const widgets = rowWidgets || []
|
||||
if (widgets.length === 0) return false
|
||||
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
|
||||
}
|
||||
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -12) : 0
|
||||
bottomPadding: isSliderOnlyRow ? (root.editMode ? 4 : -12) : 0
|
||||
|
||||
Flow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: rowWidgets || []
|
||||
|
||||
Item {
|
||||
property var widgetData: modelData
|
||||
property int globalWidgetIndex: {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
if (widgets[i].id === modelData.id) {
|
||||
if (modelData.id === "diskUsage") {
|
||||
if (widgets[i].instanceId === modelData.instanceId) {
|
||||
return i
|
||||
}
|
||||
} else {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
property int widgetWidth: modelData.width || 50
|
||||
width: {
|
||||
const baseWidth = root.width
|
||||
const spacing = Theme.spacingS
|
||||
if (widgetWidth <= 25) {
|
||||
return (baseWidth - spacing * 3) / 4
|
||||
} else if (widgetWidth <= 50) {
|
||||
return (baseWidth - spacing) / 2
|
||||
} else if (widgetWidth <= 75) {
|
||||
return (baseWidth - spacing * 2) * 0.75
|
||||
} else {
|
||||
return baseWidth
|
||||
}
|
||||
}
|
||||
height: 60
|
||||
|
||||
Loader {
|
||||
id: widgetLoader
|
||||
anchors.fill: parent
|
||||
property var widgetData: parent.widgetData
|
||||
property int widgetIndex: parent.globalWidgetIndex
|
||||
property int globalWidgetIndex: parent.globalWidgetIndex
|
||||
property int widgetWidth: parent.widgetWidth
|
||||
|
||||
sourceComponent: {
|
||||
const id = modelData.id || ""
|
||||
if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
|
||||
return compoundPillComponent
|
||||
} else if (id === "volumeSlider") {
|
||||
return audioSliderComponent
|
||||
} else if (id === "brightnessSlider") {
|
||||
return brightnessSliderComponent
|
||||
} else if (id === "inputVolumeSlider") {
|
||||
return inputAudioSliderComponent
|
||||
} else if (id === "battery") {
|
||||
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent
|
||||
} else if (id === "diskUsage") {
|
||||
return diskUsagePillComponent
|
||||
} else {
|
||||
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DetailHost {
|
||||
width: parent.width
|
||||
height: active ? (250 + Theme.spacingS) : 0
|
||||
property bool active: {
|
||||
if (root.expandedSection === "") return false
|
||||
|
||||
if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) {
|
||||
const expandedInstanceId = root.expandedWidgetData.instanceId
|
||||
return rowWidgets.some(w => w.id === "diskUsage" && w.instanceId === expandedInstanceId)
|
||||
}
|
||||
|
||||
return rowIndex === root.expandedRowIndex
|
||||
}
|
||||
visible: active
|
||||
expandedSection: root.expandedSection
|
||||
expandedWidgetData: root.expandedWidgetData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: compoundPillComponent
|
||||
CompoundPill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
width: parent.width
|
||||
height: 60
|
||||
iconName: {
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi": {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return "sync"
|
||||
}
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return "settings_ethernet"
|
||||
}
|
||||
if (NetworkService.networkStatus === "wifi") {
|
||||
return NetworkService.wifiSignalIcon
|
||||
}
|
||||
if (NetworkService.wifiEnabled) {
|
||||
return "wifi_off"
|
||||
}
|
||||
return "wifi_off"
|
||||
}
|
||||
case "bluetooth": {
|
||||
if (!BluetoothService.available) {
|
||||
return "bluetooth_disabled"
|
||||
}
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
|
||||
return "bluetooth_disabled"
|
||||
}
|
||||
return "bluetooth"
|
||||
}
|
||||
case "audioOutput": {
|
||||
if (!AudioService.sink) return "volume_off"
|
||||
let volume = AudioService.sink.audio.volume
|
||||
let muted = AudioService.sink.audio.muted
|
||||
if (muted || volume === 0.0) return "volume_off"
|
||||
if (volume <= 0.33) return "volume_down"
|
||||
if (volume <= 0.66) return "volume_up"
|
||||
return "volume_up"
|
||||
}
|
||||
case "audioInput": {
|
||||
if (!AudioService.source) return "mic_off"
|
||||
let muted = AudioService.source.audio.muted
|
||||
return muted ? "mic_off" : "mic"
|
||||
}
|
||||
default: return widgetDef?.icon || "help"
|
||||
}
|
||||
}
|
||||
primaryText: {
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi": {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
|
||||
}
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return "Ethernet"
|
||||
}
|
||||
if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID) {
|
||||
return NetworkService.currentWifiSSID
|
||||
}
|
||||
if (NetworkService.wifiEnabled) {
|
||||
return "Not connected"
|
||||
}
|
||||
return "WiFi off"
|
||||
}
|
||||
case "bluetooth": {
|
||||
if (!BluetoothService.available) {
|
||||
return "Bluetooth"
|
||||
}
|
||||
if (!BluetoothService.adapter) {
|
||||
return "No adapter"
|
||||
}
|
||||
if (!BluetoothService.adapter.enabled) {
|
||||
return "Disabled"
|
||||
}
|
||||
return "Enabled"
|
||||
}
|
||||
case "audioOutput": return AudioService.sink?.description || "No output device"
|
||||
case "audioInput": return AudioService.source?.description || "No input device"
|
||||
default: return widgetDef?.text || "Unknown"
|
||||
}
|
||||
}
|
||||
secondaryText: {
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi": {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return "Please wait..."
|
||||
}
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return "Connected"
|
||||
}
|
||||
if (NetworkService.networkStatus === "wifi") {
|
||||
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
|
||||
}
|
||||
if (NetworkService.wifiEnabled) {
|
||||
return "Select network"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
case "bluetooth": {
|
||||
if (!BluetoothService.available) {
|
||||
return "No adapters"
|
||||
}
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
|
||||
return "Off"
|
||||
}
|
||||
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 primaryDevice.name || primaryDevice.alias || primaryDevice.deviceName || "Connected Device"
|
||||
}
|
||||
return "No devices"
|
||||
}
|
||||
case "audioOutput": {
|
||||
if (!AudioService.sink) {
|
||||
return "Select device"
|
||||
}
|
||||
if (AudioService.sink.audio.muted) {
|
||||
return "Muted"
|
||||
}
|
||||
return Math.round(AudioService.sink.audio.volume * 100) + "%"
|
||||
}
|
||||
case "audioInput": {
|
||||
if (!AudioService.source) {
|
||||
return "Select device"
|
||||
}
|
||||
if (AudioService.source.audio.muted) {
|
||||
return "Muted"
|
||||
}
|
||||
return Math.round(AudioService.source.audio.volume * 100) + "%"
|
||||
}
|
||||
default: return widgetDef?.description || ""
|
||||
}
|
||||
}
|
||||
isActive: {
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi": {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return false
|
||||
}
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return true
|
||||
}
|
||||
if (NetworkService.networkStatus === "wifi") {
|
||||
return true
|
||||
}
|
||||
return NetworkService.wifiEnabled
|
||||
}
|
||||
case "bluetooth": return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled)
|
||||
case "audioOutput": return !!(AudioService.sink && !AudioService.sink.audio.muted)
|
||||
case "audioInput": return !!(AudioService.source && !AudioService.source.audio.muted)
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
enabled: (widgetDef?.enabled ?? true)
|
||||
onToggled: {
|
||||
if (root.editMode) return
|
||||
switch (widgetData.id || "") {
|
||||
case "wifi": {
|
||||
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
|
||||
NetworkService.toggleWifiRadio()
|
||||
}
|
||||
break
|
||||
}
|
||||
case "bluetooth": {
|
||||
if (BluetoothService.available && BluetoothService.adapter) {
|
||||
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
|
||||
}
|
||||
break
|
||||
}
|
||||
case "audioOutput": {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
|
||||
}
|
||||
break
|
||||
}
|
||||
case "audioInput": {
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
onExpandClicked: {
|
||||
if (root.editMode) return
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
onWheelEvent: function (wheelEvent) {
|
||||
const id = widgetData.id || ""
|
||||
if (id === "audioOutput") {
|
||||
if (!AudioService.sink || !AudioService.sink.audio) return
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
let currentVolume = AudioService.sink.audio.volume * 100
|
||||
let newVolume
|
||||
if (delta > 0)
|
||||
newVolume = Math.min(100, currentVolume + 5)
|
||||
else
|
||||
newVolume = Math.max(0, currentVolume - 5)
|
||||
AudioService.sink.audio.muted = false
|
||||
AudioService.sink.audio.volume = newVolume / 100
|
||||
wheelEvent.accepted = true
|
||||
} else if (id === "audioInput") {
|
||||
if (!AudioService.source || !AudioService.source.audio) return
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
let currentVolume = AudioService.source.audio.volume * 100
|
||||
let newVolume
|
||||
if (delta > 0)
|
||||
newVolume = Math.min(100, currentVolume + 5)
|
||||
else
|
||||
newVolume = Math.max(0, currentVolume - 5)
|
||||
AudioService.source.audio.muted = false
|
||||
AudioService.source.audio.volume = newVolume / 100
|
||||
wheelEvent.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
EditModeOverlay {
|
||||
anchors.fill: parent
|
||||
editMode: root.editMode
|
||||
widgetData: parent.widgetData
|
||||
widgetIndex: parent.widgetIndex
|
||||
showSizeControls: true
|
||||
isSlider: false
|
||||
onRemoveWidget: (index) => root.removeWidget(index)
|
||||
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: audioSliderComponent
|
||||
Item {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
width: parent.width
|
||||
height: 16
|
||||
|
||||
AudioSliderRow {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
|
||||
EditModeOverlay {
|
||||
anchors.fill: parent
|
||||
editMode: root.editMode
|
||||
widgetData: parent.widgetData
|
||||
widgetIndex: parent.widgetIndex
|
||||
showSizeControls: true
|
||||
isSlider: true
|
||||
onRemoveWidget: (index) => root.removeWidget(index)
|
||||
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: brightnessSliderComponent
|
||||
Item {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 16
|
||||
|
||||
BrightnessSliderRow {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
|
||||
EditModeOverlay {
|
||||
anchors.fill: parent
|
||||
editMode: root.editMode
|
||||
widgetData: parent.widgetData
|
||||
widgetIndex: parent.widgetIndex
|
||||
showSizeControls: true
|
||||
isSlider: true
|
||||
onRemoveWidget: (index) => root.removeWidget(index)
|
||||
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: inputAudioSliderComponent
|
||||
Item {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 16
|
||||
|
||||
InputAudioSliderRow {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.surfaceContainerHigh
|
||||
}
|
||||
|
||||
EditModeOverlay {
|
||||
anchors.fill: parent
|
||||
editMode: root.editMode
|
||||
widgetData: parent.widgetData
|
||||
widgetIndex: parent.widgetIndex
|
||||
showSizeControls: true
|
||||
isSlider: true
|
||||
onRemoveWidget: (index) => root.removeWidget(index)
|
||||
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: batteryPillComponent
|
||||
BatteryPill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
onExpandClicked: {
|
||||
if (!root.editMode) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
EditModeOverlay {
|
||||
anchors.fill: parent
|
||||
editMode: root.editMode
|
||||
widgetData: parent.widgetData
|
||||
widgetIndex: parent.widgetIndex
|
||||
showSizeControls: true
|
||||
isSlider: false
|
||||
onRemoveWidget: (index) => root.removeWidget(index)
|
||||
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: smallBatteryComponent
|
||||
SmallBatteryButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 48
|
||||
|
||||
onClicked: {
|
||||
if (!root.editMode) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
EditModeOverlay {
|
||||
anchors.fill: parent
|
||||
editMode: root.editMode
|
||||
widgetData: parent.widgetData
|
||||
widgetIndex: parent.widgetIndex
|
||||
showSizeControls: true
|
||||
isSlider: false
|
||||
onRemoveWidget: (index) => root.removeWidget(index)
|
||||
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: toggleButtonComponent
|
||||
ToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
iconName: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode": return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||
case "darkMode": return "contrast"
|
||||
case "doNotDisturb": return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
|
||||
case "idleInhibitor": return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
default: return widgetDef?.icon || "help"
|
||||
}
|
||||
}
|
||||
|
||||
text: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode": return "Night Mode"
|
||||
case "darkMode": return SessionData.isLightMode ? "Light Mode" : "Dark Mode"
|
||||
case "doNotDisturb": return "Do Not Disturb"
|
||||
case "idleInhibitor": return SessionService.idleInhibited ? "Keeping Awake" : "Keep Awake"
|
||||
default: return widgetDef?.text || "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
secondaryText: ""
|
||||
|
||||
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
|
||||
|
||||
isActive: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode": return DisplayService.nightModeEnabled || false
|
||||
case "darkMode": return !SessionData.isLightMode
|
||||
case "doNotDisturb": return SessionData.doNotDisturb || false
|
||||
case "idleInhibitor": return SessionService.idleInhibited || false
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
enabled: (widgetDef?.enabled ?? true) && !root.editMode
|
||||
|
||||
onClicked: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode": {
|
||||
if (DisplayService.automationAvailable) {
|
||||
DisplayService.toggleNightMode()
|
||||
}
|
||||
break
|
||||
}
|
||||
case "darkMode": {
|
||||
Theme.toggleLightMode()
|
||||
break
|
||||
}
|
||||
case "doNotDisturb": {
|
||||
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||
break
|
||||
}
|
||||
case "idleInhibitor": {
|
||||
SessionService.toggleIdleInhibit()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditModeOverlay {
|
||||
anchors.fill: parent
|
||||
editMode: root.editMode
|
||||
widgetData: parent.widgetData
|
||||
widgetIndex: parent.widgetIndex
|
||||
showSizeControls: true
|
||||
isSlider: false
|
||||
onRemoveWidget: (index) => root.removeWidget(index)
|
||||
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: smallToggleComponent
|
||||
SmallToggleButton {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||
width: parent.width
|
||||
height: 48
|
||||
|
||||
iconName: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode": return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||
case "darkMode": return "contrast"
|
||||
case "doNotDisturb": return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
|
||||
case "idleInhibitor": return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
default: return widgetDef?.icon || "help"
|
||||
}
|
||||
}
|
||||
|
||||
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
|
||||
|
||||
isActive: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode": return DisplayService.nightModeEnabled || false
|
||||
case "darkMode": return !SessionData.isLightMode
|
||||
case "doNotDisturb": return SessionData.doNotDisturb || false
|
||||
case "idleInhibitor": return SessionService.idleInhibited || false
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
enabled: (widgetDef?.enabled ?? true) && !root.editMode
|
||||
|
||||
onClicked: {
|
||||
switch (widgetData.id || "") {
|
||||
case "nightMode": {
|
||||
if (DisplayService.automationAvailable) {
|
||||
DisplayService.toggleNightMode()
|
||||
}
|
||||
break
|
||||
}
|
||||
case "darkMode": {
|
||||
Theme.toggleLightMode()
|
||||
break
|
||||
}
|
||||
case "doNotDisturb": {
|
||||
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||
break
|
||||
}
|
||||
case "idleInhibitor": {
|
||||
SessionService.toggleIdleInhibit()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditModeOverlay {
|
||||
anchors.fill: parent
|
||||
editMode: root.editMode
|
||||
widgetData: parent.widgetData
|
||||
widgetIndex: parent.widgetIndex
|
||||
showSizeControls: true
|
||||
isSlider: false
|
||||
onRemoveWidget: (index) => root.removeWidget(index)
|
||||
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: diskUsagePillComponent
|
||||
DiskUsagePill {
|
||||
property var widgetData: parent.widgetData || {}
|
||||
property int widgetIndex: parent.widgetIndex || 0
|
||||
width: parent.width
|
||||
height: 60
|
||||
|
||||
mountPath: widgetData.mountPath || "/"
|
||||
instanceId: widgetData.instanceId || ""
|
||||
|
||||
onExpandClicked: {
|
||||
if (!root.editMode) {
|
||||
root.expandClicked(widgetData, widgetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
EditModeOverlay {
|
||||
anchors.fill: parent
|
||||
editMode: root.editMode
|
||||
widgetData: parent.widgetData
|
||||
widgetIndex: parent.widgetIndex
|
||||
showSizeControls: true
|
||||
isSlider: false
|
||||
onRemoveWidget: (index) => root.removeWidget(index)
|
||||
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
import qs.Modules.ControlCenter.Details
|
||||
import qs.Modules.TopBar
|
||||
import qs.Modules.DankBar
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.ControlCenter.Components
|
||||
@@ -21,14 +21,11 @@ DankPopout {
|
||||
id: root
|
||||
|
||||
property string expandedSection: ""
|
||||
property bool powerOptionsExpanded: false
|
||||
property string triggerSection: "right"
|
||||
property var triggerScreen: null
|
||||
property bool editMode: false
|
||||
property int expandedWidgetIndex: -1
|
||||
property var expandedWidgetData: null
|
||||
|
||||
signal powerActionRequested(string action, string title, string message)
|
||||
signal lockRequested
|
||||
|
||||
function collapseAll() {
|
||||
@@ -66,9 +63,9 @@ DankPopout {
|
||||
popupWidth: 550
|
||||
popupHeight: Math.min((triggerScreen?.height ?? 1080) - 100, contentLoader.item && contentLoader.item.implicitHeight > 0 ? contentLoader.item.implicitHeight + 20 : 400)
|
||||
triggerX: (triggerScreen?.width ?? 1920) - 600 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.popupDistance
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.dankBarSpacing
|
||||
triggerWidth: 80
|
||||
positioning: "center"
|
||||
positioning: ""
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: false
|
||||
visible: shouldBeVisible
|
||||
@@ -102,7 +99,7 @@ DankPopout {
|
||||
property alias bluetoothCodecSelector: bluetoothCodecSelector
|
||||
|
||||
color: {
|
||||
const transparency = Theme.popupTransparency || 0.92
|
||||
const transparency = Theme.popupTransparency
|
||||
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
|
||||
return Qt.rgba(surface.r, surface.g, surface.b, transparency)
|
||||
}
|
||||
@@ -123,28 +120,24 @@ DankPopout {
|
||||
HeaderPane {
|
||||
id: headerPane
|
||||
width: parent.width
|
||||
powerOptionsExpanded: root.powerOptionsExpanded
|
||||
editMode: root.editMode
|
||||
onPowerOptionsExpandedChanged: root.powerOptionsExpanded = powerOptionsExpanded
|
||||
onEditModeToggled: root.editMode = !root.editMode
|
||||
onPowerActionRequested: (action, title, message) => root.powerActionRequested(action, title, message)
|
||||
onPowerButtonClicked: {
|
||||
if (powerMenuModalLoader) {
|
||||
powerMenuModalLoader.active = true
|
||||
if (powerMenuModalLoader.item) {
|
||||
const popoutPos = controlContent.mapToItem(null, 0, 0)
|
||||
const bounds = Qt.rect(popoutPos.x, popoutPos.y, controlContent.width, controlContent.height)
|
||||
powerMenuModalLoader.item.openFromControlCenter(bounds, root.triggerScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
onLockRequested: {
|
||||
root.close()
|
||||
root.lockRequested()
|
||||
}
|
||||
}
|
||||
|
||||
PowerOptionsPane {
|
||||
id: powerOptionsPane
|
||||
width: parent.width
|
||||
expanded: root.powerOptionsExpanded
|
||||
onPowerActionRequested: (action, title, message) => {
|
||||
root.powerOptionsExpanded = false
|
||||
root.close()
|
||||
root.powerActionRequested(action, title, message)
|
||||
}
|
||||
}
|
||||
|
||||
DragDropGrid {
|
||||
id: widgetGrid
|
||||
width: parent.width
|
||||
@@ -153,6 +146,8 @@ DankPopout {
|
||||
expandedWidgetIndex: root.expandedWidgetIndex
|
||||
expandedWidgetData: root.expandedWidgetData
|
||||
model: widgetModel
|
||||
bluetoothCodecSelector: bluetoothCodecSelector
|
||||
colorPickerModal: root.colorPickerModal
|
||||
onExpandClicked: (widgetData, globalIndex) => {
|
||||
root.expandedWidgetIndex = globalIndex
|
||||
root.expandedWidgetData = widgetData
|
||||
@@ -170,9 +165,12 @@ DankPopout {
|
||||
EditControls {
|
||||
width: parent.width
|
||||
visible: editMode
|
||||
popoutContent: controlContent
|
||||
availableWidgets: {
|
||||
if (!editMode) return []
|
||||
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id)
|
||||
return widgetModel.baseWidgetDefinitions.filter(w => w.allowMultiple || !existingIds.includes(w.id))
|
||||
const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets())
|
||||
return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id))
|
||||
}
|
||||
onAddWidget: (widgetId) => widgetModel.addWidget(widgetId)
|
||||
onResetToDefault: () => widgetModel.resetToDefault()
|
||||
@@ -222,4 +220,7 @@ DankPopout {
|
||||
id: batteryDetailComponent
|
||||
BatteryDetail {}
|
||||
}
|
||||
|
||||
property var colorPickerModal: null
|
||||
property var powerMenuModalLoader: null
|
||||
}
|
||||
@@ -30,7 +30,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: "Input Devices"
|
||||
text: qsTr("Input Devices")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -30,7 +30,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: "Audio Devices"
|
||||
text: qsTr("Audio Devices")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -133,7 +133,7 @@ Rectangle {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Health"
|
||||
text: qsTr("Health")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
@@ -168,7 +168,7 @@ Rectangle {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Capacity"
|
||||
text: qsTr("Capacity")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
@@ -237,7 +237,7 @@ Rectangle {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "Power Profile Degradation"
|
||||
text: qsTr("Power Profile Degradation")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.error
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -170,7 +170,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Audio Codec Selection"
|
||||
text: qsTr("Audio Codec Selection")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: "Bluetooth Settings"
|
||||
text: qsTr("Bluetooth Settings")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -422,7 +422,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "No Bluetooth adapter found"
|
||||
text: qsTr("No Bluetooth adapter found")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
@@ -473,7 +473,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Audio Codec"
|
||||
text: qsTr("Audio Codec")
|
||||
height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0
|
||||
visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected
|
||||
|
||||
@@ -498,7 +498,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Forget Device"
|
||||
text: qsTr("Forget Device")
|
||||
height: 32
|
||||
|
||||
contentItem: StyledText {
|
||||
|
||||
@@ -44,7 +44,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
text: "Network Settings"
|
||||
text: qsTr("Network Settings")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -136,7 +136,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: "WiFi is off"
|
||||
text: qsTr("WiFi is off")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -154,7 +154,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "Enable WiFi"
|
||||
text: qsTr("Enable WiFi")
|
||||
color: Theme.primary
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
@@ -380,7 +380,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Network Info"
|
||||
text: qsTr("Network Info")
|
||||
height: 32
|
||||
|
||||
contentItem: StyledText {
|
||||
@@ -403,7 +403,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Forget Network"
|
||||
text: qsTr("Forget Network")
|
||||
height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0
|
||||
visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.ControlCenter.BuiltinPlugins
|
||||
import "../utils/widgets.js" as WidgetUtils
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
readonly property var baseWidgetDefinitions: [
|
||||
{
|
||||
property var vpnBuiltinInstance: VpnWidget {}
|
||||
|
||||
readonly property var coreWidgetDefinitions: [{
|
||||
"id": "nightMode",
|
||||
"text": "Night Mode",
|
||||
"description": "Blue light filter",
|
||||
@@ -15,32 +17,28 @@ QtObject {
|
||||
"type": "toggle",
|
||||
"enabled": DisplayService.automationAvailable,
|
||||
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "darkMode",
|
||||
"text": "Dark Mode",
|
||||
"description": "System theme toggle",
|
||||
"icon": "contrast",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "doNotDisturb",
|
||||
"text": "Do Not Disturb",
|
||||
"description": "Block notifications",
|
||||
"icon": "do_not_disturb_on",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "idleInhibitor",
|
||||
"text": "Keep Awake",
|
||||
"description": "Prevent screen timeout",
|
||||
"icon": "motion_sensor_active",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "wifi",
|
||||
"text": "Network",
|
||||
"description": "Wi-Fi and Ethernet connection",
|
||||
@@ -48,8 +46,7 @@ QtObject {
|
||||
"type": "connection",
|
||||
"enabled": NetworkService.wifiAvailable,
|
||||
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "bluetooth",
|
||||
"text": "Bluetooth",
|
||||
"description": "Device connections",
|
||||
@@ -57,32 +54,28 @@ QtObject {
|
||||
"type": "connection",
|
||||
"enabled": BluetoothService.available,
|
||||
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "audioOutput",
|
||||
"text": "Audio Output",
|
||||
"description": "Speaker settings",
|
||||
"icon": "volume_up",
|
||||
"type": "connection",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "audioInput",
|
||||
"text": "Audio Input",
|
||||
"description": "Microphone settings",
|
||||
"icon": "mic",
|
||||
"type": "connection",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "volumeSlider",
|
||||
"text": "Volume Slider",
|
||||
"description": "Audio volume control",
|
||||
"icon": "volume_up",
|
||||
"type": "slider",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "brightnessSlider",
|
||||
"text": "Brightness Slider",
|
||||
"description": "Display brightness control",
|
||||
@@ -90,24 +83,21 @@ QtObject {
|
||||
"type": "slider",
|
||||
"enabled": DisplayService.brightnessAvailable,
|
||||
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "inputVolumeSlider",
|
||||
"text": "Input Volume Slider",
|
||||
"description": "Microphone volume control",
|
||||
"icon": "mic",
|
||||
"type": "slider",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "battery",
|
||||
"text": "Battery",
|
||||
"description": "Battery and power management",
|
||||
"icon": "battery_std",
|
||||
"type": "action",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "diskUsage",
|
||||
"text": "Disk Usage",
|
||||
"description": "Filesystem usage monitoring",
|
||||
@@ -116,8 +106,68 @@ QtObject {
|
||||
"enabled": DgopService.dgopAvailable,
|
||||
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
|
||||
"allowMultiple": true
|
||||
}, {
|
||||
"id": "colorPicker",
|
||||
"text": "Color Picker",
|
||||
"description": "Choose colors from palette",
|
||||
"icon": "palette",
|
||||
"type": "action",
|
||||
"enabled": true
|
||||
}, {
|
||||
"id": "builtin_vpn",
|
||||
"text": "VPN",
|
||||
"description": "VPN connections",
|
||||
"icon": "vpn_key",
|
||||
"type": "builtin_plugin",
|
||||
"enabled": VpnService.available,
|
||||
"warning": !VpnService.available ? "VPN not available" : undefined,
|
||||
"isBuiltinPlugin": true
|
||||
}]
|
||||
|
||||
function getPluginWidgets() {
|
||||
const plugins = []
|
||||
const loadedPlugins = PluginService.getLoadedPlugins()
|
||||
|
||||
for (var i = 0; i < loadedPlugins.length; i++) {
|
||||
const plugin = loadedPlugins[i]
|
||||
|
||||
if (plugin.type === "daemon") {
|
||||
continue
|
||||
}
|
||||
|
||||
const pluginComponent = PluginService.pluginWidgetComponents[plugin.id]
|
||||
if (!pluginComponent) {
|
||||
continue
|
||||
}
|
||||
|
||||
const tempInstance = pluginComponent.createObject(null)
|
||||
if (!tempInstance) {
|
||||
continue
|
||||
}
|
||||
|
||||
const hasCCWidget = tempInstance.ccWidgetIcon && tempInstance.ccWidgetIcon.length > 0
|
||||
tempInstance.destroy()
|
||||
|
||||
if (!hasCCWidget) {
|
||||
continue
|
||||
}
|
||||
|
||||
plugins.push({
|
||||
"id": "plugin_" + plugin.id,
|
||||
"pluginId": plugin.id,
|
||||
"text": plugin.name || "Plugin",
|
||||
"description": plugin.description || "",
|
||||
"icon": plugin.icon || "extension",
|
||||
"type": "plugin",
|
||||
"enabled": true,
|
||||
"isPlugin": true
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
return plugins
|
||||
}
|
||||
|
||||
readonly property var baseWidgetDefinitions: coreWidgetDefinitions
|
||||
|
||||
function getWidgetForId(widgetId) {
|
||||
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId)
|
||||
@@ -146,4 +196,4 @@ QtObject {
|
||||
function clearAll() {
|
||||
WidgetUtils.clearAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ PanelWindow {
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
text: "Power Options"
|
||||
text: qsTr("Power Options")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -118,7 +118,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Log Out"
|
||||
text: qsTr("Log Out")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -168,7 +168,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Suspend"
|
||||
text: qsTr("Suspend")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -218,7 +218,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Reboot"
|
||||
text: qsTr("Reboot")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -268,7 +268,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Power Off"
|
||||
text: qsTr("Power Off")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -20,11 +20,7 @@ Row {
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
|
||||
|
||||
MouseArea {
|
||||
id: iconArea
|
||||
@@ -34,7 +30,9 @@ Row {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (defaultSink) {
|
||||
AudioService.suppressOSD = true
|
||||
defaultSink.audio.muted = !defaultSink.audio.muted
|
||||
AudioService.suppressOSD = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,17 +18,13 @@ Row {
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.containsMouse
|
||||
? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
: "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
: Theme.withAlpha(Theme.primary, 0)
|
||||
|
||||
MouseArea {
|
||||
id: iconArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
cursorShape: DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
onClicked: function(event) {
|
||||
if (DisplayService.devices.length > 1) {
|
||||
@@ -41,6 +37,22 @@ Row {
|
||||
}
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
tooltipLoader.active = true
|
||||
if (tooltipLoader.item) {
|
||||
const tooltipText = DisplayService.currentDevice ? "bl device: " + DisplayService.currentDevice : "Backlight Control"
|
||||
const p = iconArea.mapToItem(null, iconArea.width / 2, 0)
|
||||
tooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hide()
|
||||
}
|
||||
tooltipLoader.active = false
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
@@ -141,4 +153,10 @@ Row {
|
||||
onObjectRemoved: (index, object) => deviceMenu.removeItem(object)
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
}
|
||||
33
Modules/ControlCenter/Widgets/ColorPickerPill.qml
Normal file
33
Modules/ControlCenter/Widgets/ColorPickerPill.qml
Normal file
@@ -0,0 +1,33 @@
|
||||
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 colorPickerModal: null
|
||||
|
||||
isActive: true
|
||||
iconName: "palette"
|
||||
iconColor: Theme.primary
|
||||
primaryText: "Color Picker"
|
||||
secondaryText: "Choose a color"
|
||||
|
||||
onToggled: {
|
||||
console.log("ColorPickerPill toggled, modal:", colorPickerModal)
|
||||
if (colorPickerModal) {
|
||||
colorPickerModal.show()
|
||||
}
|
||||
}
|
||||
|
||||
onExpandClicked: {
|
||||
console.log("ColorPickerPill expandClicked, modal:", colorPickerModal)
|
||||
if (colorPickerModal) {
|
||||
colorPickerModal.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ Rectangle {
|
||||
readonly property color _labelSecondary: Theme.surfaceVariantText
|
||||
readonly property color _tileBgActive: Theme.primary
|
||||
readonly property color _tileBgInactive: {
|
||||
const transparency = Theme.popupTransparency || 0.92
|
||||
const transparency = Theme.popupTransparency
|
||||
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
|
||||
return Qt.rgba(surface.r, surface.g, surface.b, transparency)
|
||||
}
|
||||
@@ -49,7 +49,7 @@ Rectangle {
|
||||
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||
readonly property color _tileRingInactive:
|
||||
Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18)
|
||||
readonly property color _tileIconActive: Theme.primaryContainer
|
||||
readonly property color _tileIconActive: Theme.primaryText
|
||||
readonly property color _tileIconInactive: Theme.primary
|
||||
|
||||
property int _padH: Theme.spacingS
|
||||
|
||||
@@ -20,11 +20,7 @@ Row {
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.shortDuration }
|
||||
}
|
||||
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
|
||||
|
||||
MouseArea {
|
||||
id: iconArea
|
||||
@@ -34,7 +30,9 @@ Row {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (defaultSource) {
|
||||
AudioService.suppressOSD = true
|
||||
defaultSource.audio.muted = !defaultSource.audio.muted
|
||||
AudioService.suppressOSD = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +67,9 @@ Row {
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
|
||||
onIsDraggingChanged: {
|
||||
AudioService.suppressOSD = isDragging
|
||||
}
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (defaultSource) {
|
||||
defaultSource.audio.volume = newValue / 100.0
|
||||
|
||||
@@ -28,7 +28,7 @@ Rectangle {
|
||||
readonly property color _tileBgInactive: Theme.surfaceContainerHigh
|
||||
readonly property color _tileRingActive:
|
||||
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||
readonly property color _tileIconActive: Theme.primaryContainer
|
||||
readonly property color _tileIconActive: Theme.primaryText
|
||||
readonly property color _tileIconInactive: Theme.primary
|
||||
|
||||
color: {
|
||||
@@ -91,13 +91,6 @@ Rectangle {
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
|
||||
@@ -12,6 +12,7 @@ Rectangle {
|
||||
property real iconRotation: 0
|
||||
|
||||
signal clicked()
|
||||
signal iconRotationCompleted()
|
||||
|
||||
width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48
|
||||
height: 48
|
||||
@@ -29,7 +30,7 @@ Rectangle {
|
||||
readonly property color _tileBgInactive: Theme.surfaceContainerHigh
|
||||
readonly property color _tileRingActive:
|
||||
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||
readonly property color _tileIconActive: Theme.primaryContainer
|
||||
readonly property color _tileIconActive: Theme.primaryText
|
||||
readonly property color _tileIconInactive: Theme.primary
|
||||
|
||||
color: {
|
||||
@@ -58,6 +59,7 @@ Rectangle {
|
||||
size: Theme.iconSize
|
||||
color: isActive ? _tileIconActive : _tileIconInactive
|
||||
rotation: iconRotation
|
||||
onRotationCompleted: root.iconRotationCompleted()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -69,13 +71,6 @@ Rectangle {
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
|
||||
@@ -12,8 +12,9 @@ Rectangle {
|
||||
property bool enabled: true
|
||||
property string secondaryText: ""
|
||||
property real iconRotation: 0
|
||||
|
||||
|
||||
signal clicked()
|
||||
signal iconRotationCompleted()
|
||||
|
||||
width: parent ? parent.width : 200
|
||||
height: 60
|
||||
@@ -46,7 +47,7 @@ Rectangle {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.containsMouse ? hoverTint(_containerBg) : "transparent"
|
||||
color: mouseArea.containsMouse ? hoverTint(_containerBg) : Theme.withAlpha(_containerBg, 0)
|
||||
opacity: mouseArea.containsMouse ? 0.08 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -63,9 +64,10 @@ Rectangle {
|
||||
DankIcon {
|
||||
name: root.iconName
|
||||
size: Theme.iconSize
|
||||
color: isActive ? Theme.primaryContainer : Theme.primary
|
||||
color: isActive ? Theme.primaryText : Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
rotation: root.iconRotation
|
||||
onRotationCompleted: root.iconRotationCompleted()
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -82,7 +84,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
text: root.text
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: isActive ? Theme.primaryContainer : Theme.surfaceText
|
||||
color: isActive ? Theme.primaryText : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
@@ -92,7 +94,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
text: root.secondaryText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
|
||||
color: isActive ? Theme.primaryText : Theme.surfaceVariantText
|
||||
visible: text.length > 0
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
@@ -110,13 +112,6 @@ Rectangle {
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
|
||||
179
Modules/DankBar/AutoHideManager.qml
Normal file
179
Modules/DankBar/AutoHideManager.qml
Normal file
@@ -0,0 +1,179 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var barWindow
|
||||
required property var axis
|
||||
required property var appDrawerLoader
|
||||
required property var dankDashPopoutLoader
|
||||
required property var processListPopoutLoader
|
||||
required property var notificationCenterLoader
|
||||
required property var batteryPopoutLoader
|
||||
required property var vpnPopoutLoader
|
||||
required property var controlCenterLoader
|
||||
required property var clipboardHistoryModalPopup
|
||||
required property var systemUpdateLoader
|
||||
required property var notepadInstance
|
||||
|
||||
property alias reveal: core.reveal
|
||||
property alias autoHide: core.autoHide
|
||||
property alias backgroundTransparency: core.backgroundTransparency
|
||||
property alias hasActivePopout: core.hasActivePopout
|
||||
property alias mouseArea: topBarMouseArea
|
||||
|
||||
Item {
|
||||
id: inputMask
|
||||
|
||||
readonly property int barThickness: barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing)
|
||||
|
||||
readonly property bool showing: SettingsData.dankBarVisible && (core.reveal
|
||||
|| (CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview)
|
||||
|| !core.autoHide)
|
||||
|
||||
readonly property int maskThickness: showing ? barThickness : 1
|
||||
|
||||
x: {
|
||||
if (!axis.isVertical) {
|
||||
return 0
|
||||
} else {
|
||||
switch (SettingsData.dankBarPosition) {
|
||||
case SettingsData.Position.Left: return 0
|
||||
case SettingsData.Position.Right: return parent.width - maskThickness
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
y: {
|
||||
if (axis.isVertical) {
|
||||
return 0
|
||||
} else {
|
||||
switch (SettingsData.dankBarPosition) {
|
||||
case SettingsData.Position.Top: return 0
|
||||
case SettingsData.Position.Bottom: return parent.height - maskThickness
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
width: axis.isVertical ? maskThickness : parent.width
|
||||
height: axis.isVertical ? parent.height : maskThickness
|
||||
}
|
||||
|
||||
Region {
|
||||
id: mask
|
||||
item: inputMask
|
||||
}
|
||||
|
||||
property alias maskRegion: mask
|
||||
|
||||
QtObject {
|
||||
id: core
|
||||
|
||||
property real backgroundTransparency: SettingsData.dankBarTransparency
|
||||
property bool autoHide: SettingsData.dankBarAutoHide
|
||||
property bool revealSticky: false
|
||||
|
||||
property bool notepadInstanceVisible: notepadInstance?.isVisible ?? false
|
||||
|
||||
readonly property bool hasActivePopout: {
|
||||
const loaders = [{
|
||||
"loader": appDrawerLoader,
|
||||
"prop": "shouldBeVisible"
|
||||
}, {
|
||||
"loader": dankDashPopoutLoader,
|
||||
"prop": "shouldBeVisible"
|
||||
}, {
|
||||
"loader": processListPopoutLoader,
|
||||
"prop": "shouldBeVisible"
|
||||
}, {
|
||||
"loader": notificationCenterLoader,
|
||||
"prop": "shouldBeVisible"
|
||||
}, {
|
||||
"loader": batteryPopoutLoader,
|
||||
"prop": "shouldBeVisible"
|
||||
}, {
|
||||
"loader": vpnPopoutLoader,
|
||||
"prop": "shouldBeVisible"
|
||||
}, {
|
||||
"loader": controlCenterLoader,
|
||||
"prop": "shouldBeVisible"
|
||||
}, {
|
||||
"loader": clipboardHistoryModalPopup,
|
||||
"prop": "visible"
|
||||
}, {
|
||||
"loader": systemUpdateLoader,
|
||||
"prop": "shouldBeVisible"
|
||||
}]
|
||||
return notepadInstanceVisible || loaders.some(item => {
|
||||
if (item.loader) {
|
||||
return item.loader?.item?.[item.prop]
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
property bool reveal: {
|
||||
if (CompositorService.isNiri && NiriService.inOverview) {
|
||||
return SettingsData.dankBarOpenOnOverview
|
||||
}
|
||||
return SettingsData.dankBarVisible && (!autoHide || topBarMouseArea.containsMouse || hasActivePopout || revealSticky)
|
||||
}
|
||||
|
||||
onHasActivePopoutChanged: {
|
||||
if (!hasActivePopout && autoHide && !topBarMouseArea.containsMouse) {
|
||||
revealSticky = true
|
||||
revealHold.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: revealHold
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: core.revealSticky = false
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onDankBarTransparencyChanged() {
|
||||
core.backgroundTransparency = SettingsData.dankBarTransparency
|
||||
}
|
||||
|
||||
target: SettingsData
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: topBarMouseArea
|
||||
function onContainsMouseChanged() {
|
||||
if (topBarMouseArea.containsMouse) {
|
||||
core.revealSticky = true
|
||||
revealHold.stop()
|
||||
} else {
|
||||
if (core.autoHide && !core.hasActivePopout) {
|
||||
revealHold.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: topBarMouseArea
|
||||
y: !barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? parent.height - height : 0) : 0
|
||||
x: barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.width - width : 0) : 0
|
||||
height: !barWindow.isVertical ? barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
|
||||
width: barWindow.isVertical ? barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
|
||||
anchors {
|
||||
left: !barWindow.isVertical ? parent.left : (SettingsData.dankBarPosition === SettingsData.Position.Left ? parent.left : undefined)
|
||||
right: !barWindow.isVertical ? parent.right : (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.right : undefined)
|
||||
top: barWindow.isVertical ? parent.top : undefined
|
||||
bottom: barWindow.isVertical ? parent.bottom : undefined
|
||||
}
|
||||
hoverEnabled: SettingsData.dankBarAutoHide && !core.reveal
|
||||
acceptedButtons: Qt.NoButton
|
||||
enabled: SettingsData.dankBarAutoHide && !core.reveal
|
||||
}
|
||||
}
|
||||
57
Modules/DankBar/AxisContext.qml
Normal file
57
Modules/DankBar/AxisContext.qml
Normal file
@@ -0,0 +1,57 @@
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property string edge: "top"
|
||||
|
||||
readonly property string orientation: isVertical ? "vertical" : "horizontal"
|
||||
readonly property bool isVertical: edge === "left" || edge === "right"
|
||||
readonly property bool isHorizontal: !isVertical
|
||||
|
||||
function primarySize(item) {
|
||||
return isVertical ? item.height : item.width
|
||||
}
|
||||
|
||||
function crossSize(item) {
|
||||
return isVertical ? item.width : item.height
|
||||
}
|
||||
|
||||
function setPrimaryPos(item, value) {
|
||||
if (isVertical) {
|
||||
item.y = value
|
||||
} else {
|
||||
item.x = value
|
||||
}
|
||||
}
|
||||
|
||||
function getPrimaryPos(item) {
|
||||
return isVertical ? item.y : item.x
|
||||
}
|
||||
|
||||
function primaryAnchor(anchors) {
|
||||
return isVertical ? anchors.verticalCenter : anchors.horizontalCenter
|
||||
}
|
||||
|
||||
function crossAnchor(anchors) {
|
||||
return isVertical ? anchors.horizontalCenter : anchors.verticalCenter
|
||||
}
|
||||
|
||||
function outerVisualEdge() {
|
||||
if (edge === "bottom") return "bottom"
|
||||
if (edge === "left") return "right"
|
||||
if (edge === "right") return "left"
|
||||
if (edge === "top") return "top"
|
||||
return "bottom"
|
||||
}
|
||||
|
||||
signal axisEdgeChanged()
|
||||
signal axisOrientationChanged()
|
||||
signal changed() // Single coalesced signal
|
||||
|
||||
onEdgeChanged: {
|
||||
axisEdgeChanged()
|
||||
axisOrientationChanged()
|
||||
changed()
|
||||
}
|
||||
}
|
||||
327
Modules/DankBar/BarCanvas.qml
Normal file
327
Modules/DankBar/BarCanvas.qml
Normal file
@@ -0,0 +1,327 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var barWindow
|
||||
required property var axis
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: -(SettingsData.dankBarGothCornersEnabled && axis.isVertical && axis.edge === "right" ? barWindow._wingR : 0)
|
||||
anchors.rightMargin: -(SettingsData.dankBarGothCornersEnabled && axis.isVertical && axis.edge === "left" ? barWindow._wingR : 0)
|
||||
anchors.topMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "bottom" ? barWindow._wingR : 0)
|
||||
anchors.bottomMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "top" ? barWindow._wingR : 0)
|
||||
|
||||
Canvas {
|
||||
id: barShape
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
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))
|
||||
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
|
||||
|
||||
onWingChanged: requestPaint()
|
||||
onRtChanged: requestPaint()
|
||||
onCorrectWidthChanged: requestPaint()
|
||||
onCorrectHeightChanged: requestPaint()
|
||||
onVisibleChanged: if (visible) requestPaint()
|
||||
Component.onCompleted: requestPaint()
|
||||
|
||||
Connections {
|
||||
target: barWindow
|
||||
function on_BgColorChanged() { barShape.requestPaint() }
|
||||
function on_DprChanged() { barShape.requestPaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onIsLightModeChanged() { barShape.requestPaint() }
|
||||
}
|
||||
|
||||
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 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)
|
||||
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)
|
||||
|
||||
ctx.save()
|
||||
if (isBottom) {
|
||||
ctx.translate(W, H_raw)
|
||||
ctx.rotate(Math.PI)
|
||||
} else if (isLeft) {
|
||||
ctx.translate(0, W)
|
||||
ctx.rotate(-Math.PI / 2)
|
||||
} else if (isRight) {
|
||||
ctx.translate(H_raw, 0)
|
||||
ctx.rotate(Math.PI / 2)
|
||||
}
|
||||
|
||||
drawTopPath()
|
||||
ctx.restore()
|
||||
|
||||
ctx.fillStyle = barWindow._bgColor
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: barTint
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
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))
|
||||
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
|
||||
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()
|
||||
|
||||
Connections {
|
||||
target: barWindow
|
||||
function on_BgColorChanged() { barTint.requestPaint() }
|
||||
function on_DprChanged() { barTint.requestPaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onIsLightModeChanged() { barTint.requestPaint() }
|
||||
}
|
||||
|
||||
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 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)
|
||||
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)
|
||||
|
||||
ctx.save()
|
||||
if (isBottom) {
|
||||
ctx.translate(W, H_raw)
|
||||
ctx.rotate(Math.PI)
|
||||
} else if (isLeft) {
|
||||
ctx.translate(0, W)
|
||||
ctx.rotate(-Math.PI / 2)
|
||||
} else if (isRight) {
|
||||
ctx.translate(H_raw, 0)
|
||||
ctx.rotate(Math.PI / 2)
|
||||
}
|
||||
|
||||
drawTopPath()
|
||||
ctx.restore()
|
||||
|
||||
ctx.fillStyle = Qt.rgba(Theme.surface.r, Theme.surface.g, Theme.surface.b, alphaTint)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
|
||||
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
|
||||
property bool borderEnabled: SettingsData.dankBarBorderEnabled
|
||||
|
||||
onWingChanged: requestPaint()
|
||||
onRtChanged: requestPaint()
|
||||
onBorderEnabledChanged: requestPaint()
|
||||
onCorrectWidthChanged: requestPaint()
|
||||
onCorrectHeightChanged: requestPaint()
|
||||
onVisibleChanged: if (visible) requestPaint()
|
||||
Component.onCompleted: requestPaint()
|
||||
|
||||
Connections {
|
||||
target: barWindow
|
||||
function on_DprChanged() { barBorder.requestPaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onSecondaryChanged() { barBorder.requestPaint() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onDankBarSpacingChanged() { barBorder.requestPaint() }
|
||||
function onDankBarSquareCornersChanged() { barBorder.requestPaint() }
|
||||
function onCornerRadiusChanged() { barBorder.requestPaint() }
|
||||
}
|
||||
|
||||
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 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
|
||||
|
||||
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)
|
||||
|
||||
ctx.save()
|
||||
if (isBottom) {
|
||||
ctx.translate(W, H_raw)
|
||||
ctx.rotate(Math.PI)
|
||||
} else if (isLeft) {
|
||||
ctx.translate(0, W)
|
||||
ctx.rotate(-Math.PI / 2)
|
||||
} else if (isRight) {
|
||||
ctx.translate(H_raw, 0)
|
||||
ctx.rotate(Math.PI / 2)
|
||||
}
|
||||
|
||||
drawTopBorder()
|
||||
ctx.restore()
|
||||
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeStyle = Theme.secondary
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
460
Modules/DankBar/CenterSection.qml
Normal file
460
Modules/DankBar/CenterSection.qml
Normal file
@@ -0,0 +1,460 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var widgetsModel: null
|
||||
property var components: null
|
||||
property bool noBackground: false
|
||||
required property var axis
|
||||
property string section: "center"
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
|
||||
readonly property bool isVertical: axis?.isVertical ?? false
|
||||
readonly property real spacing: noBackground ? 2 : Theme.spacingXS
|
||||
|
||||
property var centerWidgets: []
|
||||
property int totalWidgets: 0
|
||||
property real totalSize: 0
|
||||
|
||||
function updateLayout() {
|
||||
const containerSize = isVertical ? height : width
|
||||
if (containerSize <= 0 || !visible) {
|
||||
return
|
||||
}
|
||||
|
||||
centerWidgets = []
|
||||
totalWidgets = 0
|
||||
totalSize = 0
|
||||
|
||||
let configuredWidgets = 0
|
||||
for (var i = 0; i < centerRepeater.count; i++) {
|
||||
const item = centerRepeater.itemAt(i)
|
||||
if (item && getWidgetVisible(item.widgetId)) {
|
||||
configuredWidgets++
|
||||
if (item.active && item.item) {
|
||||
centerWidgets.push(item.item)
|
||||
totalWidgets++
|
||||
totalSize += isVertical ? item.item.height : item.item.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (totalWidgets > 1) {
|
||||
totalSize += spacing * (totalWidgets - 1)
|
||||
}
|
||||
|
||||
positionWidgets(configuredWidgets)
|
||||
}
|
||||
|
||||
function positionWidgets(configuredWidgets) {
|
||||
if (totalWidgets === 0 || (isVertical ? height : width) <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const parentCenter = (isVertical ? height : width) / 2
|
||||
const isOdd = configuredWidgets % 2 === 1
|
||||
|
||||
centerWidgets.forEach(widget => {
|
||||
if (isVertical) {
|
||||
widget.anchors.verticalCenter = undefined
|
||||
} else {
|
||||
widget.anchors.horizontalCenter = undefined
|
||||
}
|
||||
})
|
||||
|
||||
if (isOdd) {
|
||||
const middleIndex = Math.floor(configuredWidgets / 2)
|
||||
let currentActiveIndex = 0
|
||||
let middleWidget = null
|
||||
|
||||
for (var i = 0; i < centerRepeater.count; i++) {
|
||||
const item = centerRepeater.itemAt(i)
|
||||
if (item && getWidgetVisible(item.widgetId)) {
|
||||
if (currentActiveIndex === middleIndex && item.active && item.item) {
|
||||
middleWidget = item.item
|
||||
break
|
||||
}
|
||||
currentActiveIndex++
|
||||
}
|
||||
}
|
||||
|
||||
if (middleWidget) {
|
||||
const middleSize = isVertical ? middleWidget.height : middleWidget.width
|
||||
if (isVertical) {
|
||||
middleWidget.y = parentCenter - (middleSize / 2)
|
||||
} else {
|
||||
middleWidget.x = parentCenter - (middleSize / 2)
|
||||
}
|
||||
|
||||
let leftWidgets = []
|
||||
let rightWidgets = []
|
||||
let foundMiddle = false
|
||||
|
||||
for (var i = 0; i < centerWidgets.length; i++) {
|
||||
if (centerWidgets[i] === middleWidget) {
|
||||
foundMiddle = true
|
||||
continue
|
||||
}
|
||||
if (!foundMiddle) {
|
||||
leftWidgets.push(centerWidgets[i])
|
||||
} else {
|
||||
rightWidgets.push(centerWidgets[i])
|
||||
}
|
||||
}
|
||||
|
||||
let currentPos = isVertical ? middleWidget.y : middleWidget.x
|
||||
for (var i = leftWidgets.length - 1; i >= 0; i--) {
|
||||
const size = isVertical ? leftWidgets[i].height : leftWidgets[i].width
|
||||
currentPos -= (spacing + size)
|
||||
if (isVertical) {
|
||||
leftWidgets[i].y = currentPos
|
||||
} else {
|
||||
leftWidgets[i].x = currentPos
|
||||
}
|
||||
}
|
||||
|
||||
currentPos = (isVertical ? middleWidget.y : middleWidget.x) + middleSize
|
||||
for (var i = 0; i < rightWidgets.length; i++) {
|
||||
currentPos += spacing
|
||||
if (isVertical) {
|
||||
rightWidgets[i].y = currentPos
|
||||
} else {
|
||||
rightWidgets[i].x = currentPos
|
||||
}
|
||||
currentPos += isVertical ? rightWidgets[i].height : rightWidgets[i].width
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let configuredLeftIndex = (configuredWidgets / 2) - 1
|
||||
let configuredRightIndex = configuredWidgets / 2
|
||||
const halfSpacing = spacing / 2
|
||||
|
||||
let leftWidget = null
|
||||
let rightWidget = null
|
||||
let leftWidgets = []
|
||||
let rightWidgets = []
|
||||
|
||||
let currentConfigIndex = 0
|
||||
for (var i = 0; i < centerRepeater.count; i++) {
|
||||
const item = centerRepeater.itemAt(i)
|
||||
if (item && getWidgetVisible(item.widgetId)) {
|
||||
if (item.active && item.item) {
|
||||
if (currentConfigIndex < configuredLeftIndex) {
|
||||
leftWidgets.push(item.item)
|
||||
} else if (currentConfigIndex === configuredLeftIndex) {
|
||||
leftWidget = item.item
|
||||
} else if (currentConfigIndex === configuredRightIndex) {
|
||||
rightWidget = item.item
|
||||
} else {
|
||||
rightWidgets.push(item.item)
|
||||
}
|
||||
}
|
||||
currentConfigIndex++
|
||||
}
|
||||
}
|
||||
|
||||
if (leftWidget && rightWidget) {
|
||||
const leftSize = isVertical ? leftWidget.height : leftWidget.width
|
||||
if (isVertical) {
|
||||
leftWidget.y = parentCenter - halfSpacing - leftSize
|
||||
rightWidget.y = parentCenter + halfSpacing
|
||||
} else {
|
||||
leftWidget.x = parentCenter - halfSpacing - leftSize
|
||||
rightWidget.x = parentCenter + halfSpacing
|
||||
}
|
||||
|
||||
let currentPos = isVertical ? leftWidget.y : leftWidget.x
|
||||
for (var i = leftWidgets.length - 1; i >= 0; i--) {
|
||||
const size = isVertical ? leftWidgets[i].height : leftWidgets[i].width
|
||||
currentPos -= (spacing + size)
|
||||
if (isVertical) {
|
||||
leftWidgets[i].y = currentPos
|
||||
} else {
|
||||
leftWidgets[i].x = currentPos
|
||||
}
|
||||
}
|
||||
|
||||
currentPos = (isVertical ? rightWidget.y + rightWidget.height : rightWidget.x + rightWidget.width)
|
||||
for (var i = 0; i < rightWidgets.length; i++) {
|
||||
currentPos += spacing
|
||||
if (isVertical) {
|
||||
rightWidgets[i].y = currentPos
|
||||
} else {
|
||||
rightWidgets[i].x = currentPos
|
||||
}
|
||||
currentPos += isVertical ? rightWidgets[i].height : rightWidgets[i].width
|
||||
}
|
||||
} else if (leftWidget && !rightWidget) {
|
||||
const leftSize = isVertical ? leftWidget.height : leftWidget.width
|
||||
if (isVertical) {
|
||||
leftWidget.y = parentCenter - halfSpacing - leftSize
|
||||
} else {
|
||||
leftWidget.x = parentCenter - halfSpacing - leftSize
|
||||
}
|
||||
|
||||
let currentPos = isVertical ? leftWidget.y : leftWidget.x
|
||||
for (var i = leftWidgets.length - 1; i >= 0; i--) {
|
||||
const size = isVertical ? leftWidgets[i].height : leftWidgets[i].width
|
||||
currentPos -= (spacing + size)
|
||||
if (isVertical) {
|
||||
leftWidgets[i].y = currentPos
|
||||
} else {
|
||||
leftWidgets[i].x = currentPos
|
||||
}
|
||||
}
|
||||
|
||||
currentPos = (isVertical ? leftWidget.y + leftWidget.height : leftWidget.x + leftWidget.width) + spacing
|
||||
for (var i = 0; i < rightWidgets.length; i++) {
|
||||
currentPos += spacing
|
||||
if (isVertical) {
|
||||
rightWidgets[i].y = currentPos
|
||||
} else {
|
||||
rightWidgets[i].x = currentPos
|
||||
}
|
||||
currentPos += isVertical ? rightWidgets[i].height : rightWidgets[i].width
|
||||
}
|
||||
} else if (!leftWidget && rightWidget) {
|
||||
if (isVertical) {
|
||||
rightWidget.y = parentCenter + halfSpacing
|
||||
} else {
|
||||
rightWidget.x = parentCenter + halfSpacing
|
||||
}
|
||||
|
||||
let currentPos = (isVertical ? rightWidget.y : rightWidget.x) - spacing
|
||||
for (var i = leftWidgets.length - 1; i >= 0; i--) {
|
||||
const size = isVertical ? leftWidgets[i].height : leftWidgets[i].width
|
||||
currentPos -= size
|
||||
if (isVertical) {
|
||||
leftWidgets[i].y = currentPos
|
||||
} else {
|
||||
leftWidgets[i].x = currentPos
|
||||
}
|
||||
currentPos -= spacing
|
||||
}
|
||||
|
||||
currentPos = (isVertical ? rightWidget.y + rightWidget.height : rightWidget.x + rightWidget.width)
|
||||
for (var i = 0; i < rightWidgets.length; i++) {
|
||||
currentPos += spacing
|
||||
if (isVertical) {
|
||||
rightWidgets[i].y = currentPos
|
||||
} else {
|
||||
rightWidgets[i].x = currentPos
|
||||
}
|
||||
currentPos += isVertical ? rightWidgets[i].height : rightWidgets[i].width
|
||||
}
|
||||
} else if (totalWidgets === 1 && centerWidgets[0]) {
|
||||
const size = isVertical ? centerWidgets[0].height : centerWidgets[0].width
|
||||
if (isVertical) {
|
||||
centerWidgets[0].y = parentCenter - (size / 2)
|
||||
} else {
|
||||
centerWidgets[0].x = parentCenter - (size / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getWidgetVisible(widgetId) {
|
||||
const widgetVisibility = {
|
||||
"cpuUsage": DgopService.dgopAvailable,
|
||||
"memUsage": DgopService.dgopAvailable,
|
||||
"cpuTemp": DgopService.dgopAvailable,
|
||||
"gpuTemp": DgopService.dgopAvailable,
|
||||
"network_speed_monitor": DgopService.dgopAvailable
|
||||
}
|
||||
return widgetVisibility[widgetId] ?? true
|
||||
}
|
||||
|
||||
function getWidgetComponent(widgetId) {
|
||||
// Build dynamic component map including plugins
|
||||
let baseMap = {
|
||||
"launcherButton": "launcherButtonComponent",
|
||||
"workspaceSwitcher": "workspaceSwitcherComponent",
|
||||
"focusedWindow": "focusedWindowComponent",
|
||||
"runningApps": "runningAppsComponent",
|
||||
"clock": "clockComponent",
|
||||
"music": "mediaComponent",
|
||||
"weather": "weatherComponent",
|
||||
"systemTray": "systemTrayComponent",
|
||||
"privacyIndicator": "privacyIndicatorComponent",
|
||||
"clipboard": "clipboardComponent",
|
||||
"cpuUsage": "cpuUsageComponent",
|
||||
"memUsage": "memUsageComponent",
|
||||
"diskUsage": "diskUsageComponent",
|
||||
"cpuTemp": "cpuTempComponent",
|
||||
"gpuTemp": "gpuTempComponent",
|
||||
"notificationButton": "notificationButtonComponent",
|
||||
"battery": "batteryComponent",
|
||||
"controlCenterButton": "controlCenterButtonComponent",
|
||||
"idleInhibitor": "idleInhibitorComponent",
|
||||
"spacer": "spacerComponent",
|
||||
"separator": "separatorComponent",
|
||||
"network_speed_monitor": "networkComponent",
|
||||
"keyboard_layout_name": "keyboardLayoutNameComponent",
|
||||
"vpn": "vpnComponent",
|
||||
"notepadButton": "notepadButtonComponent",
|
||||
"colorPicker": "colorPickerComponent",
|
||||
"systemUpdate": "systemUpdateComponent"
|
||||
}
|
||||
|
||||
// For built-in components, get from components property
|
||||
const componentKey = baseMap[widgetId]
|
||||
if (componentKey && root.components[componentKey]) {
|
||||
return root.components[componentKey]
|
||||
}
|
||||
|
||||
// For plugin components, get from PluginService
|
||||
var parts = widgetId.split(":")
|
||||
var pluginId = parts[0]
|
||||
let pluginComponents = PluginService.getWidgetComponents()
|
||||
return pluginComponents[pluginId] || null
|
||||
}
|
||||
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
anchors.centerIn: parent
|
||||
|
||||
Timer {
|
||||
id: layoutTimer
|
||||
interval: 0
|
||||
repeat: false
|
||||
onTriggered: root.updateLayout()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
layoutTimer.restart()
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
if (width > 0) {
|
||||
layoutTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
onHeightChanged: {
|
||||
if (height > 0) {
|
||||
layoutTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible && (isVertical ? height : width) > 0) {
|
||||
layoutTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: centerRepeater
|
||||
model: root.widgetsModel
|
||||
|
||||
|
||||
Loader {
|
||||
property string widgetId: model.widgetId
|
||||
property var widgetData: model
|
||||
property int spacerSize: model.size || 20
|
||||
|
||||
anchors.verticalCenter: !root.isVertical ? parent.verticalCenter : undefined
|
||||
anchors.horizontalCenter: root.isVertical ? parent.horizontalCenter : undefined
|
||||
active: root.getWidgetVisible(model.widgetId) && (model.widgetId !== "music" || MprisController.activePlayer !== null)
|
||||
sourceComponent: root.getWidgetComponent(model.widgetId)
|
||||
opacity: (model.enabled !== false) ? 1 : 0
|
||||
asynchronous: false
|
||||
|
||||
onLoaded: {
|
||||
if (!item) {
|
||||
return
|
||||
}
|
||||
item.widthChanged.connect(() => layoutTimer.restart())
|
||||
item.heightChanged.connect(() => layoutTimer.restart())
|
||||
if (model.widgetId === "spacer") {
|
||||
item.spacerSize = Qt.binding(() => model.size || 20)
|
||||
}
|
||||
if (root.axis && "axis" in item) {
|
||||
item.axis = Qt.binding(() => root.axis)
|
||||
}
|
||||
if (root.axis && "isVertical" in item) {
|
||||
try {
|
||||
item.isVertical = Qt.binding(() => root.axis.isVertical)
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Inject properties for plugin widgets
|
||||
if ("section" in item) {
|
||||
item.section = root.section
|
||||
}
|
||||
if ("parentScreen" in item) {
|
||||
item.parentScreen = Qt.binding(() => root.parentScreen)
|
||||
}
|
||||
if ("widgetThickness" in item) {
|
||||
item.widgetThickness = Qt.binding(() => root.widgetThickness)
|
||||
}
|
||||
if ("barThickness" in item) {
|
||||
item.barThickness = Qt.binding(() => root.barThickness)
|
||||
}
|
||||
|
||||
// Inject PluginService for plugin widgets
|
||||
if (item.pluginService !== undefined) {
|
||||
var parts = model.widgetId.split(":")
|
||||
var pluginId = parts[0]
|
||||
var variantId = parts.length > 1 ? parts[1] : null
|
||||
|
||||
if (item.pluginId !== undefined) {
|
||||
item.pluginId = pluginId
|
||||
}
|
||||
if (item.variantId !== undefined) {
|
||||
item.variantId = variantId
|
||||
}
|
||||
if (item.variantData !== undefined && variantId) {
|
||||
item.variantData = PluginService.getPluginVariantData(pluginId, variantId)
|
||||
}
|
||||
item.pluginService = PluginService
|
||||
}
|
||||
|
||||
if (item.popoutService !== undefined) {
|
||||
item.popoutService = PopoutService
|
||||
}
|
||||
|
||||
layoutTimer.restart()
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
layoutTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: widgetsModel
|
||||
function onCountChanged() {
|
||||
layoutTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for plugin changes and refresh components
|
||||
Connections {
|
||||
target: PluginService
|
||||
function onPluginLoaded(pluginId) {
|
||||
// Force refresh of component lookups
|
||||
for (var i = 0; i < centerRepeater.count; i++) {
|
||||
var item = centerRepeater.itemAt(i)
|
||||
if (item && item.widgetId.startsWith(pluginId)) {
|
||||
item.sourceComponent = root.getWidgetComponent(item.widgetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
function onPluginUnloaded(pluginId) {
|
||||
// Force refresh of component lookups
|
||||
for (var i = 0; i < centerRepeater.count; i++) {
|
||||
var item = centerRepeater.itemAt(i)
|
||||
if (item && item.widgetId.startsWith(pluginId)) {
|
||||
item.sourceComponent = root.getWidgetComponent(item.widgetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1116
Modules/DankBar/DankBar.qml
Normal file
1116
Modules/DankBar/DankBar.qml
Normal file
File diff suppressed because it is too large
Load Diff
82
Modules/DankBar/LeftSection.qml
Normal file
82
Modules/DankBar/LeftSection.qml
Normal file
@@ -0,0 +1,82 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var widgetsModel: null
|
||||
property var components: null
|
||||
property bool noBackground: false
|
||||
required property var axis
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
|
||||
readonly property bool isVertical: axis?.isVertical ?? false
|
||||
|
||||
implicitHeight: layoutLoader.item ? (layoutLoader.item.implicitHeight || layoutLoader.item.height) : 0
|
||||
implicitWidth: layoutLoader.item ? (layoutLoader.item.implicitWidth || layoutLoader.item.width) : 0
|
||||
|
||||
Loader {
|
||||
id: layoutLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.isVertical ? columnComp : rowComp
|
||||
}
|
||||
|
||||
Component {
|
||||
id: rowComp
|
||||
Row {
|
||||
spacing: noBackground ? 2 : Theme.spacingXS
|
||||
Repeater {
|
||||
model: root.widgetsModel
|
||||
Item {
|
||||
width: widgetLoader.item ? widgetLoader.item.width : 0
|
||||
height: widgetLoader.item ? widgetLoader.item.height : 0
|
||||
WidgetHost {
|
||||
id: widgetLoader
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
widgetId: model.widgetId
|
||||
widgetData: model
|
||||
spacerSize: model.size || 20
|
||||
components: root.components
|
||||
isInColumn: false
|
||||
axis: root.axis
|
||||
section: "left"
|
||||
parentScreen: root.parentScreen
|
||||
widgetThickness: root.widgetThickness
|
||||
barThickness: root.barThickness
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: columnComp
|
||||
Column {
|
||||
width: Math.max(parent.width, 200)
|
||||
spacing: noBackground ? 2 : Theme.spacingXS
|
||||
Repeater {
|
||||
model: root.widgetsModel
|
||||
Item {
|
||||
width: parent.width
|
||||
height: widgetLoader.item ? widgetLoader.item.height : 0
|
||||
WidgetHost {
|
||||
id: widgetLoader
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
widgetId: model.widgetId
|
||||
widgetData: model
|
||||
spacerSize: model.size || 20
|
||||
components: root.components
|
||||
isInColumn: true
|
||||
axis: root.axis
|
||||
section: "left"
|
||||
parentScreen: root.parentScreen
|
||||
widgetThickness: root.widgetThickness
|
||||
barThickness: root.barThickness
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import qs.Widgets
|
||||
DankPopout {
|
||||
id: root
|
||||
|
||||
property string triggerSection: "right"
|
||||
property var triggerScreen: null
|
||||
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
@@ -45,9 +44,9 @@ DankPopout {
|
||||
popupWidth: 400
|
||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 400
|
||||
triggerX: Screen.width - 380 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.popupDistance
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.dankBarSpacing
|
||||
triggerWidth: 70
|
||||
positioning: "center"
|
||||
positioning: ""
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: false
|
||||
visible: shouldBeVisible
|
||||
@@ -274,7 +273,7 @@ DankPopout {
|
||||
height: 32
|
||||
radius: 16
|
||||
color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.top: parent.top
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
@@ -312,7 +311,7 @@ DankPopout {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Health"
|
||||
text: qsTr("Health")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
@@ -347,7 +346,7 @@ DankPopout {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Capacity"
|
||||
text: qsTr("Capacity")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
@@ -416,7 +415,7 @@ DankPopout {
|
||||
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "Power Profile Degradation"
|
||||
text: qsTr("Power Profile Degradation")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.error
|
||||
font.weight: Font.Medium
|
||||
@@ -13,7 +13,6 @@ import qs.Widgets
|
||||
DankPopout {
|
||||
id: root
|
||||
|
||||
property string triggerSection: "right"
|
||||
property var triggerScreen: null
|
||||
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
@@ -27,9 +26,9 @@ DankPopout {
|
||||
popupWidth: 360
|
||||
popupHeight: Math.min(Screen.height - 100, contentLoader.item ? contentLoader.item.implicitHeight : 260)
|
||||
triggerX: Screen.width - 380 - Theme.spacingL
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.popupDistance
|
||||
triggerY: Theme.barHeight - 4 + SettingsData.dankBarSpacing
|
||||
triggerWidth: 70
|
||||
positioning: "center"
|
||||
positioning: ""
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: false
|
||||
visible: shouldBeVisible
|
||||
@@ -97,7 +96,7 @@ DankPopout {
|
||||
height: 32
|
||||
|
||||
StyledText {
|
||||
text: "VPN Connections"
|
||||
text: qsTr("VPN Connections")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -207,7 +206,7 @@ DankPopout {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Disconnect"
|
||||
text: qsTr("Disconnect")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
@@ -263,14 +262,14 @@ DankPopout {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "No VPN profiles found"
|
||||
text: qsTr("No VPN profiles found")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Add a VPN in NetworkManager"
|
||||
text: qsTr("Add a VPN in NetworkManager")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
84
Modules/DankBar/RightSection.qml
Normal file
84
Modules/DankBar/RightSection.qml
Normal file
@@ -0,0 +1,84 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var widgetsModel: null
|
||||
property var components: null
|
||||
property bool noBackground: false
|
||||
required property var axis
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
|
||||
readonly property bool isVertical: axis?.isVertical ?? false
|
||||
|
||||
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
|
||||
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
|
||||
|
||||
Loader {
|
||||
id: layoutLoader
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
sourceComponent: root.isVertical ? columnComp : rowComp
|
||||
}
|
||||
|
||||
Component {
|
||||
id: rowComp
|
||||
Row {
|
||||
spacing: noBackground ? 2 : Theme.spacingXS
|
||||
anchors.right: parent ? parent.right : undefined
|
||||
Repeater {
|
||||
model: root.widgetsModel
|
||||
Item {
|
||||
width: widgetLoader.item ? widgetLoader.item.width : 0
|
||||
height: widgetLoader.item ? widgetLoader.item.height : 0
|
||||
WidgetHost {
|
||||
id: widgetLoader
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
widgetId: model.widgetId
|
||||
widgetData: model
|
||||
spacerSize: model.size || 20
|
||||
components: root.components
|
||||
isInColumn: false
|
||||
axis: root.axis
|
||||
section: "right"
|
||||
parentScreen: root.parentScreen
|
||||
widgetThickness: root.widgetThickness
|
||||
barThickness: root.barThickness
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: columnComp
|
||||
Column {
|
||||
width: parent ? parent.width : 0
|
||||
spacing: noBackground ? 2 : Theme.spacingXS
|
||||
Repeater {
|
||||
model: root.widgetsModel
|
||||
Item {
|
||||
width: parent.width
|
||||
height: widgetLoader.item ? widgetLoader.item.height : 0
|
||||
WidgetHost {
|
||||
id: widgetLoader
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
widgetId: model.widgetId
|
||||
widgetData: model
|
||||
spacerSize: model.size || 20
|
||||
components: root.components
|
||||
isInColumn: true
|
||||
axis: root.axis
|
||||
section: "right"
|
||||
parentScreen: root.parentScreen
|
||||
widgetThickness: root.widgetThickness
|
||||
barThickness: root.barThickness
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
169
Modules/DankBar/WidgetHost.qml
Normal file
169
Modules/DankBar/WidgetHost.qml
Normal file
@@ -0,0 +1,169 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.Mpris
|
||||
import qs.Services
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
|
||||
property string widgetId: ""
|
||||
property var widgetData: null
|
||||
property int spacerSize: 20
|
||||
property var components: null
|
||||
property bool isInColumn: false
|
||||
property var axis: null
|
||||
property string section: "center"
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
|
||||
asynchronous: false
|
||||
|
||||
active: getWidgetVisible(widgetId, DgopService.dgopAvailable) &&
|
||||
(widgetId !== "music" || MprisController.activePlayer !== null)
|
||||
sourceComponent: getWidgetComponent(widgetId, components)
|
||||
opacity: getWidgetEnabled(widgetData?.enabled) ? 1 : 0
|
||||
|
||||
signal contentItemReady(var item)
|
||||
|
||||
Binding {
|
||||
target: root.item
|
||||
when: root.item && "parentScreen" in root.item
|
||||
property: "parentScreen"
|
||||
value: root.parentScreen
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: root.item
|
||||
when: root.item && "section" in root.item
|
||||
property: "section"
|
||||
value: root.section
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: root.item
|
||||
when: root.item && "widgetThickness" in root.item
|
||||
property: "widgetThickness"
|
||||
value: root.widgetThickness
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: root.item
|
||||
when: root.item && "barThickness" in root.item
|
||||
property: "barThickness"
|
||||
value: root.barThickness
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: root.item
|
||||
when: root.item && "axis" in root.item
|
||||
property: "axis"
|
||||
value: root.axis
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: root.item
|
||||
when: root.item && "widgetData" in root.item
|
||||
property: "widgetData"
|
||||
value: root.widgetData
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
contentItemReady(item)
|
||||
if (widgetId === "spacer") {
|
||||
item.spacerSize = Qt.binding(() => spacerSize)
|
||||
}
|
||||
if (axis && "isVertical" in item) {
|
||||
try {
|
||||
item.isVertical = axis.isVertical
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (item.pluginService !== undefined) {
|
||||
var parts = widgetId.split(":")
|
||||
var pluginId = parts[0]
|
||||
var variantId = parts.length > 1 ? parts[1] : null
|
||||
|
||||
if (item.pluginId !== undefined) {
|
||||
item.pluginId = pluginId
|
||||
}
|
||||
if (item.variantId !== undefined) {
|
||||
item.variantId = variantId
|
||||
}
|
||||
if (item.variantData !== undefined && variantId) {
|
||||
item.variantData = PluginService.getPluginVariantData(pluginId, variantId)
|
||||
}
|
||||
item.pluginService = PluginService
|
||||
}
|
||||
|
||||
if (item.popoutService !== undefined) {
|
||||
item.popoutService = PopoutService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getWidgetComponent(widgetId, components) {
|
||||
const componentMap = {
|
||||
"launcherButton": components.launcherButtonComponent,
|
||||
"workspaceSwitcher": components.workspaceSwitcherComponent,
|
||||
"focusedWindow": components.focusedWindowComponent,
|
||||
"runningApps": components.runningAppsComponent,
|
||||
"clock": components.clockComponent,
|
||||
"music": components.mediaComponent,
|
||||
"weather": components.weatherComponent,
|
||||
"systemTray": components.systemTrayComponent,
|
||||
"privacyIndicator": components.privacyIndicatorComponent,
|
||||
"clipboard": components.clipboardComponent,
|
||||
"cpuUsage": components.cpuUsageComponent,
|
||||
"memUsage": components.memUsageComponent,
|
||||
"diskUsage": components.diskUsageComponent,
|
||||
"cpuTemp": components.cpuTempComponent,
|
||||
"gpuTemp": components.gpuTempComponent,
|
||||
"notificationButton": components.notificationButtonComponent,
|
||||
"battery": components.batteryComponent,
|
||||
"controlCenterButton": components.controlCenterButtonComponent,
|
||||
"idleInhibitor": components.idleInhibitorComponent,
|
||||
"spacer": components.spacerComponent,
|
||||
"separator": components.separatorComponent,
|
||||
"network_speed_monitor": components.networkComponent,
|
||||
"keyboard_layout_name": components.keyboardLayoutNameComponent,
|
||||
"vpn": components.vpnComponent,
|
||||
"notepadButton": components.notepadButtonComponent,
|
||||
"colorPicker": components.colorPickerComponent,
|
||||
"systemUpdate": components.systemUpdateComponent
|
||||
}
|
||||
|
||||
if (componentMap[widgetId]) {
|
||||
return componentMap[widgetId]
|
||||
}
|
||||
|
||||
var parts = widgetId.split(":")
|
||||
var pluginId = parts[0]
|
||||
|
||||
let pluginMap = PluginService.getWidgetComponents()
|
||||
return pluginMap[pluginId] || null
|
||||
}
|
||||
|
||||
function getWidgetVisible(widgetId, dgopAvailable) {
|
||||
const widgetVisibility = {
|
||||
"cpuUsage": dgopAvailable,
|
||||
"memUsage": dgopAvailable,
|
||||
"cpuTemp": dgopAvailable,
|
||||
"gpuTemp": dgopAvailable,
|
||||
"network_speed_monitor": dgopAvailable
|
||||
}
|
||||
|
||||
return widgetVisibility[widgetId] ?? true
|
||||
}
|
||||
|
||||
function getWidgetEnabled(enabled) {
|
||||
return enabled !== false
|
||||
}
|
||||
}
|
||||
126
Modules/DankBar/Widgets/Battery.qml
Normal file
126
Modules/DankBar/Widgets/Battery.qml
Normal file
@@ -0,0 +1,126 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: Theme.barIconSize(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
|
||||
}
|
||||
|
||||
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.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: SettingsData.dankBarNoBackground ? 1 : 2
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
}
|
||||
toggleBatteryPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Modules/DankBar/Widgets/ClipboardButton.qml
Normal file
57
Modules/DankBar/Widgets/ClipboardButton.qml
Normal file
@@ -0,0 +1,57 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
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()
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
247
Modules/DankBar/Widgets/Clock.qml
Normal file
247
Modules/DankBar/Widgets/Clock.qml
Normal file
@@ -0,0 +1,247 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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(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(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)
|
||||
}
|
||||
|
||||
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d")
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(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) {
|
||||
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.clockClicked()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,21 +5,23 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
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 widgetHeight: 30
|
||||
property real barHeight: 48
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
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()
|
||||
|
||||
width: colorPickerIcon.width + horizontalPadding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (colorPickerIcon.width + horizontalPadding * 2)
|
||||
height: isVertical ? (colorPickerIcon.height + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -32,7 +34,7 @@ Rectangle {
|
||||
|
||||
anchors.centerIn: parent
|
||||
name: "palette"
|
||||
size: Theme.iconSize - 6
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: colorPickerArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
@@ -43,12 +45,10 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
console.log("Color picker button clicked!")
|
||||
root.colorPickerRequested();
|
||||
}
|
||||
}
|
||||
|
||||
// Signal to notify TopBar to open color picker
|
||||
signal colorPickerRequested()
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool isActive: false
|
||||
property string section: "right"
|
||||
property var popupTarget: null
|
||||
@@ -14,17 +16,17 @@ Rectangle {
|
||||
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
|
||||
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
|
||||
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon
|
||||
property real widgetHeight: 30
|
||||
property real barHeight: 48
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
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()
|
||||
|
||||
width: controlIndicators.implicitWidth + horizontalPadding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (controlIndicators.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (controlColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -32,9 +34,106 @@ Rectangle {
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
||||
}
|
||||
|
||||
Column {
|
||||
id: controlColumn
|
||||
visible: root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
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.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showNetworkIcon
|
||||
}
|
||||
|
||||
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 {
|
||||
return "volume_up"
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@@ -52,7 +151,7 @@ Rectangle {
|
||||
|
||||
return NetworkService.wifiSignalIcon;
|
||||
}
|
||||
size: Theme.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return Theme.primary;
|
||||
@@ -70,7 +169,7 @@ Rectangle {
|
||||
id: bluetoothIcon
|
||||
|
||||
name: "bluetooth"
|
||||
size: Theme.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||
@@ -98,7 +197,7 @@ Rectangle {
|
||||
}
|
||||
return "volume_up";
|
||||
}
|
||||
size: Theme.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
@@ -131,7 +230,7 @@ Rectangle {
|
||||
|
||||
DankIcon {
|
||||
name: "mic"
|
||||
size: Theme.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: false // TODO: Add mic detection
|
||||
@@ -140,7 +239,7 @@ Rectangle {
|
||||
// Fallback settings icon when all other icons are hidden
|
||||
DankIcon {
|
||||
name: "settings"
|
||||
size: Theme.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
|
||||
@@ -156,11 +255,10 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0);
|
||||
const currentScreen = parentScreen || Screen;
|
||||
const screenX = currentScreen.x || 0;
|
||||
const relativeX = globalPos.x - screenX;
|
||||
popupTarget.setTriggerPosition(relativeX, barHeight + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance, width, section, currentScreen);
|
||||
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();
|
||||
}
|
||||
@@ -7,21 +7,25 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
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 barHeight: 48
|
||||
property real widgetHeight: 30
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
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: cpuContent.implicitWidth + horizontalPadding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (cpuContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (cpuColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -43,11 +47,10 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0);
|
||||
const currentScreen = parentScreen || Screen;
|
||||
const screenX = currentScreen.x || 0;
|
||||
const relativeX = globalPos.x - screenX;
|
||||
popupTarget.setTriggerPosition(relativeX, barHeight + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance, width, section, currentScreen);
|
||||
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)
|
||||
}
|
||||
DgopService.setSortBy("cpu");
|
||||
if (root.toggleProcessList) {
|
||||
@@ -57,15 +60,53 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
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.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (DgopService.cpuUsage > 80) {
|
||||
return Theme.tempDanger;
|
||||
@@ -88,7 +129,7 @@ Rectangle {
|
||||
|
||||
return DgopService.cpuUsage.toFixed(0) + "%";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -97,12 +138,12 @@ Rectangle {
|
||||
|
||||
StyledTextMetrics {
|
||||
id: cpuBaseline
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100%"
|
||||
}
|
||||
|
||||
width: Math.max(cpuBaseline.width, paintedWidth)
|
||||
width: root.minimumWidth ? Math.max(cpuBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
159
Modules/DankBar/Widgets/CpuTemperature.qml
Normal file
159
Modules/DankBar/Widgets/CpuTemperature.qml
Normal file
@@ -0,0 +1,159 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
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 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"]);
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["cpu"]);
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cpuTempArea
|
||||
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -7,10 +7,14 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property var widgetData: null
|
||||
property real widgetHeight: 30
|
||||
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.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
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) {
|
||||
@@ -46,11 +50,11 @@ Rectangle {
|
||||
return parseFloat(percentStr) || 0
|
||||
}
|
||||
|
||||
width: diskContent.implicitWidth + horizontalPadding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (diskContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (diskColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
@@ -100,16 +104,83 @@ Rectangle {
|
||||
target: SettingsData
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: diskArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: root.isVertical
|
||||
onEntered: {
|
||||
if (root.isVertical && root.selectedMount) {
|
||||
tooltipLoader.active = true
|
||||
if (tooltipLoader.item) {
|
||||
const globalPos = mapToGlobal(width / 2, height / 2)
|
||||
const currentScreen = root.parentScreen || Screen
|
||||
const screenX = currentScreen ? currentScreen.x : 0
|
||||
const screenY = currentScreen ? currentScreen.y : 0
|
||||
const relativeY = globalPos.y - screenY
|
||||
const tooltipX = root.axis?.edge === "left" ? (Theme.barHeight + SettingsData.dankBarSpacing + Theme.spacingXS) : (currentScreen.width - Theme.barHeight - SettingsData.dankBarSpacing - Theme.spacingXS)
|
||||
const isLeft = root.axis?.edge === "left"
|
||||
tooltipLoader.item.show(root.selectedMount.mount, screenX + tooltipX, relativeY, currentScreen, isLeft, !isLeft)
|
||||
}
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hide()
|
||||
}
|
||||
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.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (root.diskUsagePercent > 90) {
|
||||
return Theme.tempDanger
|
||||
@@ -129,7 +200,7 @@ Rectangle {
|
||||
}
|
||||
return root.selectedMount.mount
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -144,7 +215,7 @@ Rectangle {
|
||||
}
|
||||
return root.diskUsagePercent.toFixed(0) + "%"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -153,7 +224,7 @@ Rectangle {
|
||||
|
||||
StyledTextMetrics {
|
||||
id: diskBaseline
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100%"
|
||||
}
|
||||
273
Modules/DankBar/Widgets/FocusedApp.qml
Normal file
273
Modules/DankBar/Widgets/FocusedApp.qml
Normal file
@@ -0,0 +1,273 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
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
|
||||
property var activeDesktopEntry: null
|
||||
|
||||
Component.onCompleted: {
|
||||
updateDesktopEntry()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DesktopEntries
|
||||
function onApplicationsChanged() {
|
||||
root.updateDesktopEntry()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onActiveWindowChanged() {
|
||||
root.updateDesktopEntry()
|
||||
}
|
||||
}
|
||||
|
||||
function updateDesktopEntry() {
|
||||
if (activeWindow && activeWindow.appId) {
|
||||
const moddedId = Paths.moddedAppId(activeWindow.appId)
|
||||
activeDesktopEntry = DesktopEntries.heuristicLookup(moddedId)
|
||||
} else {
|
||||
activeDesktopEntry = null
|
||||
}
|
||||
}
|
||||
readonly property bool hasWindowsOnCurrentWorkspace: {
|
||||
if (CompositorService.isNiri) {
|
||||
let currentWorkspaceId = null
|
||||
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||
const ws = NiriService.allWorkspaces[i]
|
||||
if (ws.is_focused) {
|
||||
currentWorkspaceId = ws.id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentWorkspaceId) {
|
||||
return false
|
||||
}
|
||||
|
||||
const workspaceWindows = NiriService.windows.filter(w => w.workspace_id === currentWorkspaceId)
|
||||
return workspaceWindows.length > 0 && activeWindow && activeWindow.title
|
||||
}
|
||||
|
||||
if (CompositorService.isHyprland) {
|
||||
if (!Hyprland.focusedWorkspace || !activeWindow || !activeWindow.title) {
|
||||
return false
|
||||
}
|
||||
|
||||
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
|
||||
const activeHyprToplevel = hyprlandToplevels.find(t => t.wayland === activeWindow)
|
||||
|
||||
if (!activeHyprToplevel || !activeHyprToplevel.workspace) {
|
||||
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()
|
||||
}
|
||||
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.isVertical
|
||||
|
||||
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(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;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: root.isVertical
|
||||
onEntered: {
|
||||
if (root.isVertical && activeWindow && activeWindow.appId && root.parentScreen) {
|
||||
tooltipLoader.active = true
|
||||
if (tooltipLoader.item) {
|
||||
const globalPos = mapToGlobal(width / 2, height / 2)
|
||||
const currentScreen = root.parentScreen
|
||||
const screenX = currentScreen ? currentScreen.x : 0
|
||||
const screenY = currentScreen ? currentScreen.y : 0
|
||||
const relativeY = globalPos.y - screenY
|
||||
const tooltipX = root.axis?.edge === "left" ? (Theme.barHeight + SettingsData.dankBarSpacing + Theme.spacingXS) : (currentScreen.width - Theme.barHeight - SettingsData.dankBarSpacing - Theme.spacingXS)
|
||||
|
||||
const appName = activeDesktopEntry && activeDesktopEntry.name ? activeDesktopEntry.name : activeWindow.appId
|
||||
const title = activeWindow.title || ""
|
||||
const tooltipText = appName + (title ? " • " + title : "")
|
||||
|
||||
const isLeft = root.axis?.edge === "left"
|
||||
tooltipLoader.item.show(tooltipText, screenX + tooltipX, relativeY, currentScreen, isLeft, !isLeft)
|
||||
}
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hide()
|
||||
}
|
||||
tooltipLoader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property bool showPercentage: true
|
||||
property bool showIcon: true
|
||||
property var toggleProcessList
|
||||
@@ -14,10 +16,11 @@ Rectangle {
|
||||
property var popupTarget: null
|
||||
property var parentScreen: null
|
||||
property var widgetData: null
|
||||
property real barHeight: 48
|
||||
property real widgetHeight: 30
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex !== undefined) ? widgetData.selectedGpuIndex : 0
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
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;
|
||||
@@ -37,11 +40,11 @@ Rectangle {
|
||||
const sectionId = sections[s];
|
||||
let widgets = [];
|
||||
if (sectionId === "left") {
|
||||
widgets = SettingsData.topBarLeftWidgets.slice();
|
||||
widgets = SettingsData.dankBarLeftWidgets.slice();
|
||||
} else if (sectionId === "center") {
|
||||
widgets = SettingsData.topBarCenterWidgets.slice();
|
||||
widgets = SettingsData.dankBarCenterWidgets.slice();
|
||||
} else if (sectionId === "right") {
|
||||
widgets = SettingsData.topBarRightWidgets.slice();
|
||||
widgets = SettingsData.dankBarRightWidgets.slice();
|
||||
}
|
||||
for (let i = 0; i < widgets.length; i++) {
|
||||
const widget = widgets[i];
|
||||
@@ -53,11 +56,11 @@ Rectangle {
|
||||
"pciId": pciId
|
||||
};
|
||||
if (sectionId === "left") {
|
||||
SettingsData.setTopBarLeftWidgets(widgets);
|
||||
SettingsData.setDankBarLeftWidgets(widgets);
|
||||
} else if (sectionId === "center") {
|
||||
SettingsData.setTopBarCenterWidgets(widgets);
|
||||
SettingsData.setDankBarCenterWidgets(widgets);
|
||||
} else if (sectionId === "right") {
|
||||
SettingsData.setTopBarRightWidgets(widgets);
|
||||
SettingsData.setDankBarRightWidgets(widgets);
|
||||
}
|
||||
return ;
|
||||
}
|
||||
@@ -65,11 +68,11 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
width: gpuTempContent.implicitWidth + horizontalPadding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (gpuTempContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (gpuTempColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -78,20 +81,14 @@ Rectangle {
|
||||
}
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["gpu"]);
|
||||
console.log("GpuTemperature widget - pciId:", widgetData ? widgetData.pciId : "no widgetData", "selectedGpuIndex:", widgetData ? widgetData.selectedGpuIndex : "no widgetData");
|
||||
// Add this widget's PCI ID to the service
|
||||
if (widgetData && widgetData.pciId) {
|
||||
console.log("Adding GPU PCI ID to service:", widgetData.pciId);
|
||||
DgopService.addGpuPciId(widgetData.pciId);
|
||||
} else {
|
||||
console.log("No PCI ID in widget data, starting auto-detection");
|
||||
// No PCI ID saved, auto-detect and save the first GPU
|
||||
autoSaveTimer.running = true;
|
||||
}
|
||||
}
|
||||
Component.onDestruction: {
|
||||
DgopService.removeRef(["gpu"]);
|
||||
// Remove this widget's PCI ID from the service
|
||||
if (widgetData && widgetData.pciId) {
|
||||
DgopService.removeGpuPciId(widgetData.pciId);
|
||||
}
|
||||
@@ -117,11 +114,10 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0);
|
||||
const currentScreen = parentScreen || Screen;
|
||||
const screenX = currentScreen.x || 0;
|
||||
const relativeX = globalPos.x - screenX;
|
||||
popupTarget.setTriggerPosition(relativeX, barHeight + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance, width, section, currentScreen);
|
||||
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)
|
||||
}
|
||||
DgopService.setSortBy("cpu");
|
||||
if (root.toggleProcessList) {
|
||||
@@ -131,15 +127,53 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
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.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (root.displayTemp > 80) {
|
||||
return Theme.tempDanger;
|
||||
@@ -162,7 +196,7 @@ Rectangle {
|
||||
|
||||
return Math.round(root.displayTemp) + "°";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -171,12 +205,12 @@ Rectangle {
|
||||
|
||||
StyledTextMetrics {
|
||||
id: gpuTempBaseline
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100°"
|
||||
}
|
||||
|
||||
width: Math.max(gpuTempBaseline.width, paintedWidth)
|
||||
width: root.minimumWidth ? Math.max(gpuTempBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
@@ -8,17 +8,19 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
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 widgetHeight: 30
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
property real widgetThickness: 30
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
|
||||
|
||||
width: idleIcon.width + horizontalPadding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (idleIcon.width + horizontalPadding * 2)
|
||||
height: isVertical ? (idleIcon.height + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -31,7 +33,7 @@ Rectangle {
|
||||
|
||||
anchors.centerIn: parent
|
||||
name: SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
size: Theme.iconSize - 6
|
||||
size: Theme.barIconSize(barThickness, -4)
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
@@ -10,15 +10,19 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
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: contentRow.implicitWidth + horizontalPadding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (contentRow.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (contentColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -47,15 +51,46 @@ 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.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
118
Modules/DankBar/Widgets/LauncherButton.qml
Normal file
118
Modules/DankBar/Widgets/LauncherButton.qml
Normal file
@@ -0,0 +1,118 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
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))
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: widgetThickness
|
||||
height: widgetThickness
|
||||
|
||||
MouseArea {
|
||||
id: launcherArea
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: launcherContent
|
||||
|
||||
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 {
|
||||
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 {
|
||||
colorization: 1
|
||||
colorizationColor: Theme.effectiveLogoColor
|
||||
brightness: SettingsData.launcherLogoBrightness
|
||||
contrast: SettingsData.launcherLogoContrast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
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
|
||||
@@ -21,27 +23,36 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
readonly property int currentContentWidth: {
|
||||
// Calculate actual content width:
|
||||
// AudioViz (20) + spacing + [text + spacing] + controls (prev:20 + spacing + play:24 + spacing + next:20) + padding
|
||||
if (isVertical) {
|
||||
return widgetThickness;
|
||||
}
|
||||
const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20;
|
||||
// ~72px total
|
||||
const audioVizWidth = 20;
|
||||
const contentWidth = audioVizWidth + Theme.spacingXS + controlsWidth;
|
||||
return contentWidth + (textWidth > 0 ? textWidth + Theme.spacingXS : 0) + horizontalPadding * 2;
|
||||
}
|
||||
readonly property int currentContentHeight: {
|
||||
if (!isVertical) {
|
||||
return widgetThickness;
|
||||
}
|
||||
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 barHeight: 48
|
||||
property real widgetHeight: 30
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
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()
|
||||
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: currentContentWidth
|
||||
height: currentContentHeight
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -57,6 +68,7 @@ Rectangle {
|
||||
target: root
|
||||
opacity: 1
|
||||
width: currentContentWidth
|
||||
height: currentContentHeight
|
||||
}
|
||||
|
||||
},
|
||||
@@ -67,7 +79,8 @@ Rectangle {
|
||||
PropertyChanges {
|
||||
target: root
|
||||
opacity: 0
|
||||
width: 0
|
||||
width: isVertical ? widgetThickness : 0
|
||||
height: isVertical ? 0 : widgetThickness
|
||||
}
|
||||
|
||||
}
|
||||
@@ -83,7 +96,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
properties: "opacity,width"
|
||||
properties: isVertical ? "opacity,height" : "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
@@ -96,7 +109,7 @@ Rectangle {
|
||||
to: "shown"
|
||||
|
||||
NumberAnimation {
|
||||
properties: "opacity,width"
|
||||
properties: isVertical ? "opacity,height" : "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
@@ -104,9 +117,107 @@ Rectangle {
|
||||
}
|
||||
]
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: mediaRow
|
||||
|
||||
visible: !root.isVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
@@ -156,7 +267,7 @@ Rectangle {
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: textContainer.displayText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
wrapMode: Text.NoWrap
|
||||
@@ -208,13 +319,12 @@ Rectangle {
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onPressed: {
|
||||
if (root.popupTarget && root.popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0);
|
||||
const currentScreen = root.parentScreen || Screen;
|
||||
const screenX = currentScreen.x || 0;
|
||||
const relativeX = globalPos.x - screenX;
|
||||
root.popupTarget.setTriggerPosition(relativeX, barHeight + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance, root.width, root.section, currentScreen);
|
||||
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)
|
||||
}
|
||||
root.clicked();
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +440,13 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,10 +8,14 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
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
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
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) {
|
||||
@@ -25,11 +29,11 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
width: contentRow.implicitWidth + horizontalPadding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (contentRow.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (contentColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -51,15 +55,57 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
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.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -70,13 +116,13 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
text: "↓"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.info
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.networkRxRate > 0 ? formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -86,7 +132,7 @@ Rectangle {
|
||||
|
||||
StyledTextMetrics {
|
||||
id: rxBaseline
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "88.8 MB/s"
|
||||
}
|
||||
@@ -109,13 +155,13 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
text: "↑"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.error
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: DgopService.networkTxRate > 0 ? formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -125,7 +171,7 @@ Rectangle {
|
||||
|
||||
StyledTextMetrics {
|
||||
id: txBaseline
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "88.8 MB/s"
|
||||
}
|
||||
96
Modules/DankBar/Widgets/NotepadButton.qml
Normal file
96
Modules/DankBar/Widgets/NotepadButton.qml
Normal file
@@ -0,0 +1,96 @@
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
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 : ""
|
||||
)
|
||||
|
||||
function resolveNotepadInstance() {
|
||||
if (typeof notepadSlideoutVariants === "undefined" || !notepadSlideoutVariants || !notepadSlideoutVariants.instances) {
|
||||
return null
|
||||
}
|
||||
|
||||
const targetScreen = focusedScreenName
|
||||
if (targetScreen) {
|
||||
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
|
||||
var slideout = notepadSlideoutVariants.instances[i]
|
||||
if (slideout.modelData && slideout.modelData.name === targetScreen) {
|
||||
return slideout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return notepadSlideoutVariants.instances.length > 0 ? notepadSlideoutVariants.instances[0] : null
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
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
|
||||
onPressed: {
|
||||
const inst = root.notepadInstance
|
||||
if (inst) {
|
||||
inst.toggle()
|
||||
}
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
76
Modules/DankBar/Widgets/NotificationCenterButton.qml
Normal file
76
Modules/DankBar/Widgets/NotificationCenterButton.qml
Normal file
@@ -0,0 +1,76 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
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()
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,23 +7,27 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
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 widgetHeight: 30
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 2 : Theme.spacingS
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
|
||||
readonly property bool hasActivePrivacy: PrivacyService.anyPrivacyActive
|
||||
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
|
||||
|
||||
width: hasActivePrivacy ? (contentWidth + horizontalPadding * 2) : 0
|
||||
height: hasActivePrivacy ? widgetHeight : 0
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (hasActivePrivacy ? (contentWidth + horizontalPadding * 2) : 0)
|
||||
height: isVertical ? (hasActivePrivacy ? (contentHeight + horizontalPadding * 2) : 0) : (hasActivePrivacy ? widgetThickness : 0)
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
visible: hasActivePrivacy
|
||||
opacity: hasActivePrivacy ? 1 : 0
|
||||
enabled: hasActivePrivacy
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -43,10 +47,76 @@ 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: hasActivePrivacy
|
||||
visible: !root.isVertical && hasActivePrivacy
|
||||
|
||||
Item {
|
||||
width: 18
|
||||
@@ -129,7 +199,7 @@ Rectangle {
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: PrivacyService.getPrivacySummary()
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
@@ -158,7 +228,17 @@ Rectangle {
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
enabled: hasActivePrivacy && visible
|
||||
enabled: hasActivePrivacy && visible && !isVertical
|
||||
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
enabled: hasActivePrivacy && visible && isVertical
|
||||
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
@@ -7,27 +7,32 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
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 barHeight: 48
|
||||
property real widgetHeight: 30
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
property real barThickness: 48
|
||||
property real widgetThickness: 30
|
||||
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: ramContent.implicitWidth + horizontalPadding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : (ramContent.implicitWidth + horizontalPadding * 2)
|
||||
height: isVertical ? (ramColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
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"]);
|
||||
}
|
||||
@@ -43,11 +48,10 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
if (popupTarget && popupTarget.setTriggerPosition) {
|
||||
const globalPos = mapToGlobal(0, 0);
|
||||
const currentScreen = parentScreen || Screen;
|
||||
const screenX = currentScreen.x || 0;
|
||||
const relativeX = globalPos.x - screenX;
|
||||
popupTarget.setTriggerPosition(relativeX, barHeight + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance, width, section, currentScreen);
|
||||
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)
|
||||
}
|
||||
DgopService.setSortBy("memory");
|
||||
if (root.toggleProcessList) {
|
||||
@@ -57,15 +61,53 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
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.iconSize - 8
|
||||
size: Theme.barIconSize(barThickness)
|
||||
color: {
|
||||
if (DgopService.memoryUsage > 90) {
|
||||
return Theme.tempDanger;
|
||||
@@ -88,7 +130,7 @@ Rectangle {
|
||||
|
||||
return DgopService.memoryUsage.toFixed(0) + "%";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -97,12 +139,12 @@ Rectangle {
|
||||
|
||||
StyledTextMetrics {
|
||||
id: ramBaseline
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
text: "100%"
|
||||
}
|
||||
|
||||
width: Math.max(ramBaseline.width, paintedWidth)
|
||||
width: root.minimumWidth ? Math.max(ramBaseline.width, paintedWidth) : paintedWidth
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
@@ -10,13 +10,15 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property string section: "left"
|
||||
property var parentScreen
|
||||
property var hoveredItem: null
|
||||
property var topBar: null
|
||||
property real widgetHeight: 30
|
||||
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 2 : Theme.spacingS
|
||||
// The visual root for this window
|
||||
property real widgetThickness: 30
|
||||
property real barThickness: 48
|
||||
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
|
||||
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
|
||||
readonly property var sortedToplevels: {
|
||||
if (SettingsData.runningAppsCurrentWorkspace) {
|
||||
@@ -25,7 +27,7 @@ Rectangle {
|
||||
return CompositorService.sortedToplevels;
|
||||
}
|
||||
readonly property int windowCount: sortedToplevels.length
|
||||
readonly property int calculatedWidth: {
|
||||
readonly property int calculatedSize: {
|
||||
if (windowCount === 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -37,9 +39,9 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
width: calculatedWidth
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetThickness : calculatedSize
|
||||
height: isVertical ? calculatedSize : widgetThickness
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
visible: windowCount > 0
|
||||
clip: false
|
||||
color: {
|
||||
@@ -47,7 +49,7 @@ Rectangle {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
if (SettingsData.topBarNoBackground) {
|
||||
if (SettingsData.dankBarNoBackground) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
@@ -143,18 +145,22 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: windowRow
|
||||
|
||||
Loader {
|
||||
id: layoutLoader
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
sourceComponent: root.isVertical ? columnLayout : rowLayout
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: windowRepeater
|
||||
Component {
|
||||
id: rowLayout
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
model: sortedToplevels
|
||||
Repeater {
|
||||
id: windowRepeater
|
||||
model: sortedToplevels
|
||||
|
||||
delegate: Item {
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
|
||||
property bool isFocused: modelData.activated
|
||||
@@ -267,7 +273,7 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !SettingsData.runningAppsCompactMode
|
||||
text: windowTitle
|
||||
font.pixelSize: Theme.fontSizeMedium - 1
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
@@ -288,10 +294,10 @@ Rectangle {
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hideTooltip();
|
||||
tooltipLoader.item.hide();
|
||||
}
|
||||
tooltipLoader.active = false;
|
||||
|
||||
|
||||
windowContextMenuLoader.active = true;
|
||||
if (windowContextMenuLoader.item) {
|
||||
windowContextMenuLoader.item.currentWindow = toplevelObject;
|
||||
@@ -299,29 +305,35 @@ Rectangle {
|
||||
const screenX = root.parentScreen ? root.parentScreen.x : 0;
|
||||
const screenY = root.parentScreen ? root.parentScreen.y : 0;
|
||||
const relativeX = globalPos.x - screenX;
|
||||
const yPos = Theme.barHeight + SettingsData.topBarSpacing - 7;
|
||||
const yPos = root.isVertical ? delegateItem.height / 2 : (Theme.barHeight + SettingsData.dankBarSpacing - 7);
|
||||
windowContextMenuLoader.item.showAt(relativeX, yPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
root.hoveredItem = delegateItem;
|
||||
const globalPos = delegateItem.mapToGlobal(
|
||||
delegateItem.width / 2, delegateItem.height);
|
||||
tooltipLoader.active = true;
|
||||
if (tooltipLoader.item) {
|
||||
const tooltipY = Theme.barHeight
|
||||
+ SettingsData.topBarSpacing + Theme.spacingXS;
|
||||
tooltipLoader.item.showTooltip(
|
||||
delegateItem.tooltipText, globalPos.x,
|
||||
tooltipY, root.parentScreen);
|
||||
if (root.isVertical) {
|
||||
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.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);
|
||||
const isLeft = root.axis?.edge === "left";
|
||||
tooltipLoader.item.show(delegateItem.tooltipText, screenX + tooltipX, relativeY, root.parentScreen, isLeft, !isLeft);
|
||||
} else {
|
||||
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height);
|
||||
const tooltipY = Theme.barHeight + SettingsData.dankBarSpacing + Theme.spacingXS;
|
||||
tooltipLoader.item.show(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if (root.hoveredItem === delegateItem) {
|
||||
root.hoveredItem = null;
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hideTooltip();
|
||||
tooltipLoader.item.hide();
|
||||
}
|
||||
|
||||
tooltipLoader.active = false;
|
||||
@@ -330,6 +342,198 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: columnLayout
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
id: windowRepeater
|
||||
model: sortedToplevels
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
|
||||
property bool isFocused: modelData.activated
|
||||
property string appId: modelData.appId || ""
|
||||
property string windowTitle: modelData.title || "(Unnamed)"
|
||||
property var toplevelObject: modelData
|
||||
property string tooltipText: {
|
||||
let appName = "Unknown";
|
||||
if (appId) {
|
||||
const desktopEntry = DesktopEntries.heuristicLookup(appId);
|
||||
appName = desktopEntry
|
||||
&& desktopEntry.name ? desktopEntry.name : appId;
|
||||
}
|
||||
return appName + (windowTitle ? " • " + windowTitle : "")
|
||||
}
|
||||
|
||||
width: SettingsData.runningAppsCompactMode ? 24 : (24 + Theme.spacingXS + 120)
|
||||
height: 24
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (isFocused) {
|
||||
return mouseArea.containsMouse ? Qt.rgba(
|
||||
Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.3) : Qt.rgba(
|
||||
Theme.primary.r,
|
||||
Theme.primary.g,
|
||||
Theme.primary.b,
|
||||
0.2);
|
||||
} else {
|
||||
return mouseArea.containsMouse ? Qt.rgba(
|
||||
Theme.primaryHover.r,
|
||||
Theme.primaryHover.g,
|
||||
Theme.primaryHover.b,
|
||||
0.1) : "transparent";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: SettingsData.runningAppsCompactMode ? (parent.width - 18) / 2 : Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 18
|
||||
height: 18
|
||||
source: {
|
||||
const moddedId = Paths.moddedAppId(appId)
|
||||
if (moddedId.toLowerCase().includes("steam_app")) {
|
||||
return ""
|
||||
}
|
||||
return Quickshell.iconPath(DesktopEntries.heuristicLookup(moddedId)?.icon, true)
|
||||
}
|
||||
smooth: true
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: SettingsData.runningAppsCompactMode ? (parent.width - 18) / 2 : Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
size: 18
|
||||
name: "sports_esports"
|
||||
color: Theme.surfaceText
|
||||
visible: {
|
||||
const moddedId = Paths.moddedAppId(appId)
|
||||
return moddedId.toLowerCase().includes("steam_app")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: {
|
||||
const moddedId = Paths.moddedAppId(appId)
|
||||
const isSteamApp = moddedId.toLowerCase().includes("steam_app")
|
||||
return !iconImg.visible && !isSteamApp
|
||||
}
|
||||
text: {
|
||||
if (!appId) {
|
||||
return "?";
|
||||
}
|
||||
|
||||
const desktopEntry = DesktopEntries.heuristicLookup(appId);
|
||||
if (desktopEntry && desktopEntry.name) {
|
||||
return desktopEntry.name.charAt(0).toUpperCase();
|
||||
}
|
||||
|
||||
return appId.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 10
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.left: iconImg.right
|
||||
anchors.leftMargin: Theme.spacingXS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !SettingsData.runningAppsCompactMode
|
||||
text: windowTitle
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (toplevelObject) {
|
||||
toplevelObject.activate();
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hide();
|
||||
}
|
||||
tooltipLoader.active = false;
|
||||
|
||||
windowContextMenuLoader.active = true;
|
||||
if (windowContextMenuLoader.item) {
|
||||
windowContextMenuLoader.item.currentWindow = toplevelObject;
|
||||
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0);
|
||||
const screenX = root.parentScreen ? root.parentScreen.x : 0;
|
||||
const screenY = root.parentScreen ? root.parentScreen.y : 0;
|
||||
const relativeX = globalPos.x - screenX;
|
||||
const yPos = root.isVertical ? delegateItem.height / 2 : (Theme.barHeight + SettingsData.dankBarSpacing - 7);
|
||||
windowContextMenuLoader.item.showAt(relativeX, yPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
root.hoveredItem = delegateItem;
|
||||
tooltipLoader.active = true;
|
||||
if (tooltipLoader.item) {
|
||||
if (root.isVertical) {
|
||||
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.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);
|
||||
const isLeft = root.axis?.edge === "left";
|
||||
tooltipLoader.item.show(delegateItem.tooltipText, screenX + tooltipX, relativeY, root.parentScreen, isLeft, !isLeft);
|
||||
} else {
|
||||
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height);
|
||||
const tooltipY = Theme.barHeight + SettingsData.dankBarSpacing + Theme.spacingXS;
|
||||
tooltipLoader.item.show(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if (root.hoveredItem === delegateItem) {
|
||||
root.hoveredItem = null;
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hide();
|
||||
}
|
||||
|
||||
tooltipLoader.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -337,7 +541,7 @@ Rectangle {
|
||||
|
||||
active: false
|
||||
|
||||
sourceComponent: RunningAppsTooltip {}
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -407,7 +611,7 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "Close"
|
||||
text: qsTr("Close")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
630
Modules/DankBar/Widgets/SystemTrayBar.qml
Normal file
630
Modules/DankBar/Widgets/SystemTrayBar.qml
Normal file
@@ -0,0 +1,630 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property var parentWindow: null
|
||||
property var parentScreen: null
|
||||
property real widgetThickness: 30
|
||||
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);
|
||||
}
|
||||
visible: SystemTray.items.values.length > 0
|
||||
|
||||
Loader {
|
||||
id: layoutLoader
|
||||
anchors.centerIn: parent
|
||||
sourceComponent: root.isVertical ? columnComp : rowComp
|
||||
}
|
||||
|
||||
Component {
|
||||
id: rowComp
|
||||
Row {
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items.values
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: columnComp
|
||||
Column {
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items.values
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: trayMenuComponent
|
||||
|
||||
Rectangle {
|
||||
id: menuRoot
|
||||
|
||||
property var trayItem: null
|
||||
property var anchorItem: null
|
||||
property var parentScreen: null
|
||||
property bool isAtBottom: false
|
||||
property bool isVertical: false
|
||||
property var axis: null
|
||||
property bool showMenu: false
|
||||
property var menuHandle: null
|
||||
|
||||
ListModel { id: entryStack }
|
||||
function topEntry() {
|
||||
return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null
|
||||
}
|
||||
|
||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
||||
trayItem = item
|
||||
anchorItem = anchor
|
||||
parentScreen = screen
|
||||
isAtBottom = atBottom
|
||||
isVertical = vertical
|
||||
axis = axisObj
|
||||
menuHandle = item?.menu
|
||||
|
||||
if (parentScreen) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
const s = Quickshell.screens[i]
|
||||
if (s === parentScreen) {
|
||||
menuWindow.screen = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showMenu = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
showMenu = false
|
||||
}
|
||||
|
||||
function showSubMenu(entry) {
|
||||
if (!entry || !entry.hasChildren) return;
|
||||
|
||||
entryStack.append({ handle: entry });
|
||||
|
||||
const h = entry.menu || entry;
|
||||
if (h && typeof h.updateLayout === "function") h.updateLayout();
|
||||
|
||||
submenuHydrator.menu = h;
|
||||
submenuHydrator.open();
|
||||
Qt.callLater(() => submenuHydrator.close());
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (!entryStack.count) return;
|
||||
entryStack.remove(entryStack.count - 1);
|
||||
}
|
||||
|
||||
width: 0
|
||||
height: 0
|
||||
color: "transparent"
|
||||
|
||||
PanelWindow {
|
||||
id: menuWindow
|
||||
visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false)
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
updatePosition()
|
||||
}
|
||||
}
|
||||
|
||||
function updatePosition() {
|
||||
if (!menuRoot.anchorItem || !menuRoot.trayItem) {
|
||||
anchorPos = Qt.point(screen.width / 2, screen.height / 2)
|
||||
return
|
||||
}
|
||||
|
||||
const globalPos = menuRoot.anchorItem.mapToGlobal(0, 0)
|
||||
const screenX = screen.x || 0
|
||||
const screenY = screen.y || 0
|
||||
const relativeX = globalPos.x - screenX
|
||||
const relativeY = globalPos.y - screenY
|
||||
|
||||
const widgetThickness = Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
|
||||
const effectiveBarThickness = Math.max(widgetThickness + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
|
||||
|
||||
if (menuRoot.isVertical) {
|
||||
const edge = menuRoot.axis?.edge
|
||||
let targetX
|
||||
if (edge === "left") {
|
||||
targetX = effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance
|
||||
} else {
|
||||
const popupX = effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance
|
||||
targetX = screen.width - popupX
|
||||
}
|
||||
anchorPos = Qt.point(targetX, relativeY + menuRoot.anchorItem.height / 2)
|
||||
} else {
|
||||
let targetY
|
||||
if (menuRoot.isAtBottom) {
|
||||
const popupY = effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance
|
||||
targetY = screen.height - popupY
|
||||
} else {
|
||||
targetY = effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance
|
||||
}
|
||||
anchorPos = Qt.point(relativeX + menuRoot.anchorItem.width / 2, targetY)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: menuContainer
|
||||
|
||||
width: Math.min(500, Math.max(250, menuColumn.implicitWidth + Theme.spacingS * 2))
|
||||
height: Math.max(40, menuColumn.implicitHeight + Theme.spacingS * 2)
|
||||
|
||||
x: {
|
||||
if (menuRoot.isVertical) {
|
||||
const edge = menuRoot.axis?.edge
|
||||
if (edge === "left") {
|
||||
const targetX = menuWindow.anchorPos.x
|
||||
return Math.min(menuWindow.screen.width - width - 10, targetX)
|
||||
} else {
|
||||
const targetX = menuWindow.anchorPos.x - width
|
||||
return Math.max(10, targetX)
|
||||
}
|
||||
} else {
|
||||
const left = 10
|
||||
const right = menuWindow.width - width - 10
|
||||
const want = menuWindow.anchorPos.x - width / 2
|
||||
return Math.max(left, Math.min(right, want))
|
||||
}
|
||||
}
|
||||
|
||||
y: {
|
||||
if (menuRoot.isVertical) {
|
||||
const top = 10
|
||||
const bottom = menuWindow.height - height - 10
|
||||
const want = menuWindow.anchorPos.y - height / 2
|
||||
return Math.max(top, Math.min(bottom, want))
|
||||
} else {
|
||||
if (menuRoot.isAtBottom) {
|
||||
const targetY = menuWindow.anchorPos.y - height
|
||||
return Math.max(10, targetY)
|
||||
} else {
|
||||
const targetY = menuWindow.anchorPos.y
|
||||
return Math.min(menuWindow.screen.height - height - 10, targetY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
opacity: menuRoot.showMenu ? 1 : 0
|
||||
scale: menuRoot.showMenu ? 1 : 0.85
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: parent.z - 1
|
||||
}
|
||||
|
||||
QsMenuAnchor {
|
||||
id: submenuHydrator
|
||||
anchor.window: menuWindow
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: rootOpener
|
||||
menu: menuRoot.menuHandle
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: subOpener
|
||||
menu: {
|
||||
const e = menuRoot.topEntry();
|
||||
return e ? (e.menu || e) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
visible: entryStack.count > 0
|
||||
width: parent.width
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: backArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "arrow_back"
|
||||
size: 16
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Back")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: menuRoot.goBack()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: entryStack.count > 0
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: entryStack.count
|
||||
? (subOpener.children ? subOpener.children
|
||||
: (menuRoot.topEntry()?.children || []))
|
||||
: rootOpener.children
|
||||
|
||||
Rectangle {
|
||||
property var menuEntry: modelData
|
||||
|
||||
width: menuColumn.width
|
||||
height: menuEntry?.isSeparator ? 1 : 28
|
||||
radius: menuEntry?.isSeparator ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (menuEntry?.isSeparator) {
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
return itemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemArea
|
||||
anchors.fill: parent
|
||||
enabled: !menuEntry?.isSeparator && (menuEntry?.enabled !== false)
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (!menuEntry || menuEntry.isSeparator) return;
|
||||
|
||||
if (menuEntry.hasChildren) {
|
||||
menuRoot.showSubMenu(menuEntry);
|
||||
} else {
|
||||
if (typeof menuEntry.activate === "function") {
|
||||
menuEntry.activate();
|
||||
} else if (typeof menuEntry.triggered === "function") {
|
||||
menuEntry.triggered();
|
||||
}
|
||||
Qt.createQmlObject('import QtQuick; Timer { interval: 80; running: true; repeat: false; onTriggered: menuRoot.close() }', menuRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
visible: !menuEntry?.isSeparator
|
||||
|
||||
Rectangle {
|
||||
width: 16
|
||||
height: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: menuEntry?.buttonType !== undefined && menuEntry.buttonType !== 0
|
||||
radius: menuEntry?.buttonType === 2 ? 8 : 2
|
||||
border.width: 1
|
||||
border.color: Theme.outline
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 6
|
||||
height: parent.height - 6
|
||||
radius: parent.radius - 3
|
||||
color: Theme.primary
|
||||
visible: menuEntry?.checkState === 2
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "check"
|
||||
size: 10
|
||||
color: Theme.primaryText
|
||||
visible: menuEntry?.buttonType === 1 && menuEntry?.checkState === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 16
|
||||
height: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: menuEntry?.icon && menuEntry.icon !== ""
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: menuEntry?.icon || ""
|
||||
sourceSize.width: 16
|
||||
sourceSize.height: 16
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: menuEntry?.text || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: (menuEntry?.enabled !== false) ? Theme.surfaceText : Theme.surfaceTextMedium
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Math.max(150, parent.width - 64)
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 16
|
||||
height: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "chevron_right"
|
||||
size: 14
|
||||
color: Theme.surfaceText
|
||||
visible: menuEntry?.hasChildren ?? false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
onClicked: menuRoot.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property var currentTrayMenu: null
|
||||
|
||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
||||
if (currentTrayMenu) {
|
||||
currentTrayMenu.destroy()
|
||||
}
|
||||
currentTrayMenu = trayMenuComponent.createObject(null)
|
||||
if (currentTrayMenu) {
|
||||
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
154
Modules/DankBar/Widgets/SystemUpdate.qml
Normal file
154
Modules/DankBar/Widgets/SystemUpdate.qml
Normal file
@@ -0,0 +1,154 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
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);
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: statusIcon
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: countText
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: SystemUpdateService.updateCount.toString()
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
visible: hasUpdates && !isChecking
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: updaterArea
|
||||
|
||||
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)
|
||||
}
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
103
Modules/DankBar/Widgets/Vpn.qml
Normal file
103
Modules/DankBar/Widgets/Vpn.qml
Normal file
@@ -0,0 +1,103 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
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))
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
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 {
|
||||
id: tooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clickArea
|
||||
|
||||
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)
|
||||
}
|
||||
root.toggleVpnPopup();
|
||||
}
|
||||
onEntered: {
|
||||
if (root.parentScreen && !(popupTarget && popupTarget.shouldBeVisible)) {
|
||||
tooltipLoader.active = true
|
||||
if (tooltipLoader.item) {
|
||||
let tooltipText = ""
|
||||
if (!VpnService.connected) {
|
||||
tooltipText = "VPN Disconnected"
|
||||
} else {
|
||||
const names = VpnService.activeNames || []
|
||||
if (names.length <= 1) {
|
||||
tooltipText = "VPN Connected • " + (names[0] || "")
|
||||
} else {
|
||||
tooltipText = "VPN Connected • " + names[0] + " +" + (names.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (root.isVertical) {
|
||||
const globalPos = mapToGlobal(width / 2, 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)
|
||||
const isLeft = root.axis?.edge === "left"
|
||||
tooltipLoader.item.show(tooltipText, screenX + tooltipX, relativeY, root.parentScreen, isLeft, !isLeft)
|
||||
} else {
|
||||
const globalPos = mapToGlobal(width / 2, height)
|
||||
const tooltipY = Theme.barHeight + SettingsData.dankBarSpacing + Theme.spacingXS
|
||||
tooltipLoader.item.show(tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if (tooltipLoader.item) {
|
||||
tooltipLoader.item.hide()
|
||||
}
|
||||
tooltipLoader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
120
Modules/DankBar/Widgets/Weather.qml
Normal file
120
Modules/DankBar/Widgets/Weather.qml
Normal file
@@ -0,0 +1,120 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
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;
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: weatherRow
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C");
|
||||
}
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: weatherArea
|
||||
|
||||
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)
|
||||
}
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,8 +10,11 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isVertical: axis?.isVertical ?? false
|
||||
property var axis: null
|
||||
property string screenName: ""
|
||||
property real widgetHeight: 30
|
||||
property real barThickness: 48
|
||||
property int currentWorkspace: {
|
||||
if (CompositorService.isNiri) {
|
||||
return getNiriActiveWorkspace()
|
||||
@@ -27,6 +30,8 @@ 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
|
||||
}
|
||||
return [1]
|
||||
@@ -192,7 +197,9 @@ Rectangle {
|
||||
return currentMonitor.activeWorkspace?.id ?? 1
|
||||
}
|
||||
|
||||
readonly property real padding: (widgetHeight - workspaceRow.implicitHeight) / 2
|
||||
readonly property real padding: isVertical
|
||||
? Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
: (widgetHeight - workspaceRow.implicitHeight) / 2
|
||||
|
||||
function getRealWorkspaces() {
|
||||
return root.workspaceList.filter(ws => {
|
||||
@@ -221,11 +228,11 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
width: workspaceRow.implicitWidth + padding * 2
|
||||
height: widgetHeight
|
||||
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
||||
width: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
|
||||
height: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
|
||||
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
||||
color: {
|
||||
if (SettingsData.topBarNoBackground)
|
||||
if (SettingsData.dankBarNoBackground)
|
||||
return "transparent"
|
||||
const baseColor = Theme.widgetBaseBackgroundColor
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
|
||||
@@ -261,11 +268,12 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
Flow {
|
||||
id: workspaceRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
||||
|
||||
Repeater {
|
||||
model: root.workspaceList
|
||||
@@ -288,19 +296,30 @@ Rectangle {
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
|
||||
property var loadedWorkspaceData: null
|
||||
property bool loadedIsUrgent: false
|
||||
property bool isUrgent: {
|
||||
if (CompositorService.isHyprland) {
|
||||
return modelData?.urgent ?? false
|
||||
}
|
||||
if (CompositorService.isNiri) {
|
||||
return loadedIsUrgent
|
||||
}
|
||||
return false
|
||||
}
|
||||
property var loadedIconData: null
|
||||
property bool loadedHasIcon: false
|
||||
property var loadedIcons: []
|
||||
|
||||
Timer {
|
||||
id: dataUpdateTimer
|
||||
interval: 50 // Defer data calculation by 50ms
|
||||
interval: 50
|
||||
onTriggered: {
|
||||
if (isPlaceholder) {
|
||||
delegateRoot.loadedWorkspaceData = null
|
||||
delegateRoot.loadedIconData = null
|
||||
delegateRoot.loadedHasIcon = false
|
||||
delegateRoot.loadedIcons = []
|
||||
delegateRoot.loadedIsUrgent = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -311,6 +330,7 @@ Rectangle {
|
||||
wsData = modelData;
|
||||
}
|
||||
delegateRoot.loadedWorkspaceData = wsData;
|
||||
delegateRoot.loadedIsUrgent = wsData?.is_urgent ?? false;
|
||||
|
||||
var icData = null;
|
||||
if (wsData?.name) {
|
||||
@@ -332,17 +352,36 @@ Rectangle {
|
||||
}
|
||||
|
||||
width: {
|
||||
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 * 1.0 + Theme.spacingXS : root.widgetHeight * 0.8;
|
||||
return baseWidth + iconsWidth;
|
||||
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;
|
||||
}
|
||||
return isActive ? root.widgetHeight * 1.2 : root.widgetHeight * 0.8;
|
||||
}
|
||||
height: SettingsData.showWorkspaceApps ? widgetHeight * 0.8 : widgetHeight * 0.6
|
||||
radius: height / 2
|
||||
color: isActive ? Theme.primary : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
|
||||
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)
|
||||
@@ -352,10 +391,34 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.centerIn: parent
|
||||
width: root.isVertical ? parent.width + Theme.spacingXL : parent.width
|
||||
height: root.isVerical ? parent.height : parent.height + Theme.spacingXL
|
||||
hoverEnabled: !isPlaceholder
|
||||
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||
enabled: !isPlaceholder
|
||||
@@ -372,74 +435,149 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Loader for App Icons
|
||||
Loader {
|
||||
id: appIconsLoader
|
||||
anchors.fill: parent
|
||||
active: SettingsData.showWorkspaceApps
|
||||
sourceComponent: Item {
|
||||
Row {
|
||||
Loader {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
visible: loadedIcons.length > 0
|
||||
sourceComponent: root.isVertical ? columnLayout : rowLayout
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
||||
delegate: Item {
|
||||
width: 18
|
||||
height: 18
|
||||
Component {
|
||||
id: rowLayout
|
||||
Row {
|
||||
spacing: 4
|
||||
visible: loadedIcons.length > 0
|
||||
|
||||
IconImage {
|
||||
id: appIcon
|
||||
property var windowId: modelData.windowId
|
||||
anchors.fill: parent
|
||||
source: modelData.icon
|
||||
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||
visible: !modelData.isSteamApp
|
||||
}
|
||||
Repeater {
|
||||
model: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
||||
delegate: Item {
|
||||
width: 18
|
||||
height: 18
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: 18
|
||||
name: "sports_esports"
|
||||
color: Theme.surfaceText
|
||||
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||
visible: modelData.isSteamApp
|
||||
}
|
||||
IconImage {
|
||||
id: appIcon
|
||||
property var windowId: modelData.windowId
|
||||
anchors.fill: parent
|
||||
source: modelData.icon
|
||||
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||
visible: !modelData.isSteamApp
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: appMouseArea
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
enabled: isActive
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (CompositorService.isHyprland) {
|
||||
Hyprland.dispatch(`focuswindow address:${appIcon.windowId}`)
|
||||
} else if (CompositorService.isNiri) {
|
||||
NiriService.focusWindow(appIcon.windowId)
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: 18
|
||||
name: "sports_esports"
|
||||
color: Theme.surfaceText
|
||||
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||
visible: modelData.isSteamApp
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: appMouseArea
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
enabled: isActive
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (CompositorService.isHyprland) {
|
||||
Hyprland.dispatch(`focuswindow address:${appIcon.windowId}`)
|
||||
} else if (CompositorService.isNiri) {
|
||||
NiriService.focusWindow(appIcon.windowId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.count > 1 && !isActive
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
color: "black"
|
||||
border.color: "white"
|
||||
border.width: 1
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
z: 2
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.count
|
||||
font.pixelSize: 8
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.count > 1 && !isActive
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
color: "black"
|
||||
border.color: "white"
|
||||
border.width: 1
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
z: 2
|
||||
Component {
|
||||
id: columnLayout
|
||||
Column {
|
||||
spacing: 4
|
||||
visible: loadedIcons.length > 0
|
||||
|
||||
Text {
|
||||
Repeater {
|
||||
model: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
||||
delegate: Item {
|
||||
width: 18
|
||||
height: 18
|
||||
|
||||
IconImage {
|
||||
id: appIcon
|
||||
property var windowId: modelData.windowId
|
||||
anchors.fill: parent
|
||||
source: modelData.icon
|
||||
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||
visible: !modelData.isSteamApp
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.count
|
||||
font.pixelSize: 8
|
||||
color: "white"
|
||||
size: 18
|
||||
name: "sports_esports"
|
||||
color: Theme.surfaceText
|
||||
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
||||
visible: modelData.isSteamApp
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: appMouseArea
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
enabled: isActive
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (CompositorService.isHyprland) {
|
||||
Hyprland.dispatch(`focuswindow address:${appIcon.windowId}`)
|
||||
} else if (CompositorService.isNiri) {
|
||||
NiriService.focusWindow(appIcon.windowId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.count > 1 && !isActive
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
color: "black"
|
||||
border.color: "white"
|
||||
border.width: 1
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
z: 2
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.count
|
||||
font.pixelSize: 8
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,7 +612,7 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
text: loadedIconData ? loadedIconData.value : "" // NULL CHECK
|
||||
color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : Theme.surfaceTextMedium
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
||||
}
|
||||
}
|
||||
@@ -495,8 +633,8 @@ Rectangle {
|
||||
}
|
||||
return CompositorService.isHyprland ? (modelData?.id || "") : (modelData - 1);
|
||||
}
|
||||
color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||
font.pixelSize: Theme.barTextSize(barThickness)
|
||||
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
||||
}
|
||||
}
|
||||
@@ -513,6 +651,7 @@ Rectangle {
|
||||
target: NiriService
|
||||
enabled: CompositorService.isNiri
|
||||
function onAllWorkspacesChanged() { delegateRoot.updateAllData() }
|
||||
function onWindowUrgentChanged() { delegateRoot.updateAllData() }
|
||||
}
|
||||
Connections {
|
||||
target: SettingsData
|
||||
@@ -13,30 +13,33 @@ DankPopout {
|
||||
id: root
|
||||
|
||||
property bool dashVisible: false
|
||||
property string triggerSection: "center"
|
||||
property var triggerScreen: null
|
||||
property int currentTabIndex: 0
|
||||
|
||||
function setTriggerPosition(x, y, width, section, screen) {
|
||||
if (section === "center") {
|
||||
triggerSection = section
|
||||
triggerScreen = screen
|
||||
triggerY = y
|
||||
|
||||
if (section === "center" && (SettingsData.dankBarPosition === SettingsData.Position.Top || SettingsData.dankBarPosition === SettingsData.Position.Bottom)) {
|
||||
const screenWidth = screen ? screen.width : Screen.width
|
||||
triggerX = (screenWidth - popupWidth) / 2
|
||||
triggerWidth = popupWidth
|
||||
} else if (section === "center" && (SettingsData.dankBarPosition === SettingsData.Position.Left || SettingsData.dankBarPosition === SettingsData.Position.Right)) {
|
||||
const screenHeight = screen ? screen.height : Screen.height
|
||||
triggerX = (screenHeight - popupHeight) / 2
|
||||
triggerWidth = popupHeight
|
||||
} else {
|
||||
triggerX = x
|
||||
triggerWidth = width
|
||||
}
|
||||
triggerY = y
|
||||
triggerSection = section
|
||||
triggerScreen = screen
|
||||
}
|
||||
|
||||
popupWidth: 700
|
||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
||||
triggerX: Screen.width - 620 - Theme.spacingL
|
||||
triggerY: Math.max(26 + SettingsData.topBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.topBarInnerPadding)) + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance
|
||||
triggerY: Math.max(26 + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding)) + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap - 2
|
||||
triggerWidth: 80
|
||||
positioning: "center"
|
||||
shouldBeVisible: dashVisible
|
||||
visible: shouldBeVisible
|
||||
|
||||
@@ -128,15 +131,15 @@ DankPopout {
|
||||
|
||||
model: {
|
||||
let tabs = [
|
||||
{ icon: "dashboard", text: "Overview" },
|
||||
{ icon: "music_note", text: "Media" }
|
||||
{ icon: "dashboard", text: qsTr("Overview") },
|
||||
{ icon: "music_note", text: qsTr("Media") }
|
||||
]
|
||||
|
||||
if (SettingsData.weatherEnabled) {
|
||||
tabs.push({ icon: "wb_sunny", text: "Weather" })
|
||||
tabs.push({ icon: "wb_sunny", text: qsTr("Weather") })
|
||||
}
|
||||
|
||||
tabs.push({ icon: "settings", text: "Settings", isAction: true })
|
||||
tabs.push({ icon: "settings", text: qsTr("Settings"), isAction: true })
|
||||
return tabs
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,7 +53,7 @@ Card {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "No Media"
|
||||
text: qsTr("No Media")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user