1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 14:05:38 -05:00

Many bug fixes for media center, spotlight

This commit is contained in:
bbedward
2025-07-12 10:47:01 -04:00
parent 2375b6e7bd
commit af0522ada5
13 changed files with 180 additions and 329 deletions

View File

@@ -17,7 +17,7 @@ Singleton {
Component.onCompleted: { Component.onCompleted: {
loadSettings() loadSettings()
Qt.callLater(applyStoredTheme) // Theme will be applied after settings file is loaded in onLoaded handler
} }
Process { Process {
@@ -64,16 +64,22 @@ Singleton {
(settings.topBarTransparency > 1 ? settings.topBarTransparency / 100.0 : settings.topBarTransparency) : 0.75 (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100.0 : settings.topBarTransparency) : 0.75
recentlyUsedApps = settings.recentlyUsedApps || [] recentlyUsedApps = settings.recentlyUsedApps || []
console.log("Loaded settings - themeIndex:", themeIndex, "isDynamic:", themeIsDynamic, "lightMode:", isLightMode, "transparency:", topBarTransparency, "recentApps:", recentlyUsedApps.length) console.log("Loaded settings - themeIndex:", themeIndex, "isDynamic:", themeIsDynamic, "lightMode:", isLightMode, "transparency:", topBarTransparency, "recentApps:", recentlyUsedApps.length)
// Apply the theme immediately after loading settings
applyStoredTheme()
} else { } else {
console.log("Settings file is empty") console.log("Settings file is empty - applying default theme")
applyStoredTheme()
} }
} catch (e) { } catch (e) {
console.log("Could not parse settings, using defaults:", e) console.log("Could not parse settings, using defaults:", e)
applyStoredTheme()
} }
} }
onLoadFailed: (error) => { onLoadFailed: (error) => {
console.log("Settings file not found, using defaults. Error:", error) console.log("Settings file not found, using defaults. Error:", error)
applyStoredTheme()
} }
} }
@@ -99,13 +105,15 @@ Singleton {
} }
function applyStoredTheme() { function applyStoredTheme() {
console.log("Applying stored theme:", themeIndex, themeIsDynamic) console.log("Applying stored theme:", themeIndex, themeIsDynamic, "lightMode:", isLightMode)
if (typeof Theme !== "undefined") { if (typeof Theme !== "undefined") {
Theme.isLightMode = isLightMode
Theme.switchTheme(themeIndex, themeIsDynamic, false) Theme.switchTheme(themeIndex, themeIsDynamic, false)
} else { } else {
Qt.callLater(() => { Qt.callLater(() => {
if (typeof Theme !== "undefined") { if (typeof Theme !== "undefined") {
Theme.isLightMode = isLightMode
Theme.switchTheme(themeIndex, themeIsDynamic, false) Theme.switchTheme(themeIndex, themeIsDynamic, false)
} }
}) })

View File

@@ -10,7 +10,7 @@ QtObject {
// Reference to the main shell root for calling functions // Reference to the main shell root for calling functions
property var rootObj: null property var rootObj: null
// Apply saved theme on startup // Initialize theme system
Component.onCompleted: { Component.onCompleted: {
console.log("Theme Component.onCompleted") console.log("Theme Component.onCompleted")
@@ -19,13 +19,7 @@ QtObject {
Colors.colorsUpdated.connect(root.onColorsUpdated) Colors.colorsUpdated.connect(root.onColorsUpdated)
} }
Qt.callLater(() => { console.log("Theme initialized, waiting for Prefs to load settings and apply theme")
if (typeof Prefs !== "undefined") {
console.log("Theme applying saved preferences:", Prefs.themeIndex, Prefs.themeIsDynamic, "lightMode:", Prefs.isLightMode)
isLightMode = Prefs.isLightMode
switchTheme(Prefs.themeIndex, Prefs.themeIsDynamic, false) // Don't save during startup
}
})
} }
// Handle successful color extraction // Handle successful color extraction

View File

@@ -1,90 +0,0 @@
# System Monitor Widgets Usage Example
## Installation Complete
The CPU and RAM monitor widgets have been successfully created and integrated into your quickshell project:
### Files Created:
- `/Widgets/CpuMonitorWidget.qml` - CPU usage monitor with progress bar and percentage
- `/Widgets/RamMonitorWidget.qml` - RAM usage monitor with progress bar and percentage
- `/Services/SystemMonitorService.qml` - Backend service for system monitoring
### Files Updated:
- `/Widgets/qmldir` - Added widget exports
- `/Services/qmldir` - Added service export
## Usage in TopBar
To add the system monitor widgets to your TopBar, add them to the right section alongside the BatteryWidget:
```qml
// In TopBar.qml, around line 716 after BatteryWidget
BatteryWidget {
anchors.verticalCenter: parent.verticalCenter
}
// Add these new widgets:
CpuMonitorWidget {
anchors.verticalCenter: parent.verticalCenter
showPercentage: true
showIcon: true
}
RamMonitorWidget {
anchors.verticalCenter: parent.verticalCenter
showPercentage: true
showIcon: true
}
```
## Widget Features
### CpuMonitorWidget:
- **Real-time CPU usage monitoring** (updates every 2 seconds)
- **Visual progress bar** with color coding:
- Green: < 60% usage
- Orange: 60-80% usage
- Red: > 80% usage
- **Tooltip** showing CPU usage, core count, and frequency
- **Material Design CPU icon** (󰘚)
- **Configurable properties:**
- `showPercentage: bool` - Show/hide percentage text
- `showIcon: bool` - Show/hide CPU icon
### RamMonitorWidget:
- **Real-time RAM usage monitoring** (updates every 3 seconds)
- **Visual progress bar** with color coding:
- Blue: < 75% usage
- Orange: 75-90% usage
- Red: > 90% usage
- **Tooltip** showing memory usage, used/total memory in GB/MB
- **Material Design memory icon** (󰍛)
- **Configurable properties:**
- `showPercentage: bool` - Show/hide percentage text
- `showIcon: bool` - Show/hide RAM icon
### SystemMonitorService:
- **Centralized system monitoring** backend service
- **CPU monitoring:** usage, core count, frequency, temperature
- **Memory monitoring:** usage percentage, total/used/free memory
- **Automatic updates** with configurable intervals
- **Helper functions** for formatting and color coding
## Widget Customization
Both widgets inherit your theme colors and styling:
- Uses `Theme.cornerRadius` for rounded corners
- Uses `Theme.primary/secondary` colors for progress bars
- Uses `Theme.error/warning` for alert states
- Uses `Theme.surfaceText` for text color
- Consistent hover effects matching other widgets
## System Requirements
The widgets use standard Linux system commands:
- `/proc/stat` for CPU usage
- `/proc/meminfo` via `free` command for memory info
- `/proc/cpuinfo` for CPU details
- Works on most Linux distributions
The widgets are designed to integrate seamlessly with your existing quickshell material design theme and provide essential system monitoring information at a glance.

View File

@@ -136,4 +136,24 @@ Singleton {
this.trackedPlayer = targetPlayer this.trackedPlayer = targetPlayer
} }
// Seeking support
property bool canSeek: this.activePlayer?.canSeek ?? false
property real position: this.activePlayer?.position ?? 0
property real length: this.activePlayer?.length ?? 0
function seek(offsetUs) {
if (this.canSeek && this.activePlayer) {
this.activePlayer.seek(offsetUs)
}
}
function setPosition(trackId, positionUs) {
if (this.canSeek && this.activePlayer && typeof this.activePlayer.setPosition === "function") {
this.activePlayer.setPosition(trackId, positionUs)
} else if (this.canSeek && this.activePlayer) {
// Fallback to setting position property
this.activePlayer.position = positionUs
}
}
} }

View File

@@ -1,49 +0,0 @@
# System Monitor Widget Improvements - Complete! ✅
## 🎯 **Issues Fixed:**
### **1. Icon Swap - DONE ✅**
- **CPU Widget:** Now uses `memory` icon
- **RAM Widget:** Now uses `developer_board` icon
### **2. Percentage Values Working - DONE ✅**
- Both widgets now display real-time percentages correctly
- Service is properly collecting system data
### **3. Vertical Alignment - FIXED ✅**
- Added `anchors.verticalCenter: parent.verticalCenter` to both icon and percentage text
- Icons and percentages now properly align within the widget container
### **4. Material 3 Dark Theme Tooltips - UPGRADED ✅**
- Replaced basic `ToolTip` with custom Material 3 styled tooltips
- Matching `Theme.surfaceContainer` background
- Proper `Theme.outline` borders with opacity
- Smooth fade animations with `Theme.shortDuration`
- Better text spacing and alignment
- Wider tooltips to prevent text cutoff
## 🎨 **New Tooltip Features:**
### **CPU Tooltip:**
```
CPU Usage: X.X%
Cores: N
Frequency: X.X GHz
```
### **RAM Tooltip:**
```
Memory Usage: X.X%
Used: X.X GB
Total: X.X GB
```
## 📱 **Final Result:**
- **CPU Widget:** `memory 7%` with beautiful Material 3 tooltip
- **RAM Widget:** `developer_board 67%` with beautiful Material 3 tooltip
- Perfect vertical alignment of icons and text
- Smooth hover animations
- Professional dark theme styling
- No more cutoff tooltip text
The widgets are now production-ready with a polished Material 3 Dark expressive theme that matches your existing quickshell design language!

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
@@ -639,10 +639,13 @@ PanelWindow {
z: -1 z: -1
layer.enabled: true layer.enabled: true
layer.effect: DropShadow { layer.effect: MultiEffect {
radius: 8 shadowEnabled: true
samples: 16 shadowHorizontalOffset: 0
color: Qt.rgba(0, 0, 0, 0.2) shadowVerticalOffset: 0
shadowBlur: 0.25 // radius/32
shadowColor: Qt.rgba(0, 0, 0, 0.2)
shadowOpacity: 0.2
} }
} }

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import "../../Common" import "../../Common"
@@ -19,103 +19,32 @@ Rectangle {
border.color: Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.2) border.color: Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.2)
border.width: 1 border.width: 1
// Constants and helpers - all microseconds property real currentPosition: 0
readonly property real oneSecondUs: 1000000.0
function asSec(us) { return us / oneSecondUs } // Simple progress ratio calculation
function ratio() { return trackLenUs > 0 ? uiPosUs / trackLenUs : 0 } function ratio() {
return activePlayer && activePlayer.length > 0 ? currentPosition / activePlayer.length : 0
function normalizeLength(lenRaw) {
// If length < 86 400 it's almost certainly seconds (24 h upper bound).
// Convert to µs; otherwise return as-is.
return (lenRaw > 0 && lenRaw < 86400) ? lenRaw * oneSecondUs : lenRaw;
} }
// Updates progress bar every second
// Call seek() in safe 5-second chunks so every player obeys.
function chunkedSeek(offsetUs) {
if (Math.abs(offsetUs) < 5 * oneSecondUs) { // ≤5 s? single shot.
activePlayer.seek(offsetUs);
return;
}
const step = 5 * oneSecondUs; // 5 s
let remaining = offsetUs;
let safety = 0; // avoid infinite loops
while (Math.abs(remaining) > step && safety < 40) { // max 200 s
activePlayer.seek(Math.sign(remaining) * step);
remaining -= Math.sign(remaining) * step;
safety++;
}
if (remaining !== 0) activePlayer.seek(remaining);
}
// Returns a guaranteed-valid object-path for the current track.
function trackPath() {
const md = activePlayer.metadata || {};
// Spec: "/org/mpris/MediaPlayer2/Track/NNN"
if (typeof md["mpris:trackid"] === "string" &&
md["mpris:trackid"].length > 1 && md["mpris:trackid"].startsWith("/"))
return md["mpris:trackid"];
// Nothing reliable? Fall back to the *current* playlist entry object if exposed
if (activePlayer.currentTrackPath) return activePlayer.currentTrackPath;
// Absolute last resort—return null so caller knows SetPosition will fail
return null;
}
// Position tracking - all microseconds
property real uiPosUs: 0
property real backendPosUs: 0
property real trackLenUs: 0
property double backendStamp: Date.now() // wall-clock of last update in ms
// Optimistic timer
Timer { Timer {
id: tickTimer id: positionTimer
interval: 50 // 20 fps feels smooth, cheap interval: 1000
running: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing && activePlayer.length > 0 && !progressMouseArea.isSeeking
repeat: true repeat: true
running: activePlayer?.playbackState === MprisPlaybackState.Playing
onTriggered: { onTriggered: {
if (trackLenUs <= 0) return; if (activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing && !progressMouseArea.isSeeking) {
const projected = backendPosUs + (Date.now() - backendStamp) * 1000.0; currentPosition = activePlayer.position
uiPosUs = Math.min(projected, trackLenUs); // never exceed track end }
} }
} }
// --- 500-ms poll to keep external moves in sync -------------------
// Timer {
// id: pollTimer
// interval: 500 // ms
// repeat: true
// running: true // always on; cost is negligible
// onTriggered: {
// if (!activePlayer || trackLenUs <= 0) return;
// const polledUs = activePlayer.position; // property read
// // Compare in percent to avoid false positives
// if (Math.abs((polledUs - backendPosUs) / trackLenUs) > 0.01) { // >1 % jump
// backendPosUs = polledUs;
// backendStamp = Date.now();
// uiPosUs = polledUs; // snap instantly
// }
// }
// }
// Initialize when player changes // Initialize when player changes
onActivePlayerChanged: { onActivePlayerChanged: {
if (activePlayer) { if (activePlayer) {
backendPosUs = activePlayer.position || 0 currentPosition = activePlayer.position || 0
trackLenUs = normalizeLength(activePlayer.length || 0)
backendStamp = Date.now()
uiPosUs = backendPosUs
console.log(`player change len ${asSec(trackLenUs)} s, pos ${asSec(uiPosUs)} s`)
} else { } else {
backendPosUs = 0 currentPosition = 0
trackLenUs = 0
backendStamp = Date.now()
uiPosUs = 0
} }
} }
@@ -124,30 +53,17 @@ Rectangle {
target: activePlayer target: activePlayer
function onPositionChanged() { function onPositionChanged() {
const posUs = activePlayer.position if (!progressMouseArea.isSeeking) {
backendPosUs = posUs currentPosition = activePlayer.position
backendStamp = Date.now() }
uiPosUs = posUs // snap immediately on tick
}
function onSeeked(pos) {
backendPosUs = pos
backendStamp = Date.now()
uiPosUs = backendPosUs
} }
function onPostTrackChanged() { function onPostTrackChanged() {
backendPosUs = activePlayer?.position || 0 currentPosition = activePlayer?.position || 0
trackLenUs = normalizeLength(activePlayer?.length || 0)
backendStamp = Date.now()
uiPosUs = backendPosUs
} }
function onTrackTitleChanged() { function onTrackTitleChanged() {
backendPosUs = activePlayer?.position || 0 currentPosition = activePlayer?.position || 0
trackLenUs = normalizeLength(activePlayer?.length || 0)
backendStamp = Date.now()
uiPosUs = backendPosUs
} }
} }
@@ -233,6 +149,7 @@ Rectangle {
// Progress bar // Progress bar
Rectangle { Rectangle {
id: progressBarBackground
width: parent.width width: parent.width
height: 6 height: 6
radius: 3 radius: 3
@@ -245,27 +162,72 @@ Rectangle {
color: theme.primary color: theme.primary
width: parent.width * ratio() width: parent.width * ratio()
Behavior on width {
NumberAnimation { duration: 100 }
}
}
// Drag handle
Rectangle {
id: progressHandle
width: 12
height: 12
radius: 6
color: theme.primary
border.color: Qt.lighter(theme.primary, 1.3)
border.width: 1
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width/2))
anchors.verticalCenter: parent.verticalCenter
visible: activePlayer && activePlayer.length > 0
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation { duration: 150 }
}
} }
MouseArea { MouseArea {
id: progressMouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: activePlayer && activePlayer.length > 0 && activePlayer.canSeek
onClicked: (mouse) => { property bool isSeeking: false
if (!activePlayer || !activePlayer.canSeek || trackLenUs <= 0) return
const targetUs = (mouse.x / width) * trackLenUs onClicked: function(mouse) {
const offset = targetUs - backendPosUs if (activePlayer && activePlayer.length > 0) {
let ratio = mouse.x / width
if (typeof activePlayer.setPosition === "function") { let seekPosition = ratio * activePlayer.length
activePlayer.setPosition(trackPath() || "/", Math.round(targetUs)) activePlayer.position = seekPosition
console.log(`SetPosition ${asSec(targetUs)} s`) currentPosition = seekPosition
} else {
chunkedSeek(offset) // <-- use helper
console.log(`chunkedSeek ${asSec(offset/oneSecondUs)} s`)
} }
}
uiPosUs = backendPosUs = targetUs onPressed: function(mouse) {
isSeeking = true
if (activePlayer && activePlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / width))
let seekPosition = ratio * activePlayer.length
activePlayer.position = seekPosition
currentPosition = seekPosition
}
}
onReleased: {
isSeeking = false
}
onPositionChanged: function(mouse) {
if (pressed && activePlayer && activePlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / width))
let seekPosition = ratio * activePlayer.length
activePlayer.position = seekPosition
currentPosition = seekPosition
}
} }
} }
} }
@@ -299,15 +261,9 @@ Rectangle {
if (!activePlayer) return if (!activePlayer) return
// >8 s → jump to start, otherwise previous track // >8 s → jump to start, otherwise previous track
if (uiPosUs > 8 * oneSecondUs && activePlayer.canSeek) { if (currentPosition > 8 && activePlayer.canSeek) {
if (typeof activePlayer.setPosition === "function") { activePlayer.position = 0
activePlayer.setPosition(trackPath() || "/", 0) currentPosition = 0
console.log("Back → SetPosition 0 µs")
} else {
chunkedSeek(-backendPosUs) // <-- use helper
console.log("Back → chunkedSeek to 0")
}
uiPosUs = backendPosUs = 0
} else { } else {
activePlayer.previous() activePlayer.previous()
} }

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
@@ -230,7 +230,8 @@ PanelWindow {
sourceSize.height: size sourceSize.height: size
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: MultiEffect {
maskEnabled: true
maskSource: Rectangle { maskSource: Rectangle {
width: historyNotifImage.size width: historyNotifImage.size
height: historyNotifImage.size height: historyNotifImage.size

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
@@ -179,7 +179,8 @@ PanelWindow {
sourceSize.height: size sourceSize.height: size
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: MultiEffect {
maskEnabled: true
maskSource: Rectangle { maskSource: Rectangle {
width: notifImage.size width: notifImage.size
height: notifImage.size height: notifImage.size

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
@@ -124,17 +124,18 @@ PanelWindow {
} }
} else { } else {
// Search with category filter // Search with category filter
var baseApps = selectedCategory === "All" ?
AppSearchService.applications :
AppSearchService.getAppsInCategory(selectedCategory)
var searchResults = AppSearchService.searchApplications(searchField.text)
if (selectedCategory === "All") { if (selectedCategory === "All") {
// For "All" category, show all search results without limit // For "All" category, search all apps without limit
apps = searchResults.filter(app => baseApps.includes(app)) apps = AppSearchService.searchApplications(searchField.text)
} else { } else {
// For specific categories, still apply limit // For specific categories, filter search results by category
apps = searchResults.filter(app => baseApps.includes(app)).slice(0, maxResults) var categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
var allSearchResults = AppSearchService.searchApplications(searchField.text)
// Filter search results to only include apps from the selected category
apps = allSearchResults.filter(searchApp => {
return categoryApps.some(categoryApp => categoryApp.name === searchApp.name)
}).slice(0, maxResults)
} }
} }
@@ -240,7 +241,14 @@ PanelWindow {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
layer.enabled: true layer.enabled: true
layer.effect: DropShadow { radius: 32; samples: 64; color: Qt.rgba(0,0,0,0.3); horizontalOffset:0; verticalOffset:8 } layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 8
shadowBlur: 1.0 // radius/32
shadowColor: Qt.rgba(0, 0, 0, 0.3)
shadowOpacity: 0.3
}
transform: Scale { origin.x: width/2; origin.y: height/2; xScale: spotlightOpen?1:0.9; yScale: spotlightOpen?1:0.9; transform: Scale { origin.x: width/2; origin.y: height/2; xScale: spotlightOpen?1:0.9; yScale: spotlightOpen?1:0.9;
Behavior on xScale { NumberAnimation { duration: Theme.mediumDuration; easing.type: Easing.OutBack; easing.overshoot:1.1 } } Behavior on xScale { NumberAnimation { duration: Theme.mediumDuration; easing.type: Easing.OutBack; easing.overshoot:1.1 } }
Behavior on yScale { NumberAnimation { duration: Theme.mediumDuration; easing.type: Easing.OutBack; easing.overshoot:1.1 } } Behavior on yScale { NumberAnimation { duration: Theme.mediumDuration; easing.type: Easing.OutBack; easing.overshoot:1.1 } }
@@ -379,8 +387,8 @@ PanelWindow {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
focus: spotlightOpen focus: spotlightOpen
selectByMouse: true selectByMouse: true
onTextChanged: updateFilteredApps onTextChanged: updateFilteredApps()
Keys.onPressed: { Keys.onPressed: (event) => {
if(event.key === Qt.Key_Escape) { hide(); event.accepted = true } if(event.key === Qt.Key_Escape) { hide(); event.accepted = true }
else if(event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { launchSelected(); event.accepted = true } else if(event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { launchSelected(); event.accepted = true }
else if(event.key === Qt.Key_Down) { selectNext(); event.accepted = true } else if(event.key === Qt.Key_Down) { selectNext(); event.accepted = true }

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
@@ -44,15 +44,12 @@ PanelWindow {
property bool bluetoothAvailable: false property bool bluetoothAvailable: false
property bool bluetoothEnabled: false property bool bluetoothEnabled: false
// Shell reference to access root properties directly
property var shellRoot: null
// Notification properties // Notification properties
property bool notificationHistoryVisible: false
property int notificationCount: 0 property int notificationCount: 0
// Control center properties
property bool controlCenterVisible: false
// Calendar properties
property bool calendarVisible: false
// Clipboard properties // Clipboard properties
signal clipboardRequested() signal clipboardRequested()
@@ -64,6 +61,7 @@ PanelWindow {
property real trayMenuX: 0 property real trayMenuX: 0
property real trayMenuY: 0 property real trayMenuY: 0
// Proxy objects for external connections // Proxy objects for external connections
QtObject { QtObject {
@@ -107,13 +105,13 @@ PanelWindow {
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, topBar.backgroundTransparency) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, topBar.backgroundTransparency)
layer.enabled: true layer.enabled: true
layer.effect: DropShadow { layer.effect: MultiEffect {
horizontalOffset: 0 shadowEnabled: true
verticalOffset: 4 shadowHorizontalOffset: 0
radius: 16 shadowVerticalOffset: 4
samples: 33 shadowBlur: 0.5 // radius/32, adjusted for visual match
color: Qt.rgba(0, 0, 0, 0.15) shadowColor: Qt.rgba(0, 0, 0, 0.15)
transparentBorder: true shadowOpacity: 0.15
} }
Rectangle { Rectangle {
@@ -183,7 +181,9 @@ PanelWindow {
useFahrenheit: topBar.useFahrenheit useFahrenheit: topBar.useFahrenheit
onClockClicked: { onClockClicked: {
topBar.calendarVisible = !topBar.calendarVisible if (topBar.shellRoot) {
topBar.shellRoot.calendarVisible = !topBar.shellRoot.calendarVisible
}
} }
// Insert audio visualization into the clock widget placeholder // Insert audio visualization into the clock widget placeholder
@@ -263,9 +263,11 @@ PanelWindow {
NotificationCenterButton { NotificationCenterButton {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
hasUnread: topBar.notificationCount > 0 hasUnread: topBar.notificationCount > 0
isActive: topBar.notificationHistoryVisible isActive: topBar.shellRoot ? topBar.shellRoot.notificationHistoryVisible : false
onClicked: { onClicked: {
topBar.notificationHistoryVisible = !topBar.notificationHistoryVisible if (topBar.shellRoot) {
topBar.shellRoot.notificationHistoryVisible = !topBar.shellRoot.notificationHistoryVisible
}
} }
} }
@@ -281,13 +283,15 @@ PanelWindow {
volumeLevel: topBar.volumeLevel volumeLevel: topBar.volumeLevel
bluetoothAvailable: topBar.bluetoothAvailable bluetoothAvailable: topBar.bluetoothAvailable
bluetoothEnabled: topBar.bluetoothEnabled bluetoothEnabled: topBar.bluetoothEnabled
isActive: topBar.controlCenterVisible isActive: topBar.shellRoot ? topBar.shellRoot.controlCenterVisible : false
onClicked: { onClicked: {
topBar.controlCenterVisible = !topBar.controlCenterVisible if (topBar.shellRoot) {
if (topBar.controlCenterVisible) { topBar.shellRoot.controlCenterVisible = !topBar.shellRoot.controlCenterVisible
WifiService.scanWifi() if (topBar.shellRoot.controlCenterVisible) {
BluetoothService.scanDevices() WifiService.scanWifi()
BluetoothService.scanDevices()
}
} }
} }
} }

View File

@@ -2,7 +2,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
@@ -298,10 +298,8 @@ ShellRoot {
volumeLevel: root.volumeLevel volumeLevel: root.volumeLevel
bluetoothAvailable: root.bluetoothAvailable bluetoothAvailable: root.bluetoothAvailable
bluetoothEnabled: root.bluetoothEnabled bluetoothEnabled: root.bluetoothEnabled
notificationHistoryVisible: root.notificationHistoryVisible shellRoot: root
notificationCount: notificationHistory.count notificationCount: notificationHistory.count
controlCenterVisible: root.controlCenterVisible
calendarVisible: root.calendarVisible
// Connect tray menu properties // Connect tray menu properties
showTrayMenu: root.showTrayMenu showTrayMenu: root.showTrayMenu
@@ -315,10 +313,7 @@ ShellRoot {
clipboardHistoryPopup.toggle() clipboardHistoryPopup.toggle()
} }
// Property change handlers
onCalendarVisibleChanged: root.calendarVisible = calendarVisible
onControlCenterVisibleChanged: root.controlCenterVisible = controlCenterVisible
onNotificationHistoryVisibleChanged: root.notificationHistoryVisible = notificationHistoryVisible
onShowTrayMenuChanged: root.showTrayMenu = showTrayMenu onShowTrayMenuChanged: root.showTrayMenu = showTrayMenu
onCurrentTrayMenuChanged: root.currentTrayMenu = currentTrayMenu onCurrentTrayMenuChanged: root.currentTrayMenu = currentTrayMenu
onCurrentTrayItemChanged: root.currentTrayItem = currentTrayItem onCurrentTrayItemChanged: root.currentTrayItem = currentTrayItem