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:
@@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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!
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
shell.qml
11
shell.qml
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user