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

meta: large-scale refactor progress

This commit is contained in:
bbedward
2025-07-17 15:21:37 -04:00
parent 77cc9c288b
commit 7a40156893
24 changed files with 663 additions and 590 deletions

View File

@@ -2,7 +2,8 @@ pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import Qt.labs.platform // ← gives us StandardPaths
import Qt.labs.platform
import qs.Services
Singleton {
id: root
@@ -53,7 +54,8 @@ Singleton {
if (!matugenAvailable) {
console.warn("Matugen missing → dynamic theme disabled")
Theme.rootObj.wallpaperErrorStatus = "matugen_missing"
ToastService.wallpaperErrorStatus = "matugen_missing"
ToastService.showWarning("matugen not found - dynamic theming disabled")
return
}
@@ -74,7 +76,8 @@ Singleton {
} else {
console.error("code", code)
console.error("Wallpaper not found:", wallpaperPath)
Theme.rootObj.showWallpaperError()
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Wallpaper processing failed")
}
}
}
@@ -91,17 +94,20 @@ Singleton {
const out = matugenCollector.text
if (!out.length) {
console.error("matugen produced zero bytes\nstderr:", matugenProcess.stderr)
Theme.rootObj.showWallpaperError()
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Wallpaper processing failed")
return
}
try {
root.matugenJson = out
root.matugenColors = JSON.parse(out)
root.colorsUpdated()
Theme.rootObj.wallpaperErrorStatus = ""
ToastService.clearWallpaperError()
ToastService.showInfo("Dynamic theme colors updated")
} catch (e) {
console.error("JSON parse failed:", e)
Theme.rootObj.showWallpaperError()
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Wallpaper processing failed")
}
}
}

View File

@@ -7,9 +7,6 @@ import Quickshell.Io
Singleton {
id: root
// Reference to the main shell root for calling functions
property var rootObj: null
// Initialize theme system
Component.onCompleted: {
console.log("Theme Component.onCompleted")
@@ -609,4 +606,19 @@ Singleton {
default: return "Custom power profile"
}
}
// Wallpaper IPC handler
IpcHandler {
target: "wallpaper"
function refresh() {
console.log("Wallpaper IPC: refresh() called")
// Trigger color extraction if using dynamic theme
if (typeof Theme !== "undefined" && Theme.isDynamicTheme) {
console.log("Triggering color extraction due to wallpaper IPC")
Colors.extractColors()
}
return "WALLPAPER_REFRESH_SUCCESS"
}
}
}

83
Services/ToastService.qml Normal file
View File

@@ -0,0 +1,83 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
readonly property int levelInfo: 0
readonly property int levelWarn: 1
readonly property int levelError: 2
property string currentMessage: ""
property int currentLevel: levelInfo
property bool toastVisible: false
property var toastQueue: []
property string wallpaperErrorStatus: ""
Timer {
id: toastTimer
interval: 5000
running: false
repeat: false
onTriggered: hideToast()
}
Timer {
id: queueTimer
interval: 500
running: false
repeat: false
onTriggered: processQueue()
}
function showToast(message, level = levelInfo) {
toastQueue.push({ message, level })
if (!toastVisible) {
processQueue()
}
}
function showInfo(message) {
showToast(message, levelInfo)
}
function showWarning(message) {
showToast(message, levelWarn)
}
function showError(message) {
showToast(message, levelError)
}
function hideToast() {
toastVisible = false
currentMessage = ""
currentLevel = levelInfo
toastTimer.stop()
if (toastQueue.length > 0) {
queueTimer.start()
}
}
function processQueue() {
if (toastQueue.length === 0) return
const toast = toastQueue.shift()
currentMessage = toast.message
currentLevel = toast.level
toastVisible = true
toastTimer.interval =
toast.level === levelError ? 8000 :
toast.level === levelWarn ? 6000 : 5000
toastTimer.start()
}
function clearWallpaperError() {
wallpaperErrorStatus = ""
}
}

View File

