mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
iSome extra widgets and adjustments
This commit is contained in:
@@ -7,18 +7,19 @@ pragma ComponentBehavior: Bound
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
// Battery properties
|
||||||
property bool batteryAvailable: false
|
property bool batteryAvailable: false
|
||||||
property int batteryLevel: 0
|
property int batteryLevel: 0
|
||||||
property string batteryStatus: "Unknown" // "Charging", "Discharging", "Full", "Not charging", "Unknown"
|
property string batteryStatus: "Unknown"
|
||||||
property int timeRemaining: 0 // minutes
|
property int timeRemaining: 0
|
||||||
property bool isCharging: false
|
property bool isCharging: false
|
||||||
property bool isLowBattery: false
|
property bool isLowBattery: false
|
||||||
property int batteryHealth: 100 // percentage
|
property int batteryHealth: 100
|
||||||
property string batteryTechnology: "Unknown"
|
property string batteryTechnology: "Unknown"
|
||||||
property int cycleCount: 0
|
property int cycleCount: 0
|
||||||
property int batteryCapacity: 0 // mAh
|
property int batteryCapacity: 0
|
||||||
property var powerProfiles: []
|
property var powerProfiles: []
|
||||||
property string activePowerProfile: "balanced"
|
property string activePowerProfile: ""
|
||||||
|
|
||||||
// Check if battery is available
|
// Check if battery is available
|
||||||
Process {
|
Process {
|
||||||
@@ -238,12 +239,13 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update battery status every 30 seconds
|
|
||||||
|
// Update timer
|
||||||
Timer {
|
Timer {
|
||||||
interval: 30000
|
interval: 30000
|
||||||
running: root.batteryAvailable
|
running: root.batteryAvailable
|
||||||
repeat: true
|
repeat: true
|
||||||
triggeredOnStart: false
|
triggeredOnStart: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
batteryStatusChecker.running = true
|
batteryStatusChecker.running = true
|
||||||
powerProfilesChecker.running = true
|
powerProfilesChecker.running = true
|
||||||
|
|||||||
@@ -145,7 +145,6 @@ Singleton {
|
|||||||
running: true
|
running: true
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
console.log(`[MprisController] Players: ${Mpris.players.length}, Active: ${activePlayer?.identity || 'none'}, Playing: ${isPlaying}`)
|
|
||||||
if (activePlayer) {
|
if (activePlayer) {
|
||||||
console.log(` Track: ${activePlayer.trackTitle || 'Unknown'} by ${activePlayer.trackArtist || 'Unknown'}`)
|
console.log(` Track: ${activePlayer.trackTitle || 'Unknown'} by ${activePlayer.trackArtist || 'Unknown'}`)
|
||||||
console.log(` State: ${activePlayer.playbackState}`)
|
console.log(` State: ${activePlayer.playbackState}`)
|
||||||
|
|||||||
@@ -79,37 +79,49 @@ Singleton {
|
|||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: weatherFetcher
|
id: weatherFetcher
|
||||||
command: ["bash", "-c", "curl -s 'wttr.in/?format=j1' | jq '{current: .current_condition[0], location: .nearest_area[0], astronomy: .weather[0].astronomy[0]}'"]
|
command: ["bash", "-c", "curl -s 'wttr.in/?format=j1'"]
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (text.trim() && text.trim().startsWith("{")) {
|
const raw = text.trim()
|
||||||
try {
|
if (!raw || raw[0] !== "{") {
|
||||||
let parsedData = JSON.parse(text.trim())
|
|
||||||
if (parsedData.current && parsedData.location) {
|
|
||||||
root.weather = {
|
|
||||||
available: true,
|
|
||||||
temp: parseInt(parsedData.current.temp_C || 0),
|
|
||||||
tempF: parseInt(parsedData.current.temp_F || 0),
|
|
||||||
city: parsedData.location.areaName[0]?.value || "Unknown",
|
|
||||||
wCode: parsedData.current.weatherCode || "113",
|
|
||||||
humidity: parseInt(parsedData.current.humidity || 0),
|
|
||||||
wind: (parsedData.current.windspeedKmph || 0) + " km/h",
|
|
||||||
sunrise: parsedData.astronomy?.sunrise || "06:00",
|
|
||||||
sunset: parsedData.astronomy?.sunset || "18:00",
|
|
||||||
uv: parseInt(parsedData.current.uvIndex || 0),
|
|
||||||
pressure: parseInt(parsedData.current.pressure || 0)
|
|
||||||
}
|
|
||||||
console.log("Weather updated:", root.weather.city, root.weather.temp + "°C")
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Failed to parse weather data:", e.message)
|
|
||||||
root.weather.available = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn("No valid weather data received")
|
console.warn("No valid weather data received")
|
||||||
root.weather.available = false
|
root.weather.available = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(raw)
|
||||||
|
|
||||||
|
const current = data.current_condition?.[0] || {}
|
||||||
|
const location = data.nearest_area?.[0] || {}
|
||||||
|
const astronomy = data.weather?.[0]?.astronomy?.[0] || {}
|
||||||
|
|
||||||
|
if (!Object.keys(current).length || !Object.keys(location).length) {
|
||||||
|
throw new Error("Required fields missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
root.weather = {
|
||||||
|
available: true,
|
||||||
|
temp: Number(current.temp_C) || 0,
|
||||||
|
tempF: Number(current.temp_F) || 0,
|
||||||
|
city: location.areaName?.[0]?.value || "Unknown",
|
||||||
|
wCode: current.weatherCode || "113",
|
||||||
|
humidity: Number(current.humidity) || 0,
|
||||||
|
wind: `${current.windspeedKmph || 0} km/h`,
|
||||||
|
sunrise: astronomy.sunrise || "06:00",
|
||||||
|
sunset: astronomy.sunset || "18:00",
|
||||||
|
uv: Number(current.uvIndex) || 0,
|
||||||
|
pressure: Number(current.pressure) || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Weather updated:", root.weather.city,
|
||||||
|
`${root.weather.temp}°C`)
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Failed to parse weather data:", e.message)
|
||||||
|
root.weather.available = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
@@ -600,69 +601,6 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category dropdown
|
|
||||||
Rectangle {
|
|
||||||
width: 200
|
|
||||||
height: Math.min(250, categories.length * 40 + activeTheme.spacingM * 2)
|
|
||||||
radius: activeTheme.cornerRadiusLarge
|
|
||||||
color: activeTheme.surfaceContainer
|
|
||||||
border.color: Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
visible: showCategories
|
|
||||||
z: 100
|
|
||||||
|
|
||||||
// Drop shadow
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: -2
|
|
||||||
color: "transparent"
|
|
||||||
radius: parent.radius + 2
|
|
||||||
border.color: Qt.rgba(0, 0, 0, 0.1)
|
|
||||||
border.width: 1
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: activeTheme.spacingS
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
model: categories
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: ListView.view.width
|
|
||||||
height: 36
|
|
||||||
radius: activeTheme.cornerRadiusSmall
|
|
||||||
color: catArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.08) : "transparent"
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: activeTheme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: modelData
|
|
||||||
font.pixelSize: activeTheme.fontSizeMedium
|
|
||||||
color: selectedCategory === modelData ? activeTheme.primary : activeTheme.surfaceText
|
|
||||||
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: catArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
selectedCategory = modelData
|
|
||||||
showCategories = false
|
|
||||||
updateFilteredModel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// App grid/list container
|
// App grid/list container
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -715,32 +653,41 @@ PanelWindow {
|
|||||||
height: 56
|
height: 56
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
IconImage {
|
Loader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: Quickshell.iconPath(model.icon, "application-x-executable")
|
sourceComponent: model.icon ? iconComponent : fallbackComponent
|
||||||
smooth: true
|
|
||||||
visible: status === Image.Ready
|
|
||||||
|
|
||||||
onStatusChanged: {
|
Component {
|
||||||
if (status === Image.Error && model.name.includes("Avahi")) {
|
id: iconComponent
|
||||||
console.log("Avahi icon failed to load:", model.icon, "->", source)
|
IconImage {
|
||||||
|
source: model.icon ? Quickshell.iconPath(model.icon) : ""
|
||||||
|
smooth: true
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Error || status === Image.Null) {
|
||||||
|
parent.sourceComponent = fallbackComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for missing icons
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: !parent.children[0].visible
|
|
||||||
color: Qt.rgba(activeTheme.surfaceVariant.r, activeTheme.surfaceVariant.g, activeTheme.surfaceVariant.b, 0.3)
|
|
||||||
radius: activeTheme.cornerRadiusLarge
|
|
||||||
|
|
||||||
Text {
|
Component {
|
||||||
anchors.centerIn: parent
|
id: fallbackComponent
|
||||||
text: model.name ? model.name.charAt(0).toUpperCase() : "A"
|
Rectangle {
|
||||||
font.pixelSize: activeTheme.iconSizeLarge
|
color: Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.1)
|
||||||
color: activeTheme.surfaceVariantText
|
radius: activeTheme.cornerRadiusLarge
|
||||||
font.weight: Font.Medium
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.2)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: model.name ? model.name.charAt(0).toUpperCase() : "A"
|
||||||
|
font.pixelSize: 28
|
||||||
|
color: activeTheme.primary
|
||||||
|
font.weight: Font.Bold
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -801,8 +748,8 @@ PanelWindow {
|
|||||||
// Center the grid content
|
// Center the grid content
|
||||||
property int columnsCount: Math.floor(width / cellWidth)
|
property int columnsCount: Math.floor(width / cellWidth)
|
||||||
property int remainingSpace: width - (columnsCount * cellWidth)
|
property int remainingSpace: width - (columnsCount * cellWidth)
|
||||||
anchors.leftMargin: Math.max(activeTheme.spacingS, remainingSpace / 2)
|
leftMargin: Math.max(activeTheme.spacingS, remainingSpace / 2)
|
||||||
anchors.rightMargin: anchors.leftMargin
|
rightMargin: leftMargin
|
||||||
|
|
||||||
model: filteredModel
|
model: filteredModel
|
||||||
|
|
||||||
@@ -832,32 +779,41 @@ PanelWindow {
|
|||||||
height: iconSize
|
height: iconSize
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
IconImage {
|
Loader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: Quickshell.iconPath(model.icon, "application-x-executable")
|
sourceComponent: model.icon ? gridIconComponent : gridFallbackComponent
|
||||||
smooth: true
|
|
||||||
visible: status === Image.Ready
|
|
||||||
|
|
||||||
onStatusChanged: {
|
Component {
|
||||||
if (status === Image.Error && model.name.includes("Avahi")) {
|
id: gridIconComponent
|
||||||
console.log("Avahi grid icon failed to load:", model.icon, "->", source)
|
IconImage {
|
||||||
|
source: model.icon ? Quickshell.iconPath(model.icon) : ""
|
||||||
|
smooth: true
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Error || status === Image.Null) {
|
||||||
|
parent.sourceComponent = gridFallbackComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for missing icons
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: !parent.children[0].visible
|
|
||||||
color: Qt.rgba(activeTheme.surfaceVariant.r, activeTheme.surfaceVariant.g, activeTheme.surfaceVariant.b, 0.3)
|
|
||||||
radius: activeTheme.cornerRadiusLarge
|
|
||||||
|
|
||||||
Text {
|
Component {
|
||||||
anchors.centerIn: parent
|
id: gridFallbackComponent
|
||||||
text: model.name ? model.name.charAt(0).toUpperCase() : "A"
|
Rectangle {
|
||||||
font.pixelSize: activeTheme.iconSizeLarge
|
color: Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.1)
|
||||||
color: activeTheme.surfaceVariantText
|
radius: activeTheme.cornerRadiusLarge
|
||||||
font.weight: Font.Medium
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.2)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: model.name ? model.name.charAt(0).toUpperCase() : "A"
|
||||||
|
font.pixelSize: parent.parent.parent.iconSize / 2
|
||||||
|
color: activeTheme.primary
|
||||||
|
font.weight: Font.Bold
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -892,6 +848,80 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Category dropdown overlay - now positioned absolutely
|
||||||
|
Rectangle {
|
||||||
|
id: categoryDropdown
|
||||||
|
width: 200
|
||||||
|
height: Math.min(250, categories.length * 40 + activeTheme.spacingM * 2)
|
||||||
|
radius: activeTheme.cornerRadiusLarge
|
||||||
|
color: activeTheme.surfaceContainer
|
||||||
|
border.color: Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
visible: showCategories
|
||||||
|
z: 1000
|
||||||
|
|
||||||
|
// Position it below the category button
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 140 + (searchField.text.length === 0 ? 0 : -40)
|
||||||
|
anchors.left: parent.left
|
||||||
|
|
||||||
|
// Drop shadow
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -4
|
||||||
|
color: "transparent"
|
||||||
|
radius: parent.radius + 4
|
||||||
|
z: -1
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: DropShadow {
|
||||||
|
radius: 8
|
||||||
|
samples: 16
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: activeTheme.spacingS
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
model: categories
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: ListView.view.width
|
||||||
|
height: 36
|
||||||
|
radius: activeTheme.cornerRadiusSmall
|
||||||
|
color: catArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.08) : "transparent"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: activeTheme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: modelData
|
||||||
|
font.pixelSize: activeTheme.fontSizeMedium
|
||||||
|
color: selectedCategory === modelData ? activeTheme.primary : activeTheme.surfaceText
|
||||||
|
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: catArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
selectedCategory = modelData
|
||||||
|
showCategories = false
|
||||||
|
updateFilteredModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ PanelWindow {
|
|||||||
right: true
|
right: true
|
||||||
bottom: true
|
bottom: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Click outside to dismiss overlay
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
root.batteryPopupVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Math.min(380, parent.width - Theme.spacingL * 2)
|
width: Math.min(380, parent.width - Theme.spacingL * 2)
|
||||||
@@ -40,6 +48,14 @@ PanelWindow {
|
|||||||
opacity: root.batteryPopupVisible ? 1.0 : 0.0
|
opacity: root.batteryPopupVisible ? 1.0 : 0.0
|
||||||
scale: root.batteryPopupVisible ? 1.0 : 0.85
|
scale: root.batteryPopupVisible ? 1.0 : 0.85
|
||||||
|
|
||||||
|
// Prevent click-through to background
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
// Consume the click to prevent it from reaching the background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.mediumDuration
|
duration: Theme.mediumDuration
|
||||||
|
|||||||
@@ -8,53 +8,33 @@ Rectangle {
|
|||||||
|
|
||||||
property bool batteryPopupVisible: false
|
property bool batteryPopupVisible: false
|
||||||
|
|
||||||
width: Theme.barHeight - Theme.spacingS
|
width: 48
|
||||||
height: Theme.barHeight - Theme.spacingS
|
height: 32
|
||||||
radius: Theme.cornerRadiusSmall
|
radius: Theme.cornerRadius
|
||||||
color: batteryArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
color: batteryArea.containsMouse || batteryPopupVisible ?
|
||||||
|
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||||
|
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||||
visible: BatteryService.batteryAvailable
|
visible: BatteryService.batteryAvailable
|
||||||
|
|
||||||
Row {
|
// Battery icon - Material Design icons already show level visually
|
||||||
anchors.centerIn: parent
|
Text {
|
||||||
spacing: Theme.spacingXS
|
text: BatteryService.getBatteryIcon()
|
||||||
|
font.family: Theme.iconFont
|
||||||
// Battery icon
|
font.pixelSize: Theme.iconSize
|
||||||
Text {
|
color: {
|
||||||
text: BatteryService.getBatteryIcon()
|
if (!BatteryService.batteryAvailable) return Theme.surfaceText
|
||||||
font.family: Theme.iconFont
|
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error
|
||||||
font.pixelSize: Theme.iconSize
|
if (BatteryService.isCharging) return Theme.primary
|
||||||
color: {
|
return Theme.surfaceText
|
||||||
if (!BatteryService.batteryAvailable) return Theme.surfaceText
|
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error
|
|
||||||
if (BatteryService.isCharging) return Theme.primary
|
|
||||||
return Theme.surfaceText
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
// Subtle animation for charging
|
|
||||||
RotationAnimation on rotation {
|
|
||||||
running: BatteryService.isCharging
|
|
||||||
loops: Animation.Infinite
|
|
||||||
from: 0
|
|
||||||
to: 360
|
|
||||||
duration: 8000
|
|
||||||
easing.type: Easing.Linear
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
// Battery percentage
|
// Subtle pulse animation for charging
|
||||||
Text {
|
SequentialAnimation on opacity {
|
||||||
text: BatteryService.batteryLevel + "%"
|
running: BatteryService.isCharging
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
loops: Animation.Infinite
|
||||||
color: {
|
NumberAnimation { to: 0.6; duration: 1000; easing.type: Easing.InOutQuad }
|
||||||
if (!BatteryService.batteryAvailable) return Theme.surfaceText
|
NumberAnimation { to: 1.0; duration: 1000; easing.type: Easing.InOutQuad }
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) return Theme.error
|
|
||||||
if (BatteryService.isCharging) return Theme.primary
|
|
||||||
return Theme.surfaceText
|
|
||||||
}
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: BatteryService.batteryAvailable
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
74
Widgets/PowerButton.qml
Normal file
74
Widgets/PowerButton.qml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import "../Common"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: powerButton
|
||||||
|
|
||||||
|
width: 48
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: powerArea.containsMouse || root.powerMenuVisible ?
|
||||||
|
Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.16) :
|
||||||
|
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||||
|
|
||||||
|
// Power icon
|
||||||
|
Text {
|
||||||
|
text: "power_settings_new"
|
||||||
|
font.family: Theme.iconFont
|
||||||
|
font.pixelSize: Theme.iconSize
|
||||||
|
color: powerArea.containsMouse || root.powerMenuVisible ? Theme.error : Theme.surfaceText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: powerArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
root.powerMenuVisible = !root.powerMenuVisible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tooltip on hover
|
||||||
|
Rectangle {
|
||||||
|
id: powerTooltip
|
||||||
|
width: tooltipText.contentWidth + Theme.spacingM * 2
|
||||||
|
height: tooltipText.contentHeight + Theme.spacingS * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
visible: powerArea.containsMouse && !root.powerMenuVisible
|
||||||
|
|
||||||
|
anchors.bottom: parent.top
|
||||||
|
anchors.bottomMargin: Theme.spacingS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
opacity: powerArea.containsMouse ? 1.0 : 0.0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: tooltipText
|
||||||
|
text: "Power Menu"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
203
Widgets/PowerConfirmDialog.qml
Normal file
203
Widgets/PowerConfirmDialog.qml
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Io
|
||||||
|
import "../Common"
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: powerConfirmDialog
|
||||||
|
|
||||||
|
visible: root.powerConfirmVisible
|
||||||
|
|
||||||
|
implicitWidth: 400
|
||||||
|
implicitHeight: 300
|
||||||
|
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Darkened background
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "black"
|
||||||
|
opacity: 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.min(400, parent.width - Theme.spacingL * 2)
|
||||||
|
height: Math.min(200, parent.height - Theme.spacingL * 2)
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
opacity: root.powerConfirmVisible ? 1.0 : 0.0
|
||||||
|
scale: root.powerConfirmVisible ? 1.0 : 0.9
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
// Title
|
||||||
|
Text {
|
||||||
|
text: root.powerConfirmTitle
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: {
|
||||||
|
switch(root.powerConfirmAction) {
|
||||||
|
case "poweroff": return Theme.error
|
||||||
|
case "reboot": return Theme.warning
|
||||||
|
default: return Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
font.weight: Font.Medium
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message
|
||||||
|
Text {
|
||||||
|
text: root.powerConfirmMessage
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { height: Theme.spacingL }
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
// Cancel button
|
||||||
|
Rectangle {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Cancel"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelButton
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.powerConfirmVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm button
|
||||||
|
Rectangle {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
let baseColor
|
||||||
|
switch(root.powerConfirmAction) {
|
||||||
|
case "poweroff": baseColor = Theme.error; break
|
||||||
|
case "reboot": baseColor = Theme.warning; break
|
||||||
|
default: baseColor = Theme.primary; break
|
||||||
|
}
|
||||||
|
return confirmButton.containsMouse ?
|
||||||
|
Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) :
|
||||||
|
baseColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Confirm"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.onPrimary
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: confirmButton
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.powerConfirmVisible = false
|
||||||
|
executePowerAction(root.powerConfirmAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function executePowerAction(action) {
|
||||||
|
console.log("Executing power action:", action)
|
||||||
|
|
||||||
|
let command = []
|
||||||
|
switch(action) {
|
||||||
|
case "logout":
|
||||||
|
// Try multiple logout commands for different environments
|
||||||
|
command = ["bash", "-c", "loginctl terminate-user $USER || pkill -KILL -u $USER || gnome-session-quit --force || xfce4-session-logout --logout || i3-msg exit || swaymsg exit || niri msg quit"]
|
||||||
|
break
|
||||||
|
case "suspend":
|
||||||
|
command = ["systemctl", "suspend"]
|
||||||
|
break
|
||||||
|
case "reboot":
|
||||||
|
command = ["systemctl", "reboot"]
|
||||||
|
break
|
||||||
|
case "poweroff":
|
||||||
|
command = ["systemctl", "poweroff"]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.length > 0) {
|
||||||
|
powerActionProcess.command = command
|
||||||
|
powerActionProcess.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: powerActionProcess
|
||||||
|
running: false
|
||||||
|
|
||||||
|
onExited: (exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
console.error("Power action failed with exit code:", exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
305
Widgets/PowerMenuPopup.qml
Normal file
305
Widgets/PowerMenuPopup.qml
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Io
|
||||||
|
import "../Common"
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: powerMenuPopup
|
||||||
|
|
||||||
|
visible: root.powerMenuVisible
|
||||||
|
|
||||||
|
implicitWidth: 400
|
||||||
|
implicitHeight: 320
|
||||||
|
|
||||||
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click outside to dismiss overlay
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
root.powerMenuVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.min(320, parent.width - Theme.spacingL * 2)
|
||||||
|
height: 320 // Fixed height to prevent cropping
|
||||||
|
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
|
||||||
|
y: Theme.barHeight + Theme.spacingS
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
radius: Theme.cornerRadiusLarge
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
opacity: root.powerMenuVisible ? 1.0 : 0.0
|
||||||
|
scale: root.powerMenuVisible ? 1.0 : 0.85
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent click-through to background
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
// Consume the click to prevent it from reaching the background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
// Header
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Power Options"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { width: parent.width - 150; height: 1 }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: 16
|
||||||
|
color: closePowerArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "close"
|
||||||
|
font.family: Theme.iconFont
|
||||||
|
font.pixelSize: Theme.iconSize - 4
|
||||||
|
color: closePowerArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: closePowerArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.powerMenuVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Power options
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
// Log Out
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: logoutArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "logout"
|
||||||
|
font.family: Theme.iconFont
|
||||||
|
font.pixelSize: Theme.iconSize
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Log Out"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: logoutArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.powerMenuVisible = false
|
||||||
|
root.powerConfirmAction = "logout"
|
||||||
|
root.powerConfirmTitle = "Log Out"
|
||||||
|
root.powerConfirmMessage = "Are you sure you want to log out?"
|
||||||
|
root.powerConfirmVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suspend
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: suspendArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "bedtime"
|
||||||
|
font.family: Theme.iconFont
|
||||||
|
font.pixelSize: Theme.iconSize
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Suspend"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: suspendArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.powerMenuVisible = false
|
||||||
|
root.powerConfirmAction = "suspend"
|
||||||
|
root.powerConfirmTitle = "Suspend"
|
||||||
|
root.powerConfirmMessage = "Are you sure you want to suspend the system?"
|
||||||
|
root.powerConfirmVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reboot
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: rebootArea.containsMouse ? Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "restart_alt"
|
||||||
|
font.family: Theme.iconFont
|
||||||
|
font.pixelSize: Theme.iconSize
|
||||||
|
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Reboot"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: rebootArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.powerMenuVisible = false
|
||||||
|
root.powerConfirmAction = "reboot"
|
||||||
|
root.powerConfirmTitle = "Reboot"
|
||||||
|
root.powerConfirmMessage = "Are you sure you want to reboot the system?"
|
||||||
|
root.powerConfirmVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Power Off
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: powerOffArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "power_settings_new"
|
||||||
|
font.family: Theme.iconFont
|
||||||
|
font.pixelSize: Theme.iconSize
|
||||||
|
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Power Off"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: powerOffArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.powerMenuVisible = false
|
||||||
|
root.powerConfirmAction = "poweroff"
|
||||||
|
root.powerConfirmTitle = "Power Off"
|
||||||
|
root.powerConfirmMessage = "Are you sure you want to power off the system?"
|
||||||
|
root.powerConfirmVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,51 +2,37 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.SystemTray
|
import Quickshell.Services.SystemTray
|
||||||
|
import Quickshell.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
property var theme
|
property var theme
|
||||||
property var root
|
|
||||||
|
|
||||||
width: Math.max(40, systemTrayRow.implicitWidth + theme.spacingS * 2)
|
|
||||||
height: 32
|
height: 32
|
||||||
radius: theme.cornerRadius
|
implicitWidth: trayRow.implicitWidth
|
||||||
color: Qt.rgba(theme.secondary.r, theme.secondary.g, theme.secondary.b, 0.08)
|
visible: trayRow.children.length > 0
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: systemTrayRow.children.length > 0
|
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: systemTrayRow
|
id: trayRow
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: theme.spacingXS
|
spacing: theme.spacingXS
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: SystemTray.items
|
model: SystemTray.items
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
|
required property SystemTrayItem modelData
|
||||||
|
|
||||||
width: 24
|
width: 24
|
||||||
height: 24
|
height: 24
|
||||||
radius: theme.cornerRadiusSmall
|
radius: theme.cornerRadiusSmall
|
||||||
color: trayItemArea.containsMouse ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.12) : "transparent"
|
color: trayItemArea.containsMouse ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
property var trayItem: modelData
|
IconImage {
|
||||||
|
|
||||||
Image {
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 18
|
width: 18
|
||||||
height: 18
|
height: 18
|
||||||
source: {
|
source: parent.modelData.icon
|
||||||
let icon = trayItem?.icon || "";
|
|
||||||
if (!icon) return "";
|
|
||||||
|
|
||||||
if (icon.includes("?path=")) {
|
|
||||||
const [name, path] = icon.split("?path=");
|
|
||||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
|
||||||
return `file://${path}/${fileName}`;
|
|
||||||
}
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
asynchronous: true
|
|
||||||
smooth: true
|
smooth: true
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -57,45 +43,23 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
if (!trayItem) return;
|
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
if (mouse.button === Qt.LeftButton) {
|
||||||
if (!trayItem.onlyMenu) {
|
parent.modelData.activate()
|
||||||
trayItem.activate()
|
|
||||||
}
|
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
if (trayItem.hasMenu) {
|
menuHandler.showMenu()
|
||||||
console.log("Right-click detected, showing menu for:", trayItem.title || "Unknown")
|
|
||||||
customTrayMenu.showMenu(mouse.x, mouse.y)
|
|
||||||
} else {
|
|
||||||
console.log("No menu available for:", trayItem.title || "Unknown")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simple menu handling for now
|
||||||
QtObject {
|
QtObject {
|
||||||
id: customTrayMenu
|
id: menuHandler
|
||||||
|
|
||||||
property bool menuVisible: false
|
function showMenu() {
|
||||||
|
if (parent.modelData.hasMenu) {
|
||||||
function showMenu(x, y) {
|
console.log("Right-click menu for:", parent.modelData.title || "Unknown")
|
||||||
root.currentTrayMenu = customTrayMenu
|
// TODO: Implement proper menu positioning
|
||||||
root.currentTrayItem = trayItem
|
}
|
||||||
|
|
||||||
root.trayMenuX = parent.parent.parent.parent.x + parent.parent.parent.parent.width - 180 - theme.spacingL
|
|
||||||
root.trayMenuY = theme.barHeight + theme.spacingS
|
|
||||||
|
|
||||||
console.log("Showing menu at:", root.trayMenuX, root.trayMenuY)
|
|
||||||
menuVisible = true
|
|
||||||
root.showTrayMenu = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideMenu() {
|
|
||||||
menuVisible = false
|
|
||||||
root.showTrayMenu = false
|
|
||||||
root.currentTrayMenu = null
|
|
||||||
root.currentTrayItem = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -804,7 +804,11 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Power Button
|
||||||
|
PowerButton {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,7 @@ ClipboardHistory 1.0 ClipboardHistory.qml
|
|||||||
CustomSlider 1.0 CustomSlider.qml
|
CustomSlider 1.0 CustomSlider.qml
|
||||||
InputDialog 1.0 InputDialog.qml
|
InputDialog 1.0 InputDialog.qml
|
||||||
BatteryWidget 1.0 BatteryWidget.qml
|
BatteryWidget 1.0 BatteryWidget.qml
|
||||||
BatteryControlPopup 1.0 BatteryControlPopup.qml
|
BatteryControlPopup 1.0 BatteryControlPopup.qml
|
||||||
|
PowerButton 1.0 PowerButton.qml
|
||||||
|
PowerMenuPopup 1.0 PowerMenuPopup.qml
|
||||||
|
PowerConfirmDialog 1.0 PowerConfirmDialog.qml
|
||||||
@@ -34,6 +34,11 @@ ShellRoot {
|
|||||||
property bool hasActiveMedia: activePlayer && (activePlayer.trackTitle || activePlayer.trackArtist)
|
property bool hasActiveMedia: activePlayer && (activePlayer.trackTitle || activePlayer.trackArtist)
|
||||||
property bool controlCenterVisible: false
|
property bool controlCenterVisible: false
|
||||||
property bool batteryPopupVisible: false
|
property bool batteryPopupVisible: false
|
||||||
|
property bool powerMenuVisible: false
|
||||||
|
property bool powerConfirmVisible: false
|
||||||
|
property string powerConfirmAction: ""
|
||||||
|
property string powerConfirmTitle: ""
|
||||||
|
property string powerConfirmMessage: ""
|
||||||
|
|
||||||
// Network properties from NetworkService
|
// Network properties from NetworkService
|
||||||
property string networkStatus: NetworkService.networkStatus
|
property string networkStatus: NetworkService.networkStatus
|
||||||
@@ -259,6 +264,8 @@ ShellRoot {
|
|||||||
id: globalInputDialog
|
id: globalInputDialog
|
||||||
}
|
}
|
||||||
BatteryControlPopup {}
|
BatteryControlPopup {}
|
||||||
|
PowerMenuPopup {}
|
||||||
|
PowerConfirmDialog {}
|
||||||
|
|
||||||
// Application and clipboard components
|
// Application and clipboard components
|
||||||
AppLauncher {
|
AppLauncher {
|
||||||
|
|||||||
Reference in New Issue
Block a user