1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-25 22:12:50 -05:00

Compare commits

..

1 Commits

Author SHA1 Message Date
bbedward
db28879185 Reparations for drag and drop control center edits 2025-09-25 21:29:14 -04:00
277 changed files with 8582 additions and 37054 deletions

View File

@@ -1,213 +1,59 @@
# Release from a dispatch event from the danklinux repo name: Create Release
name: Create Release from DMS
on: on:
repository_dispatch: push:
types: [dms_release] tags:
- 'v*'
permissions:
contents: write
concurrency: concurrency:
group: release-${{ github.event.client_payload.tag }} group: release-${{ github.ref_name }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
create_release_from_dms: create_release:
name: 📦 Create GitHub Release
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
env:
TAG: ${{ github.event.client_payload.tag }}
DMS_REPO: ${{ github.event.client_payload.dms_repo }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0 # Fetch full history for changelog generation
- 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 - name: Generate Changelog
id: changelog id: changelog
run: | run: |
set -e # Get the previous tag
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "") PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then if [ -z "$PREVIOUS_TAG" ]; then
echo "No previous tag found, using all commits"
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50) CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50)
else else
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}") echo "Generating changelog from $PREVIOUS_TAG to HEAD"
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" $PREVIOUS_TAG..HEAD)
fi fi
cat > RELEASE_BODY.md << 'EOF' # Create the changelog with proper formatting
## Assets cat > CHANGELOG.md << EOF
### Complete Packages
- **`dms-full-amd64.tar.gz`** - Complete package for x86_64 systems (CLI binary + QML source + installation guide)
- **`dms-full-arm64.tar.gz`** - Complete package for ARM64 systems (CLI binary + QML source + installation guide)
### Individual Components
- **`dms-cli-amd64.gz`** - DMS CLI binary for x86_64 systems
- **`dms-cli-arm64.gz`** - DMS CLI binary for ARM64 systems
- **`dms-qml.tar.gz`** - QML source code only
### Checksums
- **`*.sha256`** - SHA256 checksums for verifying download integrity
**Installation:** Extract the `dms-full-*.tar.gz` package for your architecture and follow the `INSTALL.md` instructions inside.
---
EOF
cat >> RELEASE_BODY.md << EOF
## What's Changed ## What's Changed
$CHANGELOG $CHANGELOG
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${TAG} **Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${{ github.ref_name }}
EOF EOF
# Set output for use in release step
echo "changelog<<EOF" >> $GITHUB_OUTPUT echo "changelog<<EOF" >> $GITHUB_OUTPUT
cat RELEASE_BODY.md >> $GITHUB_OUTPUT cat CHANGELOG.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
- name: Create/Update DankMaterialShell Release # Create GitHub Release
uses: softprops/action-gh-release@v2 - name: Create GitHub Release
uses: comnoco/create-release-action@v2.0.5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ env.TAG }} tag_name: ${{ github.ref_name }}
name: Release ${{ env.TAG }} release_name: Release ${{ github.ref_name }}
body: ${{ steps.changelog.outputs.changelog }} body: ${{ steps.changelog.outputs.changelog }}
draft: false draft: false
prerelease: ${{ contains(env.TAG, '-') }} prerelease: ${{ contains(github.ref_name, '-') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download and prepare release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euxo pipefail
mkdir -p _release_assets
# Download DMS CLI binaries from the danklinux repo
gh release download "${TAG}" -R "${DMS_REPO}" --dir ./_dms_assets
# Rename CLI binaries to dms-cli-* format
for file in _dms_assets/dms-*.gz*; do
if [ -f "$file" ]; then
basename=$(basename "$file")
# dms-amd64.gz -> dms-cli-amd64.gz
# dms-amd64.gz.sha256 -> dms-cli-amd64.gz.sha256
newname=$(echo "$basename" | sed 's/^dms-/dms-cli-/')
cp "$file" "_release_assets/$newname"
fi
done
# Create QML source package (exclude .git, .github, build artifacts)
tar --exclude='.git' \
--exclude='.github' \
--exclude='_dms_assets' \
--exclude='_release_assets' \
--exclude='*.tar.gz' \
-czf _release_assets/dms-qml.tar.gz .
# Generate checksum for QML package
(cd _release_assets && sha256sum dms-qml.tar.gz > dms-qml.tar.gz.sha256)
# Create full packages for each architecture
for arch in amd64 arm64; do
mkdir -p _temp_full/dms
mkdir -p _temp_full/bin
# Extract QML source to temp directory
tar -xzf _release_assets/dms-qml.tar.gz -C _temp_full/dms
# Copy CLI binary if it exists
if [ -f "_dms_assets/dms-${arch}.gz" ]; then
gunzip -c "_dms_assets/dms-${arch}.gz" > _temp_full/bin/dms
chmod +x _temp_full/bin/dms
fi
# Create INSTALL.md
cat > _temp_full/INSTALL.md << 'EOF'
# DankMaterialShell Installation
## Requirements
- Wayland compositor (niri or Hyprland recommended)
- Quickshell framework
- Qt6
## Installation Steps
1. **Install quickshell assets:**
```bash
mkdir -p ~/.config/quickshell
cp -r dms ~/.config/quickshell/
```
2. **Install the DMS CLI binary:**
```bash
sudo install -m 755 bin/dms /usr/local/bin/dms
# or install to a local directory:
mkdir -p ~/.local/bin
install -m 755 bin/dms ~/.local/bin/dms
```
3. **Start the shell:**
```bash
dms run
# or directly with quickshell (will lack some dbus integrations & plugin management):
quickshell -p ~/.config/quickshell/dms
```
## Configuration
- Settings are stored in `~/.config/DankMaterialShell/settings.json`
- Plugins go in `~/.config/DankMaterialShell/plugins/`
- See the documentation in the `dms/` directory for more details
## Troubleshooting
- Run with verbose output: `quickshell -v -p ~/.config/quickshell/dms`
- Check logs in `~/.local/state/DankMaterialShell/`
- Ensure all dependencies are installed
EOF
# Create the full package
(cd _temp_full && tar -czf "../_release_assets/dms-full-${arch}.tar.gz" .)
# Generate checksum
(cd _release_assets && sha256sum "dms-full-${arch}.tar.gz" > "dms-full-${arch}.tar.gz.sha256")
# Cleanup
rm -rf _temp_full
done
- name: Attach all assets to release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG }}
files: _release_assets/**
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

227
CLAUDE.md
View File

@@ -63,9 +63,6 @@ quickshell -p shell.qml
# Or use the shorthand # Or use the shorthand
qs -p . qs -p .
# Run with verbose output for debugging
qs -v -p shell.qml
# Code formatting and linting # 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) 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 qmllint **/*.qml # Lint all QML files for syntax errors
@@ -92,7 +89,6 @@ shell.qml # Main entry point (minimal orchestration)
│ ├── DisplayService.qml │ ├── DisplayService.qml
│ ├── NotificationService.qml │ ├── NotificationService.qml
│ ├── WeatherService.qml │ ├── WeatherService.qml
│ ├── PluginService.qml
│ └── [14 more services] │ └── [14 more services]
├── Modules/ # UI components (93 files) ├── Modules/ # UI components (93 files)
│ ├── TopBar/ # Panel components (13 files) │ ├── TopBar/ # Panel components (13 files)
@@ -108,21 +104,15 @@ shell.qml # Main entry point (minimal orchestration)
│ ├── SettingsModal.qml │ ├── SettingsModal.qml
│ ├── ClipboardHistoryModal.qml │ ├── ClipboardHistoryModal.qml
│ ├── ProcessListModal.qml │ ├── ProcessListModal.qml
│ ├── PluginSettingsModal.qml
│ └── [7 more modals] │ └── [7 more modals]
── Widgets/ # Reusable UI controls (19 files) ── Widgets/ # Reusable UI controls (19 files)
├── DankIcon.qml ├── DankIcon.qml
├── DankSlider.qml ├── DankSlider.qml
├── DankToggle.qml ├── DankToggle.qml
├── DankTabBar.qml ├── DankTabBar.qml
├── DankGridView.qml ├── DankGridView.qml
├── DankListView.qml ├── DankListView.qml
└── [13 more widgets] └── [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 ### Component Organization
@@ -173,12 +163,6 @@ shell.qml # Main entry point (minimal orchestration)
- **DankLocationSearch**: Location picker with search - **DankLocationSearch**: Location picker with search
- **SystemLogo**: Animated system branding component - **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 ### Key Architectural Patterns
1. **Singleton Services Pattern**: 1. **Singleton Services Pattern**:
@@ -446,200 +430,6 @@ 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 ### Debugging Common Issues
1. **Import errors**: Check import paths 1. **Import errors**: Check import paths
@@ -664,7 +454,6 @@ Daemon plugins run invisibly in the background without any UI components. They'r
- **Function Discovery**: Use grep/search tools to find existing utility functions before implementing new ones - **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 - **Modern QML Patterns**: Leverage new widgets like DankTextField, DankDropdown, CachingImage
- **Structured Organization**: Follow the established Services/Modules/Widgets/Modals separation - **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 ### Common Widget Patterns

View File

@@ -1,6 +1,6 @@
# Contributing # Contributing
Contributions are welcome and encouraged. Contributions are welcome and encourages.
## Formatting ## Formatting

View File

@@ -72,6 +72,10 @@ Singleton {
saveSettings() saveSettings()
} }
function getAppUsageRanking() {
return appUsageRanking
}
function getRankedApps() { function getRankedApps() {
var apps = [] var apps = []
for (var appId in appUsageRanking) { for (var appId in appUsageRanking) {

View File

@@ -1,62 +0,0 @@
import QtQuick
import Quickshell.Io
Item {
id: root
property alias path: socket.path
property alias parser: socket.parser
property bool connected: false
property int reconnectBaseMs: 400
property int reconnectMaxMs: 15000
property int _reconnectAttempt: 0
signal connectionStateChanged()
onConnectedChanged: {
socket.connected = connected
}
Socket {
id: socket
onConnectionStateChanged: {
root.connectionStateChanged()
if (connected) {
root._reconnectAttempt = 0
return
}
if (root.connected) {
root._scheduleReconnect()
}
}
}
Timer {
id: reconnectTimer
interval: 0
repeat: false
onTriggered: {
socket.connected = false
Qt.callLater(() => socket.connected = true)
}
}
function send(data) {
const json = typeof data === "string" ? data : JSON.stringify(data)
const message = json.endsWith("\n") ? json : json + "\n"
socket.write(message)
socket.flush()
}
function _scheduleReconnect() {
const pow = Math.min(_reconnectAttempt, 10)
const base = Math.min(reconnectBaseMs * Math.pow(2, pow), reconnectMaxMs)
const jitter = Math.floor(Math.random() * Math.floor(base / 4))
reconnectTimer.interval = base + jitter
reconnectTimer.restart()
_reconnectAttempt++
}
}

View File

@@ -1,53 +0,0 @@
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)]
}
}

View File

@@ -1,113 +0,0 @@
import QtQuick
import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
readonly property string _rawLocale: Qt.locale().name
readonly property string _lang: _rawLocale.split(/[_-]/)[0]
readonly property var _candidates: {
const fullUnderscore = _rawLocale;
const fullHyphen = _rawLocale.replace("_", "-");
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
}
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
property string currentLocale: "en"
property var translations: ({})
property bool translationsLoaded: false
property url _selectedPath: ""
FolderListModel {
id: dir
folder: root.translationsFolder
nameFilters: ["*.json"]
showDirs: false
showDotAndDotDot: false
onStatusChanged: if (status === FolderListModel.Ready) root._pickTranslation()
}
FileView {
id: translationLoader
path: root._selectedPath
onLoaded: {
try {
root.translations = JSON.parse(text())
root.translationsLoaded = true
console.log(`I18n: Loaded translations for '${root.currentLocale}' ` +
`(${Object.keys(root.translations).length} contexts)`)
} catch (e) {
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e,
"- falling back to English")
root._fallbackToEnglish()
}
}
onLoadFailed: (error) => {
console.warn(`I18n: Failed to load '${root.currentLocale}' (${error}), ` +
"falling back to English")
root._fallbackToEnglish()
}
}
function _pickTranslation() {
const present = new Set()
for (let i = 0; i < dir.count; i++) {
const name = dir.get(i, "fileName") // e.g. "zh_CN.json"
if (name && name.endsWith(".json")) {
present.add(name.slice(0, -5))
}
}
for (let i = 0; i < _candidates.length; i++) {
const cand = _candidates[i]
if (present.has(cand)) {
_useLocale(cand, dir.folder + "/" + cand + ".json")
return
}
}
_fallbackToEnglish()
}
function _useLocale(localeTag, fileUrl) {
currentLocale = localeTag
_selectedPath = fileUrl
translationsLoaded = false
translations = ({})
console.log(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
}
function _fallbackToEnglish() {
currentLocale = "en"
_selectedPath = ""
translationsLoaded = false
translations = ({})
console.warn("I18n: Falling back to built-in English strings")
}
function tr(term, context) {
if (!translationsLoaded || !translations) return term
const ctx = context || term
if (translations[ctx] && translations[ctx][term]) return translations[ctx][term]
for (const c in translations) {
if (translations[c] && translations[c][term]) return translations[c][term]
}
return term
}
function trContext(context, term) {
if (!translationsLoaded || !translations) return term
if (translations[context] && translations[context][term]) return translations[context][term]
return term
}
}

View File

@@ -38,10 +38,6 @@ Singleton {
return stringify(path).replace("file://", "") return stringify(path).replace("file://", "")
} }
function toFileUrl(path: string): string {
return path.startsWith("file://") ? path : "file://" + path
}
function mkdir(path: url): void { function mkdir(path: url): void {
Quickshell.execDetached(["mkdir", "-p", strip(path)]) Quickshell.execDetached(["mkdir", "-p", strip(path)])
} }

View File