@@ -263,6 +263,21 @@ Singleton {
}
}
// Auto-refresh timer for when control center is open
property bool autoRefreshEnabled: false
Timer {
id: autoRefreshTimer
interval: 20000
running: root.autoRefreshEnabled
repeat: true
onTriggered: {
if (root.autoRefreshEnabled) {
root.scanWifi()
}
}
}
function updateCurrentWifiInfo() {
console.log("Updating current WiFi info...")
currentWifiInfo.running = true

View File

@@ -8,9 +8,11 @@ import qs.Services
import Quickshell.Services.UPower
PanelWindow {
id: batteryControlPopup
id: root
visible: root.batteryPopupVisible
property bool batteryPopupVisible: false
visible: batteryPopupVisible
implicitWidth: 400
implicitHeight: 300
@@ -32,7 +34,7 @@ PanelWindow {
MouseArea {
anchors.fill: parent
onClicked: {
root.batteryPopupVisible = false
batteryPopupVisible = false
}
}
@@ -46,8 +48,8 @@ PanelWindow {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
opacity: root.batteryPopupVisible ? 1.0 : 0.0
scale: root.batteryPopupVisible ? 1.0 : 0.85
opacity: batteryPopupVisible ? 1.0 : 0.0
scale: batteryPopupVisible ? 1.0 : 0.85
// Prevent click-through to background
MouseArea {
@@ -114,7 +116,7 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.batteryPopupVisible = false
batteryPopupVisible = false
}
}
}

View File

@@ -7,11 +7,10 @@ import qs.Services
Column {
id: calendarWidget
property var theme: Theme
property date displayDate: new Date()
property date selectedDate: new Date()
spacing: theme.spacingM
spacing: Theme.spacingM
// Load events when display date changes
onDisplayDateChanged: {
@@ -61,16 +60,16 @@ Column {
Rectangle {
width: 40
height: 40
radius: theme.cornerRadius
color: prevMonthArea.containsMouse ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.12) : "transparent"
radius: Theme.cornerRadius
color: prevMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "chevron_left"
font.family: theme.iconFont
font.pixelSize: theme.iconSize
color: theme.primary
font.weight: theme.iconFontWeight
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: Theme.primary
font.weight: Theme.iconFontWeight
}
MouseArea {
@@ -91,8 +90,8 @@ Column {
width: parent.width - 80
height: 40
text: Qt.formatDate(displayDate, "MMMM yyyy")
font.pixelSize: theme.fontSizeLarge
color: theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
@@ -101,16 +100,16 @@ Column {
Rectangle {
width: 40
height: 40
radius: theme.cornerRadius
color: nextMonthArea.containsMouse ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.12) : "transparent"
radius: Theme.cornerRadius
color: nextMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "chevron_right"
font.family: theme.iconFont
font.pixelSize: theme.iconSize
color: theme.primary
font.weight: theme.iconFontWeight
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: Theme.primary
font.weight: Theme.iconFontWeight
}
MouseArea {
@@ -144,8 +143,8 @@ Column {
Text {
anchors.centerIn: parent
text: modelData
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.6)
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
font.weight: Font.Medium
}
}
@@ -183,20 +182,20 @@ Column {
property bool isToday: dayDate.toDateString() === new Date().toDateString()
property bool isSelected: dayDate.toDateString() === selectedDate.toDateString()
color: isSelected ? theme.primary :
isToday ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.12) :
dayArea.containsMouse ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.08) : "transparent"
color: isSelected ? Theme.primary :
isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: theme.cornerRadiusSmall
radius: Theme.cornerRadiusSmall
Text {
anchors.centerIn: parent
text: dayDate.getDate()
font.pixelSize: theme.fontSizeMedium
color: isSelected ? theme.surface :
isToday ? theme.primary :
isCurrentMonth ? theme.surfaceText :
Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.4)
font.pixelSize: Theme.fontSizeMedium
color: isSelected ? Theme.surface :
isToday ? Theme.primary :
isCurrentMonth ? Theme.surfaceText :
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
font.weight: isToday || isSelected ? Font.Medium : Font.Normal
}
@@ -215,11 +214,11 @@ Column {
color: {
if (isSelected) {
// Use a lighter tint of primary for selected state
return Qt.lighter(theme.primary, 1.3)
return Qt.lighter(Theme.primary, 1.3)
} else if (isToday) {
return theme.primary
return Theme.primary
} else {
return theme.primary
return Theme.primary
}
}
@@ -238,22 +237,22 @@ Column {
Behavior on scale {
NumberAnimation {
duration: theme.shortDuration
easing.type: theme.standardEasing
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on color {
ColorAnimation {
duration: theme.shortDuration
easing.type: theme.standardEasing
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on opacity {
NumberAnimation {
duration: theme.shortDuration
easing.type: theme.standardEasing
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -9,12 +9,12 @@ import qs.Common
import qs.Services
PanelWindow {
id: centerCommandCenter
id: root
property var theme: Theme
readonly property bool hasActiveMedia: MprisController.activePlayer !== null
property bool calendarVisible: false
visible: root.calendarVisible
visible: calendarVisible
implicitWidth: 480
implicitHeight: 600
@@ -48,15 +48,15 @@ PanelWindow {
}
function calculateHeight() {
let contentHeight = theme.spacingM * 2 // margins
let contentHeight = Theme.spacingM * 2 // margins
// Main row with widgets and calendar
let widgetHeight = 160 // Media widget always present
widgetHeight += 140 + theme.spacingM // Weather widget always present
widgetHeight += 140 + Theme.spacingM // Weather widget always present
let calendarHeight = 300
let mainRowHeight = Math.max(widgetHeight, calendarHeight)
contentHeight += mainRowHeight + theme.spacingM // Add spacing between main row and events
contentHeight += mainRowHeight + Theme.spacingM
// Add events widget height - use calculated height instead of actual
if (CalendarService && CalendarService.khalAvailable) {
@@ -68,9 +68,9 @@ PanelWindow {
return Math.min(contentHeight, parent.height * 0.9)
}
color: theme.surfaceContainer
radius: theme.cornerRadiusLarge
border.color: Qt.rgba(theme.outline.r, theme.outline.g, theme.outline.b, 0.08)
color: Theme.surfaceContainer
radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
layer.enabled: true
@@ -85,27 +85,27 @@ PanelWindow {
Rectangle {
anchors.fill: parent
color: Qt.rgba(theme.surfaceTint.r, theme.surfaceTint.g, theme.surfaceTint.b, 0.04)
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
radius: parent.radius
SequentialAnimation on opacity {
running: root.calendarVisible
running: calendarVisible
loops: Animation.Infinite
NumberAnimation {
to: 0.08
duration: theme.extraLongDuration
easing.type: theme.standardEasing
duration: Theme.extraLongDuration
easing.type: Theme.standardEasing
}
NumberAnimation {
to: 0.02
duration: theme.extraLongDuration
easing.type: theme.standardEasing
duration: Theme.extraLongDuration
easing.type: Theme.standardEasing
}
}
}
opacity: root.calendarVisible ? 1.0 : 0.0
scale: root.calendarVisible ? 1.0 : 0.92
opacity: calendarVisible ? 1.0 : 0.0
scale: calendarVisible ? 1.0 : 0.92
// Update height when calendar service events change
Connections {
@@ -130,47 +130,47 @@ PanelWindow {
Behavior on opacity {
NumberAnimation {
duration: theme.longDuration
easing.type: theme.emphasizedEasing
duration: Theme.longDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: theme.longDuration
easing.type: theme.emphasizedEasing
duration: Theme.longDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on height {
NumberAnimation {
duration: theme.mediumDuration
easing.type: theme.standardEasing
duration: Theme.mediumDuration
easing.type: Theme.standardEasing
}
}
Column {
anchors.fill: parent
anchors.margins: theme.spacingM
spacing: theme.spacingM
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
// Main row with widgets and calendar
Row {
width: parent.width
height: {
let widgetHeight = 160 // Media widget always present
widgetHeight += 140 + theme.spacingM // Weather widget always present
widgetHeight += 140 + Theme.spacingM // Weather widget always present
let calendarHeight = 300
return Math.max(widgetHeight, calendarHeight)
}
spacing: theme.spacingM
spacing: Theme.spacingM
// Left section for widgets
Column {
id: leftWidgets
width: hasAnyWidgets ? parent.width * 0.45 : 0
height: childrenRect.height
spacing: theme.spacingM
spacing: Theme.spacingM
visible: hasAnyWidgets
anchors.top: parent.top
@@ -180,23 +180,20 @@ PanelWindow {
visible: true // Always visible - shows placeholder when no media
width: parent.width
height: 160
theme: centerCommandCenter.theme
}
WeatherWidget {
visible: true // Always visible - shows placeholder when no weather
width: parent.width
height: 140
theme: centerCommandCenter.theme
}
}
// Right section for calendar
CalendarWidget {
id: calendarWidget
width: leftWidgets.hasAnyWidgets ? parent.width * 0.55 - theme.spacingL : parent.width
width: leftWidgets.hasAnyWidgets ? parent.width * 0.55 - Theme.spacingL : parent.width
height: parent.height
theme: centerCommandCenter.theme
}
}
@@ -204,7 +201,6 @@ PanelWindow {
EventsWidget {
id: eventsWidget
width: parent.width
theme: centerCommandCenter.theme
selectedDate: calendarWidget.selectedDate
}
@@ -215,7 +211,7 @@ PanelWindow {
anchors.fill: parent
z: -1
onClicked: {
root.calendarVisible = false
calendarVisible = false
}
}
}

View File

@@ -8,7 +8,6 @@ import qs.Services
Rectangle {
id: eventsWidget
property var theme: Theme
property date selectedDate: new Date()
property var selectedDateEvents: []
property bool hasEvents: selectedDateEvents && selectedDateEvents.length > 0
@@ -21,9 +20,9 @@ Rectangle {
width: parent.width
height: shouldShow ? (hasEvents ? Math.min(300, 80 + selectedDateEvents.length * 60) : 120) : 0
radius: theme.cornerRadiusLarge
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.12)
border.color: Qt.rgba(theme.outline.r, theme.outline.g, theme.outline.b, 0.08)
radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
visible: shouldShow
@@ -40,8 +39,8 @@ Rectangle {
Behavior on height {
NumberAnimation {
duration: theme.mediumDuration
easing.type: theme.emphasizedEasing
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
@@ -81,14 +80,14 @@ Rectangle {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: theme.spacingL
spacing: theme.spacingS
anchors.margins: Theme.spacingL
spacing: Theme.spacingS
Text {
text: "event"
font.family: theme.iconFont
font.pixelSize: theme.iconSize - 2
color: theme.primary
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 2
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
@@ -97,8 +96,8 @@ Rectangle {
(Qt.formatDate(selectedDate, "MMM d") + " • " +
(selectedDateEvents.length === 1 ? "1 event" : selectedDateEvents.length + " events")) :
Qt.formatDate(selectedDate, "MMM d")
font.pixelSize: theme.fontSizeMedium
color: theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
@@ -107,21 +106,21 @@ Rectangle {
// No events placeholder - centered in entire widget (not just content area)
Column {
anchors.centerIn: parent
spacing: theme.spacingXS
spacing: Theme.spacingXS
visible: !hasEvents
Text {
text: "event_busy"
font.family: theme.iconFont
font.pixelSize: theme.iconSize + 8
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.3)
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize + 8
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: "No events"
font.pixelSize: theme.fontSizeMedium
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.5)
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
font.weight: Font.Normal
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -134,12 +133,12 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: theme.spacingL
anchors.topMargin: theme.spacingM
anchors.margins: Theme.spacingL
anchors.topMargin: Theme.spacingM
visible: opacity > 0
opacity: hasEvents ? 1.0 : 0.0
clip: true
spacing: theme.spacingS
spacing: Theme.spacingS
boundsMovement: Flickable.StopAtBounds
boundsBehavior: Flickable.StopAtBounds
@@ -149,28 +148,28 @@ Rectangle {
Behavior on opacity {
NumberAnimation {
duration: theme.mediumDuration
easing.type: theme.emphasizedEasing
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
delegate: Rectangle {
width: eventsList.width
height: eventContent.implicitHeight + theme.spacingM
radius: theme.cornerRadius
height: eventContent.implicitHeight + Theme.spacingM
radius: Theme.cornerRadius
color: {
if (modelData.url && eventMouseArea.containsMouse) {
return Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.12)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
} else if (eventMouseArea.containsMouse) {
return Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.06)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06)
}
return Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.06)
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.06)
}
border.color: {
if (modelData.url && eventMouseArea.containsMouse) {
return Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.3)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
} else if (eventMouseArea.containsMouse) {
return Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.15)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15)
}
return "transparent"
}
@@ -184,7 +183,7 @@ Rectangle {
anchors.leftMargin: 4
anchors.verticalCenter: parent.verticalCenter
radius: 2
color: theme.primary
color: Theme.primary
opacity: 0.8
}
@@ -193,15 +192,15 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: theme.spacingL + 4
anchors.rightMargin: theme.spacingM
anchors.leftMargin: Theme.spacingL + 4
anchors.rightMargin: Theme.spacingM
spacing: 6
Text {
width: parent.width
text: modelData.title
font.pixelSize: theme.fontSizeMedium
color: theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
wrapMode: Text.Wrap
@@ -220,9 +219,9 @@ Rectangle {
Text {
text: "schedule"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
font.family: Theme.iconFont
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.verticalCenter: parent.verticalCenter
}
@@ -240,8 +239,8 @@ Rectangle {
return startTime
}
}
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
@@ -256,16 +255,16 @@ Rectangle {
Text {
text: "location_on"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
font.family: Theme.iconFont
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: modelData.location
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
maximumLineCount: 1
@@ -293,15 +292,15 @@ Rectangle {
Behavior on color {
ColorAnimation {
duration: theme.shortDuration
easing.type: theme.standardEasing
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on border.color {
ColorAnimation {
duration: theme.shortDuration
easing.type: theme.standardEasing
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -10,7 +10,6 @@ Rectangle {
id: mediaPlayerWidget
property MprisPlayer activePlayer: MprisController.activePlayer
property var theme: Theme
property string lastValidTitle: ""
property string lastValidArtist: ""
@@ -40,9 +39,9 @@ Rectangle {
width: parent.width
height: parent.height
radius: theme.cornerRadiusLarge
color: Qt.rgba(theme.surfaceContainer.r, theme.surfaceContainer.g, theme.surfaceContainer.b, 0.4)
border.color: Qt.rgba(theme.outline.r, theme.outline.g, theme.outline.b, 0.08)
radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
layer.enabled: true
@@ -96,26 +95,26 @@ Rectangle {
Item {
anchors.fill: parent
anchors.margins: theme.spacingS
anchors.margins: Theme.spacingS
// Placeholder when no media - centered in entire widget
Column {
anchors.centerIn: parent
spacing: theme.spacingS
spacing: Theme.spacingS
visible: (!activePlayer && !lastValidTitle) || (activePlayer && activePlayer.trackTitle === "" && lastValidTitle === "")
Text {
text: "music_note"
font.family: theme.iconFont
font.pixelSize: theme.iconSize + 8
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.5)
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize + 8
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: "No Media Playing"
font.pixelSize: theme.fontSizeMedium
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter
}
}
@@ -123,21 +122,21 @@ Rectangle {
// Active content in a column
Column {
anchors.fill: parent
spacing: theme.spacingS
spacing: Theme.spacingS
visible: activePlayer && activePlayer.trackTitle !== "" || lastValidTitle !== ""
// Normal media info when playing
Row {
width: parent.width
height: 60
spacing: theme.spacingM
spacing: Theme.spacingM
// Album Art
Rectangle {
width: 60
height: 60
radius: theme.cornerRadius
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.3)
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Item {
anchors.fill: parent
@@ -164,9 +163,9 @@ Rectangle {
Text {
anchors.centerIn: parent
text: "album"
font.family: theme.iconFont
font.family: Theme.iconFont
font.pixelSize: 28
color: theme.surfaceVariantText
color: Theme.surfaceVariantText
}
}
}
@@ -174,9 +173,9 @@ Rectangle {
// Track Info
Column {
width: parent.width - 60 - theme.spacingM
width: parent.width - 60 - Theme.spacingM
height: parent.height
spacing: theme.spacingXS
spacing: Theme.spacingXS
Text {
text: activePlayer?.trackTitle || lastValidTitle || "Unknown Track"
@@ -185,9 +184,9 @@ Rectangle {
lastValidTitle = activePlayer.trackTitle;
}
}
font.pixelSize: theme.fontSizeMedium
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: theme.surfaceText
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
}
@@ -199,8 +198,8 @@ Rectangle {
lastValidArtist = activePlayer.trackArtist;
}
}
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.8)
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
width: parent.width
elide: Text.ElideRight
}
@@ -212,8 +211,8 @@ Rectangle {
lastValidAlbum = activePlayer.trackAlbum;
}
}
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.6)
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
width: parent.width
elide: Text.ElideRight
visible: text.length > 0
@@ -232,7 +231,7 @@ Rectangle {
width: parent.width
height: 6
radius: 3
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.3)
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
visible: activePlayer !== null
anchors.verticalCenter: parent.verticalCenter
@@ -240,7 +239,7 @@ Rectangle {
id: progressFill
height: parent.height
radius: parent.radius
color: theme.primary
color: Theme.primary
width: parent.width * ratio()
@@ -255,8 +254,8 @@ Rectangle {
width: 12
height: 12
radius: 6
color: theme.primary
border.color: Qt.lighter(theme.primary, 1.3)
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))
@@ -346,7 +345,7 @@ Rectangle {
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: theme.spacingM
spacing: Theme.spacingM
height: parent.height
// Previous button
@@ -354,14 +353,14 @@ Rectangle {
width: 28
height: 28
radius: 14
color: prevBtnArea.containsMouse ? Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.12) : "transparent"
color: prevBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "skip_previous"
font.family: theme.iconFont
font.family: Theme.iconFont
font.pixelSize: 16
color: theme.surfaceText
color: Theme.surfaceText
}
MouseArea {
@@ -388,14 +387,14 @@ Rectangle {
width: 32
height: 32
radius: 16
color: theme.primary
color: Theme.primary
Text {
anchors.centerIn: parent
text: activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
font.family: theme.iconFont
font.family: Theme.iconFont
font.pixelSize: 20
color: theme.background
color: Theme.background
}
MouseArea {
@@ -411,14 +410,14 @@ Rectangle {
width: 28
height: 28
radius: 14
color: nextBtnArea.containsMouse ? Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.12) : "transparent"
color: nextBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "skip_next"
font.family: theme.iconFont
font.family: Theme.iconFont
font.pixelSize: 16
color: theme.surfaceText
color: Theme.surfaceText
}
MouseArea {

View File

@@ -7,13 +7,12 @@ import qs.Services
Rectangle {
id: weatherWidget
property var theme: Theme
width: parent.width
height: parent.height
radius: theme.cornerRadiusLarge
color: Qt.rgba(theme.surfaceContainer.r, theme.surfaceContainer.g, theme.surfaceContainer.b, 0.4)
border.color: Qt.rgba(theme.outline.r, theme.outline.g, theme.outline.b, 0.08)
radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
layer.enabled: true
@@ -29,21 +28,21 @@ Rectangle {
// Placeholder when no weather - centered in entire widget
Column {
anchors.centerIn: parent
spacing: theme.spacingS
spacing: Theme.spacingS
visible: !WeatherService.weather.available || WeatherService.weather.temp === 0
Text {
text: "cloud_off"
font.family: theme.iconFont
font.pixelSize: theme.iconSize + 8
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.5)
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize + 8
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: "No Weather Data"
font.pixelSize: theme.fontSizeMedium
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter
}
}
@@ -51,8 +50,8 @@ Rectangle {
// Weather content when available - original Column structure
Column {
anchors.fill: parent
anchors.margins: theme.spacingL
spacing: theme.spacingS
anchors.margins: Theme.spacingL
spacing: Theme.spacingS
visible: WeatherService.weather.available && WeatherService.weather.temp !== 0
// Weather header info
@@ -62,25 +61,25 @@ Rectangle {
Row {
anchors.centerIn: parent
spacing: theme.spacingL
spacing: Theme.spacingL
// Weather icon
Text {
text: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
font.family: theme.iconFont
font.pixelSize: theme.iconSize + 8
color: theme.primary
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize + 8
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
spacing: theme.spacingXS
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
Text {
text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")
font.pixelSize: theme.fontSizeXLarge
color: theme.surfaceText
font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText
font.weight: Font.Light
MouseArea {
@@ -94,8 +93,8 @@ Rectangle {
Text {
text: WeatherService.weather.city || ""
font.pixelSize: theme.fontSizeMedium
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: text.length > 0
}
}
@@ -105,73 +104,73 @@ Rectangle {
// Weather details grid
Grid {
columns: 2
spacing: theme.spacingM
spacing: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Row {
spacing: theme.spacingXS
spacing: Theme.spacingXS
Text {
text: "humidity_low"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
font.family: Theme.iconFont
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--"
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: theme.spacingXS
spacing: Theme.spacingXS
Text {
text: "air"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
font.family: Theme.iconFont
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: WeatherService.weather.wind || "--"
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: theme.spacingXS
spacing: Theme.spacingXS
Text {
text: "wb_twilight"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
font.family: Theme.iconFont
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: WeatherService.weather.sunrise || "--"
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: theme.spacingXS
spacing: Theme.spacingXS
Text {
text: "bedtime"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
font.family: Theme.iconFont
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: WeatherService.weather.sunset || "--"
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}

View File

@@ -9,9 +9,16 @@ import qs.Common
import qs.Services
PanelWindow {
id: controlCenterPopup
id: root
visible: root.controlCenterVisible
property bool controlCenterVisible: false
visible: controlCenterVisible
onVisibleChanged: {
// Enable/disable WiFi auto-refresh based on control center visibility
WifiService.autoRefreshEnabled = visible && NetworkService.wifiEnabled
}
implicitWidth: 600
implicitHeight: 500
@@ -34,7 +41,7 @@ PanelWindow {
Rectangle {
width: Math.min(600, Screen.width - Theme.spacingL * 2)
height: controlCenterPopup.powerOptionsExpanded ? 570 : 500
height: root.powerOptionsExpanded ? 570 : 500
x: Math.max(Theme.spacingL, Screen.width - width - Theme.spacingL)
y: Theme.barHeight + Theme.spacingXS
color: Theme.popupBackground()
@@ -46,15 +53,15 @@ PanelWindow {
transform: [
Scale {
id: scaleTransform
origin.x: parent.width // Scale from top-right corner
origin.x: 600 // Use fixed width since popup is max 600px wide
origin.y: 0
xScale: root.controlCenterVisible ? 1.0 : 0.95
yScale: root.controlCenterVisible ? 1.0 : 0.8
xScale: controlCenterVisible ? 1.0 : 0.95
yScale: controlCenterVisible ? 1.0 : 0.8
},
Translate {
id: translateTransform
x: root.controlCenterVisible ? 0 : 15 // Slide slightly left when hidden
y: root.controlCenterVisible ? 0 : -30
x: controlCenterVisible ? 0 : 15 // Slide slightly left when hidden
y: controlCenterVisible ? 0 : -30
}
]
@@ -62,13 +69,13 @@ PanelWindow {
states: [
State {
name: "visible"
when: root.controlCenterVisible
when: controlCenterVisible
PropertyChanges { target: scaleTransform; xScale: 1.0; yScale: 1.0 }
PropertyChanges { target: translateTransform; x: 0; y: 0 }
},
State {
name: "hidden"
when: !root.controlCenterVisible
when: !controlCenterVisible
PropertyChanges { target: scaleTransform; xScale: 0.95; yScale: 0.8 }
PropertyChanges { target: translateTransform; x: 15; y: -30 }
}
@@ -96,7 +103,7 @@ PanelWindow {
}
]
opacity: root.controlCenterVisible ? 1.0 : 0.0
opacity: controlCenterVisible ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
@@ -253,7 +260,7 @@ PanelWindow {
width: 40
height: 40
radius: 20
color: powerButton.containsMouse || controlCenterPopup.powerOptionsExpanded ?
color: powerButton.containsMouse || root.powerOptionsExpanded ?
Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) :
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
@@ -267,10 +274,10 @@ PanelWindow {
Text {
anchors.centerIn: parent
text: controlCenterPopup.powerOptionsExpanded ? "expand_less" : "power_settings_new"
text: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 2
color: powerButton.containsMouse || controlCenterPopup.powerOptionsExpanded ? Theme.error : Theme.surfaceText
color: powerButton.containsMouse || root.powerOptionsExpanded ? Theme.error : Theme.surfaceText
Behavior on text {
// Smooth icon transition
@@ -302,7 +309,7 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
controlCenterPopup.powerOptionsExpanded = !controlCenterPopup.powerOptionsExpanded
root.powerOptionsExpanded = !root.powerOptionsExpanded
}
}
@@ -338,8 +345,8 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
root.controlCenterVisible = false
root.settingsVisible = true
controlCenterVisible = false
settingsPopup.settingsVisible = true
}
}
@@ -356,12 +363,12 @@ PanelWindow {
// Animated Collapsible Power Options (optimized)
Rectangle {
width: parent.width
height: controlCenterPopup.powerOptionsExpanded ? 60 : 0
height: root.powerOptionsExpanded ? 60 : 0
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: controlCenterPopup.powerOptionsExpanded ? 1 : 0
opacity: controlCenterPopup.powerOptionsExpanded ? 1.0 : 0.0
border.width: root.powerOptionsExpanded ? 1 : 0
opacity: root.powerOptionsExpanded ? 1.0 : 0.0
clip: true
// Single coordinated animation for power options
@@ -382,7 +389,7 @@ PanelWindow {
Row {
anchors.centerIn: parent
spacing: Theme.spacingL
visible: controlCenterPopup.powerOptionsExpanded
visible: root.powerOptionsExpanded
// Logout
Rectangle {
@@ -421,11 +428,13 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
controlCenterPopup.powerOptionsExpanded = false
root.powerConfirmAction = "logout"
root.powerConfirmTitle = "Logout"
root.powerConfirmMessage = "Are you sure you want to logout?"
root.powerConfirmVisible = true
root.powerOptionsExpanded = false
if (typeof root !== "undefined" && root.powerConfirmDialog) {
root.powerConfirmDialog.powerConfirmAction = "logout"
root.powerConfirmDialog.powerConfirmTitle = "Logout"
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to logout?"
root.powerConfirmDialog.powerConfirmVisible = true
}
}
}
@@ -474,11 +483,13 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
controlCenterPopup.powerOptionsExpanded = false
root.powerConfirmAction = "reboot"
root.powerConfirmTitle = "Restart"
root.powerConfirmMessage = "Are you sure you want to restart?"
root.powerConfirmVisible = true
root.powerOptionsExpanded = false
if (typeof root !== "undefined" && root.powerConfirmDialog) {
root.powerConfirmDialog.powerConfirmAction = "reboot"
root.powerConfirmDialog.powerConfirmTitle = "Restart"
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to restart?"
root.powerConfirmDialog.powerConfirmVisible = true
}
}
}
@@ -527,11 +538,13 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
controlCenterPopup.powerOptionsExpanded = false
root.powerConfirmAction = "poweroff"
root.powerConfirmTitle = "Shutdown"
root.powerConfirmMessage = "Are you sure you want to shutdown?"
root.powerConfirmVisible = true
root.powerOptionsExpanded = false
if (typeof root !== "undefined" && root.powerConfirmDialog) {
root.powerConfirmDialog.powerConfirmAction = "poweroff"
root.powerConfirmDialog.powerConfirmTitle = "Shutdown"
root.powerConfirmDialog.powerConfirmMessage = "Are you sure you want to shutdown?"
root.powerConfirmDialog.powerConfirmVisible = true
}
}
}
@@ -579,7 +592,7 @@ PanelWindow {
width: (parent.width - Theme.spacingXS * (tabCount - 1)) / tabCount
height: 40
radius: Theme.cornerRadius
color: controlCenterPopup.currentTab === modelData.id ?
color: root.currentTab === modelData.id ?
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
tabArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
@@ -591,15 +604,15 @@ PanelWindow {
text: modelData.icon
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4
color: controlCenterPopup.currentTab === modelData.id ? Theme.primary : Theme.surfaceText
color: root.currentTab === modelData.id ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: modelData.name
font.pixelSize: Theme.fontSizeSmall
color: controlCenterPopup.currentTab === modelData.id ? Theme.primary : Theme.surfaceText
font.weight: controlCenterPopup.currentTab === modelData.id ? Font.Medium : Font.Normal
color: root.currentTab === modelData.id ? Theme.primary : Theme.surfaceText
font.weight: root.currentTab === modelData.id ? Font.Medium : Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
@@ -611,7 +624,7 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
controlCenterPopup.currentTab = modelData.id
root.currentTab = modelData.id
}
}
@@ -629,7 +642,7 @@ PanelWindow {
// Tab content area
Rectangle {
width: parent.width
height: controlCenterPopup.powerOptionsExpanded ? 240 : 300
height: root.powerOptionsExpanded ? 240 : 300
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.1)
@@ -644,36 +657,29 @@ PanelWindow {
NetworkTab {
anchors.fill: parent
anchors.margins: Theme.spacingM
visible: controlCenterPopup.currentTab === "network"
visible: root.currentTab === "network"
wifiPasswordSSID: root.wifiPasswordSSID
wifiPasswordInput: root.wifiPasswordInput
wifiPasswordDialogVisible: root.wifiPasswordDialogVisible
onWifiAutoRefreshEnabledChanged: {
root.wifiAutoRefreshEnabled = wifiAutoRefreshEnabled
}
}
// Audio Tab
AudioTab {
anchors.fill: parent
anchors.margins: Theme.spacingM
visible: controlCenterPopup.currentTab === "audio"
visible: root.currentTab === "audio"
}
// Bluetooth Tab
BluetoothTab {
anchors.fill: parent
anchors.margins: Theme.spacingM
visible: BluetoothService.available && controlCenterPopup.currentTab === "bluetooth"
visible: BluetoothService.available && root.currentTab === "bluetooth"
}
// Display Tab
DisplayTab {
anchors.fill: parent
anchors.margins: Theme.spacingM
visible: controlCenterPopup.currentTab === "display"
visible: root.currentTab === "display"
}
}
@@ -685,7 +691,7 @@ PanelWindow {
anchors.fill: parent
z: -1
onClicked: {
root.controlCenterVisible = false
controlCenterVisible = false
}
}
}

View File

@@ -16,11 +16,6 @@ Item {
else return 1 // Default to WiFi when nothing is connected
}
// Expose properties that the parent needs to bind to
property bool wifiAutoRefreshEnabled: false
property string wifiPasswordSSID: ""
property string wifiPasswordInput: ""
property bool wifiPasswordDialogVisible: false
Column {
anchors.fill: parent
@@ -67,7 +62,7 @@ Item {
cursorShape: Qt.PointingHandCursor
onClicked: {
networkTab.networkSubTab = 0
networkTab.wifiAutoRefreshEnabled = false
WifiService.autoRefreshEnabled = false
}
}
}
@@ -108,7 +103,7 @@ Item {
cursorShape: Qt.PointingHandCursor
onClicked: {
networkTab.networkSubTab = 1
networkTab.wifiAutoRefreshEnabled = true
WifiService.autoRefreshEnabled = true
if (NetworkService.wifiEnabled) {
WifiService.scanWifi()
}
@@ -795,9 +790,9 @@ Item {
WifiService.connectToWifi(modelData.ssid)
} else if (modelData.secured) {
// Secured network, need password - use root dialog
root.wifiPasswordSSID = modelData.ssid
root.wifiPasswordInput = ""
root.wifiPasswordDialogVisible = true
wifiPasswordDialog.wifiPasswordSSID = modelData.ssid
wifiPasswordDialog.wifiPasswordInput = ""
wifiPasswordDialog.wifiPasswordDialogVisible = true
} else {
// Open network, connect directly
WifiService.connectToWifi(modelData.ssid)

View File

@@ -9,7 +9,6 @@ Rectangle {
property bool showPercentage: true
property bool showIcon: true
property var processDropdown: null
width: 55
height: 30
@@ -18,10 +17,6 @@ Rectangle {
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)
Component.onCompleted: {
// CPU widget initialized
}
MouseArea {
id: cpuArea
anchors.fill: parent
@@ -29,10 +24,8 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (processDropdown) {
ProcessMonitorService.setSortBy("cpu")
processDropdown.toggle()
}
processListDropdown.toggle()
}
}

View File

@@ -8,10 +8,9 @@ import qs.Common
import qs.Services
PanelWindow {
id: notificationHistoryPopup
id: root
property bool notificationHistoryVisible: false
signal closeRequested()
visible: notificationHistoryVisible
@@ -35,7 +34,7 @@ PanelWindow {
MouseArea {
anchors.fill: parent
onClicked: {
closeRequested()
notificationHistoryVisible = false
}
}
@@ -53,7 +52,7 @@ PanelWindow {
transform: [
Scale {
id: scaleTransform
origin.x: parent.width
origin.x: 400 // Use fixed width since popup is 400px wide
origin.y: 0
xScale: notificationHistoryVisible ? 1.0 : 0.95
yScale: notificationHistoryVisible ? 1.0 : 0.8

View File

@@ -7,9 +7,14 @@ import Quickshell.Io
import qs.Common
PanelWindow {
id: powerConfirmDialog
id: root
visible: root.powerConfirmVisible
property bool powerConfirmVisible: false
property string powerConfirmAction: ""
property string powerConfirmTitle: ""
property string powerConfirmMessage: ""
visible: powerConfirmVisible
implicitWidth: 400
implicitHeight: 300
@@ -43,8 +48,8 @@ PanelWindow {
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
opacity: powerConfirmVisible ? 1.0 : 0.0
scale: powerConfirmVisible ? 1.0 : 0.9
Behavior on opacity {
NumberAnimation {
@@ -67,10 +72,10 @@ PanelWindow {
// Title
Text {
text: root.powerConfirmTitle
text: powerConfirmTitle
font.pixelSize: Theme.fontSizeLarge
color: {
switch(root.powerConfirmAction) {
switch(powerConfirmAction) {
case "poweroff": return Theme.error
case "reboot": return Theme.warning
default: return Theme.surfaceText
@@ -83,7 +88,7 @@ PanelWindow {
// Message
Text {
text: root.powerConfirmMessage
text: powerConfirmMessage
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
@@ -119,7 +124,7 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.powerConfirmVisible = false
powerConfirmVisible = false
}
}
}
@@ -131,7 +136,7 @@ PanelWindow {
radius: Theme.cornerRadius
color: {
let baseColor
switch(root.powerConfirmAction) {
switch(powerConfirmAction) {
case "poweroff": baseColor = Theme.error; break
case "reboot": baseColor = Theme.warning; break
default: baseColor = Theme.primary; break
@@ -155,8 +160,8 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.powerConfirmVisible = false
executePowerAction(root.powerConfirmAction)
powerConfirmVisible = false
executePowerAction(powerConfirmAction)
}
}
}

View File

@@ -7,9 +7,11 @@ import Quickshell.Io
import qs.Common
PanelWindow {
id: powerMenuPopup
id: root
visible: root.powerMenuVisible
property bool powerMenuVisible: false
visible: powerMenuVisible
implicitWidth: 400
implicitHeight: 320
@@ -31,7 +33,7 @@ PanelWindow {
MouseArea {
anchors.fill: parent
onClicked: {
root.powerMenuVisible = false
powerMenuVisible = false
}
}
@@ -45,8 +47,8 @@ PanelWindow {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
opacity: root.powerMenuVisible ? 1.0 : 0.0
scale: root.powerMenuVisible ? 1.0 : 0.85
opacity: powerMenuVisible ? 1.0 : 0.0
scale: powerMenuVisible ? 1.0 : 0.85
Behavior on opacity {
NumberAnimation {
@@ -109,7 +111,7 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.powerMenuVisible = false
powerMenuVisible = false
}
}
}
@@ -156,7 +158,7 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.powerMenuVisible = false
powerMenuVisible = false
root.powerConfirmAction = "logout"
root.powerConfirmTitle = "Log Out"
root.powerConfirmMessage = "Are you sure you want to log out?"
@@ -201,7 +203,7 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.powerMenuVisible = false
powerMenuVisible = false
root.powerConfirmAction = "suspend"
root.powerConfirmTitle = "Suspend"
root.powerConfirmMessage = "Are you sure you want to suspend the system?"
@@ -246,7 +248,7 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.powerMenuVisible = false
powerMenuVisible = false
root.powerConfirmAction = "reboot"
root.powerConfirmTitle = "Reboot"
root.powerConfirmMessage = "Are you sure you want to reboot the system?"
@@ -291,7 +293,7 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.powerMenuVisible = false
powerMenuVisible = false
root.powerConfirmAction = "poweroff"
root.powerConfirmTitle = "Power Off"
root.powerConfirmMessage = "Are you sure you want to power off the system?"

View File

@@ -10,7 +10,7 @@ import qs.Common
import qs.Services
PanelWindow {
id: processDropdown
id: processListDropdown
property bool isVisible: false
property var parentWidget: null
@@ -41,7 +41,7 @@ PanelWindow {
// Click outside to close
MouseArea {
anchors.fill: parent
onClicked: processDropdown.hide()
onClicked: processListDropdown.hide()
}
Rectangle {
@@ -63,8 +63,8 @@ PanelWindow {
id: scaleTransform
origin.x: parent.width * 0.85 // Scale from top-right
origin.y: 0
xScale: processDropdown.isVisible ? 1.0 : 0.95
yScale: processDropdown.isVisible ? 1.0 : 0.8
xScale: processListDropdown.isVisible ? 1.0 : 0.95
yScale: processListDropdown.isVisible ? 1.0 : 0.8
Behavior on xScale {
NumberAnimation {
@@ -82,8 +82,8 @@ PanelWindow {
},
Translate {
id: translateTransform
x: processDropdown.isVisible ? 0 : 20
y: processDropdown.isVisible ? 0 : -30
x: processListDropdown.isVisible ? 0 : 20
y: processListDropdown.isVisible ? 0 : -30
Behavior on x {
NumberAnimation {
@@ -101,7 +101,7 @@ PanelWindow {
}
]
opacity: processDropdown.isVisible ? 1.0 : 0.0
opacity: processListDropdown.isVisible ? 1.0 : 0.0
// Add shadow effect
layer.enabled: true
@@ -111,7 +111,7 @@ PanelWindow {
shadowVerticalOffset: 8
shadowBlur: 1.0
shadowColor: Qt.rgba(0, 0, 0, 0.15)
shadowOpacity: processDropdown.isVisible ? 0.15 : 0
shadowOpacity: processListDropdown.isVisible ? 0.15 : 0
}
Behavior on opacity {

View File

@@ -9,7 +9,6 @@ Rectangle {
property bool showPercentage: true
property bool showIcon: true
property var processDropdown: null
width: 55
height: 30
@@ -18,10 +17,6 @@ Rectangle {
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)
Component.onCompleted: {
// RAM widget initialized
}
MouseArea {
id: ramArea
anchors.fill: parent
@@ -29,10 +24,8 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (processDropdown) {
ProcessMonitorService.setSortBy("memory")
processDropdown.toggle()
}
processListDropdown.toggle()
}
}

View File

@@ -1,5 +1,6 @@
import QtQuick
import qs.Common
import qs.Services
Column {
id: themePicker
@@ -198,7 +199,7 @@ Column {
anchors.horizontalCenter: parent.horizontalCenter
color: {
if (root.wallpaperErrorStatus === "error" || root.wallpaperErrorStatus === "matugen_missing") {
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
} else {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
@@ -206,7 +207,7 @@ Column {
}
border.color: {
if (root.wallpaperErrorStatus === "error" || root.wallpaperErrorStatus === "matugen_missing") {
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.5)
} else if (Theme.isDynamicTheme) {
return Theme.primary
@@ -223,13 +224,13 @@ Column {
Text {
text: {
if (root.wallpaperErrorStatus === "error" || root.wallpaperErrorStatus === "matugen_missing") return "error"
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") return "error"
else return "palette"
}
font.family: Theme.iconFont
font.pixelSize: 16
color: {
if (root.wallpaperErrorStatus === "error" || root.wallpaperErrorStatus === "matugen_missing") return Theme.error
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") return Theme.error
else return Theme.surfaceText
}
font.weight: Theme.iconFontWeight
@@ -238,13 +239,13 @@ Column {
Text {
text: {
if (root.wallpaperErrorStatus === "error") return "Error"
else if (root.wallpaperErrorStatus === "matugen_missing") return "No matugen"
if (ToastService.wallpaperErrorStatus === "error") return "Error"
else if (ToastService.wallpaperErrorStatus === "matugen_missing") return "No matugen"
else return "Auto"
}
font.pixelSize: Theme.fontSizeMedium
color: {
if (root.wallpaperErrorStatus === "error" || root.wallpaperErrorStatus === "matugen_missing") return Theme.error
if (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") return Theme.error
else return Theme.surfaceText
}
font.weight: Font.Medium
@@ -297,21 +298,21 @@ Column {
anchors.bottom: parent.top
anchors.bottomMargin: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter
visible: autoMouseArea.containsMouse && (!Theme.isDynamicTheme || root.wallpaperErrorStatus === "error" || root.wallpaperErrorStatus === "matugen_missing")
visible: autoMouseArea.containsMouse && (!Theme.isDynamicTheme || ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing")
Text {
id: autoTooltipText
text: {
if (root.wallpaperErrorStatus === "error") {
if (ToastService.wallpaperErrorStatus === "error") {
return "Wallpaper symlink missing at ~/quickshell/current_wallpaper"
} else if (root.wallpaperErrorStatus === "matugen_missing") {
} else if (ToastService.wallpaperErrorStatus === "matugen_missing") {
return "Install matugen package for dynamic themes"
} else {
return "Dynamic wallpaper-based colors"
}
}
font.pixelSize: Theme.fontSizeSmall
color: (root.wallpaperErrorStatus === "error" || root.wallpaperErrorStatus === "matugen_missing") ? Theme.error : Theme.surfaceText
color: (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") ? Theme.error : Theme.surfaceText
anchors.centerIn: parent
wrapMode: Text.WordWrap
width: Math.min(implicitWidth, 250)

125
Widgets/ToastWidget.qml Normal file
View File

@@ -0,0 +1,125 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import qs.Common
import qs.Services
PanelWindow {
id: root
visible: ToastService.toastVisible
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
// Makes the background transparent to mouse events
mask: Region {
item: toast
}
Rectangle {
id: toast
width: Math.min(400, Screen.width - Theme.spacingL * 2)
height: toastContent.height + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter
y: Theme.barHeight + Theme.spacingL
color: {
switch (ToastService.currentLevel) {
case ToastService.levelError: return Theme.error
case ToastService.levelWarn: return Theme.warning
case ToastService.levelInfo: return Theme.primary
default: return Theme.primary
}
}
radius: Theme.cornerRadiusLarge
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 4
shadowBlur: 0.8
shadowColor: Qt.rgba(0, 0, 0, 0.3)
shadowOpacity: 0.3
}
opacity: ToastService.toastVisible ? 0.9 : 0.0
scale: ToastService.toastVisible ? 1.0 : 0.9
transform: Translate {
y: ToastService.toastVisible ? 0 : -20
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Row {
id: toastContent
anchors.centerIn: parent
spacing: Theme.spacingM
Text {
text: {
switch (ToastService.currentLevel) {
case ToastService.levelError: return "error"
case ToastService.levelWarn: return "warning"
case ToastService.levelInfo: return "info"
default: return "info"
}
}
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: Theme.background
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: ToastService.currentMessage
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
width: Math.min(implicitWidth, 300)
elide: Text.ElideRight
}
}
MouseArea {
anchors.fill: parent
onClicked: ToastService.hideToast()
}
}
}

View File

@@ -14,7 +14,7 @@ import qs.Widgets
import "../../Common/Utilities.js" as Utils
PanelWindow {
id: topBar
id: root
property var modelData
screen: modelData
@@ -26,31 +26,16 @@ PanelWindow {
Connections {
target: Prefs
function onTopBarTransparencyChanged() {
topBar.backgroundTransparency = Prefs.topBarTransparency
root.backgroundTransparency = Prefs.topBarTransparency
}
}
// Properties exposed to shell
// Shell reference to access root properties directly
property var shellRoot: null
// Notification properties
property int notificationCount: 0
// Process dropdown reference
property var processDropdown: null
readonly property int notificationCount: NotificationService.notifications.length
// Clipboard properties
signal clipboardRequested()
// Tray menu properties
property bool showTrayMenu: false
property var currentTrayMenu: null
property var currentTrayItem: null
property real trayMenuX: 0
property real trayMenuY: 0
@@ -83,7 +68,7 @@ PanelWindow {
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadiusXLarge
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, root.backgroundTransparency)
layer.enabled: true
layer.effect: MultiEffect {
@@ -146,7 +131,7 @@ PanelWindow {
WorkspaceSwitcher {
anchors.verticalCenter: parent.verticalCenter
screenName: topBar.screenName
screenName: root.screenName
}
FocusedAppWidget {
@@ -160,9 +145,7 @@ PanelWindow {
anchors.centerIn: parent
onClockClicked: {
if (topBar.shellRoot) {
topBar.shellRoot.calendarVisible = !topBar.shellRoot.calendarVisible
}
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible
}
}
@@ -173,9 +156,7 @@ PanelWindow {
visible: Prefs.showMusic && MprisController.activePlayer
onClicked: {
if (topBar.shellRoot) {
topBar.shellRoot.calendarVisible = !topBar.shellRoot.calendarVisible
}
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible
}
}
@@ -188,9 +169,7 @@ PanelWindow {
visible: Prefs.showWeather && WeatherService.weather.available && WeatherService.weather.temp > 0 && WeatherService.weather.tempF > 0
onClicked: {
if (topBar.shellRoot) {
topBar.shellRoot.calendarVisible = !topBar.shellRoot.calendarVisible
}
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible
}
}
@@ -205,13 +184,11 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemTray
onMenuRequested: (menu, item, x, y) => {
if (topBar.shellRoot) {
topBar.shellRoot.currentTrayMenu = menu
topBar.shellRoot.currentTrayItem = item
topBar.shellRoot.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL
topBar.shellRoot.trayMenuY = Theme.barHeight - Theme.spacingXS
topBar.shellRoot.showTrayMenu = true
}
trayMenuPopup.currentTrayMenu = menu
trayMenuPopup.currentTrayItem = item
trayMenuPopup.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL
trayMenuPopup.trayMenuY = Theme.barHeight - Theme.spacingXS
trayMenuPopup.showTrayMenu = true
menu.menuVisible = true
}
}
@@ -240,7 +217,7 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
topBar.clipboardRequested()
clipboardHistoryPopup.toggle()
}
}
@@ -256,43 +233,38 @@ PanelWindow {
CpuMonitorWidget {
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemResources
processDropdown: topBar.processDropdown
}
RamMonitorWidget {
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemResources
processDropdown: topBar.processDropdown
}
NotificationCenterButton {
anchors.verticalCenter: parent.verticalCenter
hasUnread: topBar.notificationCount > 0
isActive: topBar.shellRoot ? topBar.shellRoot.notificationHistoryVisible : false
hasUnread: root.notificationCount > 0
isActive: notificationCenter.notificationHistoryVisible
onClicked: {
if (topBar.shellRoot) {
topBar.shellRoot.notificationHistoryVisible = !topBar.shellRoot.notificationHistoryVisible
}
notificationCenter.notificationHistoryVisible = !notificationCenter.notificationHistoryVisible
}
}
// Battery Widget
BatteryWidget {
anchors.verticalCenter: parent.verticalCenter
batteryPopupVisible: topBar.shellRoot.batteryPopupVisible
batteryPopupVisible: batteryControlPopup.batteryPopupVisible
onToggleBatteryPopup: {
topBar.shellRoot.batteryPopupVisible = !topBar.shellRoot.batteryPopupVisible
batteryControlPopup.batteryPopupVisible = !batteryControlPopup.batteryPopupVisible
}
}
ControlCenterButton {
anchors.verticalCenter: parent.verticalCenter
isActive: topBar.shellRoot ? topBar.shellRoot.controlCenterVisible : false
isActive: controlCenterPopup.controlCenterVisible
onClicked: {
if (topBar.shellRoot) {
topBar.shellRoot.controlCenterVisible = !topBar.shellRoot.controlCenterVisible
if (topBar.shellRoot.controlCenterVisible) {
controlCenterPopup.controlCenterVisible = !controlCenterPopup.controlCenterVisible
if (controlCenterPopup.controlCenterVisible) {
if (NetworkService.wifiEnabled) {
WifiService.scanWifi()
}
@@ -303,5 +275,4 @@ PanelWindow {
}
}
}
}
}

View File

@@ -6,9 +6,15 @@ import Quickshell.Wayland
import qs.Common
PanelWindow {
id: trayMenuPopup
id: root
visible: root.showTrayMenu
property bool showTrayMenu: false
property real trayMenuX: 0
property real trayMenuY: 0
property var currentTrayMenu: null
property var currentTrayItem: null
visible: showTrayMenu
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
@@ -25,8 +31,8 @@ PanelWindow {
Rectangle {
id: menuContainer
x: root.trayMenuX
y: root.trayMenuY
x: trayMenuX
y: trayMenuY
width: Math.max(180, Math.min(300, menuList.maxTextWidth + Theme.spacingL * 2))
height: Math.max(60, menuList.contentHeight + Theme.spacingS * 2)
color: Theme.popupBackground()
@@ -47,8 +53,8 @@ PanelWindow {
}
// Material 3 animations
opacity: root.showTrayMenu ? 1.0 : 0.0
scale: root.showTrayMenu ? 1.0 : 0.85
opacity: showTrayMenu ? 1.0 : 0.0
scale: showTrayMenu ? 1.0 : 0.85
Behavior on opacity {
NumberAnimation {
@@ -70,7 +76,7 @@ PanelWindow {
QsMenuOpener {
id: menuOpener
menu: root.currentTrayItem ? root.currentTrayItem.menu : null
menu: currentTrayItem ? currentTrayItem.menu : null
}
// Custom menu styling using ListView
@@ -151,7 +157,7 @@ PanelWindow {
if (modelData.triggered) {
modelData.triggered()
}
root.showTrayMenu = false
showTrayMenu = false
}
}
@@ -171,7 +177,7 @@ PanelWindow {
anchors.fill: parent
z: -1
onClicked: {
root.showTrayMenu = false
showTrayMenu = false
}
}
}

View File

@@ -7,9 +7,13 @@ import qs.Common
import qs.Services
PanelWindow {
id: wifiPasswordDialog
id: root
visible: root.wifiPasswordDialogVisible
property bool wifiPasswordDialogVisible: false
property string wifiPasswordSSID: ""
property string wifiPasswordInput: ""
visible: wifiPasswordDialogVisible
anchors {
top: true
left: true
@@ -19,7 +23,7 @@ PanelWindow {
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: root.wifiPasswordDialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: wifiPasswordDialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
color: "transparent"
@@ -32,7 +36,7 @@ PanelWindow {
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
opacity: root.wifiPasswordDialogVisible ? 1.0 : 0.0
opacity: wifiPasswordDialogVisible ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
@@ -44,8 +48,8 @@ PanelWindow {
MouseArea {
anchors.fill: parent
onClicked: {
root.wifiPasswordDialogVisible = false
root.wifiPasswordInput = ""
wifiPasswordDialogVisible = false
wifiPasswordInput = ""
}
}
}
@@ -59,8 +63,8 @@ PanelWindow {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
opacity: root.wifiPasswordDialogVisible ? 1.0 : 0.0
scale: root.wifiPasswordDialogVisible ? 1.0 : 0.9
opacity: wifiPasswordDialogVisible ? 1.0 : 0.0
scale: wifiPasswordDialogVisible ? 1.0 : 0.9
Behavior on opacity {
NumberAnimation {
@@ -97,7 +101,7 @@ PanelWindow {
}
Text {
text: "Enter password for \"" + root.wifiPasswordSSID + "\""
text: "Enter password for \"" + wifiPasswordSSID + "\""
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
width: parent.width
@@ -125,8 +129,8 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.wifiPasswordDialogVisible = false
root.wifiPasswordInput = ""
wifiPasswordDialogVisible = false
wifiPasswordInput = ""
}
}
}
@@ -162,15 +166,15 @@ PanelWindow {
}
onTextChanged: {
root.wifiPasswordInput = text
wifiPasswordInput = text
}
onAccepted: {
WifiService.connectToWifiWithPassword(root.wifiPasswordSSID, root.wifiPasswordInput)
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput)
}
Component.onCompleted: {
if (root.wifiPasswordDialogVisible) {
if (wifiPasswordDialogVisible) {
forceActiveFocus()
}
}
@@ -260,8 +264,8 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.wifiPasswordDialogVisible = false
root.wifiPasswordInput = ""
wifiPasswordDialogVisible = false
wifiPasswordInput = ""
}
}
}
@@ -271,7 +275,7 @@ PanelWindow {
height: 36
radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: root.wifiPasswordInput.length > 0
enabled: wifiPasswordInput.length > 0
opacity: enabled ? 1.0 : 0.5
Text {
@@ -290,7 +294,7 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: {
WifiService.connectToWifiWithPassword(root.wifiPasswordSSID, root.wifiPasswordInput)
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput)
}
}

185
shell.qml
View File

@@ -1,161 +1,51 @@
//@ pragma UseQApplication
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Io
import Quickshell.Services.SystemTray
import Quickshell.Services.Notifications
import Quickshell.Services.Mpris
import qs.Services
import qs.Widgets
import qs.Widgets.CenterCommandCenter
import qs.Widgets.ControlCenter
import qs.Widgets.TopBar
import qs.Common
import "./Common/Utilities.js" as Utils
ShellRoot {
id: root
Component.onCompleted: {
// Make root accessible to Theme singleton for error handling
Theme.rootObj = root
// Initialize service monitoring states based on preferences
SystemMonitorService.enableTopBarMonitoring(Prefs.showSystemResources)
ProcessMonitorService.enableMonitoring(false) // Start disabled, enable when process dropdown is opened
// Audio service auto-updates devices, no manual scanning needed
}
property bool calendarVisible: false
property bool showTrayMenu: false
property real trayMenuX: 0
property real trayMenuY: 0
property var currentTrayMenu: null
property var currentTrayItem: null
property bool notificationHistoryVisible: false
property bool mediaPlayerVisible: false
property bool hasActiveMedia: MprisController.active && (MprisController.active.trackTitle || MprisController.active.trackArtist)
property bool controlCenterVisible: false
property bool batteryPopupVisible: false
property bool powerMenuVisible: false
property bool powerConfirmVisible: false
property string powerConfirmAction: ""
property string powerConfirmTitle: ""
property string powerConfirmMessage: ""
property bool settingsVisible: false
// WiFi password dialog
property bool wifiPasswordDialogVisible: false
property string wifiPasswordSSID: ""
property string wifiPasswordInput: ""
property bool wifiAutoRefreshEnabled: false
// Wallpaper error status
property string wallpaperErrorStatus: ""
// Screen size breakpoints for responsive design
property real screenWidth: Screen.width
property bool isSmallScreen: screenWidth < 1200
property bool isMediumScreen: screenWidth >= 1200 && screenWidth < 1600
property bool isLargeScreen: screenWidth >= 1600
// Weather configuration
Timer {
id: wifiAutoRefreshTimer
interval: 20000
running: root.wifiAutoRefreshEnabled && root.controlCenterVisible
repeat: true
onTriggered: {
if (root.wifiAutoRefreshEnabled && root.controlCenterVisible && NetworkService.wifiEnabled) {
WifiService.scanWifi()
}
}
}
// WiFi Connection Status Timer
Timer {
id: wifiConnectionStatusTimer
interval: 3000 // 3 seconds
running: false
repeat: false
onTriggered: {
root.wifiConnectionStatus = ""
}
}
// Wallpaper Error Status Timer
Timer {
id: wallpaperErrorTimer
interval: 5000 // 5 seconds
running: false
repeat: false
onTriggered: {
root.wallpaperErrorStatus = ""
}
}
// Function to show wallpaper error
function showWallpaperError() {
console.log("showWallpaperError called - setting error status")
root.wallpaperErrorStatus = "error"
wallpaperErrorTimer.restart()
}
// Multi-monitor support using Variants
Variants {
model: Quickshell.screens
delegate: TopBar {
modelData: item
// Connect shell properties
shellRoot: root
notificationCount: NotificationService.notifications.length
processDropdown: processListDropdown
// Connect tray menu properties
showTrayMenu: root.showTrayMenu
currentTrayMenu: root.currentTrayMenu
currentTrayItem: root.currentTrayItem
trayMenuX: root.trayMenuX
trayMenuY: root.trayMenuY
// Connect clipboard
onClipboardRequested: {
clipboardHistoryPopup.toggle()
}
}
}
// Global popup windows
CenterCommandCenter {}
TrayMenuPopup {}
CenterCommandCenter {
id: centerCommandCenter
}
TrayMenuPopup {
id: trayMenuPopup
}
NotificationInit {}
NotificationCenter {
notificationHistoryVisible: root.notificationHistoryVisible
onCloseRequested: {
root.notificationHistoryVisible = false
id: notificationCenter
}
ControlCenterPopup {
id: controlCenterPopup
}
WifiPasswordDialog {
id: wifiPasswordDialog
}
ControlCenterPopup {}
WifiPasswordDialog {}
InputDialog {
id: globalInputDialog
}
BatteryControlPopup {}
PowerMenuPopup {}
PowerConfirmDialog {}
BatteryControlPopup {
id: batteryControlPopup
}
PowerMenuPopup {
id: powerMenuPopup
}
PowerConfirmDialog {
id: powerConfirmDialog
}
ProcessListDropdown {
id: processListDropdown
@@ -163,24 +53,6 @@ ShellRoot {
SettingsPopup {
id: settingsPopup
settingsVisible: root.settingsVisible
// Use a more direct approach for two-way binding
onSettingsVisibleChanged: {
if (settingsVisible !== root.settingsVisible) {
root.settingsVisible = settingsVisible
}
}
// Also listen to root changes
Connections {
target: root
function onSettingsVisibleChanged() {
if (settingsPopup.settingsVisible !== root.settingsVisible) {
settingsPopup.settingsVisible = root.settingsVisible
}
}
}
}
// Application and clipboard components
@@ -200,17 +72,8 @@ ShellRoot {
id: clipboardHistoryPopup
}
IpcHandler {
target: "wallpaper"
ToastWidget {
id: toastWidget
}
function refresh() {
console.log("Wallpaper IPC: refresh() called")
// Trigger color extraction if using dynamic theme
if (typeof Theme !== "undefined" && Theme.isDynamicTheme) {
console.log("Triggering color extraction due to wallpaper IPC")
Colors.extractColors()
}
return "WALLPAPER_REFRESH_SUCCESS"
}
}
}