From c6df2bc11d5aa8b3c9d5e0481988089d09d60dfc Mon Sep 17 00:00:00 2001 From: bbedward Date: Thu, 24 Jul 2025 15:09:28 -0400 Subject: [PATCH] smarter distance calculation --- Modules/TopBar/AudioVisualization.qml | 13 +- Modules/TopBar/BatteryPopout.qml | 683 +++++++++++++------------- Modules/TopBar/Clock.qml | 3 + Modules/TopBar/CpuMonitor.qml | 3 +- Modules/TopBar/FocusedApp.qml | 73 +-- Modules/TopBar/Media.qml | 68 +-- Modules/TopBar/RamMonitor.qml | 3 +- Modules/TopBar/SystemTrayBar.qml | 15 +- Modules/TopBar/TopBar.qml | 46 +- Modules/TopBar/Weather.qml | 2 +- 10 files changed, 423 insertions(+), 486 deletions(-) diff --git a/Modules/TopBar/AudioVisualization.qml b/Modules/TopBar/AudioVisualization.qml index 4ffdffa7..36acdcb6 100644 --- a/Modules/TopBar/AudioVisualization.qml +++ b/Modules/TopBar/AudioVisualization.qml @@ -23,15 +23,14 @@ Item { running: true onExited: (exitCode) => { root.cavaAvailable = exitCode === 0; - if (root.cavaAvailable) { + if (root.cavaAvailable) cavaProcess.running = Qt.binding(() => { - return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing; - }); - } else { + return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing; + }); + else fallbackTimer.running = Qt.binding(() => { - return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing; - }); - } + return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing; + }); } } diff --git a/Modules/TopBar/BatteryPopout.qml b/Modules/TopBar/BatteryPopout.qml index b18295e1..a9f558f4 100644 --- a/Modules/TopBar/BatteryPopout.qml +++ b/Modules/TopBar/BatteryPopout.qml @@ -28,6 +28,7 @@ PanelWindow { PowerProfiles.profile = profile; if (PowerProfiles.profile !== profile) ToastService.showError("Failed to set power profile"); + } visible: batteryPopupVisible @@ -51,381 +52,190 @@ PanelWindow { onClicked: function(mouse) { // Only close if click is outside the content loader var localPos = mapToItem(contentLoader, mouse.x, mouse.y); - if (localPos.x < 0 || localPos.x > contentLoader.width || - localPos.y < 0 || localPos.y > contentLoader.height) { + if (localPos.x < 0 || localPos.x > contentLoader.width || localPos.y < 0 || localPos.y > contentLoader.height) batteryPopupVisible = false; - } + } } Loader { id: contentLoader - asynchronous: true - active: batteryPopupVisible - + readonly property real targetWidth: Math.min(380, Screen.width - Theme.spacingL * 2) readonly property real targetHeight: Math.min(450, Screen.height - Theme.barHeight - Theme.spacingS * 2) + + asynchronous: true + active: batteryPopupVisible width: targetWidth height: targetHeight x: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL) y: Theme.barHeight + Theme.spacingS - // GPU-accelerated scale + opacity animation opacity: batteryPopupVisible ? 1 : 0 scale: batteryPopupVisible ? 1 : 0.9 - + Behavior on opacity { NumberAnimation { duration: Anims.durMed easing.type: Easing.BezierSpline easing.bezierCurve: Anims.emphasized } + } - + Behavior on scale { NumberAnimation { duration: Anims.durMed easing.type: Easing.BezierSpline easing.bezierCurve: Anims.emphasized } + } - + sourceComponent: Rectangle { color: Theme.popupBackground() radius: Theme.cornerRadiusLarge border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.width: 1 - // Remove layer rendering for better performance antialiasing: true smooth: true + ScrollView { + anchors.fill: parent + anchors.margins: Theme.spacingL + clip: true - ScrollView { - anchors.fill: parent - anchors.margins: Theme.spacingL - clip: true - - Column { - width: parent.width - spacing: Theme.spacingL - - // Header - Row { + Column { width: parent.width + spacing: Theme.spacingL - Text { - text: BatteryService.batteryAvailable ? "Battery Information" : "Power Management" - font.pixelSize: Theme.fontSizeLarge - color: Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } + // Header + Row { + width: parent.width + + Text { + text: BatteryService.batteryAvailable ? "Battery Information" : "Power Management" + font.pixelSize: Theme.fontSizeLarge + color: Theme.surfaceText + font.weight: Font.Medium + anchors.verticalCenter: parent.verticalCenter + } + + Item { + width: parent.width - 200 + height: 1 + } + + Rectangle { + width: 32 + height: 32 + radius: 16 + color: closeBatteryArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" + + DankIcon { + anchors.centerIn: parent + name: "close" + size: Theme.iconSize - 4 + color: closeBatteryArea.containsMouse ? Theme.error : Theme.surfaceText + } + + MouseArea { + id: closeBatteryArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + batteryPopupVisible = false; + } + } + + } - Item { - width: parent.width - 200 - height: 1 } Rectangle { - width: 32 - height: 32 - radius: 16 - color: closeBatteryArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" - - DankIcon { - anchors.centerIn: parent - name: "close" - size: Theme.iconSize - 4 - color: closeBatteryArea.containsMouse ? Theme.error : Theme.surfaceText - } - - MouseArea { - id: closeBatteryArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - batteryPopupVisible = false; - } - } - - } - - } - - Rectangle { - width: parent.width - height: 80 - radius: Theme.cornerRadius - color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5) - border.color: BatteryService.isCharging ? Theme.primary : (BatteryService.isLowBattery ? Theme.error : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)) - border.width: BatteryService.isCharging || BatteryService.isLowBattery ? 2 : 1 - visible: BatteryService.batteryAvailable - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingL - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingL - - DankIcon { - name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable) - size: Theme.iconSizeLarge - color: { - if (BatteryService.isLowBattery && !BatteryService.isCharging) - return Theme.error; - - if (BatteryService.isCharging) - return Theme.primary; - - return Theme.surfaceText; - } - anchors.verticalCenter: parent.verticalCenter - } - - Column { - spacing: 2 - anchors.verticalCenter: parent.verticalCenter - - Row { - spacing: Theme.spacingM - - Text { - text: BatteryService.batteryLevel + "%" - font.pixelSize: Theme.fontSizeLarge - color: { - if (BatteryService.isLowBattery && !BatteryService.isCharging) - return Theme.error; - - if (BatteryService.isCharging) - return Theme.primary; - - return Theme.surfaceText; - } - font.weight: Font.Bold - } - - Text { - text: BatteryService.batteryStatus - font.pixelSize: Theme.fontSizeMedium - color: { - 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 - } - - } - - Text { - text: { - let time = BatteryService.formatTimeRemaining(); - if (time !== "Unknown") - return BatteryService.isCharging ? "Time until full: " + time : "Time remaining: " + time; - - return ""; - } - font.pixelSize: Theme.fontSizeSmall - color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) - visible: text.length > 0 - } - - } - - } - - } - - // No battery info card - Rectangle { - width: parent.width - height: 80 - radius: Theme.cornerRadius - color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5) - border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) - border.width: 1 - visible: !BatteryService.batteryAvailable - - Row { - anchors.centerIn: parent - spacing: Theme.spacingL - - DankIcon { - name: Theme.getBatteryIcon(0, false, false) - size: 36 - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - Column { - spacing: Theme.spacingS - anchors.verticalCenter: parent.verticalCenter - - Text { - text: "No Battery Detected" - font.pixelSize: Theme.fontSizeLarge - color: Theme.surfaceText - font.weight: Font.Medium - } - - Text { - text: "Power profile management is available" - font.pixelSize: Theme.fontSizeMedium - color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) - } - - } - - } - - } - - // Battery details - Column { - width: parent.width - spacing: Theme.spacingM - visible: BatteryService.batteryAvailable - - Text { - text: "Battery Details" - font.pixelSize: Theme.fontSizeLarge - color: Theme.surfaceText - font.weight: Font.Medium - } - - Row { width: parent.width - spacing: Theme.spacingXL + height: 80 + radius: Theme.cornerRadius + color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5) + border.color: BatteryService.isCharging ? Theme.primary : (BatteryService.isLowBattery ? Theme.error : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)) + border.width: BatteryService.isCharging || BatteryService.isLowBattery ? 2 : 1 + visible: BatteryService.batteryAvailable - // Health - Column { - spacing: 2 - width: (parent.width - Theme.spacingXL) / 2 + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingL + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingL - Text { - text: "Health" - font.pixelSize: Theme.fontSizeSmall - color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) - font.weight: Font.Medium - } - - Text { - text: BatteryService.batteryHealth - font.pixelSize: Theme.fontSizeMedium + DankIcon { + name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable) + size: Theme.iconSizeLarge color: { - if (BatteryService.batteryHealth === "N/A") - return Theme.surfaceText; + if (BatteryService.isLowBattery && !BatteryService.isCharging) + return Theme.error; - var healthNum = parseInt(BatteryService.batteryHealth); - return healthNum < 80 ? Theme.error : Theme.surfaceText; + if (BatteryService.isCharging) + return Theme.primary; + + return Theme.surfaceText; } + anchors.verticalCenter: parent.verticalCenter } - } - - // Capacity - Column { - spacing: 2 - width: (parent.width - Theme.spacingXL) / 2 - - Text { - text: "Capacity" - font.pixelSize: Theme.fontSizeSmall - color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) - font.weight: Font.Medium - } - - Text { - text: BatteryService.batteryCapacity > 0 ? BatteryService.batteryCapacity.toFixed(1) + " Wh" : "Unknown" - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - } - - } - - } - - } - - // Power profiles - Column { - width: parent.width - spacing: Theme.spacingM - visible: true - - Text { - text: "Power Profile" - font.pixelSize: Theme.fontSizeLarge - color: Theme.surfaceText - font.weight: Font.Medium - } - - Column { - width: parent.width - spacing: Theme.spacingS - - Repeater { - model: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance] - - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - color: profileArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (batteryPopout.isActiveProfile(modelData) ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)) - border.color: batteryPopout.isActiveProfile(modelData) ? Theme.primary : "transparent" - border.width: 2 + Column { + spacing: 2 + anchors.verticalCenter: parent.verticalCenter Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingL - anchors.verticalCenter: parent.verticalCenter spacing: Theme.spacingM - DankIcon { - name: Theme.getPowerProfileIcon(modelData) - size: Theme.iconSize - color: batteryPopout.isActiveProfile(modelData) ? Theme.primary : Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter + Text { + text: BatteryService.batteryLevel + "%" + font.pixelSize: Theme.fontSizeLarge + color: { + if (BatteryService.isLowBattery && !BatteryService.isCharging) + return Theme.error; + + if (BatteryService.isCharging) + return Theme.primary; + + return Theme.surfaceText; + } + font.weight: Font.Bold } - Column { - spacing: 2 + Text { + text: BatteryService.batteryStatus + font.pixelSize: Theme.fontSizeMedium + color: { + 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 - - Text { - text: Theme.getPowerProfileLabel(modelData) - font.pixelSize: Theme.fontSizeMedium - color: batteryPopout.isActiveProfile(modelData) ? Theme.primary : Theme.surfaceText - font.weight: batteryPopout.isActiveProfile(modelData) ? Font.Medium : Font.Normal - } - - Text { - text: Theme.getPowerProfileDescription(modelData) - font.pixelSize: Theme.fontSizeSmall - color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) - } - } } - MouseArea { - id: profileArea + Text { + text: { + let time = BatteryService.formatTimeRemaining(); + if (time !== "Unknown") + return BatteryService.isCharging ? "Time until full: " + time : "Time remaining: " + time; - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - batteryPopout.setProfile(modelData); + return ""; } + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) + visible: text.length > 0 } } @@ -434,46 +244,238 @@ PanelWindow { } - } + // No battery info card + Rectangle { + width: parent.width + height: 80 + radius: Theme.cornerRadius + color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5) + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) + border.width: 1 + visible: !BatteryService.batteryAvailable - // Degradation reason warning - Rectangle { - width: parent.width - height: 60 - radius: Theme.cornerRadius - color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) - border.color: Theme.error - border.width: 2 - visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None + Row { + anchors.centerIn: parent + spacing: Theme.spacingL - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingL - anchors.verticalCenter: parent.verticalCenter + DankIcon { + name: Theme.getBatteryIcon(0, false, false) + size: 36 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + Column { + spacing: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + + Text { + text: "No Battery Detected" + font.pixelSize: Theme.fontSizeLarge + color: Theme.surfaceText + font.weight: Font.Medium + } + + Text { + text: "Power profile management is available" + font.pixelSize: Theme.fontSizeMedium + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) + } + + } + + } + + } + + // Battery details + Column { + width: parent.width spacing: Theme.spacingM + visible: BatteryService.batteryAvailable - DankIcon { - name: "warning" - size: Theme.iconSize - color: Theme.error - anchors.verticalCenter: parent.verticalCenter + Text { + text: "Battery Details" + font.pixelSize: Theme.fontSizeLarge + color: Theme.surfaceText + font.weight: Font.Medium + } + + Row { + width: parent.width + spacing: Theme.spacingXL + + // Health + Column { + spacing: 2 + width: (parent.width - Theme.spacingXL) / 2 + + Text { + text: "Health" + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) + font.weight: Font.Medium + } + + Text { + text: BatteryService.batteryHealth + font.pixelSize: Theme.fontSizeMedium + color: { + if (BatteryService.batteryHealth === "N/A") + return Theme.surfaceText; + + var healthNum = parseInt(BatteryService.batteryHealth); + return healthNum < 80 ? Theme.error : Theme.surfaceText; + } + } + + } + + // Capacity + Column { + spacing: 2 + width: (parent.width - Theme.spacingXL) / 2 + + Text { + text: "Capacity" + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) + font.weight: Font.Medium + } + + Text { + text: BatteryService.batteryCapacity > 0 ? BatteryService.batteryCapacity.toFixed(1) + " Wh" : "Unknown" + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + } + + } + + } + + } + + // Power profiles + Column { + width: parent.width + spacing: Theme.spacingM + visible: true + + Text { + text: "Power Profile" + font.pixelSize: Theme.fontSizeLarge + color: Theme.surfaceText + font.weight: Font.Medium } Column { - spacing: 2 - anchors.verticalCenter: parent.verticalCenter + width: parent.width + spacing: Theme.spacingS + + Repeater { + model: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance] + + Rectangle { + width: parent.width + height: 50 + radius: Theme.cornerRadius + color: profileArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (batteryPopout.isActiveProfile(modelData) ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)) + border.color: batteryPopout.isActiveProfile(modelData) ? Theme.primary : "transparent" + border.width: 2 + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingL + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingM + + DankIcon { + name: Theme.getPowerProfileIcon(modelData) + size: Theme.iconSize + color: batteryPopout.isActiveProfile(modelData) ? Theme.primary : Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + Column { + spacing: 2 + anchors.verticalCenter: parent.verticalCenter + + Text { + text: Theme.getPowerProfileLabel(modelData) + font.pixelSize: Theme.fontSizeMedium + color: batteryPopout.isActiveProfile(modelData) ? Theme.primary : Theme.surfaceText + font.weight: batteryPopout.isActiveProfile(modelData) ? Font.Medium : Font.Normal + } + + Text { + text: Theme.getPowerProfileDescription(modelData) + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) + } + + } + + } + + MouseArea { + id: profileArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + batteryPopout.setProfile(modelData); + } + } + + } - Text { - text: "Power Profile Degradation" - font.pixelSize: Theme.fontSizeMedium - color: Theme.error - font.weight: Font.Medium } - Text { - text: (typeof PowerProfiles !== "undefined") ? PerformanceDegradationReason.toString(PowerProfiles.degradationReason) : "" - font.pixelSize: Theme.fontSizeSmall - color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8) + } + + } + + // Degradation reason warning + Rectangle { + width: parent.width + height: 60 + radius: Theme.cornerRadius + color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) + border.color: Theme.error + border.width: 2 + visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingL + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingM + + DankIcon { + name: "warning" + size: Theme.iconSize + color: Theme.error + anchors.verticalCenter: parent.verticalCenter + } + + Column { + spacing: 2 + anchors.verticalCenter: parent.verticalCenter + + Text { + text: "Power Profile Degradation" + font.pixelSize: Theme.fontSizeMedium + color: Theme.error + font.weight: Font.Medium + } + + Text { + text: (typeof PowerProfiles !== "undefined") ? PerformanceDegradationReason.toString(PowerProfiles.degradationReason) : "" + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8) + } + } } @@ -486,9 +488,6 @@ PanelWindow { } - - } } - } diff --git a/Modules/TopBar/Clock.qml b/Modules/TopBar/Clock.qml index 00793574..9e2cbdc6 100644 --- a/Modules/TopBar/Clock.qml +++ b/Modules/TopBar/Clock.qml @@ -6,6 +6,7 @@ Rectangle { id: root property date currentDate: new Date() + property bool compactMode: false signal clockClicked() @@ -36,6 +37,7 @@ Rectangle { font.pixelSize: Theme.fontSizeMedium color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) anchors.verticalCenter: parent.verticalCenter + visible: !compactMode } Text { @@ -44,6 +46,7 @@ Rectangle { color: Theme.surfaceText font.weight: Font.Medium anchors.verticalCenter: parent.verticalCenter + visible: !compactMode } } diff --git a/Modules/TopBar/CpuMonitor.qml b/Modules/TopBar/CpuMonitor.qml index 71fe31da..5bd007cb 100644 --- a/Modules/TopBar/CpuMonitor.qml +++ b/Modules/TopBar/CpuMonitor.qml @@ -16,11 +16,9 @@ Rectangle { height: 30 radius: Theme.cornerRadius color: cpuArea.containsMouse ? 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: { SysMonitorService.addRef(); } - Component.onDestruction: { SysMonitorService.removeRef(); } @@ -35,6 +33,7 @@ Rectangle { SysMonitorService.setSortBy("cpu"); if (root.toggleProcessList) root.toggleProcessList(); + } } diff --git a/Modules/TopBar/FocusedApp.qml b/Modules/TopBar/FocusedApp.qml index 7f7a8cb6..770d2773 100644 --- a/Modules/TopBar/FocusedApp.qml +++ b/Modules/TopBar/FocusedApp.qml @@ -5,64 +5,13 @@ import qs.Services Rectangle { id: root - // Dynamic screen detection for laptop vs desktop monitor - readonly property bool isSmallScreen: { - // Walk up the parent chain to find the TopBar PanelWindow - let current = root.parent - while (current && !current.screen) { - current = current.parent - } - - if (!current || !current.screen) { - return true // Default to small if we can't detect - } - - const s = current.screen - - // Multi-method detection for laptop/small screens: - - // Method 1: Check screen name for laptop indicators - const screenName = (s.name || "").toLowerCase() - if (screenName.includes("edp") || screenName.includes("lvds")) { - return true - } - - // Method 2: Check pixel density if available - try { - if (s.pixelDensity && s.pixelDensity > 1.5) { - return true - } - } catch (e) { /* ignore */ } - - // Method 3: Check device pixel ratio if available - try { - if (s.devicePixelRatio && s.devicePixelRatio > 1.25) { - return true - } - } catch (e) { /* ignore */ } - - // Method 4: Resolution-based fallback for smaller displays - if (s.width <= 1920 && s.height <= 1200) { - return true - } - - // Method 5: Check for high-res laptop displays - if ((s.width === 2400 && s.height === 1600) || - (s.width === 2560 && s.height === 1600) || - (s.width === 2880 && s.height === 1800)) { - return true - } - - return false // Default to large screen - } - - // Dynamic sizing based on screen type - readonly property int maxWidth: isSmallScreen ? 288 : 456 - readonly property int appNameMaxWidth: isSmallScreen ? 130 : 180 - readonly property int separatorWidth: 15 - readonly property int titleMaxWidth: maxWidth - appNameMaxWidth - separatorWidth - (Theme.spacingS * 2) + property bool compactMode: false + property int availableWidth: 400 + readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2 + readonly property int maxNormalWidth: 456 + readonly property int maxCompactWidth: 200 - width: Math.min(contentRow.implicitWidth + Theme.spacingS * 2, maxWidth) + width: compactMode ? Math.min(baseWidth, maxCompactWidth) : Math.min(baseWidth, maxNormalWidth) height: 30 radius: Theme.cornerRadius color: mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) @@ -83,10 +32,9 @@ Rectangle { font.weight: Font.Medium color: Theme.surfaceText anchors.verticalCenter: parent.verticalCenter - // App name gets reasonable space - only truncate if absolutely necessary elide: Text.ElideRight maximumLineCount: 1 - width: Math.min(implicitWidth, root.appNameMaxWidth) + width: Math.min(implicitWidth, compactMode ? 80 : 180) } Text { @@ -105,17 +53,15 @@ Rectangle { font.weight: Font.Medium color: Theme.surfaceText anchors.verticalCenter: parent.verticalCenter - // Window title gets remaining space and shows ellipsis when truncated elide: Text.ElideRight maximumLineCount: 1 - width: Math.min(implicitWidth, root.titleMaxWidth) + width: Math.min(implicitWidth, compactMode ? 60 : 250) + visible: !compactMode || text.length < 15 } } MouseArea { - // Non-interactive widget - just provides hover state for visual feedback - id: mouseArea anchors.fill: parent @@ -130,7 +76,6 @@ Rectangle { } - // Smooth width animation when the text changes Behavior on width { NumberAnimation { duration: Theme.shortDuration diff --git a/Modules/TopBar/Media.qml b/Modules/TopBar/Media.qml index 940e8c9a..cd7ec863 100644 --- a/Modules/TopBar/Media.qml +++ b/Modules/TopBar/Media.qml @@ -9,59 +9,10 @@ Rectangle { readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property bool playerAvailable: activePlayer !== null - - // Screen detection for responsive design (same logic as FocusedApp) - readonly property bool isSmallScreen: { - // Walk up the parent chain to find the TopBar PanelWindow - let current = root.parent - while (current && !current.screen) { - current = current.parent - } - - if (!current || !current.screen) { - return true // Default to small if we can't detect - } - - const s = current.screen - - // Multi-method detection for laptop/small screens: - - // Method 1: Check screen name for laptop indicators - const screenName = (s.name || "").toLowerCase() - if (screenName.includes("edp") || screenName.includes("lvds")) { - return true - } - - // Method 2: Check pixel density if available - try { - if (s.pixelDensity && s.pixelDensity > 1.5) { - return true - } - } catch (e) { /* ignore */ } - - // Method 3: Check device pixel ratio if available - try { - if (s.devicePixelRatio && s.devicePixelRatio > 1.25) { - return true - } - } catch (e) { /* ignore */ } - - // Method 4: Resolution-based fallback for smaller displays - if (s.width <= 1920 && s.height <= 1200) { - return true - } - - // Method 5: Check for high-res laptop displays - if ((s.width === 2400 && s.height === 1600) || - (s.width === 2560 && s.height === 1600) || - (s.width === 2880 && s.height === 1800)) { - return true - } - - return false // Default to large screen - } - - readonly property int contentWidth: Math.min(280, mediaRow.implicitWidth + Theme.spacingS * 2) + property bool compactMode: false + readonly property int baseContentWidth: mediaRow.implicitWidth + Theme.spacingS * 2 + readonly property int normalContentWidth: Math.min(280, baseContentWidth) + readonly property int compactContentWidth: Math.min(120, baseContentWidth) signal clicked() @@ -76,7 +27,7 @@ Rectangle { PropertyChanges { target: root opacity: 1 - width: contentWidth + width: compactMode ? compactContentWidth : normalContentWidth } }, @@ -130,7 +81,6 @@ Rectangle { anchors.centerIn: parent spacing: Theme.spacingXS - // Media info section (clickable to open full player) Row { id: mediaInfo @@ -144,8 +94,8 @@ Rectangle { id: mediaText anchors.verticalCenter: parent.verticalCenter - width: 140 - visible: !root.isSmallScreen // Hide title text on small screens + width: compactMode ? 60 : 140 + visible: !compactMode text: { if (!activePlayer || !activePlayer.trackTitle) return ""; @@ -185,12 +135,10 @@ Rectangle { } - // Control buttons Row { spacing: Theme.spacingXS anchors.verticalCenter: parent.verticalCenter - // Previous button Rectangle { width: 20 height: 20 @@ -222,7 +170,6 @@ Rectangle { } - // Play/Pause button Rectangle { width: 24 height: 24 @@ -252,7 +199,6 @@ Rectangle { } - // Next button Rectangle { width: 20 height: 20 diff --git a/Modules/TopBar/RamMonitor.qml b/Modules/TopBar/RamMonitor.qml index 326ce733..71c58c2b 100644 --- a/Modules/TopBar/RamMonitor.qml +++ b/Modules/TopBar/RamMonitor.qml @@ -16,11 +16,9 @@ Rectangle { height: 30 radius: Theme.cornerRadius color: ramArea.containsMouse ? 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: { SysMonitorService.addRef(); } - Component.onDestruction: { SysMonitorService.removeRef(); } @@ -35,6 +33,7 @@ Rectangle { SysMonitorService.setSortBy("memory"); if (root.toggleProcessList) root.toggleProcessList(); + } } diff --git a/Modules/TopBar/SystemTrayBar.qml b/Modules/TopBar/SystemTrayBar.qml index 4da3565e..b0dbdcc2 100644 --- a/Modules/TopBar/SystemTrayBar.qml +++ b/Modules/TopBar/SystemTrayBar.qml @@ -44,14 +44,17 @@ Rectangle { anchors.fill: parent radius: Theme.cornerRadiusSmall color: trayItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" - + Behavior on color { enabled: trayItemArea.containsMouse !== undefined + ColorAnimation { duration: Theme.shortDuration easing.type: Theme.standardEasing } + } + } Image { @@ -66,23 +69,27 @@ Rectangle { MouseArea { id: trayItemArea + anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: (mouse) => { - if (!trayItem) return; + if (!trayItem) + return ; if (mouse.button === Qt.LeftButton) { if (!trayItem.onlyMenu) trayItem.activate(); + } else if (mouse.button === Qt.RightButton) { - if (trayItem && trayItem.hasMenu) { + if (trayItem && trayItem.hasMenu) root.menuRequested(null, trayItem, mouse.x, mouse.y); - } + } } } + } } diff --git a/Modules/TopBar/TopBar.qml b/Modules/TopBar/TopBar.qml index 153b6590..e77a38a1 100644 --- a/Modules/TopBar/TopBar.qml +++ b/Modules/TopBar/TopBar.qml @@ -110,6 +110,27 @@ PanelWindow { } Item { + id: topBarContent + + readonly property int availableWidth: width + readonly property int leftSectionWidth: leftSection.width + readonly property int rightSectionWidth: rightSection.width + readonly property int clockWidth: clock.width + readonly property int mediaWidth: media.visible ? media.width : 0 + readonly property int weatherWidth: weather.visible ? weather.width : 0 + readonly property bool validLayout: availableWidth > 100 && leftSectionWidth > 0 && rightSectionWidth > 0 + readonly property int clockLeftEdge: validLayout ? (availableWidth - clockWidth) / 2 : 0 + readonly property int clockRightEdge: clockLeftEdge + clockWidth + readonly property int leftSectionRightEdge: leftSectionWidth + readonly property int mediaLeftEdge: clockLeftEdge - mediaWidth - Theme.spacingS + readonly property int rightSectionLeftEdge: availableWidth - rightSectionWidth + readonly property int leftToClockGap: validLayout ? Math.max(0, clockLeftEdge - leftSectionRightEdge) : 1000 + readonly property int leftToMediaGap: (mediaWidth > 0 && validLayout) ? Math.max(0, mediaLeftEdge - leftSectionRightEdge) : leftToClockGap + readonly property int mediaToClockGap: mediaWidth > 0 ? Theme.spacingS : 0 + readonly property int clockToRightGap: validLayout ? Math.max(0, rightSectionLeftEdge - clockRightEdge) : 1000 + readonly property bool spacingTight: validLayout && (leftToMediaGap < 100 || clockToRightGap < 100) + readonly property bool overlapping: validLayout && (leftToMediaGap < 50 || clockToRightGap < 50) + anchors.fill: parent anchors.leftMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM @@ -129,8 +150,9 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter isActive: appDrawerPopout ? appDrawerPopout.isVisible : false onClicked: { - if (appDrawerPopout) + if (appDrawerPopout) appDrawerPopout.toggle(); + } } @@ -140,8 +162,12 @@ PanelWindow { } FocusedApp { + id: focusedApp + anchors.verticalCenter: parent.verticalCenter visible: Prefs.showFocusedWindow + compactMode: topBarContent.spacingTight + availableWidth: topBarContent.leftToMediaGap } } @@ -150,16 +176,20 @@ PanelWindow { id: clock anchors.centerIn: parent + compactMode: topBarContent.overlapping onClockClicked: { centcomPopout.calendarVisible = !centcomPopout.calendarVisible; } } Media { + id: media + anchors.verticalCenter: parent.verticalCenter anchors.right: clock.left anchors.rightMargin: Theme.spacingS visible: Prefs.showMusic && MprisController.activePlayer + compactMode: topBarContent.spacingTight || topBarContent.overlapping onClicked: { centcomPopout.calendarVisible = !centcomPopout.calendarVisible; } @@ -237,21 +267,31 @@ PanelWindow { Loader { anchors.verticalCenter: parent.verticalCenter active: Prefs.showSystemResources + sourceComponent: Component { CpuMonitor { - toggleProcessList: () => processListPopout.toggle() + toggleProcessList: () => { + return processListPopout.toggle(); + } } + } + } Loader { anchors.verticalCenter: parent.verticalCenter active: Prefs.showSystemResources + sourceComponent: Component { RamMonitor { - toggleProcessList: () => processListPopout.toggle() + toggleProcessList: () => { + return processListPopout.toggle(); + } } + } + } NotificationCenterButton { diff --git a/Modules/TopBar/Weather.qml b/Modules/TopBar/Weather.qml index 6f22acc5..527b9dc6 100644 --- a/Modules/TopBar/Weather.qml +++ b/Modules/TopBar/Weather.qml @@ -13,7 +13,7 @@ Rectangle { height: 30 radius: Theme.cornerRadius color: weatherArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) - + Ref { service: WeatherService }