@@ -12,19 +12,12 @@ Singleton {
id: root id: root
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
property bool isLightMode: false property bool isLightMode: false
property string wallpaperPath: "" property string wallpaperPath: ""
property string wallpaperLastPath: "" property string wallpaperLastPath: ""
property string profileLastPath: "" property string profileLastPath: ""
property bool perMonitorWallpaper: false property bool perMonitorWallpaper: false
property var monitorWallpapers: ({}) 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 doNotDisturb: false
property bool nightModeEnabled: false property bool nightModeEnabled: false
property int nightModeTemperature: 4500 property int nightModeTemperature: 4500
@@ -70,23 +63,14 @@ Singleton {
property int batteryHibernateTimeout: 0 // Never property int batteryHibernateTimeout: 0 // Never
property bool lockBeforeSuspend: false property bool lockBeforeSuspend: false
property bool loginctlLockIntegration: true
property var recentColors: []
property bool showThirdPartyPlugins: false
Component.onCompleted: { Component.onCompleted: {
if (!isGreeterMode) { loadSettings()
loadSettings()
}
} }
function loadSettings() { function loadSettings() {
if (isGreeterMode) { parseSettings(settingsFile.text())
parseSettings(greeterSessionFile.text())
} else {
parseSettings(settingsFile.text())
}
} }
function parseSettings(content) { function parseSettings(content) {
@@ -99,11 +83,6 @@ Singleton {
profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : "" profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : ""
perMonitorWallpaper = settings.perMonitorWallpaper !== undefined ? settings.perMonitorWallpaper : false perMonitorWallpaper = settings.perMonitorWallpaper !== undefined ? settings.perMonitorWallpaper : false
monitorWallpapers = settings.monitorWallpapers !== undefined ? settings.monitorWallpapers : {} 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 doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500 nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500
@@ -153,14 +132,10 @@ Singleton {
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0 batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0 batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
loginctlLockIntegration = settings.loginctlLockIntegration !== undefined ? settings.loginctlLockIntegration : true
recentColors = settings.recentColors !== undefined ? settings.recentColors : []
showThirdPartyPlugins = settings.showThirdPartyPlugins !== undefined ? settings.showThirdPartyPlugins : false
if (!isGreeterMode) { // Generate system themes but don't override user's theme choice
if (typeof Theme !== "undefined") { if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme() Theme.generateSystemThemesFromCurrentTheme()
}
} }
} }
} catch (e) { } catch (e) {
@@ -169,7 +144,6 @@ Singleton {
} }
function saveSettings() { function saveSettings() {
if (isGreeterMode) return
settingsFile.setText(JSON.stringify({ settingsFile.setText(JSON.stringify({
"isLightMode": isLightMode, "isLightMode": isLightMode,
"wallpaperPath": wallpaperPath, "wallpaperPath": wallpaperPath,
@@ -177,11 +151,6 @@ Singleton {
"profileLastPath": profileLastPath, "profileLastPath": profileLastPath,
"perMonitorWallpaper": perMonitorWallpaper, "perMonitorWallpaper": perMonitorWallpaper,
"monitorWallpapers": monitorWallpapers, "monitorWallpapers": monitorWallpapers,
"perModeWallpaper": perModeWallpaper,
"wallpaperPathLight": wallpaperPathLight,
"wallpaperPathDark": wallpaperPathDark,
"monitorWallpapersLight": monitorWallpapersLight,
"monitorWallpapersDark": monitorWallpapersDark,
"doNotDisturb": doNotDisturb, "doNotDisturb": doNotDisturb,
"nightModeEnabled": nightModeEnabled, "nightModeEnabled": nightModeEnabled,
"nightModeTemperature": nightModeTemperature, "nightModeTemperature": nightModeTemperature,
@@ -216,30 +185,15 @@ Singleton {
"batteryLockTimeout": batteryLockTimeout, "batteryLockTimeout": batteryLockTimeout,
"batterySuspendTimeout": batterySuspendTimeout, "batterySuspendTimeout": batterySuspendTimeout,
"batteryHibernateTimeout": batteryHibernateTimeout, "batteryHibernateTimeout": batteryHibernateTimeout,
"lockBeforeSuspend": lockBeforeSuspend, "lockBeforeSuspend": lockBeforeSuspend
"loginctlLockIntegration": loginctlLockIntegration,
"recentColors": recentColors,
"showThirdPartyPlugins": showThirdPartyPlugins
}, null, 2)) }, null, 2))
} }
function setLightMode(lightMode) { function setLightMode(lightMode) {
isLightMode = lightMode isLightMode = lightMode
syncWallpaperForCurrentMode()
saveSettings() saveSettings()
} }
function syncWallpaperForCurrentMode() {
if (!perModeWallpaper) return
if (perMonitorWallpaper) {
monitorWallpapers = isLightMode ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark)
return
}
wallpaperPath = isLightMode ? wallpaperPathLight : wallpaperPathDark
}
function setDoNotDisturb(enabled) { function setDoNotDisturb(enabled) {
doNotDisturb = enabled doNotDisturb = enabled
saveSettings() saveSettings()
@@ -310,13 +264,6 @@ Singleton {
function setWallpaper(imagePath) { function setWallpaper(imagePath) {
wallpaperPath = imagePath wallpaperPath = imagePath
if (perModeWallpaper) {
if (isLightMode) {
wallpaperPathLight = imagePath
} else {
wallpaperPathDark = imagePath
}
}
saveSettings() saveSettings()
if (typeof Theme !== "undefined") { if (typeof Theme !== "undefined") {
@@ -326,13 +273,6 @@ Singleton {
function setWallpaperColor(color) { function setWallpaperColor(color) {
wallpaperPath = color wallpaperPath = color
if (perModeWallpaper) {
if (isLightMode) {
wallpaperPathLight = color
} else {
wallpaperPathDark = color
}
}
saveSettings() saveSettings()
if (typeof Theme !== "undefined") { if (typeof Theme !== "undefined") {
@@ -368,16 +308,6 @@ Singleton {
saveSettings() 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) { function addPinnedApp(appId) {
if (!appId) if (!appId)
return return
@@ -490,51 +420,9 @@ Singleton {
function setPerMonitorWallpaper(enabled) { function setPerMonitorWallpaper(enabled) {
perMonitorWallpaper = 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() saveSettings()
// Refresh dynamic theming when per-monitor mode changes
if (typeof Theme !== "undefined") { if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme() Theme.generateSystemThemesFromCurrentTheme()
} }
@@ -548,29 +436,9 @@ Singleton {
delete newMonitorWallpapers[screenName] delete newMonitorWallpapers[screenName]
} }
monitorWallpapers = newMonitorWallpapers 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() saveSettings()
// Trigger dynamic theming if this is the first monitor and dynamic theming is enabled
if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined") { if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined") {
var screens = Quickshell.screens var screens = Quickshell.screens
if (screens.length > 0 && screenName === screens[0].name) { if (screens.length > 0 && screenName === screens[0].name) {
@@ -646,61 +514,30 @@ Singleton {
saveSettings() saveSettings()
} }
function setLoginctlLockIntegration(enabled) {
loginctlLockIntegration = enabled
saveSettings()
}
function setShowThirdPartyPlugins(enabled) {
showThirdPartyPlugins = enabled
saveSettings()
}
FileView { FileView {
id: settingsFile id: settingsFile
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json" path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
blockLoading: isGreeterMode blockLoading: true
blockWrites: true blockWrites: true
watchChanges: !isGreeterMode watchChanges: true
onLoaded: { onLoaded: {
if (!isGreeterMode) { parseSettings(settingsFile.text())
parseSettings(settingsFile.text()) hasTriedDefaultSession = false
hasTriedDefaultSession = false
}
} }
onLoadFailed: error => { onLoadFailed: error => {
if (!isGreeterMode && !hasTriedDefaultSession) { if (!hasTriedDefaultSession) {
hasTriedDefaultSession = true hasTriedDefaultSession = true
defaultSessionCheckProcess.running = 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 { Process {
id: defaultSessionCheckProcess id: defaultSessionCheckProcess
command: ["sh", "-c", "CONFIG_DIR=\"" + _stateDir command: ["sh", "-c", "CONFIG_DIR=\"" + _stateDir
+ "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-session.json\" ] && [ ! -f \"$CONFIG_DIR/session.json\" ]; then cp --no-preserve=mode \"$CONFIG_DIR/default-session.json\" \"$CONFIG_DIR/session.json\" && echo 'copied'; else echo 'not_found'; fi"] + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-session.json\" ] && [ ! -f \"$CONFIG_DIR/session.json\" ]; then cp \"$CONFIG_DIR/default-session.json\" \"$CONFIG_DIR/session.json\" && echo 'copied'; else echo 'not_found'; fi"]
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {

View File

@@ -12,29 +12,12 @@ import qs.Services
Singleton { Singleton {
id: root 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 // Theme settings
property string currentThemeName: "blue" property string currentThemeName: "blue"
property string customThemeFile: "" property string customThemeFile: ""
property string matugenScheme: "scheme-tonal-spot" property string matugenScheme: "scheme-tonal-spot"
property real dankBarTransparency: 1.0 property real topBarTransparency: 0.75
property real dankBarWidgetTransparency: 1.0 property real topBarWidgetTransparency: 0.85
property real popupTransparency: 1.0 property real popupTransparency: 1.0
property real dockTransparency: 1 property real dockTransparency: 1
property bool use24HourClock: true property bool use24HourClock: true
@@ -87,13 +70,12 @@ Singleton {
property string clockDateFormat: "" property string clockDateFormat: ""
property string lockDateFormat: "" property string lockDateFormat: ""
property int mediaSize: 1 property int mediaSize: 1
property var dankBarLeftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"] property var topBarLeftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"]
property var dankBarCenterWidgets: ["music", "clock", "weather"] property var topBarCenterWidgets: ["music", "clock", "weather"]
property var dankBarRightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"] property var topBarRightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"]
property var dankBarWidgetOrder: [] property alias topBarLeftWidgetsModel: leftWidgetsModel
property alias dankBarLeftWidgetsModel: leftWidgetsModel property alias topBarCenterWidgetsModel: centerWidgetsModel
property alias dankBarCenterWidgetsModel: centerWidgetsModel property alias topBarRightWidgetsModel: rightWidgetsModel
property alias dankBarRightWidgetsModel: rightWidgetsModel
property string appLauncherViewMode: "list" property string appLauncherViewMode: "list"
property string spotlightModalViewMode: "list" property string spotlightModalViewMode: "list"
property string networkPreference: "auto" property string networkPreference: "auto"
@@ -103,19 +85,15 @@ Singleton {
property bool qt5ctAvailable: false property bool qt5ctAvailable: false
property bool qt6ctAvailable: false property bool qt6ctAvailable: false
property bool gtkAvailable: false property bool gtkAvailable: false
property string launcherLogoMode: "apps" property bool useOSLogo: false
property string launcherLogoCustomPath: "" property string osLogoColorOverride: ""
property string launcherLogoColorOverride: "" property real osLogoBrightness: 0.5
property bool launcherLogoColorInvertOnMode: false property real osLogoContrast: 1
property real launcherLogoBrightness: 0.5
property real launcherLogoContrast: 1
property int launcherLogoSizeOffset: 0
property bool weatherEnabled: true property bool weatherEnabled: true
property string fontFamily: "Inter Variable" property string fontFamily: "Inter Variable"
property string monoFontFamily: "Fira Code" property string monoFontFamily: "Fira Code"
property int fontWeight: Font.Normal property int fontWeight: Font.Normal
property real fontScale: 1.0 property real fontScale: 1.0
property real dankBarFontScale: 1.0
property bool notepadUseMonospace: true property bool notepadUseMonospace: true
property string notepadFontFamily: "" property string notepadFontFamily: ""
property real notepadFontSize: 14 property real notepadFontSize: 14
@@ -139,32 +117,17 @@ Singleton {
property bool showDock: false property bool showDock: false
property bool dockAutoHide: false property bool dockAutoHide: false
property bool dockGroupByApp: 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 real cornerRadius: 12
property bool notificationOverlayEnabled: false property bool notificationOverlayEnabled: false
property bool dankBarAutoHide: false property bool topBarAutoHide: false
property bool dankBarOpenOnOverview: false property bool topBarOpenOnOverview: false
property bool dankBarVisible: true property bool topBarVisible: true
property real dankBarSpacing: 4 property real topBarSpacing: 4
property real dankBarBottomGap: 0 property real topBarBottomGap: 0
property real dankBarInnerPadding: 4 property real topBarInnerPadding: 8
property bool dankBarSquareCorners: false property bool topBarSquareCorners: false
property bool dankBarNoBackground: false property bool topBarNoBackground: false
property bool dankBarGothCornersEnabled: false property bool topBarGothCornersEnabled: false
property bool dankBarBorderEnabled: false
property string dankBarBorderColor: "surfaceText"
property real dankBarBorderOpacity: 1.0
property real dankBarBorderThickness: 1
onDankBarBorderColorChanged: saveSettings()
onDankBarBorderOpacityChanged: saveSettings()
onDankBarBorderThicknessChanged: saveSettings()
property int dankBarPosition: SettingsData.Position.Top
property bool dankBarIsVertical: dankBarPosition === SettingsData.Position.Left || dankBarPosition === SettingsData.Position.Right
property bool lockScreenShowPowerActions: true property bool lockScreenShowPowerActions: true
property bool hideBrightnessSlider: false property bool hideBrightnessSlider: false
property string widgetBackgroundColor: "sch" property string widgetBackgroundColor: "sch"
@@ -172,26 +135,18 @@ Singleton {
property int notificationTimeoutLow: 5000 property int notificationTimeoutLow: 5000
property int notificationTimeoutNormal: 5000 property int notificationTimeoutNormal: 5000
property int notificationTimeoutCritical: 0 property int notificationTimeoutCritical: 0
property int notificationPopupPosition: SettingsData.Position.Top
property bool osdAlwaysShowValue: false
property var screenPreferences: ({}) property var screenPreferences: ({})
property int animationSpeed: SettingsData.AnimationSpeed.Short
readonly property string defaultFontFamily: "Inter Variable" readonly property string defaultFontFamily: "Inter Variable"
readonly property string defaultMonoFontFamily: "Fira Code" readonly property string defaultMonoFontFamily: "Fira Code"
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation) readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation) readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation)
readonly property string _configDir: Paths.strip(_configUrl) readonly property string _configDir: Paths.strip(_configUrl)
readonly property string pluginSettingsPath: _configDir + "/DankMaterialShell/plugin_settings.json"
signal forceDankBarLayoutRefresh signal forceTopBarLayoutRefresh
signal forceDockLayoutRefresh
signal widgetDataChanged signal widgetDataChanged
signal workspaceIconsUpdated signal workspaceIconsUpdated
property bool _loading: false property bool _loading: false
property bool _pluginSettingsLoading: false
property var pluginSettings: ({})
function getEffectiveTimeFormat() { function getEffectiveTimeFormat() {
if (use24HourClock) { if (use24HourClock) {
@@ -218,57 +173,28 @@ Singleton {
"size": 20, "size": 20,
"selectedGpuIndex": 0, "selectedGpuIndex": 0,
"pciId": "", "pciId": "",
"mountPath": "/", "mountPath": "/"
"minimumWidth": true
} }
leftWidgetsModel.append(dummyItem) leftWidgetsModel.append(dummyItem)
centerWidgetsModel.append(dummyItem) centerWidgetsModel.append(dummyItem)
rightWidgetsModel.append(dummyItem) rightWidgetsModel.append(dummyItem)
updateListModel(leftWidgetsModel, dankBarLeftWidgets) updateListModel(leftWidgetsModel, topBarLeftWidgets)
updateListModel(centerWidgetsModel, dankBarCenterWidgets) updateListModel(centerWidgetsModel, topBarCenterWidgets)
updateListModel(rightWidgetsModel, dankBarRightWidgets) updateListModel(rightWidgetsModel, topBarRightWidgets)
} }
function loadSettings() { function loadSettings() {
_loading = true _loading = true
parseSettings(settingsFile.text()) parseSettings(settingsFile.text())
_loading = false _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) { function parseSettings(content) {
_loading = true _loading = true
var shouldMigrate = false
try { try {
if (content && content.trim()) { if (content && content.trim()) {
var settings = JSON.parse(content) var settings = JSON.parse(content)
if (settings.pluginSettings !== undefined) {
pluginSettings = settings.pluginSettings
shouldMigrate = true
}
// Auto-migrate from old theme system // Auto-migrate from old theme system
if (settings.themeIndex !== undefined || settings.themeIsDynamic !== undefined) { if (settings.themeIndex !== undefined || settings.themeIsDynamic !== undefined) {
const themeNames = ["blue", "deepBlue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral"] const themeNames = ["blue", "deepBlue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral"]
@@ -283,8 +209,8 @@ Singleton {
} }
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : "" customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot" matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot"
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) topBarTransparency = settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 0.75
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) topBarWidgetTransparency = settings.topBarWidgetTransparency !== undefined ? (settings.topBarWidgetTransparency > 1 ? settings.topBarWidgetTransparency / 100 : settings.topBarWidgetTransparency) : 0.85
popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 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 dockTransparency = settings.dockTransparency !== undefined ? (settings.dockTransparency > 1 ? settings.dockTransparency / 100 : settings.dockTransparency) : 1
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true
@@ -338,24 +264,23 @@ Singleton {
clockDateFormat = settings.clockDateFormat !== undefined ? settings.clockDateFormat : "" clockDateFormat = settings.clockDateFormat !== undefined ? settings.clockDateFormat : ""
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "" lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : ""
mediaSize = settings.mediaSize !== undefined ? settings.mediaSize : (settings.mediaCompactMode !== undefined ? (settings.mediaCompactMode ? 0 : 1) : 1) mediaSize = settings.mediaSize !== undefined ? settings.mediaSize : (settings.mediaCompactMode !== undefined ? (settings.mediaCompactMode ? 0 : 1) : 1)
if (settings.dankBarWidgetOrder || settings.topBarWidgetOrder) { if (settings.topBarWidgetOrder) {
var widgetOrder = settings.dankBarWidgetOrder || settings.topBarWidgetOrder topBarLeftWidgets = settings.topBarWidgetOrder.filter(w => {
dankBarLeftWidgets = widgetOrder.filter(w => {
return ["launcherButton", "workspaceSwitcher", "focusedWindow"].includes(w) return ["launcherButton", "workspaceSwitcher", "focusedWindow"].includes(w)
}) })
dankBarCenterWidgets = widgetOrder.filter(w => { topBarCenterWidgets = settings.topBarWidgetOrder.filter(w => {
return ["clock", "music", "weather"].includes(w) return ["clock", "music", "weather"].includes(w)
}) })
dankBarRightWidgets = widgetOrder.filter(w => { topBarRightWidgets = settings.topBarWidgetOrder.filter(w => {
return ["systemTray", "clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"].includes(w) return ["systemTray", "clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"].includes(w)
}) })
} else { } else {
var leftWidgets = settings.dankBarLeftWidgets !== undefined ? settings.dankBarLeftWidgets : (settings.topBarLeftWidgets !== undefined ? settings.topBarLeftWidgets : ["launcherButton", "workspaceSwitcher", "focusedWindow"]) var leftWidgets = settings.topBarLeftWidgets !== undefined ? settings.topBarLeftWidgets : ["launcherButton", "workspaceSwitcher", "focusedWindow"]
var centerWidgets = settings.dankBarCenterWidgets !== undefined ? settings.dankBarCenterWidgets : (settings.topBarCenterWidgets !== undefined ? settings.topBarCenterWidgets : ["music", "clock", "weather"]) var centerWidgets = 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"]) var rightWidgets = settings.topBarRightWidgets !== undefined ? settings.topBarRightWidgets : ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"]
dankBarLeftWidgets = leftWidgets topBarLeftWidgets = leftWidgets
dankBarCenterWidgets = centerWidgets topBarCenterWidgets = centerWidgets
dankBarRightWidgets = rightWidgets topBarRightWidgets = rightWidgets
updateListModel(leftWidgetsModel, leftWidgets) updateListModel(leftWidgetsModel, leftWidgets)
updateListModel(centerWidgetsModel, centerWidgets) updateListModel(centerWidgetsModel, centerWidgets)
updateListModel(rightWidgetsModel, rightWidgets) updateListModel(rightWidgetsModel, rightWidgets)
@@ -364,25 +289,14 @@ Singleton {
spotlightModalViewMode = settings.spotlightModalViewMode !== undefined ? settings.spotlightModalViewMode : "list" spotlightModalViewMode = settings.spotlightModalViewMode !== undefined ? settings.spotlightModalViewMode : "list"
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto" networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto"
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default" iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default"
if (settings.useOSLogo !== undefined) { useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false
launcherLogoMode = settings.useOSLogo ? "os" : "apps" osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : ""
launcherLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : "" osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5
launcherLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5 osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1
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 fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : defaultFontFamily
monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : defaultMonoFontFamily monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : defaultMonoFontFamily
fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal
fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0 fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0
dankBarFontScale = settings.dankBarFontScale !== undefined ? settings.dankBarFontScale : 1.0
notepadUseMonospace = settings.notepadUseMonospace !== undefined ? settings.notepadUseMonospace : true notepadUseMonospace = settings.notepadUseMonospace !== undefined ? settings.notepadUseMonospace : true
notepadFontFamily = settings.notepadFontFamily !== undefined ? settings.notepadFontFamily : "" notepadFontFamily = settings.notepadFontFamily !== undefined ? settings.notepadFontFamily : ""
notepadFontSize = settings.notepadFontSize !== undefined ? settings.notepadFontSize : 14 notepadFontSize = settings.notepadFontSize !== undefined ? settings.notepadFontSize : 14
@@ -394,37 +308,25 @@ Singleton {
showDock = settings.showDock !== undefined ? settings.showDock : false showDock = settings.showDock !== undefined ? settings.showDock : false
dockAutoHide = settings.dockAutoHide !== undefined ? settings.dockAutoHide : false dockAutoHide = settings.dockAutoHide !== undefined ? settings.dockAutoHide : false
dockGroupByApp = settings.dockGroupByApp !== undefined ? settings.dockGroupByApp : 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 cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12
notificationOverlayEnabled = settings.notificationOverlayEnabled !== undefined ? settings.notificationOverlayEnabled : false notificationOverlayEnabled = settings.notificationOverlayEnabled !== undefined ? settings.notificationOverlayEnabled : false
dankBarAutoHide = settings.dankBarAutoHide !== undefined ? settings.dankBarAutoHide : (settings.topBarAutoHide !== undefined ? settings.topBarAutoHide : false) topBarAutoHide = settings.topBarAutoHide !== undefined ? settings.topBarAutoHide : false
dankBarOpenOnOverview = settings.dankBarOpenOnOverview !== undefined ? settings.dankBarOpenOnOverview : (settings.topBarOpenOnOverview !== undefined ? settings.topBarOpenOnOverview : false) topBarOpenOnOverview = settings.topBarOpenOnOverview !== undefined ? settings.topBarOpenOnOverview : false
dankBarVisible = settings.dankBarVisible !== undefined ? settings.dankBarVisible : (settings.topBarVisible !== undefined ? settings.topBarVisible : true) topBarVisible = settings.topBarVisible !== undefined ? settings.topBarVisible : true
dockOpenOnOverview = settings.dockOpenOnOverview !== undefined ? settings.dockOpenOnOverview : false
notificationTimeoutLow = settings.notificationTimeoutLow !== undefined ? settings.notificationTimeoutLow : 5000 notificationTimeoutLow = settings.notificationTimeoutLow !== undefined ? settings.notificationTimeoutLow : 5000
notificationTimeoutNormal = settings.notificationTimeoutNormal !== undefined ? settings.notificationTimeoutNormal : 5000 notificationTimeoutNormal = settings.notificationTimeoutNormal !== undefined ? settings.notificationTimeoutNormal : 5000
notificationTimeoutCritical = settings.notificationTimeoutCritical !== undefined ? settings.notificationTimeoutCritical : 0 notificationTimeoutCritical = settings.notificationTimeoutCritical !== undefined ? settings.notificationTimeoutCritical : 0
notificationPopupPosition = settings.notificationPopupPosition !== undefined ? settings.notificationPopupPosition : SettingsData.Position.Top topBarSpacing = settings.topBarSpacing !== undefined ? settings.topBarSpacing : 4
osdAlwaysShowValue = settings.osdAlwaysShowValue !== undefined ? settings.osdAlwaysShowValue : false topBarBottomGap = settings.topBarBottomGap !== undefined ? settings.topBarBottomGap : 0
dankBarSpacing = settings.dankBarSpacing !== undefined ? settings.dankBarSpacing : (settings.topBarSpacing !== undefined ? settings.topBarSpacing : 4) topBarInnerPadding = settings.topBarInnerPadding !== undefined ? settings.topBarInnerPadding : 8
dankBarBottomGap = settings.dankBarBottomGap !== undefined ? settings.dankBarBottomGap : (settings.topBarBottomGap !== undefined ? settings.topBarBottomGap : 0) topBarSquareCorners = settings.topBarSquareCorners !== undefined ? settings.topBarSquareCorners : false
dankBarInnerPadding = settings.dankBarInnerPadding !== undefined ? settings.dankBarInnerPadding : (settings.topBarInnerPadding !== undefined ? settings.topBarInnerPadding : 4) topBarNoBackground = settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false
dankBarSquareCorners = settings.dankBarSquareCorners !== undefined ? settings.dankBarSquareCorners : (settings.topBarSquareCorners !== undefined ? settings.topBarSquareCorners : false) topBarGothCornersEnabled = settings.topBarGothCornersEnabled !== undefined ? settings.topBarGothCornersEnabled : 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
dankBarBorderColor = settings.dankBarBorderColor !== undefined ? settings.dankBarBorderColor : "surfaceText"
dankBarBorderOpacity = settings.dankBarBorderOpacity !== undefined ? settings.dankBarBorderOpacity : 1.0
dankBarBorderThickness = settings.dankBarBorderThickness !== undefined ? settings.dankBarBorderThickness : 1
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 lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true
hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch" widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch"
surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s" surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s"
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({}) screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : SettingsData.AnimationSpeed.Short
applyStoredTheme() applyStoredTheme()
detectAvailableIconThemes() detectAvailableIconThemes()
detectQtTools() detectQtTools()
@@ -438,11 +340,6 @@ Singleton {
} finally { } finally {
_loading = false _loading = false
} }
if (shouldMigrate) {
savePluginSettings()
saveSettings()
}
} }
function saveSettings() { function saveSettings() {
@@ -452,8 +349,8 @@ Singleton {
"currentThemeName": currentThemeName, "currentThemeName": currentThemeName,
"customThemeFile": customThemeFile, "customThemeFile": customThemeFile,
"matugenScheme": matugenScheme, "matugenScheme": matugenScheme,
"dankBarTransparency": dankBarTransparency, "topBarTransparency": topBarTransparency,
"dankBarWidgetTransparency": dankBarWidgetTransparency, "topBarWidgetTransparency": topBarWidgetTransparency,
"popupTransparency": popupTransparency, "popupTransparency": popupTransparency,
"dockTransparency": dockTransparency, "dockTransparency": dockTransparency,
"use24HourClock": use24HourClock, "use24HourClock": use24HourClock,
@@ -498,25 +395,21 @@ Singleton {
"clockDateFormat": clockDateFormat, "clockDateFormat": clockDateFormat,
"lockDateFormat": lockDateFormat, "lockDateFormat": lockDateFormat,
"mediaSize": mediaSize, "mediaSize": mediaSize,
"dankBarLeftWidgets": dankBarLeftWidgets, "topBarLeftWidgets": topBarLeftWidgets,
"dankBarCenterWidgets": dankBarCenterWidgets, "topBarCenterWidgets": topBarCenterWidgets,
"dankBarRightWidgets": dankBarRightWidgets, "topBarRightWidgets": topBarRightWidgets,
"appLauncherViewMode": appLauncherViewMode, "appLauncherViewMode": appLauncherViewMode,
"spotlightModalViewMode": spotlightModalViewMode, "spotlightModalViewMode": spotlightModalViewMode,
"networkPreference": networkPreference, "networkPreference": networkPreference,
"iconTheme": iconTheme, "iconTheme": iconTheme,
"launcherLogoMode": launcherLogoMode, "useOSLogo": useOSLogo,
"launcherLogoCustomPath": launcherLogoCustomPath, "osLogoColorOverride": osLogoColorOverride,
"launcherLogoColorOverride": launcherLogoColorOverride, "osLogoBrightness": osLogoBrightness,
"launcherLogoColorInvertOnMode": launcherLogoColorInvertOnMode, "osLogoContrast": osLogoContrast,
"launcherLogoBrightness": launcherLogoBrightness,
"launcherLogoContrast": launcherLogoContrast,
"launcherLogoSizeOffset": launcherLogoSizeOffset,
"fontFamily": fontFamily, "fontFamily": fontFamily,
"monoFontFamily": monoFontFamily, "monoFontFamily": monoFontFamily,
"fontWeight": fontWeight, "fontWeight": fontWeight,
"fontScale": fontScale, "fontScale": fontScale,
"dankBarFontScale": dankBarFontScale,
"notepadUseMonospace": notepadUseMonospace, "notepadUseMonospace": notepadUseMonospace,
"notepadFontFamily": notepadFontFamily, "notepadFontFamily": notepadFontFamily,
"notepadFontSize": notepadFontSize, "notepadFontSize": notepadFontSize,
@@ -528,26 +421,17 @@ Singleton {
"showDock": showDock, "showDock": showDock,
"dockAutoHide": dockAutoHide, "dockAutoHide": dockAutoHide,
"dockGroupByApp": dockGroupByApp, "dockGroupByApp": dockGroupByApp,
"dockOpenOnOverview": dockOpenOnOverview,
"dockPosition": dockPosition,
"dockSpacing": dockSpacing,
"dockBottomGap": dockBottomGap,
"cornerRadius": cornerRadius, "cornerRadius": cornerRadius,
"notificationOverlayEnabled": notificationOverlayEnabled, "notificationOverlayEnabled": notificationOverlayEnabled,
"dankBarAutoHide": dankBarAutoHide, "topBarAutoHide": topBarAutoHide,
"dankBarOpenOnOverview": dankBarOpenOnOverview, "topBarOpenOnOverview": topBarOpenOnOverview,
"dankBarVisible": dankBarVisible, "topBarVisible": topBarVisible,
"dankBarSpacing": dankBarSpacing, "topBarSpacing": topBarSpacing,
"dankBarBottomGap": dankBarBottomGap, "topBarBottomGap": topBarBottomGap,
"dankBarInnerPadding": dankBarInnerPadding, "topBarInnerPadding": topBarInnerPadding,
"dankBarSquareCorners": dankBarSquareCorners, "topBarSquareCorners": topBarSquareCorners,
"dankBarNoBackground": dankBarNoBackground, "topBarNoBackground": topBarNoBackground,
"dankBarGothCornersEnabled": dankBarGothCornersEnabled, "topBarGothCornersEnabled": topBarGothCornersEnabled,
"dankBarBorderEnabled": dankBarBorderEnabled,
"dankBarBorderColor": dankBarBorderColor,
"dankBarBorderOpacity": dankBarBorderOpacity,
"dankBarBorderThickness": dankBarBorderThickness,
"dankBarPosition": dankBarPosition,
"lockScreenShowPowerActions": lockScreenShowPowerActions, "lockScreenShowPowerActions": lockScreenShowPowerActions,
"hideBrightnessSlider": hideBrightnessSlider, "hideBrightnessSlider": hideBrightnessSlider,
"widgetBackgroundColor": widgetBackgroundColor, "widgetBackgroundColor": widgetBackgroundColor,
@@ -555,19 +439,10 @@ Singleton {
"notificationTimeoutLow": notificationTimeoutLow, "notificationTimeoutLow": notificationTimeoutLow,
"notificationTimeoutNormal": notificationTimeoutNormal, "notificationTimeoutNormal": notificationTimeoutNormal,
"notificationTimeoutCritical": notificationTimeoutCritical, "notificationTimeoutCritical": notificationTimeoutCritical,
"notificationPopupPosition": notificationPopupPosition, "screenPreferences": screenPreferences
"osdAlwaysShowValue": osdAlwaysShowValue,
"screenPreferences": screenPreferences,
"animationSpeed": animationSpeed
}, null, 2)) }, null, 2))
} }
function savePluginSettings() {
if (_pluginSettingsLoading)
return
pluginSettingsFile.setText(JSON.stringify(pluginSettings, null, 2))
}
function setShowWorkspaceIndex(enabled) { function setShowWorkspaceIndex(enabled) {
showWorkspaceIndex = enabled showWorkspaceIndex = enabled
saveSettings() saveSettings()
@@ -680,11 +555,11 @@ Singleton {
function applyStoredTheme() { function applyStoredTheme() {
if (typeof Theme !== "undefined") if (typeof Theme !== "undefined")
Theme.switchTheme(currentThemeName, false, false) Theme.switchTheme(currentThemeName, false)
else else
Qt.callLater(() => { Qt.callLater(() => {
if (typeof Theme !== "undefined") if (typeof Theme !== "undefined")
Theme.switchTheme(currentThemeName, false, false) Theme.switchTheme(currentThemeName, false)
}) })
} }
@@ -711,13 +586,13 @@ Singleton {
} }
} }
function setDankBarTransparency(transparency) { function setTopBarTransparency(transparency) {
dankBarTransparency = transparency topBarTransparency = transparency
saveSettings() saveSettings()
} }
function setDankBarWidgetTransparency(transparency) { function setTopBarWidgetTransparency(transparency) {
dankBarWidgetTransparency = transparency topBarWidgetTransparency = transparency
saveSettings() saveSettings()
} }
@@ -852,25 +727,25 @@ Singleton {
saveSettings() saveSettings()
} }
function setDankBarWidgetOrder(order) { function setTopBarWidgetOrder(order) {
dankBarWidgetOrder = order topBarWidgetOrder = order
saveSettings() saveSettings()
} }
function setDankBarLeftWidgets(order) { function setTopBarLeftWidgets(order) {
dankBarLeftWidgets = order topBarLeftWidgets = order
updateListModel(leftWidgetsModel, order) updateListModel(leftWidgetsModel, order)
saveSettings() saveSettings()
} }
function setDankBarCenterWidgets(order) { function setTopBarCenterWidgets(order) {
dankBarCenterWidgets = order topBarCenterWidgets = order
updateListModel(centerWidgetsModel, order) updateListModel(centerWidgetsModel, order)
saveSettings() saveSettings()
} }
function setDankBarRightWidgets(order) { function setTopBarRightWidgets(order) {
dankBarRightWidgets = order topBarRightWidgets = order
updateListModel(rightWidgetsModel, order) updateListModel(rightWidgetsModel, order)
saveSettings() saveSettings()
} }
@@ -884,7 +759,6 @@ Singleton {
var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex
var pciId = typeof order[i] === "string" ? undefined : order[i].pciId var pciId = typeof order[i] === "string" ? undefined : order[i].pciId
var mountPath = typeof order[i] === "string" ? undefined : order[i].mountPath var mountPath = typeof order[i] === "string" ? undefined : order[i].mountPath
var minimumWidth = typeof order[i] === "string" ? undefined : order[i].minimumWidth
var item = { var item = {
"widgetId": widgetId, "widgetId": widgetId,
"enabled": enabled "enabled": enabled
@@ -897,8 +771,6 @@ Singleton {
item.pciId = pciId item.pciId = pciId
if (mountPath !== undefined) if (mountPath !== undefined)
item.mountPath = mountPath item.mountPath = mountPath
if (minimumWidth !== undefined)
item.minimumWidth = minimumWidth
listModel.append(item) listModel.append(item)
} }
@@ -906,13 +778,13 @@ Singleton {
widgetDataChanged() widgetDataChanged()
} }
function resetDankBarWidgetsToDefault() { function resetTopBarWidgetsToDefault() {
var defaultLeft = ["launcherButton", "workspaceSwitcher", "focusedWindow"] var defaultLeft = ["launcherButton", "workspaceSwitcher", "focusedWindow"]
var defaultCenter = ["music", "clock", "weather"] var defaultCenter = ["music", "clock", "weather"]
var defaultRight = ["systemTray", "clipboard", "notificationButton", "battery", "controlCenterButton"] var defaultRight = ["systemTray", "clipboard", "notificationButton", "battery", "controlCenterButton"]
dankBarLeftWidgets = defaultLeft topBarLeftWidgets = defaultLeft
dankBarCenterWidgets = defaultCenter topBarCenterWidgets = defaultCenter
dankBarRightWidgets = defaultRight topBarRightWidgets = defaultRight
updateListModel(leftWidgetsModel, defaultLeft) updateListModel(leftWidgetsModel, defaultLeft)
updateListModel(centerWidgetsModel, defaultCenter) updateListModel(centerWidgetsModel, defaultCenter)
updateListModel(rightWidgetsModel, defaultRight) updateListModel(rightWidgetsModel, defaultRight)
@@ -983,7 +855,7 @@ Singleton {
updateQtIconTheme(themeName) updateQtIconTheme(themeName)
saveSettings() saveSettings()
if (typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic) if (typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic)
Theme.generateSystemThemesFromCurrentTheme() Theme.generateSystemThemes()
} }
function updateGtkIconTheme(themeName) { function updateGtkIconTheme(themeName) {
@@ -1021,38 +893,23 @@ Singleton {
updateQtIconTheme(iconTheme) updateQtIconTheme(iconTheme)
} }
function setLauncherLogoMode(mode) { function setUseOSLogo(enabled) {
launcherLogoMode = mode useOSLogo = enabled
saveSettings() saveSettings()
} }
function setLauncherLogoCustomPath(path) { function setOSLogoColorOverride(color) {
launcherLogoCustomPath = path osLogoColorOverride = color
saveSettings() saveSettings()
} }
function setLauncherLogoColorOverride(color) { function setOSLogoBrightness(brightness) {
launcherLogoColorOverride = color osLogoBrightness = brightness
saveSettings() saveSettings()
} }
function setLauncherLogoColorInvertOnMode(invert) { function setOSLogoContrast(contrast) {
launcherLogoColorInvertOnMode = invert osLogoContrast = contrast
saveSettings()
}
function setLauncherLogoBrightness(brightness) {
launcherLogoBrightness = brightness
saveSettings()
}
function setLauncherLogoContrast(contrast) {
launcherLogoContrast = contrast
saveSettings()
}
function setLauncherLogoSizeOffset(offset) {
launcherLogoSizeOffset = offset
saveSettings() saveSettings()
} }
@@ -1076,11 +933,6 @@ Singleton {
saveSettings() saveSettings()
} }
function setDankBarFontScale(scale) {
dankBarFontScale = scale
saveSettings()
}
function setGtkThemingEnabled(enabled) { function setGtkThemingEnabled(enabled) {
gtkThemingEnabled = enabled gtkThemingEnabled = enabled
saveSettings() saveSettings()
@@ -1099,24 +951,6 @@ Singleton {
function setShowDock(enabled) { function setShowDock(enabled) {
showDock = 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() saveSettings()
} }
@@ -1130,11 +964,6 @@ Singleton {
saveSettings() saveSettings()
} }
function setdockOpenOnOverview(enabled) {
dockOpenOnOverview = enabled
saveSettings()
}
function setCornerRadius(radius) { function setCornerRadius(radius) {
cornerRadius = radius cornerRadius = radius
saveSettings() saveSettings()
@@ -1145,23 +974,23 @@ Singleton {
saveSettings() saveSettings()
} }
function setDankBarAutoHide(enabled) { function setTopBarAutoHide(enabled) {
dankBarAutoHide = enabled topBarAutoHide = enabled
saveSettings() saveSettings()
} }
function setDankBarOpenOnOverview(enabled) { function setTopBarOpenOnOverview(enabled) {
dankBarOpenOnOverview = enabled topBarOpenOnOverview = enabled
saveSettings() saveSettings()
} }
function setDankBarVisible(visible) { function setTopBarVisible(visible) {
dankBarVisible = visible topBarVisible = visible
saveSettings() saveSettings()
} }
function toggleDankBarVisible() { function toggleTopBarVisible() {
dankBarVisible = !dankBarVisible topBarVisible = !topBarVisible
saveSettings() saveSettings()
} }
@@ -1180,172 +1009,36 @@ Singleton {
saveSettings() saveSettings()
} }
function setNotificationPopupPosition(position) { function setTopBarSpacing(spacing) {
notificationPopupPosition = position topBarSpacing = spacing
saveSettings() saveSettings()
} }
function setOsdAlwaysShowValue(enabled) { function setTopBarBottomGap(gap) {
osdAlwaysShowValue = enabled topBarBottomGap = gap
saveSettings() saveSettings()
} }
function sendTestNotifications() { function setTopBarInnerPadding(padding) {
sendTestNotification(0) topBarInnerPadding = padding
testNotifTimer1.start()
testNotifTimer2.start()
}
function sendTestNotification(index) {
const notifications = [
["Notification Position Test", "DMS test notification 1 of 3 ~ Hi there!", "preferences-system"],
["Second Test", "DMS Notification 2 of 3 ~ Check it out!", "applications-graphics"],
["Third Test", "DMS notification 3 of 3 ~ Enjoy!", "face-smile"]
]
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() saveSettings()
} }
function setDankBarBottomGap(gap) { function setTopBarSquareCorners(enabled) {
dankBarBottomGap = gap topBarSquareCorners = enabled
saveSettings() saveSettings()
} }
function setDankBarInnerPadding(padding) { function setTopBarNoBackground(enabled) {
dankBarInnerPadding = padding topBarNoBackground = enabled
saveSettings() saveSettings()
} }
function setDankBarSquareCorners(enabled) { function setTopBarGothCornersEnabled(enabled) {
dankBarSquareCorners = enabled topBarGothCornersEnabled = enabled
saveSettings() 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) { function setLockScreenShowPowerActions(enabled) {
lockScreenShowPowerActions = enabled lockScreenShowPowerActions = enabled
saveSettings() saveSettings()
@@ -1382,48 +1075,14 @@ Singleton {
return Quickshell.screens.filter(screen => prefs.includes(screen.name)) 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) { function _shq(s) {
return "'" + String(s).replace(/'/g, "'\\''") + "'" return "'" + String(s).replace(/'/g, "'\\''") + "'"
} }
Component.onCompleted: { Component.onCompleted: {
if (!isGreeterMode) { loadSettings()
loadSettings() fontCheckTimer.start()
fontCheckTimer.start() initializeListModels()
initializeListModels()
}
} }
ListModel { ListModel {
@@ -1464,47 +1123,25 @@ Singleton {
FileView { FileView {
id: settingsFile id: settingsFile
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json" path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
blockLoading: true blockLoading: true
blockWrites: true blockWrites: true
atomicWrites: true atomicWrites: true
watchChanges: !isGreeterMode watchChanges: true
onLoaded: { onLoaded: {
if (!isGreeterMode) { parseSettings(settingsFile.text())
parseSettings(settingsFile.text()) hasTriedDefaultSettings = false
hasTriedDefaultSettings = false
}
} }
onLoadFailed: error => { onLoadFailed: error => {
if (!isGreeterMode && !hasTriedDefaultSettings) { if (!hasTriedDefaultSettings) {
hasTriedDefaultSettings = true hasTriedDefaultSettings = true
defaultSettingsCheckProcess.running = true defaultSettingsCheckProcess.running = true
} else if (!isGreeterMode) { } else {
applyStoredTheme() 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 { Process {
id: systemDefaultDetectionProcess id: systemDefaultDetectionProcess
@@ -1569,7 +1206,7 @@ Singleton {
id: defaultSettingsCheckProcess id: defaultSettingsCheckProcess
command: ["sh", "-c", "CONFIG_DIR=\"" + _configDir command: ["sh", "-c", "CONFIG_DIR=\"" + _configDir
+ "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-settings.json\" ] && [ ! -f \"$CONFIG_DIR/settings.json\" ]; then cp --no-preserve=mode \"$CONFIG_DIR/default-settings.json\" \"$CONFIG_DIR/settings.json\" && echo 'copied'; else echo 'not_found'; fi"] + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-settings.json\" ] && [ ! -f \"$CONFIG_DIR/settings.json\" ]; then cp \"$CONFIG_DIR/default-settings.json\" \"$CONFIG_DIR/settings.json\" && echo 'copied'; else echo 'not_found'; fi"]
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {
@@ -1584,22 +1221,22 @@ Singleton {
IpcHandler { IpcHandler {
function reveal(): string { function reveal(): string {
root.setDankBarVisible(true) root.setTopBarVisible(true)
return "BAR_SHOW_SUCCESS" return "BAR_SHOW_SUCCESS"
} }
function hide(): string { function hide(): string {
root.setDankBarVisible(false) root.setTopBarVisible(false)
return "BAR_HIDE_SUCCESS" return "BAR_HIDE_SUCCESS"
} }
function toggle(): string { function toggle(): string {
root.toggleDankBarVisible() root.toggleTopBarVisible()
return root.dankBarVisible ? "BAR_SHOW_SUCCESS" : "BAR_HIDE_SUCCESS" return topBarVisible ? "BAR_SHOW_SUCCESS" : "BAR_HIDE_SUCCESS"
} }
function status(): string { function status(): string {
return root.dankBarVisible ? "visible" : "hidden" return topBarVisible ? "visible" : "hidden"
} }
target: "bar" target: "bar"

View File

@@ -2,101 +2,101 @@
// Separated from Theme.qml to keep that file clean // Separated from Theme.qml to keep that file clean
const CatppuccinMocha = { const CatppuccinMocha = {
surface: "#313244", surface: "#45475a",
surfaceText: "#cdd6f4", surfaceText: "#cdd6f4",
surfaceVariant: "#313244", surfaceVariant: "#45475a",
surfaceVariantText: "#a6adc8", surfaceVariantText: "#a6adc8",
background: "#1e1e2e", background: "#1e1e2e",
backgroundText: "#cdd6f4", backgroundText: "#cdd6f4",
outline: "#6c7086", outline: "#6c7086",
surfaceContainer: "#45475a", surfaceContainer: "#313244",
surfaceContainerHigh: "#585b70", surfaceContainerHigh: "#585b70",
surfaceContainerHighest: "#6c7086" surfaceContainerHighest: "#7f849c"
} }
const CatppuccinLatte = { const CatppuccinLatte = {
surface: "#e6e9ef", surface: "#bcc0cc",
surfaceText: "#4c4f69", surfaceText: "#4c4f69",
surfaceVariant: "#e6e9ef", surfaceVariant: "#bcc0cc",
surfaceVariantText: "#6c6f85", surfaceVariantText: "#6c6f85",
background: "#eff1f5", background: "#eff1f5",
backgroundText: "#4c4f69", backgroundText: "#4c4f69",
outline: "#9ca0b0", outline: "#9ca0b0",
surfaceContainer: "#dce0e8", surfaceContainer: "#ccd0da",
surfaceContainerHigh: "#ccd0da", surfaceContainerHigh: "#acb0be",
surfaceContainerHighest: "#bcc0cc" surfaceContainerHighest: "#8c8fa1"
} }
const CatppuccinVariants = { const CatppuccinVariants = {
"cat-rosewater": { "cat-rosewater": {
name: "Rosewater", name: "Rosewater",
dark: { primary: "#f5e0dc", secondary: "#f2cdcd", primaryText: "#1e1e2e", primaryContainer: "#7d5d56", surfaceTint: "#f5e0dc" }, dark: { primary: "#f5e0dc", secondary: "#f2cdcd", primaryText: "#1e1e2e", primaryContainer: "#8b6b5e", surfaceTint: "#f5e0dc" },
light: { primary: "#dc8a78", secondary: "#dd7878", primaryText: "#ffffff", primaryContainer: "#f6e7e3", surfaceTint: "#dc8a78" } light: { primary: "#dc8a78", secondary: "#dd7878", primaryText: "#ffffff", primaryContainer: "#f4d2ca", surfaceTint: "#dc8a78" }
}, },
"cat-flamingo": { "cat-flamingo": {
name: "Flamingo", name: "Flamingo",
dark: { primary: "#f2cdcd", secondary: "#f5e0dc", primaryText: "#1e1e2e", primaryContainer: "#7a555a", surfaceTint: "#f2cdcd" }, dark: { primary: "#f2cdcd", secondary: "#f5e0dc", primaryText: "#1e1e2e", primaryContainer: "#885d62", surfaceTint: "#f2cdcd" },
light: { primary: "#dd7878", secondary: "#dc8a78", primaryText: "#ffffff", primaryContainer: "#f6e5e5", surfaceTint: "#dd7878" } light: { primary: "#dd7878", secondary: "#dc8a78", primaryText: "#ffffff", primaryContainer: "#f4caca", surfaceTint: "#dd7878" }
}, },
"cat-pink": { "cat-pink": {
name: "Pink", name: "Pink",
dark: { primary: "#f5c2e7", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#7a3f69", surfaceTint: "#f5c2e7" }, dark: { primary: "#f5c2e7", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#8b537a", surfaceTint: "#f5c2e7" },
light: { primary: "#ea76cb", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#f7d7ee", surfaceTint: "#ea76cb" } light: { primary: "#ea76cb", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#f7c9e7", surfaceTint: "#ea76cb" }
}, },
"cat-mauve": { "cat-mauve": {
name: "Mauve", name: "Mauve",
dark: { primary: "#cba6f7", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#55307f", surfaceTint: "#cba6f7" }, dark: { primary: "#cba6f7", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#61378a", surfaceTint: "#cba6f7" },
light: { primary: "#8839ef", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#eadcff", surfaceTint: "#8839ef" } light: { primary: "#8839ef", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#e4d3ff", surfaceTint: "#8839ef" }
}, },
"cat-red": { "cat-red": {
name: "Red", name: "Red",
dark: { primary: "#f38ba8", secondary: "#eba0ac", primaryText: "#1e1e2e", primaryContainer: "#6f2438", surfaceTint: "#f38ba8" }, dark: { primary: "#f38ba8", secondary: "#eba0ac", primaryText: "#1e1e2e", primaryContainer: "#891c3b", surfaceTint: "#f38ba8" },
light: { primary: "#d20f39", secondary: "#e64553", primaryText: "#ffffff", primaryContainer: "#f6d0d6", surfaceTint: "#d20f39" } light: { primary: "#d20f39", secondary: "#e64553", primaryText: "#ffffff", primaryContainer: "#f1b8c4", surfaceTint: "#d20f39" }
}, },
"cat-maroon": { "cat-maroon": {
name: "Maroon", name: "Maroon",
dark: { primary: "#eba0ac", secondary: "#f38ba8", primaryText: "#1e1e2e", primaryContainer: "#6d3641", surfaceTint: "#eba0ac" }, dark: { primary: "#eba0ac", secondary: "#f38ba8", primaryText: "#1e1e2e", primaryContainer: "#81313f", surfaceTint: "#eba0ac" },
light: { primary: "#e64553", secondary: "#d20f39", primaryText: "#ffffff", primaryContainer: "#f7d8dc", surfaceTint: "#e64553" } light: { primary: "#e64553", secondary: "#d20f39", primaryText: "#ffffff", primaryContainer: "#f4c3c8", surfaceTint: "#e64553" }
}, },
"cat-peach": { "cat-peach": {
name: "Peach", name: "Peach",
dark: { primary: "#fab387", secondary: "#f9e2af", primaryText: "#1e1e2e", primaryContainer: "#734226", surfaceTint: "#fab387" }, dark: { primary: "#fab387", secondary: "#f9e2af", primaryText: "#1e1e2e", primaryContainer: "#90441a", surfaceTint: "#fab387" },
light: { primary: "#fe640b", secondary: "#df8e1d", primaryText: "#ffffff", primaryContainer: "#ffe4d5", surfaceTint: "#fe640b" } light: { primary: "#fe640b", secondary: "#df8e1d", primaryText: "#ffffff", primaryContainer: "#ffddcc", surfaceTint: "#fe640b" }
}, },
"cat-yellow": { "cat-yellow": {
name: "Yellow", name: "Yellow",
dark: { primary: "#f9e2af", secondary: "#a6e3a1", primaryText: "#1e1e2e", primaryContainer: "#6e5a2f", surfaceTint: "#f9e2af" }, dark: { primary: "#f9e2af", secondary: "#a6e3a1", primaryText: "#1e1e2e", primaryContainer: "#8f7342", surfaceTint: "#f9e2af" },
light: { primary: "#df8e1d", secondary: "#40a02b", primaryText: "#ffffff", primaryContainer: "#fff6d6", surfaceTint: "#df8e1d" } light: { primary: "#df8e1d", secondary: "#40a02b", primaryText: "#ffffff", primaryContainer: "#fff3cc", surfaceTint: "#df8e1d" }
}, },
"cat-green": { "cat-green": {
name: "Green", name: "Green",
dark: { primary: "#a6e3a1", secondary: "#94e2d5", primaryText: "#1e1e2e", primaryContainer: "#2f5f36", surfaceTint: "#a6e3a1" }, dark: { primary: "#a6e3a1", secondary: "#94e2d5", primaryText: "#1e1e2e", primaryContainer: "#3c7534", surfaceTint: "#a6e3a1" },
light: { primary: "#40a02b", secondary: "#179299", primaryText: "#ffffff", primaryContainer: "#dff4e0", surfaceTint: "#40a02b" } light: { primary: "#40a02b", secondary: "#179299", primaryText: "#ffffff", primaryContainer: "#d4f5d4", surfaceTint: "#40a02b" }
}, },
"cat-teal": { "cat-teal": {
name: "Teal", name: "Teal",
dark: { primary: "#94e2d5", secondary: "#89dceb", primaryText: "#1e1e2e", primaryContainer: "#2e5e59", surfaceTint: "#94e2d5" }, dark: { primary: "#94e2d5", secondary: "#89dceb", primaryText: "#1e1e2e", primaryContainer: "#2a7468", surfaceTint: "#94e2d5" },
light: { primary: "#179299", secondary: "#04a5e5", primaryText: "#ffffff", primaryContainer: "#daf3f1", surfaceTint: "#179299" } light: { primary: "#179299", secondary: "#04a5e5", primaryText: "#ffffff", primaryContainer: "#ccf2f2", surfaceTint: "#179299" }
}, },
"cat-sky": { "cat-sky": {
name: "Sky", name: "Sky",
dark: { primary: "#89dceb", secondary: "#74c7ec", primaryText: "#1e1e2e", primaryContainer: "#24586a", surfaceTint: "#89dceb" }, dark: { primary: "#89dceb", secondary: "#74c7ec", primaryText: "#1e1e2e", primaryContainer: "#196e7e", surfaceTint: "#89dceb" },
light: { primary: "#04a5e5", secondary: "#209fb5", primaryText: "#ffffff", primaryContainer: "#dbf1fb", surfaceTint: "#04a5e5" } light: { primary: "#04a5e5", secondary: "#209fb5", primaryText: "#ffffff", primaryContainer: "#ccebff", surfaceTint: "#04a5e5" }
}, },
"cat-sapphire": { "cat-sapphire": {
name: "Sapphire", name: "Sapphire",
dark: { primary: "#74c7ec", secondary: "#89b4fa", primaryText: "#1e1e2e", primaryContainer: "#1f4d6f", surfaceTint: "#74c7ec" }, dark: { primary: "#74c7ec", secondary: "#89b4fa", primaryText: "#1e1e2e", primaryContainer: "#0a597f", surfaceTint: "#74c7ec" },
light: { primary: "#209fb5", secondary: "#1e66f5", primaryText: "#ffffff", primaryContainer: "#def3f8", surfaceTint: "#209fb5" } light: { primary: "#209fb5", secondary: "#1e66f5", primaryText: "#ffffff", primaryContainer: "#d0f0f5", surfaceTint: "#209fb5" }
}, },
"cat-blue": { "cat-blue": {
name: "Blue", name: "Blue",
dark: { primary: "#89b4fa", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#243f75", surfaceTint: "#89b4fa" }, dark: { primary: "#89b4fa", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#19468d", surfaceTint: "#89b4fa" },
light: { primary: "#1e66f5", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#e0e9ff", surfaceTint: "#1e66f5" } light: { primary: "#1e66f5", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#ccd9ff", surfaceTint: "#1e66f5" }
}, },
"cat-lavender": { "cat-lavender": {
name: "Lavender", name: "Lavender",
dark: { primary: "#b4befe", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#3f4481", surfaceTint: "#b4befe" }, dark: { primary: "#b4befe", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#4a5091", surfaceTint: "#b4befe" },
light: { primary: "#7287fd", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#e5e8ff", surfaceTint: "#7287fd" } light: { primary: "#7287fd", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#dde1ff", surfaceTint: "#7287fd" }
} }
} }
@@ -290,9 +290,9 @@ const StockThemes = {
background: "#131315", background: "#131315",
backgroundText: "#e4e2e3", backgroundText: "#e4e2e3",
outline: "#929092", outline: "#929092",
surfaceContainer: "#353535", surfaceContainer: "#2a2a2a",
surfaceContainerHigh: "#424242", surfaceContainerHigh: "#2a2a2b",
surfaceContainerHighest: "#505050", surfaceContainerHighest: "#353535",
error: "#ffb4ab", error: "#ffb4ab",
warning: "#3f4759", warning: "#3f4759",
info: "#595e6c", info: "#595e6c",
@@ -476,9 +476,8 @@ const StockThemes = {
background: "#ffffff", background: "#ffffff",
backgroundText: "#1a1a1a", backgroundText: "#1a1a1a",
outline: "#757577", outline: "#757577",
surfaceContainer: "#e8e8ea", surfaceContainer: "#f5f5f6",
surfaceContainerHigh: "#dcdcde", surfaceContainerHigh: "#eaeaeb",
surfaceContainerHighest: "#d0d0d2",
error: "#ba1a1a", error: "#ba1a1a",
warning: "#f9e79f", warning: "#f9e79f",
info: "#5d6475", info: "#5d6475",

View File

@@ -16,12 +16,11 @@ Singleton {
readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true" readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true"
// ! TODO - Synchronize with niri/hyprland gaps? readonly property real popupDistance: 4
readonly property real popupDistance: 2
property string currentTheme: "blue" property string currentTheme: "blue"
property string currentThemeCategory: "generic" property string currentThemeCategory: "generic"
property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false property bool isLightMode: false
readonly property string dynamic: "dynamic" readonly property string dynamic: "dynamic"
readonly property string custom : "custom" readonly property string custom : "custom"
@@ -33,6 +32,7 @@ Singleton {
if (typeof SessionData === "undefined") return "" if (typeof SessionData === "undefined") return ""
if (SessionData.perMonitorWallpaper) { if (SessionData.perMonitorWallpaper) {
// Use first monitor's wallpaper for dynamic theming
var screens = Quickshell.screens var screens = Quickshell.screens
if (screens.length > 0) { if (screens.length > 0) {
var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name) var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name)
@@ -74,6 +74,7 @@ Singleton {
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
property var workerRunning: false property var workerRunning: false
property var matugenColors: ({}) property var matugenColors: ({})
property int colorUpdateTrigger: 0
property var customThemeData: null property var customThemeData: null
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell" readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell"
@@ -81,23 +82,16 @@ Singleton {
Component.onCompleted: { Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", stateDir]) Quickshell.execDetached(["mkdir", "-p", stateDir])
matugenCheck.running = true matugenCheck.running = true
if (typeof SessionData !== "undefined") { if (typeof SessionData !== "undefined")
SessionData.isLightModeChanged.connect(root.onLightModeChanged) SessionData.isLightModeChanged.connect(root.onLightModeChanged)
}
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) { if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
switchTheme(SettingsData.currentThemeName, false, false) switchTheme(SettingsData.currentThemeName, false)
}
}
function applyGreeterTheme(themeName) {
switchTheme(themeName, false, false)
if (themeName === dynamic && dynamicColorsFileView.path) {
dynamicColorsFileView.reload()
} }
} }
function getMatugenColor(path, fallback) { function getMatugenColor(path, fallback) {
colorUpdateTrigger
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark" const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode] let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode]
for (const part of path.split(".")) { for (const part of path.split(".")) {
@@ -238,22 +232,11 @@ Singleton {
property color shadowMedium: Qt.rgba(0, 0, 0, 0.08) property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
property color shadowStrong: Qt.rgba(0, 0, 0, 0.3) property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
readonly property var animationDurations: [ property int shorterDuration: 100
{ shorter: 0, short: 0, medium: 0, long: 0, extraLong: 0 }, property int shortDuration: 150
{ shorter: 50, short: 75, medium: 150, long: 250, extraLong: 500 }, property int mediumDuration: 300
{ shorter: 100, short: 150, medium: 300, long: 500, extraLong: 1000 }, property int longDuration: 500
{ shorter: 150, short: 225, medium: 450, long: 750, extraLong: 1500 }, property int extraLongDuration: 1000
{ 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 standardEasing: Easing.OutCubic
property int emphasizedEasing: Easing.OutQuart property int emphasizedEasing: Easing.OutQuart
@@ -273,8 +256,8 @@ Singleton {
property real iconSizeLarge: 32 property real iconSizeLarge: 32
property real panelTransparency: 0.85 property real panelTransparency: 0.85
property real widgetTransparency: typeof SettingsData !== "undefined" && SettingsData.dankBarWidgetTransparency !== undefined ? SettingsData.dankBarWidgetTransparency : 1.0 property real widgetTransparency: typeof SettingsData !== "undefined" && SettingsData.topBarWidgetTransparency !== undefined ? SettingsData.topBarWidgetTransparency : 0.85
property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 1.0 property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 0.92
function screenTransition() { function screenTransition() {
CompositorService.isNiri && NiriService.doScreenTransition() CompositorService.isNiri && NiriService.doScreenTransition()
@@ -283,12 +266,7 @@ Singleton {
function switchTheme(themeName, savePrefs = true, enableTransition = true) { function switchTheme(themeName, savePrefs = true, enableTransition = true) {
if (enableTransition) { if (enableTransition) {
screenTransition() screenTransition()
themeTransitionTimer.themeName = themeName
themeTransitionTimer.savePrefs = savePrefs
themeTransitionTimer.restart()
return
} }
if (themeName === dynamic) { if (themeName === dynamic) {
currentTheme = dynamic currentTheme = dynamic
currentThemeCategory = dynamic currentThemeCategory = dynamic
@@ -300,45 +278,34 @@ Singleton {
} }
} else { } else {
currentTheme = themeName currentTheme = themeName
// Determine category based on theme name
if (StockThemes.isCatppuccinVariant(themeName)) { if (StockThemes.isCatppuccinVariant(themeName)) {
currentThemeCategory = "catppuccin" currentThemeCategory = "catppuccin"
} else { } else {
currentThemeCategory = "generic" currentThemeCategory = "generic"
} }
} }
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode) if (savePrefs && typeof SettingsData !== "undefined")
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode)
SettingsData.setTheme(currentTheme) SettingsData.setTheme(currentTheme)
if (!isGreeterMode) { generateSystemThemesFromCurrentTheme()
generateSystemThemesFromCurrentTheme()
}
} }
function setLightMode(light, savePrefs = true, enableTransition = false) { function setLightMode(light, savePrefs = true) {
if (enableTransition) { screenTransition()
screenTransition()
lightModeTransitionTimer.lightMode = light
lightModeTransitionTimer.savePrefs = savePrefs
lightModeTransitionTimer.restart()
return
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
isLightMode = light isLightMode = light
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) if (savePrefs && typeof SessionData !== "undefined")
SessionData.setLightMode(isLightMode) SessionData.setLightMode(isLightMode)
if (!isGreeterMode) { PortalService.setLightMode(isLightMode)
PortalService.setLightMode(isLightMode) generateSystemThemesFromCurrentTheme()
generateSystemThemesFromCurrentTheme()
}
} }
function toggleLightMode(savePrefs = true) { function toggleLightMode(savePrefs = true) {
setLightMode(!isLightMode, savePrefs, true) setLightMode(!isLightMode, savePrefs)
} }
function forceGenerateSystemThemes() { function forceGenerateSystemThemes() {
screenTransition()
if (!matugenAvailable) { if (!matugenAvailable) {
return return
} }
@@ -362,10 +329,8 @@ Singleton {
} }
function switchThemeCategory(category, defaultTheme) { function switchThemeCategory(category, defaultTheme) {
screenTransition() currentThemeCategory = category
themeCategoryTransitionTimer.category = category switchTheme(defaultTheme, true, false)
themeCategoryTransitionTimer.defaultTheme = defaultTheme
themeCategoryTransitionTimer.restart()
} }
function getCatppuccinColor(variantName) { function getCatppuccinColor(variantName) {
@@ -389,6 +354,7 @@ Singleton {
} }
function loadCustomTheme(themeData) { function loadCustomTheme(themeData) {
screenTransition()
if (themeData.dark || themeData.light) { if (themeData.dark || themeData.light) {
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark" const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light
@@ -470,19 +436,6 @@ Singleton {
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5 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) { function getBatteryIcon(level, isCharging, batteryAvailable) {
if (!batteryAvailable) if (!batteryAvailable)
return _getBatteryPowerProfileIcon() return _getBatteryPowerProfileIcon()
@@ -575,6 +528,10 @@ Singleton {
function onLightModeChanged() { function onLightModeChanged() {
if (matugenColors && Object.keys(matugenColors).length > 0) {
colorUpdateTrigger++
}
if (currentTheme === "custom" && customThemeFileView.path) { if (currentTheme === "custom" && customThemeFileView.path) {
customThemeFileView.reload() customThemeFileView.reload()
} }
@@ -608,17 +565,16 @@ Singleton {
console.log("calling matugen worker") console.log("calling matugen worker")
systemThemeGenerator.command = [ systemThemeGenerator.command = [
"sh", "-c", "sh", "-c",
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' --run` `sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' --run`
] ]
} else { } else {
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, "--run"] systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, "--run"]
} }
systemThemeGenerator.running = true systemThemeGenerator.running = true
} }
function generateSystemThemesFromCurrentTheme() { function generateSystemThemesFromCurrentTheme() {
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode) if (!matugenAvailable)
if (!matugenAvailable || isGreeterMode)
return return
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
@@ -682,79 +638,14 @@ Singleton {
qtApplier.running = true qtApplier.running = true
} }
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
function snap(value, dpr) {
const s = dpr || 1
return Math.round(value * s) / s
}
function px(value, dpr) {
const s = dpr || 1
return Math.round(value * s) / s
}
function hairline(dpr) {
return 1 / (dpr || 1)
}
function invertHex(hex) {
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 ""
if (colorOverride === "primary") return primary
if (colorOverride === "surface") return surfaceText
return colorOverride
}
property string effectiveLogoColor: {
if (typeof SettingsData === "undefined") return ""
const colorOverride = SettingsData.launcherLogoColorOverride
if (!colorOverride || colorOverride === "") return ""
if (colorOverride === "primary") return primary
if (colorOverride === "surface") return surfaceText
if (!SettingsData.launcherLogoColorInvertOnMode) {
return colorOverride
}
if (isLightMode) {
return invertHex(colorOverride)
}
return colorOverride
}
Process { Process {
id: matugenCheck id: matugenCheck
command: ["which", "matugen"] command: ["which", "matugen"]
onExited: code => { onExited: code => {
matugenAvailable = (code === 0) && !envDisableMatugen matugenAvailable = (code === 0) && !envDisableMatugen
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode) if (!matugenAvailable) {
console.log("matugen not not available in path or disabled via DMS_DISABLE_MATUGEN")
if (!matugenAvailable || isGreeterMode) {
return return
} }
@@ -805,7 +696,10 @@ Singleton {
onExited: exitCode => { onExited: exitCode => {
workerRunning = false workerRunning = false
if (exitCode !== 0 && exitCode !== 2) { 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 (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.showError("Theme worker failed (" + exitCode + ")") ToastService.showError("Theme worker failed (" + exitCode + ")")
} }
@@ -894,26 +788,20 @@ Singleton {
FileView { FileView {
id: dynamicColorsFileView id: dynamicColorsFileView
path: { path: stateDir + "/dms-colors.json"
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms" watchChanges: currentTheme === dynamic
const colorsPath = SessionData.isGreeterMode
? greetCfgDir + "/colors.json"
: stateDir + "/dms-colors.json"
return colorsPath
}
watchChanges: currentTheme === dynamic && !SessionData.isGreeterMode
function parseAndLoadColors() { function parseAndLoadColors() {
try { try {
const colorsText = dynamicColorsFileView.text() const colorsText = dynamicColorsFileView.text()
if (colorsText) { if (colorsText) {
root.matugenColors = JSON.parse(colorsText) root.matugenColors = JSON.parse(colorsText)
root.colorUpdateTrigger++
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.clearWallpaperError() ToastService.clearWallpaperError()
} }
} }
} catch (e) { } catch (e) {
console.error("Theme: Failed to parse dynamic colors:", e)
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error" ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Dynamic colors parse error: " + e.message) ToastService.showError("Dynamic colors parse error: " + e.message)
@@ -945,16 +833,16 @@ Singleton {
function toggle(): string { function toggle(): string {
root.toggleLightMode() root.toggleLightMode()
return root.isLightMode ? "dark" : "light" return root.isLightMode ? "light" : "dark"
} }
function light(): string { function light(): string {
root.setLightMode(true, true, true) root.setLightMode(true)
return "light" return "light"
} }
function dark(): string { function dark(): string {
root.setLightMode(false, true, true) root.setLightMode(false)
return "dark" return "dark"
} }
@@ -962,35 +850,4 @@ Singleton {
return root.isLightMode ? "light" : "dark" 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)
}
}
} }

View File

@@ -1,29 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
import qs.Common
import qs.Modules.Greetd
Item {
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
}
}
}

View File

@@ -1,524 +0,0 @@
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
Item {
id: root
Instantiator {
id: daemonPluginInstantiator
asynchronous: true
model: 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 {}
LazyLoader {
id: lockLoader
active: false
Lock {
id: lock
anchors.fill: parent
Component.onCompleted: {
IdleService.lockComponent = lock
}
}
}
Timer {
id: lockInitTimer
interval: 100
running: true
repeat: false
onTriggered: lockLoader.active = true
}
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: {
lockLoader.item.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: I18n.tr("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
}
}
}
DMSShellIPC {
powerMenuModalLoader: powerMenuModalLoader
processListModalLoader: processListModalLoader
controlCenterLoader: controlCenterLoader
dankDashPopoutLoader: dankDashPopoutLoader
notepadSlideoutVariants: notepadSlideoutVariants
}
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
}
}
}

View File

@@ -1,265 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import qs.Common
import qs.Services
Item {
id: root
required property var powerMenuModalLoader
required property var processListModalLoader
required property var controlCenterLoader
required property var dankDashPopoutLoader
required property var notepadSlideoutVariants
IpcHandler {
function open() {
root.powerMenuModalLoader.active = true
if (root.powerMenuModalLoader.item)
root.powerMenuModalLoader.item.openCentered()
return "POWERMENU_OPEN_SUCCESS"
}
function close() {
if (root.powerMenuModalLoader.item)
root.powerMenuModalLoader.item.close()
return "POWERMENU_CLOSE_SUCCESS"
}
function toggle() {
root.powerMenuModalLoader.active = true
if (root.powerMenuModalLoader.item) {
if (root.powerMenuModalLoader.item.shouldBeVisible) {
root.powerMenuModalLoader.item.close()
} else {
root.powerMenuModalLoader.item.openCentered()
}
}
return "POWERMENU_TOGGLE_SUCCESS"
}
target: "powermenu"
}
IpcHandler {
function open(): string {
root.processListModalLoader.active = true
if (root.processListModalLoader.item)
root.processListModalLoader.item.show()
return "PROCESSLIST_OPEN_SUCCESS"
}
function close(): string {
if (root.processListModalLoader.item)
root.processListModalLoader.item.hide()
return "PROCESSLIST_CLOSE_SUCCESS"
}
function toggle(): string {
root.processListModalLoader.active = true
if (root.processListModalLoader.item)
root.processListModalLoader.item.toggle()
return "PROCESSLIST_TOGGLE_SUCCESS"
}
target: "processlist"
}
IpcHandler {
function open(): string {
root.controlCenterLoader.active = true
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.open()
return "CONTROL_CENTER_OPEN_SUCCESS"
}
return "CONTROL_CENTER_OPEN_FAILED"
}
function close(): string {
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.close()
return "CONTROL_CENTER_CLOSE_SUCCESS"
}
return "CONTROL_CENTER_CLOSE_FAILED"
}
function toggle(): string {
root.controlCenterLoader.active = true
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.toggle()
return "CONTROL_CENTER_TOGGLE_SUCCESS"
}
return "CONTROL_CENTER_TOGGLE_FAILED"
}
target: "control-center"
}
IpcHandler {
function open(tab: string): string {
root.dankDashPopoutLoader.active = true
if (root.dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0
break
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
root.dankDashPopoutLoader.item.dashVisible = true
return "DASH_OPEN_SUCCESS"
}
return "DASH_OPEN_FAILED"
}
function close(): string {
if (root.dankDashPopoutLoader.item) {
root.dankDashPopoutLoader.item.dashVisible = false
return "DASH_CLOSE_SUCCESS"
}
return "DASH_CLOSE_FAILED"
}
function toggle(tab: string): string {
root.dankDashPopoutLoader.active = true
if (root.dankDashPopoutLoader.item) {
if (root.dankDashPopoutLoader.item.dashVisible) {
root.dankDashPopoutLoader.item.dashVisible = false
} else {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0
break
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
root.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 (root.notepadSlideoutVariants.instances.length === 0) {
return null
}
if (root.notepadSlideoutVariants.instances.length === 1) {
return root.notepadSlideoutVariants.instances[0]
}
var focusedScreen = getFocusedScreenName()
if (focusedScreen && root.notepadSlideoutVariants.instances.length > 0) {
for (var i = 0; i < root.notepadSlideoutVariants.instances.length; i++) {
var slideout = root.notepadSlideoutVariants.instances[i]
if (slideout.modelData && slideout.modelData.name === focusedScreen) {
return slideout
}
}
}
for (var i = 0; i < root.notepadSlideoutVariants.instances.length; i++) {
var slideout = root.notepadSlideoutVariants.instances[i]
if (slideout.isVisible) {
return slideout
}
}
return root.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"
}
}

View File

@@ -30,7 +30,7 @@ Item {
showKeyboardHints: modal.showKeyboardHints showKeyboardHints: modal.showKeyboardHints
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
onClearAllClicked: { onClearAllClicked: {
clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () { clearConfirmDialog.show("Clear All History?", "This will permanently delete all clipboard history.", function () {
modal.clearAll() modal.clearAll()
modal.hide() modal.hide()
}, function () {}) }, function () {})
@@ -46,7 +46,7 @@ Item {
leftIconName: "search" leftIconName: "search"
showClearButton: true showClearButton: true
focus: true focus: true
ignoreTabKeys: true ignoreLeftRightKeys: true
keyForwardTargets: [modal.modalFocusScope] keyForwardTargets: [modal.modalFocusScope]
onTextChanged: { onTextChanged: {
modal.searchText = text modal.searchText = text
@@ -116,7 +116,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("No clipboard entries found") text: "No clipboard entries found"
anchors.centerIn: parent anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText

View File

@@ -80,11 +80,11 @@ Rectangle {
text: { text: {
switch (entryType) { switch (entryType) {
case "image": case "image":
return I18n.tr("Image") + " • " + entryPreview return "Image • " + entryPreview
case "long_text": case "long_text":
return I18n.tr("Long Text") return "Long Text"
default: default:
return I18n.tr("Text") return "Text"
} }
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall

View File

@@ -28,7 +28,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("Clipboard History") + ` (${totalCount})` text: `Clipboard History (${totalCount})`
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -91,7 +91,7 @@ DankModal {
function copyEntry(entry) { function copyEntry(entry) {
const entryId = entry.split('\t')[0] const entryId = entry.split('\t')[0]
Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`]) Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`])
ToastService.showInfo(I18n.tr("Copied to clipboard")) ToastService.showInfo("Copied to clipboard")
hide() hide()
} }
@@ -153,7 +153,7 @@ DankModal {
ConfirmModal { ConfirmModal {
id: clearConfirmDialog id: clearConfirmDialog
confirmButtonText: I18n.tr("Clear All") confirmButtonText: "Clear All"
confirmButtonColor: Theme.primary confirmButtonColor: Theme.primary
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {

View File

@@ -53,7 +53,7 @@ QtObject {
modal.hide() modal.hide()
event.accepted = true event.accepted = true
} }
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) { } else if (event.key === Qt.Key_Down) {
if (!modal.keyboardNavigationActive) { if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true modal.keyboardNavigationActive = true
modal.selectedIndex = 0 modal.selectedIndex = 0
@@ -62,7 +62,7 @@ QtObject {
selectNext() selectNext()
event.accepted = true event.accepted = true
} }
} else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) { } else if (event.key === Qt.Key_Up) {
if (!modal.keyboardNavigationActive) { if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true modal.keyboardNavigationActive = true
modal.selectedIndex = 0 modal.selectedIndex = 0
@@ -74,42 +74,6 @@ QtObject {
selectPrevious() selectPrevious()
event.accepted = true 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)) { } else if (event.key === Qt.Key_Delete && (event.modifiers & Qt.ShiftModifier)) {
modal.clearAll() modal.clearAll()
modal.hide() modal.hide()

View File

@@ -6,8 +6,6 @@ import qs.Modals.Clipboard
Rectangle { Rectangle {
id: keyboardHints id: keyboardHints
readonly property string hintsText: I18n.tr("Shift+Del: Clear All • Esc: Close")
height: ClipboardConstants.keyboardHintsHeight height: ClipboardConstants.keyboardHintsHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
@@ -28,7 +26,7 @@ Rectangle {
} }
StyledText { StyledText {
text: keyboardHints.hintsText text: "Shift+Del: Clear All • Esc: Close"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -0,0 +1,37 @@
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)
}
}
}

View File

@@ -68,11 +68,9 @@ DankModal {
} }
} }
onOpened: { onOpened: {
Qt.callLater(function () { modalFocusScope.forceActiveFocus()
modalFocusScope.forceActiveFocus() modalFocusScope.focus = true
modalFocusScope.focus = true shouldHaveFocus = true
shouldHaveFocus = true
})
} }
modalFocusScope.Keys.onPressed: function (event) { modalFocusScope.Keys.onPressed: function (event) {
switch (event.key) { switch (event.key) {
@@ -95,48 +93,6 @@ DankModal {
selectedButton = 1 selectedButton = 1
event.accepted = true event.accepted = true
break 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: case Qt.Key_Tab:
keyboardNavigation = true keyboardNavigation = true
selectedButton = selectedButton === -1 ? 0 : (selectedButton + 1) % 2 selectedButton = selectedButton === -1 ? 0 : (selectedButton + 1) % 2

View File

@@ -1,9 +1,8 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services
PanelWindow { PanelWindow {
id: root id: root
@@ -16,17 +15,6 @@ PanelWindow {
property real height: 300 property real height: 300
readonly property real screenWidth: screen ? screen.width : 1920 readonly property real screenWidth: screen ? screen.width : 1920
readonly property real screenHeight: screen ? screen.height : 1080 readonly property real screenHeight: screen ? screen.height : 1080
readonly property real dpr: {
if (CompositorService.isNiri && screen) {
const niriScale = NiriService.displayScales[screen.name]
if (niriScale !== undefined) return niriScale
}
if (CompositorService.isHyprland && screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === screen.name)
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return (screen?.devicePixelRatio) || 1
}
property bool showBackground: true property bool showBackground: true
property real backgroundOpacity: 0.5 property real backgroundOpacity: 0.5
property string positioning: "center" property string positioning: "center"
@@ -34,7 +22,7 @@ PanelWindow {
property bool closeOnEscapeKey: true property bool closeOnEscapeKey: true
property bool closeOnBackgroundClick: true property bool closeOnBackgroundClick: true
property string animationType: "scale" property string animationType: "scale"
property int animationDuration: Theme.shortDuration property int animationDuration: Theme.shorterDuration
property var animationEasing: Theme.emphasizedEasing property var animationEasing: Theme.emphasizedEasing
property color backgroundColor: Theme.surfaceContainer property color backgroundColor: Theme.surfaceContainer
property color borderColor: Theme.outlineMedium property color borderColor: Theme.outlineMedium
@@ -46,7 +34,6 @@ PanelWindow {
property bool shouldHaveFocus: shouldBeVisible property bool shouldHaveFocus: shouldBeVisible
property bool allowFocusOverride: false property bool allowFocusOverride: false
property bool allowStacking: false property bool allowStacking: false
property bool keepContentLoaded: false
signal opened signal opened
signal dialogClosed signal dialogClosed
@@ -103,7 +90,7 @@ PanelWindow {
Timer { Timer {
id: closeTimer id: closeTimer
interval: animationDuration + 100 interval: animationDuration + 50
onTriggered: { onTriggered: {
visible = false visible = false
} }
@@ -146,26 +133,22 @@ PanelWindow {
Rectangle { Rectangle {
id: contentContainer id: contentContainer
width: Theme.px(root.width, dpr) width: root.width
height: Theme.px(root.height, dpr) height: root.height
anchors.centerIn: undefined anchors.centerIn: positioning === "center" ? parent : undefined
x: { x: {
if (positioning === "center") { if (positioning === "top-right") {
return Theme.snap((root.screenWidth - width) / 2, dpr) return Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL)
} else if (positioning === "top-right") {
return Theme.px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL), dpr)
} else if (positioning === "custom") { } else if (positioning === "custom") {
return Theme.snap(root.customPosition.x, dpr) return root.customPosition.x
} }
return 0 return 0
} }
y: { y: {
if (positioning === "center") { if (positioning === "top-right") {
return Theme.snap((root.screenHeight - height) / 2, dpr) return Theme.barHeight + Theme.spacingXS
} else if (positioning === "top-right") {
return Theme.px(Theme.barHeight + Theme.spacingXS, dpr)
} else if (positioning === "custom") { } else if (positioning === "custom") {
return Theme.snap(root.customPosition.y, dpr) return root.customPosition.y
} }
return 0 return 0
} }
@@ -173,49 +156,50 @@ PanelWindow {
radius: root.cornerRadius radius: root.cornerRadius
border.color: root.borderColor border.color: root.borderColor
border.width: root.borderWidth border.width: root.borderWidth
clip: false layer.enabled: root.enableShadow
layer.enabled: true
opacity: root.shouldBeVisible ? 1 : 0 opacity: root.shouldBeVisible ? 1 : 0
scale: root.animationType === "scale" ? (root.shouldBeVisible ? 1 : 0.9) : 1
transform: root.animationType === "slide" ? slideTransform : null transform: root.animationType === "slide" ? slideTransform : null
Translate { Translate {
id: slideTransform id: slideTransform
readonly property real rawX: root.shouldBeVisible ? 0 : 15 x: root.shouldBeVisible ? 0 : 15
readonly property real rawY: root.shouldBeVisible ? 0 : -30 y: root.shouldBeVisible ? 0 : -30
}
x: Theme.snap(rawX, root.dpr) Loader {
y: Theme.snap(rawY, root.dpr) id: contentLoader
anchors.fill: parent
active: root.visible
asynchronous: false
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: animationDuration duration: root.animationDuration
easing.type: animationEasing easing.type: root.animationEasing
} }
} }
FocusScope { Behavior on scale {
anchors.fill: parent enabled: root.animationType === "scale"
focus: root.shouldBeVisible
clip: false
Loader { NumberAnimation {
id: contentLoader duration: root.animationDuration
easing.type: root.animationEasing
anchors.fill: parent
active: root.keepContentLoaded || root.shouldBeVisible || root.visible
asynchronous: false
focus: true
clip: false
onLoaded: {
if (item) {
Qt.callLater(() => item.forceActiveFocus())
}
}
} }
} }
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 8
shadowBlur: 1
shadowColor: Theme.shadowStrong
shadowOpacity: 0.3
}
} }
FocusScope { FocusScope {
@@ -223,8 +207,8 @@ PanelWindow {
objectName: "modalFocusScope" objectName: "modalFocusScope"
anchors.fill: parent anchors.fill: parent
visible: root.shouldBeVisible || root.visible visible: root.visible // Only active when the modal is visible
focus: root.shouldBeVisible focus: root.visible
Keys.onEscapePressed: event => { Keys.onEscapePressed: event => {
if (root.closeOnEscapeKey && shouldHaveFocus) { if (root.closeOnEscapeKey && shouldHaveFocus) {
root.close() root.close()
@@ -239,7 +223,7 @@ PanelWindow {
Connections { Connections {
function onShouldHaveFocusChanged() { function onShouldHaveFocusChanged() {
if (shouldHaveFocus && shouldBeVisible) { if (shouldHaveFocus && visible) {
Qt.callLater(() => focusScope.forceActiveFocus()) Qt.callLater(() => focusScope.forceActiveFocus())
} }
} }

View File

@@ -1,580 +0,0 @@
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: I18n.tr("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: I18n.tr("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: I18n.tr("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: I18n.tr("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: I18n.tr("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: I18n.tr("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: I18n.tr("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: I18n.tr("Copy")
backgroundColor: Theme.primary
textColor: Theme.background
anchors.verticalCenter: parent.verticalCenter
onClicked: {
const colorString = root.currentColor.toString()
copyColorToClipboard(colorString)
}
}
}
}
}
}
}

View File

@@ -35,7 +35,6 @@ DankModal {
property bool weAvailable: false property bool weAvailable: false
property string wePath: "" property string wePath: ""
property bool weMode: false property bool weMode: false
property var parentModal: null
signal fileSelected(string path) signal fileSelected(string path)
@@ -132,8 +131,6 @@ DankModal {
objectName: "fileBrowserModal" objectName: "fileBrowserModal"
allowStacking: true allowStacking: true
closeOnEscapeKey: false
shouldHaveFocus: shouldBeVisible
Component.onCompleted: { Component.onCompleted: {
currentPath = getLastPath() currentPath = getLastPath()
} }
@@ -168,23 +165,10 @@ DankModal {
visible: false visible: false
onBackgroundClicked: close() onBackgroundClicked: close()
onOpened: { onOpened: {
if (parentModal) { modalFocusScope.forceActiveFocus()
parentModal.shouldHaveFocus = false
parentModal.allowFocusOverride = true
}
Qt.callLater(() => {
if (contentLoader && contentLoader.item) {
contentLoader.item.forceActiveFocus()
}
})
} }
onDialogClosed: { modalFocusScope.Keys.onPressed: function (event) {
if (parentModal) { keyboardController.handleKey(event)
parentModal.allowFocusOverride = false
parentModal.shouldHaveFocus = Qt.binding(() => {
return parentModal.shouldBeVisible
})
}
} }
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
@@ -255,12 +239,7 @@ DankModal {
return return
} }
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
const isInitKey = event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right || if (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 keyboardNavigationActive = true
if (currentPath !== homeDir) { if (currentPath !== homeDir) {
backButtonFocused = true backButtonFocused = true
@@ -302,69 +281,6 @@ DankModal {
} }
event.accepted = true event.accepted = true
break 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: case Qt.Key_Left:
if (backButtonFocused) if (backButtonFocused)
return return
@@ -471,16 +387,6 @@ DankModal {
Item { Item {
anchors.fill: parent anchors.fill: parent
Keys.onPressed: event => {
keyboardController.handleKey(event)
}
onVisibleChanged: {
if (visible) {
forceActiveFocus()
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
@@ -621,6 +527,7 @@ DankModal {
required property bool fileIsDir required property bool fileIsDir
required property string filePath required property string filePath
required property string fileName required property string fileName
required property url fileURL
required property int index required property int index
width: weMode ? 245 : 140 width: weMode ? 245 : 140
@@ -780,7 +687,7 @@ DankModal {
width: parent.width - saveButton.width - Theme.spacingM width: parent.width - saveButton.width - Theme.spacingM
height: 40 height: 40
text: defaultFileName text: defaultFileName
placeholderText: I18n.tr("Enter filename...") placeholderText: "Enter filename..."
ignoreLeftRightKeys: false ignoreLeftRightKeys: false
focus: saveMode focus: saveMode
topPadding: Theme.spacingS topPadding: Theme.spacingS
@@ -813,7 +720,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Save") text: "Save"
color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
} }
@@ -917,7 +824,7 @@ DankModal {
spacing: Theme.spacingM spacing: Theme.spacingM
StyledText { StyledText {
text: I18n.tr("File Already Exists") text: qsTr("File Already Exists")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -925,7 +832,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("A file with this name already exists. Do you want to overwrite it?") text: qsTr("A file with this name already exists. Do you want to overwrite it?")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
width: parent.width width: parent.width
@@ -947,7 +854,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Cancel") text: qsTr("Cancel")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -973,7 +880,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Overwrite") text: qsTr("Overwrite")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -134,7 +134,7 @@ Rectangle {
} }
StyledText { StyledText {
text: I18n.tr("File Information") text: "File Information"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -197,7 +197,7 @@ Rectangle {
} }
StyledText { StyledText {
text: I18n.tr("F1/I: Toggle • F10: Help") text: "F1/I: Toggle • F10: Help"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
anchors.bottom: parent.bottom anchors.bottom: parent.bottom

View File

@@ -23,7 +23,7 @@ Rectangle {
spacing: 2 spacing: 2
StyledText { StyledText {
text: I18n.tr("Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select") text: "Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width
@@ -32,7 +32,7 @@ Rectangle {
} }
StyledText { StyledText {
text: I18n.tr("Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close") text: "Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width

View File

@@ -56,7 +56,7 @@ DankModal {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Network Information") text: "Network Information"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -126,7 +126,7 @@ DankModal {
id: closeText id: closeText
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Close") text: "Close"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -54,9 +54,6 @@ DankModal {
height: 700 height: 700
visible: false visible: false
onBackgroundClicked: hide() onBackgroundClicked: hide()
onOpened: () => {
Qt.callLater(() => modalFocusScope.forceActiveFocus());
}
onShouldBeVisibleChanged: (shouldBeVisible) => { onShouldBeVisibleChanged: (shouldBeVisible) => {
if (!shouldBeVisible) { if (!shouldBeVisible) {
notificationModalOpen = false notificationModalOpen = false

View File

@@ -9,47 +9,31 @@ DankModal {
property int selectedIndex: 0 property int selectedIndex: 0
property int optionCount: SessionService.hibernateSupported ? 5 : 4 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) 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) { function selectOption(action) {
close(); close();
const actions = { const actions = {
"logout": { "logout": {
"title": I18n.tr("Log Out"), "title": "Log Out",
"message": I18n.tr("Are you sure you want to log out?") "message": "Are you sure you want to log out?"
}, },
"suspend": { "suspend": {
"title": I18n.tr("Suspend"), "title": "Suspend",
"message": I18n.tr("Are you sure you want to suspend the system?") "message": "Are you sure you want to suspend the system?"
}, },
"hibernate": { "hibernate": {
"title": I18n.tr("Hibernate"), "title": "Hibernate",
"message": I18n.tr("Are you sure you want to hibernate the system?") "message": "Are you sure you want to hibernate the system?"
}, },
"reboot": { "reboot": {
"title": I18n.tr("Reboot"), "title": "Reboot",
"message": I18n.tr("Are you sure you want to reboot the system?") "message": "Are you sure you want to reboot the system?"
}, },
"poweroff": { "poweroff": {
"title": I18n.tr("Power Off"), "title": "Power Off",
"message": I18n.tr("Are you sure you want to power off the system?") "message": "Are you sure you want to power off the system?"
} }
} }
const selected = actions[action] const selected = actions[action]
@@ -63,31 +47,23 @@ DankModal {
width: 320 width: 320
height: contentLoader.item ? contentLoader.item.implicitHeight : 300 height: contentLoader.item ? contentLoader.item.implicitHeight : 300
enableShadow: true 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: () => { onBackgroundClicked: () => {
return close(); return close();
} }
onOpened: () => { onOpened: () => {
selectedIndex = 0; selectedIndex = 0;
Qt.callLater(() => modalFocusScope.forceActiveFocus()); modalFocusScope.forceActiveFocus();
} }
modalFocusScope.Keys.onPressed: (event) => { modalFocusScope.Keys.onPressed: (event) => {
switch (event.key) { switch (event.key) {
case Qt.Key_Up: case Qt.Key_Up:
case Qt.Key_Backtab:
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount; selectedIndex = (selectedIndex - 1 + optionCount) % optionCount;
event.accepted = true; event.accepted = true;
break; break;
case Qt.Key_Down: case Qt.Key_Down:
selectedIndex = (selectedIndex + 1) % optionCount;
event.accepted = true;
break;
case Qt.Key_Tab: case Qt.Key_Tab:
selectedIndex = (selectedIndex + 1) % optionCount; selectedIndex = (selectedIndex + 1) % optionCount;
event.accepted = true; event.accepted = true;
@@ -102,30 +78,6 @@ DankModal {
} }
event.accepted = true; event.accepted = true;
break; 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;
} }
} }
@@ -144,7 +96,7 @@ DankModal {
width: parent.width width: parent.width
StyledText { StyledText {
text: I18n.tr("Power Options") text: "Power Options"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -201,7 +153,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Log Out") text: "Log Out"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -254,7 +206,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Suspend") text: "Suspend"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -308,7 +260,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Hibernate") text: "Hibernate"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -362,7 +314,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Reboot") text: "Reboot"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -416,7 +368,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Power Off") text: "Power Off"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -123,7 +123,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("System Monitor Unavailable") text: "System Monitor Unavailable"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.error color: Theme.error
@@ -131,7 +131,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature.") text: "The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature."
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -154,7 +154,7 @@ DankModal {
height: 40 height: 40
StyledText { StyledText {
text: I18n.tr("System Monitor") text: "System Monitor"
font.pixelSize: Theme.fontSizeLarge + 4 font.pixelSize: Theme.fontSizeLarge + 4
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.surfaceText color: Theme.surfaceText

View File

@@ -19,7 +19,7 @@ Item {
spacing: Theme.spacingXL spacing: Theme.spacingXL
StyledText { StyledText {
text: I18n.tr("Battery not detected - only AC power settings available") text: "Battery not detected - only AC power settings available"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: !BatteryService.batteryAvailable visible: !BatteryService.batteryAvailable
@@ -51,7 +51,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("Idle Settings") text: "Idle Settings"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -79,7 +79,8 @@ 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 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] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: I18n.tr("Automatically lock after") width: parent.width
text: "Automatically lock after"
options: timeoutOptions options: timeoutOptions
Connections { Connections {
@@ -115,7 +116,8 @@ 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 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] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: I18n.tr("Turn off monitors after") width: parent.width
text: "Turn off monitors after"
options: timeoutOptions options: timeoutOptions
Connections { Connections {
@@ -151,7 +153,8 @@ 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 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] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: I18n.tr("Suspend system after") width: parent.width
text: "Suspend system after"
options: timeoutOptions options: timeoutOptions
Connections { Connections {
@@ -187,7 +190,8 @@ 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 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] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
text: I18n.tr("Hibernate system after") width: parent.width
text: "Hibernate system after"
options: timeoutOptions options: timeoutOptions
visible: SessionService.hibernateSupported visible: SessionService.hibernateSupported
@@ -221,23 +225,14 @@ Item {
DankToggle { DankToggle {
width: parent.width width: parent.width
text: I18n.tr("Enable loginctl lock integration") text: "Lock before suspend"
description: "Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen."
checked: SessionData.loginctlLockIntegration
onToggled: checked => SessionData.setLoginctlLockIntegration(checked)
}
DankToggle {
width: parent.width
text: I18n.tr("Lock before suspend")
description: "Automatically lock the screen when the system prepares to suspend" description: "Automatically lock the screen when the system prepares to suspend"
checked: SessionData.lockBeforeSuspend checked: SessionData.lockBeforeSuspend
visible: SessionData.loginctlLockIntegration
onToggled: checked => SessionData.setLockBeforeSuspend(checked) onToggled: checked => SessionData.setLockBeforeSuspend(checked)
} }
StyledText { StyledText {
text: I18n.tr("Idle monitoring not supported - requires newer Quickshell version") text: "Idle monitoring not supported - requires newer Quickshell version"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.error color: Theme.error
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -2,12 +2,11 @@ import QtQuick
import qs.Common import qs.Common
import qs.Modules.Settings import qs.Modules.Settings
FocusScope { Item {
id: root id: root
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
focus: true
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -68,8 +67,7 @@ FocusScope {
visible: active visible: active
asynchronous: true asynchronous: true
sourceComponent: DankBarTab { sourceComponent: TopBarTab {
parentModal: root.parentModal
} }
} }
@@ -155,25 +153,11 @@ FocusScope {
} }
Loader {
id: pluginsLoader
anchors.fill: parent
active: root.currentIndex === 10
visible: active
asynchronous: true
sourceComponent: PluginsTab {
parentModal: root.parentModal
}
}
Loader { Loader {
id: aboutLoader id: aboutLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 11 active: root.currentIndex === 10
visible: active visible: active
asynchronous: true asynchronous: true

View File

@@ -34,7 +34,7 @@ DankModal {
objectName: "settingsModal" objectName: "settingsModal"
width: 800 width: 800
height: 800 height: 750
visible: false visible: false
onBackgroundClicked: () => { onBackgroundClicked: () => {
return hide(); return hide();
@@ -78,7 +78,6 @@ DankModal {
id: profileBrowser id: profileBrowser
allowStacking: true allowStacking: true
parentModal: settingsModal
browserTitle: "Select Profile Image" browserTitle: "Select Profile Image"
browserIcon: "person" browserIcon: "person"
browserType: "profile" browserType: "profile"
@@ -88,6 +87,12 @@ DankModal {
close(); close();
} }
onDialogClosed: () => { onDialogClosed: () => {
if (settingsModal) {
settingsModal.allowFocusOverride = false;
settingsModal.shouldHaveFocus = Qt.binding(() => {
return settingsModal.shouldBeVisible;
});
}
allowStacking = true; allowStacking = true;
} }
} }
@@ -96,7 +101,6 @@ DankModal {
id: wallpaperBrowser id: wallpaperBrowser
allowStacking: true allowStacking: true
parentModal: settingsModal
browserTitle: "Select Wallpaper" browserTitle: "Select Wallpaper"
browserIcon: "wallpaper" browserIcon: "wallpaper"
browserType: "wallpaper" browserType: "wallpaper"
@@ -111,7 +115,7 @@ DankModal {
} }
settingsContent: Component { settingsContent: Component {
FocusScope { Item {
anchors.fill: parent anchors.fill: parent
focus: true focus: true
@@ -140,7 +144,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Settings") text: "Settings"
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -9,40 +9,37 @@ Rectangle {
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
readonly property var sidebarItems: [{ readonly property var sidebarItems: [{
"text": I18n.tr("Personalization"), "text": "Personalization",
"icon": "person" "icon": "person"
}, { }, {
"text": I18n.tr("Time & Date"), "text": "Time & Date",
"icon": "schedule" "icon": "schedule"
}, { }, {
"text": I18n.tr("Weather"), "text": "Weather",
"icon": "cloud" "icon": "cloud"
}, { }, {
"text": I18n.tr("Dank Bar"), "text": "Top Bar",
"icon": "toolbar" "icon": "toolbar"
}, { }, {
"text": I18n.tr("Widgets"), "text": "Widgets",
"icon": "widgets" "icon": "widgets"
}, { }, {
"text": I18n.tr("Dock"), "text": "Dock",
"icon": "dock_to_bottom" "icon": "dock_to_bottom"
}, { }, {
"text": I18n.tr("Displays"), "text": "Displays",
"icon": "monitor" "icon": "monitor"
}, { }, {
"text": I18n.tr("Launcher"), "text": "Launcher",
"icon": "apps" "icon": "apps"
}, { }, {
"text": I18n.tr("Theme & Colors"), "text": "Theme & Colors",
"icon": "palette" "icon": "palette"
}, { }, {
"text": I18n.tr("Power"), "text": "Power",
"icon": "power_settings_new" "icon": "power_settings_new"
}, { }, {
"text": I18n.tr("Plugins"), "text": "About",
"icon": "extension"
}, {
"text": I18n.tr("About"),
"icon": "info" "icon": "info"
}] }]
@@ -86,7 +83,7 @@ Rectangle {
width: parent.width - Theme.spacingS * 2 width: parent.width - Theme.spacingS * 2
height: 44 height: 44
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: isActive ? Theme.primary : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent" color: isActive ? Theme.primaryContainer : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -97,14 +94,14 @@ Rectangle {
DankIcon { DankIcon {
name: modelData.icon || "" name: modelData.icon || ""
size: Theme.iconSize - 2 size: Theme.iconSize - 2
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: modelData.text || "" text: modelData.text || ""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText
font.weight: parent.parent.isActive ? Font.Medium : Font.Normal font.weight: parent.parent.isActive ? Font.Medium : Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }

View File

@@ -13,13 +13,8 @@ Item {
property alias searchField: searchField property alias searchField: searchField
property var parentModal: null property var parentModal: null
function resetScroll() {
resultsView.resetScroll()
}
anchors.fill: parent anchors.fill: parent
focus: true focus: true
clip: false
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
if (parentModal) if (parentModal)
@@ -38,49 +33,13 @@ Item {
} else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") { } else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow() appLauncher.selectPreviousInRow()
event.accepted = true 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) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
appLauncher.launchSelected() appLauncher.launchSelected()
event.accepted = true event.accepted = true
} else if (!searchField.activeFocus && event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
searchField.forceActiveFocus()
searchField.insertText(event.text)
event.accepted = true
} }
} }
@@ -102,7 +61,27 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
spacing: Theme.spacingM spacing: Theme.spacingM
clip: false
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)
}
}
}
Row { Row {
width: parent.width width: parent.width
@@ -127,8 +106,7 @@ Item {
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: parentModal ? parentModal.spotlightOpen : true enabled: parentModal ? parentModal.spotlightOpen : true
placeholderText: "" placeholderText: ""
ignoreLeftRightKeys: appLauncher.viewMode !== "list" ignoreLeftRightKeys: true
ignoreTabKeys: true
keyForwardTargets: [spotlightKeyHandler] keyForwardTargets: [spotlightKeyHandler]
text: appLauncher.searchQuery text: appLauncher.searchQuery
onTextEdited: () => { onTextEdited: () => {
@@ -146,7 +124,7 @@ Item {
else if (appLauncher.model.count > 0) else if (appLauncher.model.count > 0)
appLauncher.launchApp(appLauncher.model.get(0)) appLauncher.launchApp(appLauncher.model.get(0))
event.accepted = true 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_Tab || event.key === Qt.Key_Backtab || ((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_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
event.accepted = false event.accepted = false
} }
} }
@@ -210,7 +188,6 @@ Item {
} }
SpotlightResults { SpotlightResults {
id: resultsView
appLauncher: spotlightKeyHandler.appLauncher appLauncher: spotlightKeyHandler.appLauncher
contextMenu: contextMenu contextMenu: contextMenu
} }
@@ -228,7 +205,7 @@ Item {
visible: contextMenu.visible visible: contextMenu.visible
z: 999 z: 999
onClicked: () => { onClicked: () => {
contextMenu.hide() contextMenu.close()
} }
MouseArea { MouseArea {

View File

@@ -1,72 +1,65 @@
import QtQuick import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Popup { Rectangle {
id: contextMenu id: contextMenu
property var currentApp: null property var currentApp: null
property bool menuVisible: false
property var appLauncher: null property var appLauncher: null
property var parentHandler: null property var parentHandler: null
function show(x, y, app) { function show(x, y, app) {
currentApp = app currentApp = app
contextMenu.x = x + 4 const menuWidth = 180
contextMenu.y = y + 4 const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2
contextMenu.open() 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
} }
function hide() { function close() {
contextMenu.close() contextMenu.menuVisible = false
Qt.callLater(() => {
contextMenu.visible = false
})
} }
width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2) visible: false
width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
padding: 0 radius: Theme.cornerRadius
closePolicy: Popup.CloseOnPressOutside color: Theme.popupBackground()
modal: false border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
dim: false border.width: 1
z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
background: Rectangle { Rectangle {
radius: Theme.cornerRadius anchors.fill: parent
color: Theme.popupBackground() anchors.topMargin: 4
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) anchors.leftMargin: 2
border.width: 1 anchors.rightMargin: -2
anchors.bottomMargin: -4
Rectangle { radius: parent.radius
anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.15)
anchors.topMargin: 4 z: parent.z - 1
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 { Column {
@@ -77,14 +70,12 @@ Popup {
spacing: 1 spacing: 1
Rectangle { Rectangle {
implicitWidth: pinRow.implicitWidth + Theme.spacingS * 2 width: parent.width
width: implicitWidth
height: 32 height: 32
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row { Row {
id: pinRow
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -107,10 +98,10 @@ Popup {
StyledText { StyledText {
text: { text: {
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
return I18n.tr("Pin to Dock") return "Pin to Dock"
const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || "" const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
return SessionData.isPinnedApp(appId) ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock") return SessionData.isPinnedApp(appId) ? "Unpin from Dock" : "Pin to Dock"
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
@@ -134,7 +125,7 @@ Popup {
SessionData.removePinnedApp(appId) SessionData.removePinnedApp(appId)
else else
SessionData.addPinnedApp(appId) SessionData.addPinnedApp(appId)
contextMenu.hide() contextMenu.close()
} }
} }
} }
@@ -153,89 +144,13 @@ Popup {
} }
} }
Repeater {
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
Rectangle {
implicitWidth: actionRow.implicitWidth + Theme.spacingS * 2
width: implicitWidth
height: 32
radius: Theme.cornerRadius
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
id: actionRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
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)
if (appLauncher) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
}
}
}
}
Rectangle { Rectangle {
visible: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions && contextMenu.currentApp.desktopEntry.actions.length > 0 width: parent.width
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 {
implicitWidth: launchRow.implicitWidth + Theme.spacingS * 2
width: implicitWidth
height: 32 height: 32
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row { Row {
id: launchRow
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -250,7 +165,7 @@ Popup {
} }
StyledText { StyledText {
text: I18n.tr("Launch") text: "Launch"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -268,74 +183,23 @@ Popup {
if (contextMenu.currentApp && appLauncher) if (contextMenu.currentApp && appLauncher)
appLauncher.launchApp(contextMenu.currentApp) appLauncher.launchApp(contextMenu.currentApp)
contextMenu.hide() contextMenu.close()
}
}
}
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
implicitWidth: primeRunRow.implicitWidth + Theme.spacingS * 2
width: implicitWidth
height: 32
radius: Theme.cornerRadius
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
id: primeRunRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "memory"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("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
}
}
} }

View File

@@ -18,32 +18,24 @@ DankModal {
function show() { function show() {
spotlightOpen = true spotlightOpen = true
open() open()
if (contentLoader.item && contentLoader.item.appLauncher) {
contentLoader.item.appLauncher.searchQuery = ""
}
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) { if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.forceActiveFocus() contentLoader.item.searchField.forceActiveFocus()
} }
}) })
} }
function hide() { function hide() {
spotlightOpen = false spotlightOpen = false
close() close()
} if (contentLoader.item && contentLoader.item.appLauncher) {
contentLoader.item.appLauncher.searchQuery = ""
onDialogClosed: { contentLoader.item.appLauncher.selectedIndex = 0
if (contentLoader.item) { contentLoader.item.appLauncher.setCategory("All")
if (contentLoader.item.appLauncher) {
contentLoader.item.appLauncher.searchQuery = ""
contentLoader.item.appLauncher.selectedIndex = 0
contentLoader.item.appLauncher.setCategory(I18n.tr("All"))
}
if (contentLoader.item.resetScroll) {
contentLoader.item.resetScroll()
}
if (contentLoader.item.searchField) {
contentLoader.item.searchField.text = ""
}
} }
} }
@@ -57,13 +49,12 @@ DankModal {
shouldBeVisible: spotlightOpen shouldBeVisible: spotlightOpen
width: 550 width: 550
height: 700 height: 600
backgroundColor: Theme.popupBackground() backgroundColor: Theme.popupBackground()
cornerRadius: Theme.cornerRadius cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
borderWidth: 1 borderWidth: 1
enableShadow: true enableShadow: true
keepContentLoaded: true
onVisibleChanged: () => { onVisibleChanged: () => {
if (visible && !spotlightOpen) { if (visible && !spotlightOpen) {
show() show()

View File

@@ -10,16 +10,10 @@ Rectangle {
property var appLauncher: null property var appLauncher: null
property var contextMenu: null property var contextMenu: null
function resetScroll() {
resultsList.contentY = 0
resultsGrid.contentY = 0
}
width: parent.width width: parent.width
height: parent.height - y height: parent.height - y
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: "transparent" color: "transparent"
clip: true
DankListView { DankListView {
id: resultsList id: resultsList
@@ -163,8 +157,7 @@ Rectangle {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
resultsList.itemClicked(index, model) resultsList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
const globalPos = mapToItem(null, mouse.x, mouse.y) const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y) resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y)
} }
} }
@@ -315,8 +308,7 @@ Rectangle {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
resultsGrid.itemClicked(index, model) resultsGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
const globalPos = mapToItem(null, mouse.x, mouse.y) const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y) resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y)
} }
} }

View File

@@ -9,53 +9,33 @@ DankModal {
property string wifiPasswordSSID: "" property string wifiPasswordSSID: ""
property string wifiPasswordInput: "" property string wifiPasswordInput: ""
property string wifiUsernameInput: ""
property bool requiresEnterprise: false
function show(ssid) { function show(ssid) {
wifiPasswordSSID = ssid wifiPasswordSSID = ssid
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid)
requiresEnterprise = network?.enterprise || false
open() open()
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item) { if (contentLoader.item && contentLoader.item.passwordInput)
if (requiresEnterprise && contentLoader.item.usernameInput) { contentLoader.item.passwordInput.forceActiveFocus()
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus()
}
}
}) })
} }
shouldBeVisible: false shouldBeVisible: false
width: 420 width: 420
height: requiresEnterprise ? 310 : 230 height: 230
onShouldBeVisibleChanged: () => { onShouldBeVisibleChanged: () => {
if (!shouldBeVisible) { if (!shouldBeVisible)
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
}
} }
onOpened: { onOpened: {
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item) { if (contentLoader.item && contentLoader.item.passwordInput)
if (requiresEnterprise && contentLoader.item.usernameInput) { contentLoader.item.passwordInput.forceActiveFocus()
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus()
}
}
}) })
} }
onBackgroundClicked: () => { onBackgroundClicked: () => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
} }
Connections { Connections {
@@ -75,7 +55,6 @@ DankModal {
FocusScope { FocusScope {
id: wifiContent id: wifiContent
property alias usernameInput: usernameInput
property alias passwordInput: passwordInput property alias passwordInput: passwordInput
anchors.fill: parent anchors.fill: parent
@@ -83,7 +62,6 @@ DankModal {
Keys.onEscapePressed: event => { Keys.onEscapePressed: event => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
event.accepted = true event.accepted = true
} }
@@ -100,14 +78,14 @@ DankModal {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Connect to Wi-Fi") text: "Connect to Wi-Fi"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
StyledText { StyledText {
text: requiresEnterprise ? `Enter credentials for "${wifiPasswordSSID}"` : `Enter password for "${wifiPasswordSSID}"` text: `Enter password for "${wifiPasswordSSID}"`
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
width: parent.width width: parent.width
@@ -122,48 +100,10 @@ DankModal {
onClicked: () => { onClicked: () => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
} }
} }
} }
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: usernameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: usernameInput.activeFocus ? 2 : 1
visible: requiresEnterprise
MouseArea {
anchors.fill: parent
onClicked: () => {
usernameInput.forceActiveFocus()
}
}
DankTextField {
id: usernameInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiUsernameInput
placeholderText: "Username"
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiUsernameInput = text
}
onAccepted: () => {
if (passwordInput) {
passwordInput.forceActiveFocus()
}
}
}
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
@@ -187,24 +127,21 @@ DankModal {
textColor: Theme.surfaceText textColor: Theme.surfaceText
text: wifiPasswordInput text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
placeholderText: requiresEnterprise ? "Password" : "" placeholderText: ""
backgroundColor: "transparent" backgroundColor: "transparent"
focus: !requiresEnterprise focus: true
enabled: root.shouldBeVisible enabled: root.shouldBeVisible
onTextEdited: () => { onTextEdited: () => {
wifiPasswordInput = text wifiPasswordInput = text
} }
onAccepted: () => { onAccepted: () => {
const username = requiresEnterprise ? usernameInput.text : "" NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text)
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text, username)
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
passwordInput.text = "" passwordInput.text = ""
if (requiresEnterprise) usernameInput.text = ""
} }
Component.onCompleted: () => { Component.onCompleted: () => {
if (root.shouldBeVisible && !requiresEnterprise) if (root.shouldBeVisible)
focusDelayTimer.start() focusDelayTimer.start()
} }
@@ -214,13 +151,8 @@ DankModal {
interval: 100 interval: 100
repeat: false repeat: false
onTriggered: () => { onTriggered: () => {
if (root.shouldBeVisible) { if (root.shouldBeVisible)
if (requiresEnterprise && usernameInput) { passwordInput.forceActiveFocus()
usernameInput.forceActiveFocus()
} else {
passwordInput.forceActiveFocus()
}
}
} }
} }
@@ -269,7 +201,7 @@ DankModal {
} }
StyledText { StyledText {
text: I18n.tr("Show password") text: "Show password"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -297,7 +229,7 @@ DankModal {
id: cancelText id: cancelText
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Cancel") text: "Cancel"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -312,7 +244,6 @@ DankModal {
onClicked: () => { onClicked: () => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
} }
} }
} }
@@ -322,14 +253,14 @@ DankModal {
height: 36 height: 36
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0 enabled: passwordInput.text.length > 0
opacity: enabled ? 1 : 0.5 opacity: enabled ? 1 : 0.5
StyledText { StyledText {
id: connectText id: connectText
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Connect") text: "Connect"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium
@@ -343,13 +274,10 @@ DankModal {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: () => { onClicked: () => {
const username = requiresEnterprise ? usernameInput.text : "" NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text)
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text, username)
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
passwordInput.text = "" passwordInput.text = ""
if (requiresEnterprise) usernameInput.text = ""
} }
} }

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -13,6 +12,7 @@ import qs.Widgets
DankPopout { DankPopout {
id: appDrawerPopout id: appDrawerPopout
property string triggerSection: "left"
property var triggerScreen: null property var triggerScreen: null
// Setting to Exclusive, so virtual keyboards can send input to app drawer // Setting to Exclusive, so virtual keyboards can send input to app drawer
@@ -33,16 +33,16 @@ DankPopout {
popupWidth: 520 popupWidth: 520
popupHeight: 600 popupHeight: 600
triggerX: Theme.spacingL triggerX: Theme.spacingL
triggerY: Math.max(26 + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding)) + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap - 2 triggerY: Math.max(26 + SettingsData.topBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.topBarInnerPadding)) + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance
triggerWidth: 40 triggerWidth: 40
positioning: "" positioning: "center"
screen: triggerScreen screen: triggerScreen
onShouldBeVisibleChanged: { onShouldBeVisibleChanged: {
if (shouldBeVisible) { if (shouldBeVisible) {
appLauncher.searchQuery = "" appLauncher.searchQuery = ""
appLauncher.selectedIndex = 0 appLauncher.selectedIndex = 0
appLauncher.setCategory(I18n.tr("All")) appLauncher.setCategory("All")
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) { if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.text = "" contentLoader.item.searchField.text = ""
@@ -112,8 +112,6 @@ DankPopout {
mappings[Qt.Key_Up] = () => appLauncher.selectPrevious() mappings[Qt.Key_Up] = () => appLauncher.selectPrevious()
mappings[Qt.Key_Return] = () => appLauncher.launchSelected() mappings[Qt.Key_Return] = () => appLauncher.launchSelected()
mappings[Qt.Key_Enter] = () => appLauncher.launchSelected() mappings[Qt.Key_Enter] = () => appLauncher.launchSelected()
mappings[Qt.Key_Tab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : appLauncher.selectNext()
mappings[Qt.Key_Backtab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : appLauncher.selectPrevious()
if (appLauncher.viewMode === "grid") { if (appLauncher.viewMode === "grid") {
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow() mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow()
@@ -130,44 +128,11 @@ DankPopout {
return return
} }
if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) { if (!searchField.activeFocus && event.text && /[a-zA-Z0-9\s]/.test(event.text)) {
appLauncher.selectNext() searchField.forceActiveFocus()
searchField.insertText(event.text)
event.accepted = true 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
}
}
} }
Column { Column {
@@ -177,23 +142,25 @@ DankPopout {
y: Theme.spacingS y: Theme.spacingS
spacing: Theme.spacingS spacing: Theme.spacingS
Item { Row {
width: parent.width width: parent.width
height: 40 height: 40
leftPadding: Theme.spacingS
StyledText { StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Applications") text: "Applications"
font.pixelSize: Theme.fontSizeLarge + 4 font.pixelSize: Theme.fontSizeLarge + 4
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.surfaceText color: Theme.surfaceText
} }
Item {
width: parent.width - 200
height: 1
}
StyledText { StyledText {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: appLauncher.model.count + " apps" text: appLauncher.model.count + " apps"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -218,8 +185,7 @@ DankPopout {
showClearButton: true showClearButton: true
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: appDrawerPopout.shouldBeVisible enabled: appDrawerPopout.shouldBeVisible
ignoreLeftRightKeys: appLauncher.viewMode !== "list" ignoreLeftRightKeys: true
ignoreTabKeys: true
keyForwardTargets: [keyHandler] keyForwardTargets: [keyHandler]
onTextEdited: { onTextEdited: {
appLauncher.searchQuery = text appLauncher.searchQuery = text
@@ -244,7 +210,7 @@ DankPopout {
return return
} }
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right, Qt.Key_Tab, Qt.Key_Backtab] const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right]
const isNavigationKey = navigationKeys.includes(event.key) const isNavigationKey = navigationKeys.includes(event.key)
const isEmptyEnter = isEnterKey && !hasText const isEmptyEnter = isEnterKey && !hasText
@@ -268,17 +234,18 @@ DankPopout {
spacing: Theme.spacingM spacing: Theme.spacingM
visible: searchField.text.length === 0 visible: searchField.text.length === 0
leftPadding: Theme.spacingS leftPadding: Theme.spacingS
topPadding: Theme.spacingXS
Rectangle { Item {
width: 180 width: 200
height: 40 height: 36
radius: Theme.cornerRadius
color: "transparent"
DankDropdown { DankDropdown {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
text: "" text: ""
dropdownWidth: 180
currentValue: appLauncher.selectedCategory currentValue: appLauncher.selectedCategory
options: appLauncher.categories options: appLauncher.categories
optionIcons: appLauncher.categoryIcons optionIcons: appLauncher.categoryIcons
@@ -289,7 +256,7 @@ DankPopout {
} }
Item { Item {
width: parent.width - 290 width: parent.width - 310
height: 1 height: 1
} }
@@ -485,8 +452,7 @@ DankPopout {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appList.itemClicked(index, model) appList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var globalPos = mapToItem(null, mouse.x, mouse.y) var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appList.itemRightClicked(index, model, panelPos.x, panelPos.y) appList.itemRightClicked(index, model, panelPos.x, panelPos.y)
} }
} }
@@ -647,8 +613,7 @@ DankPopout {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appGrid.itemClicked(index, model) appGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var globalPos = mapToItem(null, mouse.x, mouse.y) var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y) appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y)
} }
} }
@@ -661,68 +626,68 @@ DankPopout {
} }
} }
Popup { Rectangle {
id: contextMenu id: contextMenu
property var currentApp: null property var currentApp: null
property bool menuVisible: false
readonly property string appId: (currentApp && currentApp.desktopEntry) ? (currentApp.desktopEntry.id || currentApp.desktopEntry.execString || "") : "" readonly property string appId: (currentApp && currentApp.desktopEntry) ? (currentApp.desktopEntry.id || currentApp.desktopEntry.execString || "") : ""
readonly property bool isPinned: appId && SessionData.isPinnedApp(appId) readonly property bool isPinned: appId && SessionData.isPinnedApp(appId)
function show(x, y, app) { function show(x, y, app) {
currentApp = app currentApp = app
contextMenu.x = x + 4
contextMenu.y = y + 4 const menuWidth = 180
contextMenu.open() 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
} }
function hide() { function close() {
contextMenu.close() contextMenu.menuVisible = false
Qt.callLater(() => {
contextMenu.visible = false
})
} }
width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2) visible: false
width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
padding: 0 radius: Theme.cornerRadius
closePolicy: Popup.CloseOnPressOutside color: Theme.popupBackground()
modal: false border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
dim: false border.width: 0
z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
background: Rectangle { Rectangle {
radius: Theme.cornerRadius anchors.fill: parent
color: Theme.popupBackground() anchors.topMargin: 4
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) anchors.leftMargin: 2
border.width: 1 anchors.rightMargin: -2
anchors.bottomMargin: -4
Rectangle { radius: parent.radius
anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.15)
anchors.topMargin: 4 z: parent.z - 1
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 { Column {
@@ -753,7 +718,7 @@ DankPopout {
} }
StyledText { StyledText {
text: contextMenu.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock") text: contextMenu.isPinned ? "Unpin from Dock" : "Pin to Dock"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -777,7 +742,7 @@ DankPopout {
} else { } else {
SessionData.addPinnedApp(contextMenu.appId) SessionData.addPinnedApp(contextMenu.appId)
} }
contextMenu.hide() contextMenu.close()
} }
} }
} }
@@ -796,77 +761,6 @@ DankPopout {
} }
} }
Repeater {
model: contextMenu.currentApp && contextMenu.currentApp.desktopEntry && contextMenu.currentApp.desktopEntry.actions ? contextMenu.currentApp.desktopEntry.actions : []
Rectangle {
width: Math.max(parent.width, actionRow.implicitWidth + Theme.spacingS * 2)
height: 32
radius: Theme.cornerRadius
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
id: actionRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
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 { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
@@ -888,7 +782,7 @@ DankPopout {
} }
StyledText { StyledText {
text: I18n.tr("Launch") text: "Launch"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -906,70 +800,23 @@ DankPopout {
if (contextMenu.currentApp) if (contextMenu.currentApp)
appLauncher.launchApp(contextMenu.currentApp) appLauncher.launchApp(contextMenu.currentApp)
contextMenu.hide() contextMenu.close()
} }
} }
} }
}
Rectangle { Behavior on opacity {
visible: SessionService.hasPrimeRun NumberAnimation {
width: parent.width - Theme.spacingS * 2 duration: Theme.mediumDuration
height: 5 easing.type: Theme.emphasizedEasing
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 { Behavior on scale {
visible: SessionService.hasPrimeRun NumberAnimation {
width: parent.width duration: Theme.mediumDuration
height: 32 easing.type: Theme.emphasizedEasing
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: I18n.tr("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()
}
}
} }
} }
} }
@@ -979,7 +826,7 @@ DankPopout {
visible: contextMenu.visible visible: contextMenu.visible
z: 999 z: 999
onClicked: { onClicked: {
contextMenu.hide() contextMenu.close()
} }
MouseArea { MouseArea {

View File

@@ -9,7 +9,7 @@ Item {
id: root id: root
property string searchQuery: "" property string searchQuery: ""
property string selectedCategory: I18n.tr("All") property string selectedCategory: "All"
property string viewMode: "list" // "list" or "grid" property string viewMode: "list" // "list" or "grid"
property int selectedIndex: 0 property int selectedIndex: 0
property int maxResults: 50 property int maxResults: 50
@@ -20,8 +20,8 @@ Item {
property bool suppressUpdatesWhileLaunching: false property bool suppressUpdatesWhileLaunching: false
readonly property var categories: { readonly property var categories: {
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science") const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
const result = [I18n.tr("All")] const result = ["All"]
return result.concat(allCategories.filter(cat => cat !== I18n.tr("All"))) return result.concat(allCategories.filter(cat => cat !== "All"))
} }
readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category)) readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {} property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
@@ -42,11 +42,10 @@ Item {
keyboardNavigationActive = false keyboardNavigationActive = false
let apps = [] let apps = []
const allCategory = I18n.tr("All")
if (searchQuery.length === 0) { if (searchQuery.length === 0) {
apps = selectedCategory === allCategory ? AppSearchService.getAppsInCategory(allCategory) : AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults) apps = selectedCategory === "All" ? AppSearchService.getAppsInCategory("All") : AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
} else { } else {
if (selectedCategory === allCategory) { if (selectedCategory === "All") {
apps = AppSearchService.searchApplications(searchQuery) apps = AppSearchService.searchApplications(searchQuery)
} else { } else {
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory) const categoryApps = AppSearchService.getAppsInCategory(selectedCategory)

View File

@@ -1,14 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Services
import qs.Widgets import qs.Widgets
Item { Item {
id: root id: root
property var categories: [] property var categories: []
property string selectedCategory: I18n.tr("All") property string selectedCategory: "All"
property bool compact: false property bool compact: false
signal categorySelected(string category) signal categorySelected(string category)
@@ -19,6 +18,7 @@ Item {
readonly property color unselectedBorderColor: "transparent" readonly property color unselectedBorderColor: "transparent"
function handleCategoryClick(category) { function handleCategoryClick(category) {
selectedCategory = category
categorySelected(category) categorySelected(category)
} }

View File

@@ -1,246 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
Ref {
service: VpnService
}
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: I18n.tr("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: I18n.tr("No VPN profiles found")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("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)
}
}
}
}
}
}
}
}
}

View File

@@ -59,7 +59,7 @@ Rectangle {
DankIcon { DankIcon {
name: root.iconName name: root.iconName
size: Theme.iconSize size: Theme.iconSize
color: isActive ? Theme.primaryText : Theme.primary color: isActive ? Theme.primaryContainer : Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -77,7 +77,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.text text: root.text
style: Typography.Style.Body style: Typography.Style.Body
color: isActive ? Theme.primaryText : Theme.surfaceText color: isActive ? Theme.primaryContainer : Theme.surfaceText
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
@@ -86,7 +86,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.secondaryText text: root.secondaryText
style: Typography.Style.Caption style: Typography.Style.Caption
color: isActive ? Theme.primaryText : Theme.surfaceVariantText color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
visible: text.length > 0 visible: text.length > 0
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap

View File

@@ -1,107 +1,38 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services
import qs.Modules.ControlCenter.Details import qs.Modules.ControlCenter.Details
import qs.Modules.ControlCenter.Models
Item { Item {
id: root id: root
property string expandedSection: "" property string expandedSection: ""
property var expandedWidgetData: null property var expandedWidgetData: null
property var bluetoothCodecSelector: null
property var pluginDetailInstance: null
property var widgetModel: null
Loader { Loader {
id: pluginDetailLoader
width: parent.width width: parent.width
height: 250 height: 250
y: Theme.spacingS y: Theme.spacingS
active: false active: parent.height > 0
sourceComponent: null property string sectionKey: root.expandedSection
} sourceComponent: {
switch (root.expandedSection) {
Loader { case "network":
id: coreDetailLoader case "wifi": return networkDetailComponent
width: parent.width case "bluetooth": return bluetoothDetailComponent
height: 250 case "audioOutput": return audioOutputDetailComponent
y: Theme.spacingS case "audioInput": return audioInputDetailComponent
active: false case "battery": return batteryDetailComponent
sourceComponent: null default:
} if (root.expandedSection.startsWith("diskUsage_")) {
return diskUsageDetailComponent
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") {
if (widgetModel?.vpnLoader) {
widgetModel.vpnLoader.active = true
} }
builtinInstance = widgetModel.vpnBuiltinInstance return null
} }
if (!builtinInstance || !builtinInstance.ccDetailContent) {
return
}
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent
pluginDetailLoader.active = parent.height > 0
return
} }
onSectionKeyChanged: {
if (root.expandedSection.startsWith("plugin_")) { active = false
const pluginId = root.expandedSection.replace("plugin_", "") active = true
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 { Component {
@@ -111,17 +42,7 @@ Item {
Component { Component {
id: bluetoothDetailComponent 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 { Component {

View File

@@ -13,8 +13,6 @@ Column {
property int expandedWidgetIndex: -1 property int expandedWidgetIndex: -1
property var model: null property var model: null
property var expandedWidgetData: null property var expandedWidgetData: null
property var bluetoothCodecSelector: null
property bool darkModeTransitionPending: false
signal expandClicked(var widgetData, int globalIndex) signal expandClicked(var widgetData, int globalIndex)
signal removeWidget(int index) signal removeWidget(int index)
@@ -26,7 +24,6 @@ Column {
property var currentRowWidgets: [] property var currentRowWidgets: []
property real currentRowWidth: 0 property real currentRowWidth: 0
property int expandedRowIndex: -1 property int expandedRowIndex: -1
property var colorPickerModal: null
function calculateRowsAndWidgets() { function calculateRowsAndWidgets() {
return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex) return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex)
@@ -62,8 +59,7 @@ Column {
property var rowWidgets: modelData property var rowWidgets: modelData
property bool isSliderOnlyRow: { property bool isSliderOnlyRow: {
const widgets = rowWidgets || [] const widgets = rowWidgets || []
if (widgets.length === 0) if (widgets.length === 0) return false
return false
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider") return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
} }
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0 topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
@@ -122,11 +118,7 @@ Column {
widgetComponent: { widgetComponent: {
const id = modelData.id || "" const id = modelData.id || ""
if (id.startsWith("builtin_")) { if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
return builtinPluginWidgetComponent
} else if (id.startsWith("plugin_")) {
return pluginWidgetComponent
} else if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
return compoundPillComponent return compoundPillComponent
} else if (id === "volumeSlider") { } else if (id === "volumeSlider") {
return audioSliderComponent return audioSliderComponent
@@ -138,8 +130,6 @@ Column {
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent
} else if (id === "diskUsage") { } else if (id === "diskUsage") {
return diskUsagePillComponent return diskUsagePillComponent
} else if (id === "colorPicker") {
return colorPickerPillComponent
} else { } else {
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
} }
@@ -156,8 +146,7 @@ Column {
width: parent.width width: parent.width
height: active ? (250 + Theme.spacingS) : 0 height: active ? (250 + Theme.spacingS) : 0
property bool active: { property bool active: {
if (root.expandedSection === "") if (root.expandedSection === "") return false
return false
if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) { if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) {
const expandedInstanceId = root.expandedWidgetData.instanceId const expandedInstanceId = root.expandedWidgetData.instanceId
@@ -169,28 +158,10 @@ Column {
visible: active visible: active
expandedSection: root.expandedSection expandedSection: root.expandedSection
expandedWidgetData: root.expandedWidgetData expandedWidgetData: root.expandedWidgetData
bluetoothCodecSelector: root.bluetoothCodecSelector
widgetModel: root.model
} }
} }
} }
Component {
id: errorPillComponent
ErrorPill {
property var widgetData: parent.widgetData || {}
width: parent.width
height: 60
primaryMessage: {
if (!DMSService.dmsAvailable) {
return I18n.tr("DMS_SOCKET not available")
}
return I18n.tr("NM not supported")
}
secondaryMessage: I18n.tr("update dms for NM integration.")
}
}
Component { Component {
id: compoundPillComponent id: compoundPillComponent
CompoundPill { CompoundPill {
@@ -368,8 +339,7 @@ Column {
} }
enabled: widgetDef?.enabled ?? true enabled: widgetDef?.enabled ?? true
onToggled: { onToggled: {
if (root.editMode) if (root.editMode) return
return
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "wifi": case "wifi":
{ {
@@ -402,13 +372,11 @@ Column {
} }
} }
onExpandClicked: { onExpandClicked: {
if (root.editMode) if (root.editMode) return
return
root.expandClicked(widgetData, widgetIndex) root.expandClicked(widgetData, widgetIndex)
} }
onWheelEvent: function (wheelEvent) { onWheelEvent: function (wheelEvent) {
if (root.editMode) if (root.editMode) return
return
const id = widgetData.id || "" const id = widgetData.id || ""
if (id === "audioOutput") { if (id === "audioOutput") {
if (!AudioService.sink || !AudioService.sink.audio) if (!AudioService.sink || !AudioService.sink.audio)
@@ -562,21 +530,14 @@ Column {
} }
} }
iconRotation: { iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
if (widgetData.id !== "darkMode")
return 0
if (darkModeTransitionPending) {
return SessionData.isLightMode ? 180 : 0
}
return SessionData.isLightMode ? 180 : 0
}
isActive: { isActive: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "nightMode": case "nightMode":
return DisplayService.nightModeEnabled || false return DisplayService.nightModeEnabled || false
case "darkMode": case "darkMode":
return SessionData.isLightMode return !SessionData.isLightMode
case "doNotDisturb": case "doNotDisturb":
return SessionData.doNotDisturb || false return SessionData.doNotDisturb || false
case "idleInhibitor": case "idleInhibitor":
@@ -586,7 +547,7 @@ Column {
} }
} }
enabled: !root.editMode enabled: !root.editMode
onClicked: { onClicked: {
if (root.editMode) if (root.editMode)
@@ -600,8 +561,7 @@ Column {
} }
case "darkMode": case "darkMode":
{ {
Theme.screenTransition() Theme.toggleLightMode()
Theme.setLightMode(!SessionData.isLightMode)
break break
} }
case "doNotDisturb": case "doNotDisturb":
@@ -642,21 +602,14 @@ Column {
} }
} }
iconRotation: { iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
if (widgetData.id !== "darkMode")
return 0
if (darkModeTransitionPending) {
return SessionData.isLightMode ? 180 : 0
}
return SessionData.isLightMode ? 180 : 0
}
isActive: { isActive: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "nightMode": case "nightMode":
return DisplayService.nightModeEnabled || false return DisplayService.nightModeEnabled || false
case "darkMode": case "darkMode":
return SessionData.isLightMode return !SessionData.isLightMode
case "doNotDisturb": case "doNotDisturb":
return SessionData.doNotDisturb || false return SessionData.doNotDisturb || false
case "idleInhibitor": case "idleInhibitor":
@@ -666,7 +619,7 @@ Column {
} }
} }
enabled: !root.editMode enabled: !root.editMode
onClicked: { onClicked: {
if (root.editMode) if (root.editMode)
@@ -680,8 +633,7 @@ Column {
} }
case "darkMode": case "darkMode":
{ {
Theme.screenTransition() Theme.toggleLightMode()
Theme.setLightMode(!SessionData.isLightMode)
break break
} }
case "doNotDisturb": case "doNotDisturb":
@@ -717,263 +669,4 @@ Column {
} }
} }
} }
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: null
Component.onCompleted: {
const id = widgetData.id || ""
if (id === "builtin_vpn") {
if (root.model?.vpnLoader) {
root.model.vpnLoader.active = true
}
builtinInstance = Qt.binding(() => root.model?.vpnBuiltinInstance)
}
}
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()
}
}
}
}
} }

View File

@@ -231,7 +231,7 @@ Item {
} }
} }
SizeControls { PieChartSizeControl {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.margins: -6 anchors.margins: -6

View File

@@ -7,7 +7,6 @@ Row {
id: root id: root
property var availableWidgets: [] property var availableWidgets: []
property Item popoutContent: null
signal addWidget(string widgetId) signal addWidget(string widgetId)
signal resetToDefault() signal resetToDefault()
@@ -20,9 +19,7 @@ Row {
Popup { Popup {
id: addWidgetPopup id: addWidgetPopup
parent: popoutContent anchors.centerIn: parent
x: parent ? Math.round((parent.width - width) / 2) : 0
y: parent ? Math.round((parent.height - height) / 2) : 0
width: 400 width: 400
height: 300 height: 300
modal: true modal: true
@@ -55,7 +52,7 @@ Row {
} }
Typography { Typography {
text: I18n.tr("Add Widget") text: "Add Widget"
style: Typography.Style.Subtitle style: Typography.Style.Subtitle
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -155,7 +152,7 @@ Row {
} }
Typography { Typography {
text: I18n.tr("Add Widget") text: "Add Widget"
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -189,7 +186,7 @@ Row {
} }
Typography { Typography {
text: I18n.tr("Defaults") text: "Defaults"
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.warning color: Theme.warning
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -223,7 +220,7 @@ Row {
} }
Typography { Typography {
text: I18n.tr("Reset") text: "Reset"
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.error color: Theme.error
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -6,9 +6,10 @@ import qs.Widgets
Rectangle { Rectangle {
id: root id: root
property bool powerOptionsExpanded: false
property bool editMode: false property bool editMode: false
signal powerButtonClicked() signal powerActionRequested(string action, string title, string message)
signal lockRequested() signal lockRequested()
signal editModeToggled() signal editModeToggled()
@@ -82,11 +83,13 @@ Rectangle {
DankActionButton { DankActionButton {
buttonSize: 36 buttonSize: 36
iconName: "power_settings_new" iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText iconColor: root.powerOptionsExpanded ? Theme.primary : Theme.surfaceText
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: root.powerButtonClicked() onClicked: {
root.powerOptionsExpanded = !root.powerOptionsExpanded
}
} }
DankActionButton { DankActionButton {

View File

@@ -0,0 +1,70 @@
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?")
}
}
}
}

View File

@@ -10,7 +10,7 @@ import qs.Common
import qs.Modules.ControlCenter import qs.Modules.ControlCenter
import qs.Modules.ControlCenter.Widgets import qs.Modules.ControlCenter.Widgets
import qs.Modules.ControlCenter.Details import qs.Modules.ControlCenter.Details
import qs.Modules.DankBar import qs.Modules.TopBar
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.ControlCenter.Components import qs.Modules.ControlCenter.Components
@@ -21,11 +21,14 @@ DankPopout {
id: root id: root
property string expandedSection: "" property string expandedSection: ""
property bool powerOptionsExpanded: false
property string triggerSection: "right"
property var triggerScreen: null property var triggerScreen: null
property bool editMode: false property bool editMode: false
property int expandedWidgetIndex: -1 property int expandedWidgetIndex: -1
property var expandedWidgetData: null property var expandedWidgetData: null
signal powerActionRequested(string action, string title, string message)
signal lockRequested signal lockRequested
function collapseAll() { function collapseAll() {
@@ -63,9 +66,9 @@ DankPopout {
popupWidth: 550 popupWidth: 550
popupHeight: Math.min((triggerScreen?.height ?? 1080) - 100, contentLoader.item && contentLoader.item.implicitHeight > 0 ? contentLoader.item.implicitHeight + 20 : 400) 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 triggerX: (triggerScreen?.width ?? 1920) - 600 - Theme.spacingL
triggerY: Theme.barHeight - 4 + SettingsData.dankBarSpacing triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.popupDistance
triggerWidth: 80 triggerWidth: 80
positioning: "" positioning: "center"
screen: triggerScreen screen: triggerScreen
shouldBeVisible: false shouldBeVisible: false
visible: shouldBeVisible visible: shouldBeVisible
@@ -73,17 +76,13 @@ DankPopout {
onShouldBeVisibleChanged: { onShouldBeVisibleChanged: {
if (shouldBeVisible) { if (shouldBeVisible) {
Qt.callLater(() => { Qt.callLater(() => {
if (NetworkService.activeService) { NetworkService.autoRefreshEnabled = NetworkService.wifiEnabled
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled
}
if (UserInfoService) if (UserInfoService)
UserInfoService.getUptime() UserInfoService.getUptime()
}) })
} else { } else {
Qt.callLater(() => { Qt.callLater(() => {
if (NetworkService.activeService) { NetworkService.autoRefreshEnabled = false
NetworkService.activeService.autoRefreshEnabled = false
}
if (BluetoothService.adapter && BluetoothService.adapter.discovering) if (BluetoothService.adapter && BluetoothService.adapter.discovering)
BluetoothService.adapter.discovering = false BluetoothService.adapter.discovering = false
editMode = false editMode = false
@@ -103,7 +102,7 @@ DankPopout {
property alias bluetoothCodecSelector: bluetoothCodecSelector property alias bluetoothCodecSelector: bluetoothCodecSelector
color: { color: {
const transparency = Theme.popupTransparency const transparency = Theme.popupTransparency || 0.92
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1) const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
return Qt.rgba(surface.r, surface.g, surface.b, transparency) return Qt.rgba(surface.r, surface.g, surface.b, transparency)
} }
@@ -124,24 +123,28 @@ DankPopout {
HeaderPane { HeaderPane {
id: headerPane id: headerPane
width: parent.width width: parent.width
powerOptionsExpanded: root.powerOptionsExpanded
editMode: root.editMode editMode: root.editMode
onPowerOptionsExpandedChanged: root.powerOptionsExpanded = powerOptionsExpanded
onEditModeToggled: root.editMode = !root.editMode onEditModeToggled: root.editMode = !root.editMode
onPowerButtonClicked: { onPowerActionRequested: (action, title, message) => root.powerActionRequested(action, title, message)
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: { onLockRequested: {
root.close() root.close()
root.lockRequested() 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 { DragDropGrid {
id: widgetGrid id: widgetGrid
width: parent.width width: parent.width
@@ -150,8 +153,6 @@ DankPopout {
expandedWidgetIndex: root.expandedWidgetIndex expandedWidgetIndex: root.expandedWidgetIndex
expandedWidgetData: root.expandedWidgetData expandedWidgetData: root.expandedWidgetData
model: widgetModel model: widgetModel
bluetoothCodecSelector: bluetoothCodecSelector
colorPickerModal: root.colorPickerModal
onExpandClicked: (widgetData, globalIndex) => { onExpandClicked: (widgetData, globalIndex) => {
root.expandedWidgetIndex = globalIndex root.expandedWidgetIndex = globalIndex
root.expandedWidgetData = widgetData root.expandedWidgetData = widgetData
@@ -169,12 +170,9 @@ DankPopout {
EditControls { EditControls {
width: parent.width width: parent.width
visible: editMode visible: editMode
popoutContent: controlContent
availableWidgets: { availableWidgets: {
if (!editMode) return []
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id) const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id)
const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets()) return widgetModel.baseWidgetDefinitions.filter(w => w.allowMultiple || !existingIds.includes(w.id))
return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id))
} }
onAddWidget: (widgetId) => widgetModel.addWidget(widgetId) onAddWidget: (widgetId) => widgetModel.addWidget(widgetId)
onResetToDefault: () => widgetModel.resetToDefault() onResetToDefault: () => widgetModel.resetToDefault()
@@ -224,7 +222,4 @@ DankPopout {
id: batteryDetailComponent id: batteryDetailComponent
BatteryDetail {} BatteryDetail {}
} }
property var colorPickerModal: null
property var powerMenuModalLoader: null
} }

View File

@@ -30,7 +30,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: I18n.tr("Input Devices") text: "Input Devices"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -30,7 +30,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: I18n.tr("Audio Devices") text: "Audio Devices"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -133,7 +133,7 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Health") text: "Health"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -168,7 +168,7 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Capacity") text: "Capacity"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -237,7 +237,7 @@ Rectangle {
width: parent.width - Theme.iconSize - Theme.spacingM width: parent.width - Theme.iconSize - Theme.spacingM
StyledText { StyledText {
text: I18n.tr("Power Profile Degradation") text: "Power Profile Degradation"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.error color: Theme.error
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -170,7 +170,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("Audio Codec Selection") text: "Audio Codec Selection"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
} }

View File

@@ -39,7 +39,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: I18n.tr("Bluetooth Settings") text: "Bluetooth Settings"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -422,7 +422,7 @@ Rectangle {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("No Bluetooth adapter found") text: "No Bluetooth adapter found"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
@@ -473,7 +473,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: I18n.tr("Audio Codec") text: "Audio Codec"
height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0 height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0
visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected
@@ -498,7 +498,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: I18n.tr("Forget Device") text: "Forget Device"
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {

View File

@@ -23,6 +23,9 @@ Rectangle {
Component.onCompleted: { Component.onCompleted: {
NetworkService.addRef() NetworkService.addRef()
if (NetworkService.wifiEnabled) {
NetworkService.scanWifi()
}
} }
Component.onDestruction: { Component.onDestruction: {
@@ -41,7 +44,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: I18n.tr("Network Settings") text: "Network Settings"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -56,30 +59,15 @@ Rectangle {
DankButtonGroup { DankButtonGroup {
id: preferenceControls id: preferenceControls
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.ethernetConnected visible: NetworkService.ethernetConnected && NetworkService.wifiConnected
property int currentPreferenceIndex: { property int currentPreferenceIndex: NetworkService.userPreference === "ethernet" ? 0 : 1
const pref = NetworkService.userPreference
const status = NetworkService.networkStatus
let index = 1
if (pref === "ethernet") {
index = 0
} else if (pref === "wifi") {
index = 1
} else {
index = status === "ethernet" ? 0 : 1
}
return index
}
model: ["Ethernet", "WiFi"] model: ["Ethernet", "WiFi"]
currentIndex: currentPreferenceIndex currentIndex: currentPreferenceIndex
selectionMode: "single" selectionMode: "single"
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (!selected) return if (!selected) return
console.log("NetworkDetail: Setting preference to", index === 0 ? "ethernet" : "wifi")
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi") NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi")
} }
} }
@@ -148,7 +136,7 @@ Rectangle {
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: I18n.tr("WiFi is off") text: "WiFi is off"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -166,7 +154,7 @@ Rectangle {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: I18n.tr("Enable WiFi") text: "Enable WiFi"
color: Theme.primary color: Theme.primary
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
@@ -204,7 +192,7 @@ Rectangle {
Item { Item {
width: parent.width width: parent.width
height: 200 height: 200
visible: NetworkService.wifiInterface && NetworkService.wifiNetworks?.length < 1 && !NetworkService.wifiToggling && NetworkService.isScanning visible: NetworkService.wifiInterface && NetworkService.wifiNetworks?.length < 1 && !NetworkService.wifiToggling
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -213,7 +201,7 @@ Rectangle {
color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.3) color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.3)
RotationAnimation on rotation { RotationAnimation on rotation {
running: NetworkService.isScanning running: true
loops: Animation.Infinite loops: Animation.Infinite
from: 0 from: 0
to: 360 to: 360
@@ -223,18 +211,14 @@ Rectangle {
} }
Repeater { Repeater {
model: sortedNetworks model: {
let networks = [...NetworkService.wifiNetworks]
property var sortedNetworks: { networks.sort((a, b) => {
const ssid = NetworkService.currentWifiSSID if (a.ssid === NetworkService.currentWifiSSID) return -1
const networks = NetworkService.wifiNetworks if (b.ssid === NetworkService.currentWifiSSID) return 1
let sorted = [...networks]
sorted.sort((a, b) => {
if (a.ssid === ssid) return -1
if (b.ssid === ssid) return 1
return b.signal - a.signal return b.signal - a.signal
}) })
return sorted return networks
} }
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
@@ -396,7 +380,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: I18n.tr("Network Info") text: "Network Info"
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {
@@ -419,7 +403,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: I18n.tr("Forget Network") text: "Forget Network"
height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0 height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0
visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected

View File

@@ -1,38 +1,13 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Modules.ControlCenter.BuiltinPlugins
import "../utils/widgets.js" as WidgetUtils import "../utils/widgets.js" as WidgetUtils
QtObject { QtObject {
id: root id: root
property var vpnBuiltinInstance: null readonly property var baseWidgetDefinitions: [
{
property var vpnLoader: Loader {
active: false
sourceComponent: Component {
VpnWidget {}
}
onItemChanged: {
root.vpnBuiltinInstance = item
}
Connections {
target: SettingsData
function onControlCenterWidgetsChanged() {
const widgets = SettingsData.controlCenterWidgets || []
const hasVpnWidget = widgets.some(w => w.id === "builtin_vpn")
if (!hasVpnWidget && vpnLoader.active) {
console.log("VpnWidget: No VPN widget in control center, deactivating loader")
vpnLoader.active = false
}
}
}
}
readonly property var coreWidgetDefinitions: [{
"id": "nightMode", "id": "nightMode",
"text": "Night Mode", "text": "Night Mode",
"description": "Blue light filter", "description": "Blue light filter",
@@ -40,28 +15,32 @@ QtObject {
"type": "toggle", "type": "toggle",
"enabled": DisplayService.automationAvailable, "enabled": DisplayService.automationAvailable,
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined "warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
}, { },
{
"id": "darkMode", "id": "darkMode",
"text": "Dark Mode", "text": "Dark Mode",
"description": "System theme toggle", "description": "System theme toggle",
"icon": "contrast", "icon": "contrast",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, { },
{
"id": "doNotDisturb", "id": "doNotDisturb",
"text": "Do Not Disturb", "text": "Do Not Disturb",
"description": "Block notifications", "description": "Block notifications",
"icon": "do_not_disturb_on", "icon": "do_not_disturb_on",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, { },
{
"id": "idleInhibitor", "id": "idleInhibitor",
"text": "Keep Awake", "text": "Keep Awake",
"description": "Prevent screen timeout", "description": "Prevent screen timeout",
"icon": "motion_sensor_active", "icon": "motion_sensor_active",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, { },
{
"id": "wifi", "id": "wifi",
"text": "Network", "text": "Network",
"description": "Wi-Fi and Ethernet connection", "description": "Wi-Fi and Ethernet connection",
@@ -69,7 +48,8 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": NetworkService.wifiAvailable, "enabled": NetworkService.wifiAvailable,
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined "warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
}, { },
{
"id": "bluetooth", "id": "bluetooth",
"text": "Bluetooth", "text": "Bluetooth",
"description": "Device connections", "description": "Device connections",
@@ -77,28 +57,32 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": BluetoothService.available, "enabled": BluetoothService.available,
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined "warning": !BluetoothService.available ? "Bluetooth not available" : undefined
}, { },
{
"id": "audioOutput", "id": "audioOutput",
"text": "Audio Output", "text": "Audio Output",
"description": "Speaker settings", "description": "Speaker settings",
"icon": "volume_up", "icon": "volume_up",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, { },
{
"id": "audioInput", "id": "audioInput",
"text": "Audio Input", "text": "Audio Input",
"description": "Microphone settings", "description": "Microphone settings",
"icon": "mic", "icon": "mic",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, { },
{
"id": "volumeSlider", "id": "volumeSlider",
"text": "Volume Slider", "text": "Volume Slider",
"description": "Audio volume control", "description": "Audio volume control",
"icon": "volume_up", "icon": "volume_up",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, { },
{
"id": "brightnessSlider", "id": "brightnessSlider",
"text": "Brightness Slider", "text": "Brightness Slider",
"description": "Display brightness control", "description": "Display brightness control",
@@ -106,21 +90,24 @@ QtObject {
"type": "slider", "type": "slider",
"enabled": DisplayService.brightnessAvailable, "enabled": DisplayService.brightnessAvailable,
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined "warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined
}, { },
{
"id": "inputVolumeSlider", "id": "inputVolumeSlider",
"text": "Input Volume Slider", "text": "Input Volume Slider",
"description": "Microphone volume control", "description": "Microphone volume control",
"icon": "mic", "icon": "mic",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, { },
{
"id": "battery", "id": "battery",
"text": "Battery", "text": "Battery",
"description": "Battery and power management", "description": "Battery and power management",
"icon": "battery_std", "icon": "battery_std",
"type": "action", "type": "action",
"enabled": true "enabled": true
}, { },
{
"id": "diskUsage", "id": "diskUsage",
"text": "Disk Usage", "text": "Disk Usage",
"description": "Filesystem usage monitoring", "description": "Filesystem usage monitoring",
@@ -129,68 +116,8 @@ QtObject {
"enabled": DgopService.dgopAvailable, "enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined, "warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
"allowMultiple": true "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) { function getWidgetForId(widgetId) {
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId) return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId)

View File

@@ -10,12 +10,6 @@ import qs.Widgets
PanelWindow { PanelWindow {
id: root id: root
readonly property string powerOptionsText: I18n.tr("Power Options")
readonly property string logOutText: I18n.tr("Log Out")
readonly property string suspendText: I18n.tr("Suspend")
readonly property string rebootText: I18n.tr("Reboot")
readonly property string powerOffText: I18n.tr("Power Off")
property bool powerMenuVisible: false property bool powerMenuVisible: false
signal powerActionRequested(string action, string title, string message) signal powerActionRequested(string action, string title, string message)
@@ -71,7 +65,7 @@ PanelWindow {
width: parent.width width: parent.width
StyledText { StyledText {
text: root.powerOptionsText text: "Power Options"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -124,7 +118,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: root.logOutText text: "Log Out"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -174,7 +168,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: root.suspendText text: "Suspend"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -224,7 +218,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: root.rebootText text: "Reboot"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -274,7 +268,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: root.powerOffText text: "Power Off"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -20,7 +20,11 @@ Row {
height: Theme.iconSize + Theme.spacingS * 2 height: Theme.iconSize + Theme.spacingS * 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0) color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
MouseArea { MouseArea {
id: iconArea id: iconArea
@@ -30,9 +34,7 @@ Row {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (defaultSink) { if (defaultSink) {
AudioService.suppressOSD = true
defaultSink.audio.muted = !defaultSink.audio.muted defaultSink.audio.muted = !defaultSink.audio.muted
AudioService.suppressOSD = false
} }
} }
} }

View File

@@ -18,13 +18,17 @@ Row {
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse color: iconArea.containsMouse
? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
: Theme.withAlpha(Theme.primary, 0) : "transparent"
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: Qt.PointingHandCursor
onClicked: function(event) { onClicked: function(event) {
if (DisplayService.devices.length > 1) { if (DisplayService.devices.length > 1) {
@@ -37,22 +41,6 @@ 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 { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: { name: {
@@ -153,10 +141,4 @@ Row {
onObjectRemoved: (index, object) => deviceMenu.removeItem(object) onObjectRemoved: (index, object) => deviceMenu.removeItem(object)
} }
} }
Loader {
id: tooltipLoader
active: false
sourceComponent: DankTooltip {}
}
} }

View File

@@ -1,33 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var 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()
}
}
}

View File

@@ -41,7 +41,7 @@ Rectangle {
readonly property color _labelSecondary: Theme.surfaceVariantText readonly property color _labelSecondary: Theme.surfaceVariantText
readonly property color _tileBgActive: Theme.primary readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive: { readonly property color _tileBgInactive: {
const transparency = Theme.popupTransparency const transparency = Theme.popupTransparency || 0.92
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1) const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
return Qt.rgba(surface.r, surface.g, surface.b, transparency) 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) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileRingInactive: readonly property color _tileRingInactive:
Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18) Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18)
readonly property color _tileIconActive: Theme.primaryText readonly property color _tileIconActive: Theme.primaryContainer
readonly property color _tileIconInactive: Theme.primary readonly property color _tileIconInactive: Theme.primary
property int _padH: Theme.spacingS property int _padH: Theme.spacingS

View File

@@ -1,51 +0,0 @@
import QtQuick
import qs.Common
import qs.Widgets
StyledRect {
id: root
property string primaryMessage: ""
property string secondaryMessage: ""
radius: Theme.cornerRadius
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.1)
border.color: Theme.warning
border.width: 1
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
DankIcon {
name: "warning"
size: 16
color: Theme.warning
anchors.top: parent.top
anchors.topMargin: 2
}
Column {
width: parent.width - 16 - parent.spacing
spacing: Theme.spacingXS
StyledText {
width: parent.width
text: root.primaryMessage
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
font.weight: Font.Medium
wrapMode: Text.WordWrap
}
StyledText {
width: parent.width
text: root.secondaryMessage
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
visible: text.length > 0
}
}
}
}

View File

@@ -20,7 +20,11 @@ Row {
height: Theme.iconSize + Theme.spacingS * 2 height: Theme.iconSize + Theme.spacingS * 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0) color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
MouseArea { MouseArea {
id: iconArea id: iconArea
@@ -30,9 +34,7 @@ Row {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (defaultSource) { if (defaultSource) {
AudioService.suppressOSD = true
defaultSource.audio.muted = !defaultSource.audio.muted defaultSource.audio.muted = !defaultSource.audio.muted
AudioService.suppressOSD = false
} }
} }
} }
@@ -67,9 +69,6 @@ Row {
valueOverride: actualVolumePercent valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
onIsDraggingChanged: {
AudioService.suppressOSD = isDragging
}
onSliderValueChanged: function(newValue) { onSliderValueChanged: function(newValue) {
if (defaultSource) { if (defaultSource) {
defaultSource.audio.volume = newValue / 100.0 defaultSource.audio.volume = newValue / 100.0

View File

@@ -0,0 +1,78 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
isActive: {
if (NetworkService.wifiToggling) {
return false
}
if (NetworkService.networkStatus === "ethernet") {
return true
}
if (NetworkService.networkStatus === "wifi") {
return true
}
return NetworkService.wifiEnabled
}
iconName: {
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"
}
primaryText: {
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"
}
secondaryText: {
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 ""
}
onToggled: {
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
NetworkService.toggleWifiRadio()
}
}
}

View File

@@ -28,7 +28,7 @@ Rectangle {
readonly property color _tileBgInactive: Theme.surfaceContainerHigh readonly property color _tileBgInactive: Theme.surfaceContainerHigh
readonly property color _tileRingActive: readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryText readonly property color _tileIconActive: Theme.primaryContainer
readonly property color _tileIconInactive: Theme.primary readonly property color _tileIconInactive: Theme.primary
color: { color: {
@@ -91,6 +91,13 @@ Rectangle {
onClicked: root.clicked() onClicked: root.clicked()
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on radius { Behavior on radius {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration

View File

@@ -12,7 +12,6 @@ Rectangle {
property real iconRotation: 0 property real iconRotation: 0
signal clicked() signal clicked()
signal iconRotationCompleted()
width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48 width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48
height: 48 height: 48
@@ -30,7 +29,7 @@ Rectangle {
readonly property color _tileBgInactive: Theme.surfaceContainerHigh readonly property color _tileBgInactive: Theme.surfaceContainerHigh
readonly property color _tileRingActive: readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryText readonly property color _tileIconActive: Theme.primaryContainer
readonly property color _tileIconInactive: Theme.primary readonly property color _tileIconInactive: Theme.primary
color: { color: {
@@ -59,7 +58,6 @@ Rectangle {
size: Theme.iconSize size: Theme.iconSize
color: isActive ? _tileIconActive : _tileIconInactive color: isActive ? _tileIconActive : _tileIconInactive
rotation: iconRotation rotation: iconRotation
onRotationCompleted: root.iconRotationCompleted()
} }
MouseArea { MouseArea {
@@ -71,6 +69,13 @@ Rectangle {
onClicked: root.clicked() onClicked: root.clicked()
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on radius { Behavior on radius {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration

View File

@@ -14,7 +14,6 @@ Rectangle {
property real iconRotation: 0 property real iconRotation: 0
signal clicked() signal clicked()
signal iconRotationCompleted()
width: parent ? parent.width : 200 width: parent ? parent.width : 200
height: 60 height: 60
@@ -47,7 +46,7 @@ Rectangle {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: mouseArea.containsMouse ? hoverTint(_containerBg) : Theme.withAlpha(_containerBg, 0) color: mouseArea.containsMouse ? hoverTint(_containerBg) : "transparent"
opacity: mouseArea.containsMouse ? 0.08 : 0.0 opacity: mouseArea.containsMouse ? 0.08 : 0.0
Behavior on opacity { Behavior on opacity {
@@ -64,10 +63,9 @@ Rectangle {
DankIcon { DankIcon {
name: root.iconName name: root.iconName
size: Theme.iconSize size: Theme.iconSize
color: isActive ? Theme.primaryText : Theme.primary color: isActive ? Theme.primaryContainer : Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
rotation: root.iconRotation rotation: root.iconRotation
onRotationCompleted: root.iconRotationCompleted()
} }
Item { Item {
@@ -84,7 +82,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.text text: root.text
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: isActive ? Theme.primaryText : Theme.surfaceText color: isActive ? Theme.primaryContainer : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
@@ -94,7 +92,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.secondaryText text: root.secondaryText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: isActive ? Theme.primaryText : Theme.surfaceVariantText color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
visible: text.length > 0 visible: text.length > 0
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
@@ -112,6 +110,13 @@ Rectangle {
onClicked: root.clicked() onClicked: root.clicked()
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on radius { Behavior on radius {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration

View File

@@ -1,179 +0,0 @@
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
}
}

View File

@@ -1,57 +0,0 @@
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()
}
}

View File

@@ -1,321 +0,0 @@
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(Math.ceil(correctWidth), Math.ceil(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() }
}
Connections {
target: Theme
function onIsLightModeChanged() { barShape.requestPaint() }
}
onPaint: {
const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const R = wing
const RT = rt
const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
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, Math.ceil(W), Math.ceil(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(Math.ceil(correctWidth), Math.ceil(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() }
}
Connections {
target: Theme
function onIsLightModeChanged() { barTint.requestPaint() }
}
onPaint: {
const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const R = wing
const RT = rt
const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
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, Math.ceil(W), Math.ceil(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: false
visible: SettingsData.dankBarBorderEnabled
renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative
readonly property real correctWidth: root.width
readonly property real correctHeight: root.height
canvasSize: Qt.size(Math.ceil(correctWidth), Math.ceil(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: Theme
function onIsLightModeChanged() { barBorder.requestPaint() }
}
Connections {
target: SettingsData
function onDankBarBorderColorChanged() { barBorder.requestPaint() }
function onDankBarBorderOpacityChanged() { barBorder.requestPaint() }
function onDankBarBorderThicknessChanged() { barBorder.requestPaint() }
function onDankBarSpacingChanged() { barBorder.requestPaint() }
function onDankBarSquareCornersChanged() { barBorder.requestPaint() }
}
onPaint: {
if (!borderEnabled) return
const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const R = wing
const RT = rt
const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
const spacing = SettingsData.dankBarSpacing
const hasEdgeGap = spacing > 0 || RT > 0
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, Math.ceil(W), Math.ceil(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()
const key = SettingsData.dankBarBorderColor || "surfaceText"
const base = (key === "surfaceText") ? Theme.surfaceText
: (key === "primary") ? Theme.primary
: Theme.secondary
const color = Theme.withAlpha(base, SettingsData.dankBarBorderOpacity ?? 1.0)
const thickness = Math.max(1, SettingsData.dankBarBorderThickness ?? 1)
ctx.globalCompositeOperation = "source-over"
ctx.lineWidth = thickness
ctx.strokeStyle = color
ctx.stroke()
}
}
}

View File

@@ -1,460 +0,0 @@
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)
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,82 +0,0 @@
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
}
}
}
}
}
}

View File

@@ -1,84 +0,0 @@
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
}
}
}
}
}
}

View File

@@ -1,169 +0,0 @@
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
}
}

View File

@@ -1,126 +0,0 @@
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();
}
}
}

View File

@@ -1,57 +0,0 @@
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
}
}
}

View File

@@ -1,247 +0,0 @@
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()
}
}
}

View File

@@ -1,268 +0,0 @@
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 var widgetData: null
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
signal clicked()
width: isVertical ? widgetThickness : (controlIndicators.implicitWidth + horizontalPadding * 2)
height: isVertical ? (controlColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = controlCenterArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
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 && NetworkService.networkAvailable
}
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
DankIcon {
id: networkIcon
name: {
if (NetworkService.wifiToggling) {
return "sync";
}
if (NetworkService.networkStatus === "ethernet") {
return "lan";
}
return NetworkService.wifiSignalIcon;
}
size: Theme.barIconSize(barThickness)
color: {
if (NetworkService.wifiToggling) {
return Theme.primary;
}
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
}
anchors.verticalCenter: parent.verticalCenter
visible: root.showNetworkIcon && NetworkService.networkAvailable
}
DankIcon {
id: bluetoothIcon
name: "bluetooth"
size: Theme.barIconSize(barThickness)
color: BluetoothService.enabled ? Theme.primary : Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
}
Rectangle {
width: audioIcon.implicitWidth + 4
height: audioIcon.implicitHeight + 4
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
visible: root.showAudioIcon
DankIcon {
id: audioIcon
name: {
if (AudioService.sink && AudioService.sink.audio) {
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off";
} else if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down";
} else {
return "volume_up";
}
}
return "volume_up";
}
size: Theme.barIconSize(barThickness)
color: Theme.surfaceText
anchors.centerIn: parent
}
MouseArea {
id: audioWheelArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y;
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
let newVolume;
if (delta > 0) {
newVolume = Math.min(100, currentVolume + 5);
} else {
newVolume = Math.max(0, currentVolume - 5);
}
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = false;
AudioService.sink.audio.volume = newVolume / 100;
AudioService.volumeChanged();
}
wheelEvent.accepted = true;
}
}
}
DankIcon {
name: "mic"
size: Theme.barIconSize(barThickness)
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
visible: false // TODO: Add mic detection
}
// Fallback settings icon when all other icons are hidden
DankIcon {
name: "settings"
size: Theme.barIconSize(barThickness)
color: controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
}
}
MouseArea {
id: controlCenterArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
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();
}
}
}

View File

@@ -1,159 +0,0 @@
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
}
}
}
}
}

View File

@@ -1,273 +0,0 @@
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
}
}
}

View File

@@ -1,120 +0,0 @@
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 {
saturation: 0
colorization: 1
colorizationColor: Theme.effectiveLogoColor
brightness: SettingsData.launcherLogoBrightness
contrast: SettingsData.launcherLogoContrast
}
}
IconImage {
visible: SettingsData.launcherLogoMode === "custom" && SettingsData.launcherLogoCustomPath !== ""
anchors.centerIn: parent
width: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(barThickness, SettingsData.launcherLogoSizeOffset)
smooth: true
asynchronous: true
source: SettingsData.launcherLogoCustomPath ? "file://" + SettingsData.launcherLogoCustomPath.replace("file://", "") : ""
layer.enabled: Theme.effectiveLogoColor !== ""
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.effectiveLogoColor
brightness: SettingsData.launcherLogoBrightness
contrast: SettingsData.launcherLogoContrast
}
}
}
}

View File

@@ -1,96 +0,0 @@
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()
}
}
}

View File

@@ -1,76 +0,0 @@
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
}
}
}

View File

@@ -1,633 +0,0 @@
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];
let fileName = name.substring(name.lastIndexOf("/") + 1);
if (fileName.startsWith("dropboxstatus")) {
fileName = `hicolor/16x16/status/${fileName}`;
}
return `file://${path}/${fileName}`;
}
if (icon.startsWith("/") && !icon.startsWith("file://")) {
return `file://${icon}`;
}
return icon;
}
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: I18n.tr("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)
}
}
}

View File

@@ -1,154 +0,0 @@
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();
}
}
}

View File

@@ -1,107 +0,0 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: root
Ref {
service: VpnService
}
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property int widgetThickness: 28
property int barThickness: 32
property string section: "right"
property var popupTarget: null
property var parentScreen: null
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
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
}
}
}

View File

@@ -1,120 +0,0 @@
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
}
}
}

View File

@@ -13,33 +13,30 @@ DankPopout {
id: root id: root
property bool dashVisible: false property bool dashVisible: false
property string triggerSection: "center"
property var triggerScreen: null property var triggerScreen: null
property int currentTabIndex: 0 property int currentTabIndex: 0
function setTriggerPosition(x, y, width, section, screen) { function setTriggerPosition(x, y, width, section, screen) {
triggerSection = section if (section === "center") {
triggerScreen = screen
triggerY = y
if (section === "center" && (SettingsData.dankBarPosition === SettingsData.Position.Top || SettingsData.dankBarPosition === SettingsData.Position.Bottom)) {
const screenWidth = screen ? screen.width : Screen.width const screenWidth = screen ? screen.width : Screen.width
triggerX = (screenWidth - popupWidth) / 2 triggerX = (screenWidth - popupWidth) / 2
triggerWidth = popupWidth 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 { } else {
triggerX = x triggerX = x
triggerWidth = width triggerWidth = width
} }
triggerY = y
triggerSection = section
triggerScreen = screen
} }
popupWidth: 700 popupWidth: 700
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500 popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
triggerX: Screen.width - 620 - Theme.spacingL triggerX: Screen.width - 620 - Theme.spacingL
triggerY: Math.max(26 + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding)) + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap - 2 triggerY: Math.max(26 + SettingsData.topBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.topBarInnerPadding)) + SettingsData.topBarSpacing + SettingsData.topBarBottomGap - 2 + Theme.popupDistance
triggerWidth: 80 triggerWidth: 80
positioning: "center"
shouldBeVisible: dashVisible shouldBeVisible: dashVisible
visible: shouldBeVisible visible: shouldBeVisible
@@ -131,15 +128,15 @@ DankPopout {
model: { model: {
let tabs = [ let tabs = [
{ icon: "dashboard", text: I18n.tr("Overview") }, { icon: "dashboard", text: "Overview" },
{ icon: "music_note", text: I18n.tr("Media") } { icon: "music_note", text: "Media" }
] ]
if (SettingsData.weatherEnabled) { if (SettingsData.weatherEnabled) {
tabs.push({ icon: "wb_sunny", text: I18n.tr("Weather") }) tabs.push({ icon: "wb_sunny", text: "Weather" })
} }
tabs.push({ icon: "settings", text: I18n.tr("Settings"), isAction: true }) tabs.push({ icon: "settings", text: "Settings", isAction: true })
return tabs return tabs
} }

File diff suppressed because it is too large Load Diff

View File

@@ -53,7 +53,7 @@ Card {
} }
StyledText { StyledText {
text: I18n.tr("No Media") text: "No Media"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -13,22 +13,83 @@ Card {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
DankCircularImage { Item {
id: avatarContainer id: avatarContainer
property bool hasImage: profileImageLoader.status === Image.Ready
width: 77 width: 77
height: 77 height: 77
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
imageSource: {
if (PortalService.profileImage === "")
return ""
if (PortalService.profileImage.startsWith("/")) Rectangle {
return "file://" + PortalService.profileImage anchors.fill: parent
radius: 36
color: Theme.primary
visible: !avatarContainer.hasImage
return PortalService.profileImage StyledText {
anchors.centerIn: parent
text: UserInfoService.username.length > 0 ? UserInfoService.username.charAt(0).toUpperCase() : "b"
font.pixelSize: Theme.fontSizeXLarge + 4
font.weight: Font.Bold
color: Theme.background
}
}
Image {
id: profileImageLoader
source: {
if (PortalService.profileImage === "")
return ""
if (PortalService.profileImage.startsWith("/"))
return "file://" + PortalService.profileImage
return PortalService.profileImage
}
smooth: true
asynchronous: true
mipmap: true
cache: true
visible: false
}
MultiEffect {
anchors.fill: parent
anchors.margins: 2
source: profileImageLoader
maskEnabled: true
maskSource: circularMask
visible: avatarContainer.hasImage
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: circularMask
width: 77 - 4
height: 77 - 4
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: width / 2
color: "black"
antialiasing: true
}
}
DankIcon {
anchors.centerIn: parent
name: "person"
size: Theme.iconSize + 8
color: Theme.error
visible: PortalService.profileImage !== "" && profileImageLoader.status === Image.Error
} }
fallbackIcon: "person"
} }
Column { Column {

View File

@@ -33,7 +33,7 @@ Card {
} }
Button { Button {
text: I18n.tr("Refresh") text: "Refresh"
flat: true flat: true
visible: !WeatherService.weather.loading visible: !WeatherService.weather.loading
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -24,7 +24,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("No Weather Data Available") text: "No Weather Data Available"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -257,7 +257,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: I18n.tr("Feels Like") text: "Feels Like"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -304,7 +304,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: I18n.tr("Humidity") text: "Humidity"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -351,7 +351,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: I18n.tr("Wind") text: "Wind"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -398,7 +398,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: I18n.tr("Pressure") text: "Pressure"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -445,7 +445,7 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: I18n.tr("Rain Chance") text: "Rain Chance"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -492,14 +492,14 @@ Item {
spacing: 2 spacing: 2
StyledText { StyledText {
text: I18n.tr("Visibility") text: "Visibility"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: I18n.tr("Good") text: "Good"
font.pixelSize: Theme.fontSizeSmall + 1 font.pixelSize: Theme.fontSizeSmall + 1
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -522,7 +522,7 @@ Item {
spacing: Theme.spacingS spacing: Theme.spacingS
StyledText { StyledText {
text: I18n.tr("7-Day Forecast") text: "7-Day Forecast"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -7,59 +7,22 @@ import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
pragma ComponentBehavior: Bound PanelWindow {
id: dock
Variants { WlrLayershell.namespace: "quickshell:dock"
id: dockVariants
model: SettingsData.getFilteredScreens("dock")
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
property var modelData
property var contextMenu property var contextMenu
delegate: PanelWindow {
id: dock
WlrLayershell.namespace: "quickshell:dock"
readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
anchors {
top: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
bottom: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
left: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
right: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
}
property var modelData: item
property bool autoHide: SettingsData.dockAutoHide property bool autoHide: SettingsData.dockAutoHide
property real backgroundTransparency: SettingsData.dockTransparency property real backgroundTransparency: SettingsData.dockTransparency
property bool groupByApp: SettingsData.dockGroupByApp property bool groupByApp: SettingsData.dockGroupByApp
readonly property real widgetHeight: Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6) property bool contextMenuOpen: (contextMenu && contextMenu.visible && contextMenu.screen === modelData)
readonly property real effectiveBarHeight: Math.max(widgetHeight + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
readonly property real barSpacing: {
const barIsHorizontal = (SettingsData.dankBarPosition === SettingsData.Position.Top || SettingsData.dankBarPosition === SettingsData.Position.Bottom)
const barIsVertical = (SettingsData.dankBarPosition === SettingsData.Position.Left || SettingsData.dankBarPosition === SettingsData.Position.Right)
const samePosition = (SettingsData.dockPosition === SettingsData.dankBarPosition)
const dockIsHorizontal = !isVertical
const dockIsVertical = isVertical
if (!SettingsData.dankBarVisible) return 0
if (dockIsHorizontal && barIsHorizontal && samePosition) {
return SettingsData.dankBarSpacing + effectiveBarHeight + SettingsData.dankBarBottomGap
}
if (dockIsVertical && barIsVertical && samePosition) {
return SettingsData.dankBarSpacing + effectiveBarHeight + SettingsData.dankBarBottomGap
}
return 0
}
readonly property real dockMargin: SettingsData.dockSpacing
readonly property real positionSpacing: barSpacing + SettingsData.dockBottomGap
readonly property real _dpr: (dock.screen && dock.screen.devicePixelRatio) ? dock.screen.devicePixelRatio : 1
function px(v) { return Math.round(v * _dpr) / _dpr }
property bool contextMenuOpen: (dockVariants.contextMenu && dockVariants.contextMenu.visible && dockVariants.contextMenu.screen === modelData)
property bool windowIsFullscreen: { property bool windowIsFullscreen: {
if (!ToplevelManager.activeToplevel) { if (!ToplevelManager.activeToplevel) {
return false return false
@@ -68,28 +31,7 @@ Variants {
const fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"] const fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"]
return fullscreenApps.some(app => activeWindow.appId && activeWindow.appId.toLowerCase().includes(app)) return fullscreenApps.some(app => activeWindow.appId && activeWindow.appId.toLowerCase().includes(app))
} }
property bool revealSticky: false property bool reveal: (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen) && !windowIsFullscreen
Timer {
id: revealHold
interval: 250
repeat: false
onTriggered: dock.revealSticky = false
}
property bool reveal: {
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
return true
}
return (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky) && !windowIsFullscreen
}
onContextMenuOpenChanged: {
if (!contextMenuOpen && autoHide && !dockMouseArea.containsMouse) {
revealSticky = true
revealHold.restart()
}
}
Connections { Connections {
target: SettingsData target: SettingsData
@@ -99,258 +41,155 @@ Variants {
} }
screen: modelData screen: modelData
visible: SettingsData.showDock || (CompositorService.isNiri && SettingsData.dockOpenOnOverview && NiriService.inOverview) visible: SettingsData.showDock
color: "transparent" color: "transparent"
anchors {
exclusiveZone: { bottom: true
if (!SettingsData.showDock || autoHide) return -1 left: true
if (barSpacing > 0) return -1 right: true
return px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap)
} }
margins {
left: 0
right: 0
}
implicitHeight: 100
exclusiveZone: autoHide ? -1 : 65 - 16
mask: Region { mask: Region {
item: dockMouseArea item: dockMouseArea
} }
Rectangle { MouseArea {
id: appTooltip id: dockMouseArea
z: 1000 property real currentScreen: modelData ? modelData : dock.screen
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920
property real maxDockWidth: Math.min(screenWidth * 0.8, 1200)
property var hoveredButton: { height: dock.reveal ? 65 : 20
if (!dockApps.children[0]) { width: dock.reveal ? Math.min(dockBackground.width + 32, maxDockWidth) : Math.min(Math.max(dockBackground.width + 64, 200), screenWidth * 0.5)
return null anchors {
} bottom: parent.bottom
const layoutItem = dockApps.children[0] horizontalCenter: parent.horizontalCenter
const flowLayout = layoutItem.children[0]
let repeater = null
for (var i = 0; i < flowLayout.children.length; i++) {
const child = flowLayout.children[i]
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
repeater = child
break
}
}
if (!repeater || !repeater.itemAt) {
return null
}
for (var i = 0; i < repeater.count; i++) {
const item = repeater.itemAt(i)
if (item && item.dockButton && item.dockButton.showTooltip) {
return item.dockButton
}
}
return null
} }
hoverEnabled: true
property string tooltipText: hoveredButton ? hoveredButton.tooltipText : "" Behavior on height {
NumberAnimation {
visible: hoveredButton !== null && tooltipText !== "" duration: 200
width: px(tooltipLabel.implicitWidth + 24) easing.type: Easing.OutCubic
height: px(tooltipLabel.implicitHeight + 12)
color: Theme.surfaceContainer
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.outlineMedium
x: {
if (!hoveredButton) return 0
const buttonPos = hoveredButton.mapToItem(dock.contentItem, 0, 0)
if (!dock.isVertical) {
return buttonPos.x + hoveredButton.width / 2 - width / 2
} else {
if (SettingsData.dockPosition === SettingsData.Position.Right) {
return buttonPos.x - width - Theme.spacingS
} else {
return buttonPos.x + hoveredButton.width + Theme.spacingS
}
}
}
y: {
if (!hoveredButton) return 0
const buttonPos = hoveredButton.mapToItem(dock.contentItem, 0, 0)
if (!dock.isVertical) {
if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
return buttonPos.y - height - Theme.spacingS
} else {
return buttonPos.y + hoveredButton.height + Theme.spacingS
}
} else {
return buttonPos.y + hoveredButton.height / 2 - height / 2
} }
} }
StyledText { Item {
id: tooltipLabel id: dockContainer
anchors.centerIn: parent anchors.fill: parent
text: appTooltip.tooltipText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
}
}
Item { transform: Translate {
id: dockCore id: dockSlide
anchors.fill: parent y: dock.reveal ? 0 : 60
Connections { Behavior on y {
target: dockMouseArea NumberAnimation {
function onContainsMouseChanged() { duration: 200
if (dockMouseArea.containsMouse) { easing.type: Easing.OutCubic
dock.revealSticky = true
revealHold.stop()
} else {
if (dock.autoHide && !dock.contextMenuOpen) {
revealHold.restart()
} }
} }
} }
}
MouseArea { Rectangle {
id: dockMouseArea id: dockBackground
property real currentScreen: modelData ? modelData : dock.screen objectName: "dockBackground"
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920 anchors {
property real screenHeight: currentScreen ? currentScreen.geometry.height : 1080 top: parent.top
property real maxDockWidth: Math.min(screenWidth * 0.8, 1200) bottom: parent.bottom
property real maxDockHeight: Math.min(screenHeight * 0.8, 1200) horizontalCenter: parent.horizontalCenter
height: {
if (dock.isVertical) {
return dock.reveal ? Math.min(dockBackground.implicitHeight + 32, maxDockHeight) : Math.min(Math.max(dockBackground.implicitHeight + 64, 200), screenHeight * 0.5)
} else {
return dock.reveal ? px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1
} }
}
width: {
if (dock.isVertical) {
return dock.reveal ? px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1
} else {
return dock.reveal ? Math.min(dockBackground.implicitWidth + 32, maxDockWidth) : Math.min(Math.max(dockBackground.implicitWidth + 64, 200), screenWidth * 0.5)
}
}
anchors {
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.left) : undefined
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
}
hoverEnabled: true
acceptedButtons: Qt.NoButton
Behavior on height { width: dockApps.implicitWidth + 12
NumberAnimation { height: parent.height - 8
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on width { anchors.topMargin: 4
NumberAnimation { anchors.bottomMargin: 1
duration: 200
easing.type: Easing.OutCubic
}
}
Item { color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, backgroundTransparency)
id: dockContainer radius: Theme.cornerRadius
anchors.fill: parent border.width: 1
border.color: Theme.outlineMedium
transform: Translate { layer.enabled: true
id: dockSlide
x: {
if (!dock.isVertical) return 0
if (dock.reveal) return 0
const hideDistance = 58 + SettingsData.dockSpacing + SettingsData.dockBottomGap + 10
if (SettingsData.dockPosition === SettingsData.Position.Right) {
return hideDistance
} else {
return -hideDistance
}
}
y: {
if (dock.isVertical) return 0
if (dock.reveal) return 0
const hideDistance = 58 + SettingsData.dockSpacing + SettingsData.dockBottomGap + 10
if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
return hideDistance
} else {
return -hideDistance
}
}
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on y {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
Rectangle { Rectangle {
id: dockBackground anchors.fill: parent
objectName: "dockBackground" color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
anchors { radius: parent.radius
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined }
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.left) : undefined
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
}
anchors.topMargin: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? 0 : barSpacing + 4) : 0
anchors.bottomMargin: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? barSpacing + 1 : 0) : 0
anchors.leftMargin: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? 0 : barSpacing + 4) : 0
anchors.rightMargin: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? barSpacing + 1 : 0) : 0
implicitWidth: dock.isVertical ? (dockApps.implicitHeight + SettingsData.dockSpacing * 2) : (dockApps.implicitWidth + SettingsData.dockSpacing * 2) DockApps {
implicitHeight: dock.isVertical ? (dockApps.implicitWidth + SettingsData.dockSpacing * 2) : (dockApps.implicitHeight + SettingsData.dockSpacing * 2) id: dockApps
width: implicitWidth
height: implicitHeight
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, backgroundTransparency) anchors.top: parent.top
radius: Theme.cornerRadius anchors.bottom: parent.bottom
border.width: 1 anchors.horizontalCenter: parent.horizontalCenter
border.color: Theme.outlineMedium anchors.topMargin: 4
layer.enabled: true anchors.bottomMargin: 4
Rectangle { contextMenu: dock.contextMenu
anchors.fill: parent groupByApp: dock.groupByApp
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04) }
radius: parent.radius }
}
Rectangle {
DockApps { id: appTooltip
id: dockApps
property var hoveredButton: {
anchors.top: !dock.isVertical ? parent.top : undefined if (!dockApps.children[0]) {
anchors.bottom: !dock.isVertical ? parent.bottom : undefined return null
anchors.horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined }
anchors.left: dock.isVertical ? parent.left : undefined const row = dockApps.children[0]
anchors.right: dock.isVertical ? parent.right : undefined let repeater = null
anchors.verticalCenter: dock.isVertical ? parent.verticalCenter : undefined for (var i = 0; i < row.children.length; i++) {
anchors.topMargin: !dock.isVertical ? SettingsData.dockSpacing : 0 const child = row.children[i]
anchors.bottomMargin: !dock.isVertical ? SettingsData.dockSpacing : 0 if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
anchors.leftMargin: dock.isVertical ? SettingsData.dockSpacing : 0 repeater = child
anchors.rightMargin: dock.isVertical ? SettingsData.dockSpacing : 0 break
}
contextMenu: dockVariants.contextMenu }
groupByApp: dock.groupByApp if (!repeater || !repeater.itemAt) {
isVertical: dock.isVertical return null
} }
for (var i = 0; i < repeater.count; i++) {
const item = repeater.itemAt(i)
if (item && item.dockButton && item.dockButton.showTooltip) {
return item.dockButton
}
}
return null
}
property string tooltipText: hoveredButton ? hoveredButton.tooltipText : ""
visible: hoveredButton !== null && tooltipText !== ""
width: tooltipLabel.implicitWidth + 24
height: tooltipLabel.implicitHeight + 12
color: Theme.surfaceContainer
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.outlineMedium
y: -height - 8
x: hoveredButton ? hoveredButton.mapToItem(dockContainer, hoveredButton.width / 2, 0).x - width / 2 : 0
StyledText {
id: tooltipLabel
anchors.centerIn: parent
text: appTooltip.tooltipText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
} }
} }
}
} }
} }
} }

View File

@@ -25,27 +25,6 @@ Item {
property string windowTitle: "" property string windowTitle: ""
property bool isHovered: mouseArea.containsMouse && !dragging property bool isHovered: mouseArea.containsMouse && !dragging
property bool showTooltip: mouseArea.containsMouse && !dragging property bool showTooltip: mouseArea.containsMouse && !dragging
property var cachedDesktopEntry: null
function updateDesktopEntry() {
if (!appData || appData.appId === "__SEPARATOR__") {
cachedDesktopEntry = null
return
}
const moddedId = Paths.moddedAppId(appData.appId)
cachedDesktopEntry = DesktopEntries.heuristicLookup(moddedId)
}
Component.onCompleted: updateDesktopEntry()
onAppDataChanged: updateDesktopEntry()
Connections {
target: DesktopEntries
function onApplicationsChanged() {
updateDesktopEntry()
}
}
property bool isWindowFocused: { property bool isWindowFocused: {
if (!appData) { if (!appData) {
return false return false
@@ -76,7 +55,8 @@ Item {
} }
if ((appData.type === "window" && showWindowTitle) || (appData.type === "grouped" && appData.windowTitle)) { if ((appData.type === "window" && showWindowTitle) || (appData.type === "grouped" && appData.windowTitle)) {
const appName = cachedDesktopEntry && cachedDesktopEntry.name ? cachedDesktopEntry.name : appData.appId const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
const appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId
const title = appData.type === "window" ? windowTitle : appData.windowTitle const title = appData.type === "window" ? windowTitle : appData.windowTitle
return appName + (title ? " • " + title : "") return appName + (title ? " • " + title : "")
} }
@@ -85,7 +65,8 @@ Item {
return "" return ""
} }
return cachedDesktopEntry && cachedDesktopEntry.name ? cachedDesktopEntry.name : appData.appId const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
return desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId
} }
width: 40 width: 40
@@ -274,7 +255,7 @@ Item {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
if (appData.type === "pinned") { if (appData.type === "pinned") {
if (appData && appData.appId) { if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
if (desktopEntry) { if (desktopEntry) {
AppUsageHistoryData.addAppUsage({ AppUsageHistoryData.addAppUsage({
"id": appData.appId, "id": appData.appId,
@@ -294,7 +275,7 @@ Item {
} else if (appData.type === "grouped") { } else if (appData.type === "grouped") {
if (appData.windowCount === 0) { if (appData.windowCount === 0) {
if (appData && appData.appId) { if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
if (desktopEntry) { if (desktopEntry) {
AppUsageHistoryData.addAppUsage({ AppUsageHistoryData.addAppUsage({
"id": appData.appId, "id": appData.appId,
@@ -316,28 +297,15 @@ Item {
console.warn("No toplevel found for grouped app") console.warn("No toplevel found for grouped app")
} }
} else { } else {
// For multiple windows, show context menu (hide pin option for left-click)
if (contextMenu) { if (contextMenu) {
contextMenu.showForButton(root, appData, 65, true, cachedDesktopEntry) contextMenu.showForButton(root, appData, 65, true)
} }
} }
} }
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
if (appData && appData.type === "window") { if (appData && appData.appId) {
const sortedToplevels = CompositorService.sortedToplevels const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
for (var i = 0; i < sortedToplevels.length; i++) {
const toplevel = sortedToplevels[i]
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
if (checkId === appData.uniqueId) {
toplevel.close()
break
}
}
} else if (appData && appData.type === "grouped") {
if (contextMenu) {
contextMenu.showForButton(root, appData, 40, false, cachedDesktopEntry)
}
} else if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry
if (desktopEntry) { if (desktopEntry) {
AppUsageHistoryData.addAppUsage({ AppUsageHistoryData.addAppUsage({
"id": appData.appId, "id": appData.appId,
@@ -347,11 +315,12 @@ Item {
"comment": desktopEntry.comment || "" "comment": desktopEntry.comment || ""
}) })
} }
SessionService.launchDesktopEntry(desktopEntry) SessionService.launchDesktopEntry(desktopEntry)
} }
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
if (contextMenu && appData) { if (contextMenu && appData) {
contextMenu.showForButton(root, appData, 40, false, cachedDesktopEntry) console.log("Right-clicked on app:", appData.appId, "type:", appData.type, "windowCount:", appData.windowCount || 0)
contextMenu.showForButton(root, appData, 40, false)
} else { } else {
console.warn("No context menu or appData available") console.warn("No context menu or appData available")
} }
@@ -372,7 +341,8 @@ Item {
if (moddedId.toLowerCase().includes("steam_app")) { if (moddedId.toLowerCase().includes("steam_app")) {
return "" return ""
} }
return cachedDesktopEntry && cachedDesktopEntry.icon ? Quickshell.iconPath(cachedDesktopEntry.icon, true) : "" const desktopEntry = DesktopEntries.heuristicLookup(moddedId)
return desktopEntry && desktopEntry.icon ? Quickshell.iconPath(desktopEntry.icon, true) : ""
} }
mipmap: true mipmap: true
smooth: true smooth: true
@@ -411,7 +381,7 @@ Item {
return "?" return "?"
} }
const desktopEntry = cachedDesktopEntry const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
if (desktopEntry && desktopEntry.name) { if (desktopEntry && desktopEntry.name) {
return desktopEntry.name.charAt(0).toUpperCase() return desktopEntry.name.charAt(0).toUpperCase()
} }

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