diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 9492961..9b59c1c 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -6,118 +6,112 @@ import qs.Services import qs.Widgets Variants { - model: Quickshell.screens + model: Quickshell.screens - delegate: PanelWindow { - id: root + delegate: PanelWindow { + id: root - required property ShellScreen modelData - readonly property real scaling: Scaling.scale(screen) + required property ShellScreen modelData + readonly property real scaling: Scaling.scale(screen) - screen: modelData - implicitHeight: Style.barHeight * scaling - color: "transparent" - visible: Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0) - - anchors { - top: true - left: true - right: true - } - - Item { - anchors.fill: parent - clip: true - - // Background fill - Rectangle { - id: bar - - anchors.fill: parent - color: Colors.backgroundPrimary - layer.enabled: true - } - - Row { - id: leftSection - - height: parent.height - anchors.left: parent.left - anchors.leftMargin: Style.marginSmall * scaling - anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginSmall * scaling - - NText { - text: screen.name - anchors.verticalCenter: parent.verticalCenter - } - - } - - Row { - id: centerSection - - height: parent.height - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginSmall * scaling - - Workspace { - } - - } - - Row { - id: rightSection - - height: parent.height - anchors.right: bar.right - anchors.rightMargin: Style.marginSmall * scaling - anchors.verticalCenter: bar.verticalCenter - spacing: Style.marginSmall * scaling - - Battery { - anchors.verticalCenter: parent.verticalCenter - } - - Clock { - anchors.verticalCenter: parent.verticalCenter - } - - NIconButton { - id: demoPanelToggle - - icon: "experiment" - anchors.verticalCenter: parent.verticalCenter - onClicked: function() { - demoPanel.isLoaded = !demoPanel.isLoaded; - } - } - - NIconButton { - id: sidePanelToggle - - icon: "widgets" - anchors.verticalCenter: parent.verticalCenter - onClicked: function() { - // Map this button's center to the screen and open the side panel below it - const localCenterX = width / 2; - const localCenterY = height / 2; - const globalPoint = mapToItem(null, localCenterX, localCenterY); - if (sidePanel.isLoaded) - sidePanel.isLoaded = false; - else if (sidePanel.openAt) - sidePanel.openAt(globalPoint.x, screen); - else - // Fallback: toggle if API unavailable - sidePanel.isLoaded = true; - } - } - - } - - } + screen: modelData + implicitHeight: Style.barHeight * scaling + color: "transparent" + visible: Settings.data.bar.monitors.includes(modelData.name) + || (Settings.data.bar.monitors.length === 0) + anchors { + top: true + left: true + right: true } + Item { + anchors.fill: parent + clip: true + + // Background fill + Rectangle { + id: bar + + anchors.fill: parent + color: Colors.backgroundPrimary + layer.enabled: true + } + + Row { + id: leftSection + + height: parent.height + anchors.left: parent.left + anchors.leftMargin: Style.marginSmall * scaling + anchors.verticalCenter: parent.verticalCenter + spacing: Style.marginSmall * scaling + + NText { + text: screen.name + anchors.verticalCenter: parent.verticalCenter + } + } + + Row { + id: centerSection + + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + spacing: Style.marginSmall * scaling + + Workspace {} + } + + Row { + id: rightSection + + height: parent.height + anchors.right: bar.right + anchors.rightMargin: Style.marginSmall * scaling + anchors.verticalCenter: bar.verticalCenter + spacing: Style.marginSmall * scaling + + Battery { + anchors.verticalCenter: parent.verticalCenter + } + + Clock { + anchors.verticalCenter: parent.verticalCenter + } + + NIconButton { + id: demoPanelToggle + + icon: "experiment" + anchors.verticalCenter: parent.verticalCenter + onClicked: function () { + demoPanel.isLoaded = !demoPanel.isLoaded + } + } + + NIconButton { + id: sidePanelToggle + + icon: "widgets" + anchors.verticalCenter: parent.verticalCenter + onClicked: function () { + // Map this button's center to the screen and open the side panel below it + const localCenterX = width / 2 + const localCenterY = height / 2 + const globalPoint = mapToItem(null, localCenterX, localCenterY) + if (sidePanel.isLoaded) + sidePanel.isLoaded = false + else if (sidePanel.openAt) + sidePanel.openAt(globalPoint.x, screen) + else + // Fallback: toggle if API unavailable + sidePanel.isLoaded = true + } + } + } + } + } } diff --git a/Modules/Bar/Battery.qml b/Modules/Bar/Battery.qml index 7b1f33a..4342abd 100644 --- a/Modules/Bar/Battery.qml +++ b/Modules/Bar/Battery.qml @@ -7,113 +7,117 @@ import qs.Widgets import "../../Helpers/Duration.js" as Duration Item { - id: root + id: root - // Test mode - property bool testMode: true - property int testPercent: 49 - property bool testCharging: false + // Test mode + property bool testMode: true + property int testPercent: 49 + property bool testCharging: false - property var battery: UPower.displayDevice - property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent) - property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0) - property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false) - property bool show: isReady && percent > 0 + property var battery: UPower.displayDevice + property bool isReady: testMode ? true : (battery && battery.ready + && battery.isLaptopBattery + && battery.isPresent) + property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0) + property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false) + property bool show: isReady && percent > 0 - // Choose icon based on charge and charging state - function batteryIcon() { - if (!show) - return ""; + // Choose icon based on charge and charging state + function batteryIcon() { + if (!show) + return "" - if (charging) - return "battery_android_bolt"; + if (charging) + return "battery_android_bolt" - if (percent >= 95) - return "battery_android_full"; + if (percent >= 95) + return "battery_android_full" - // Hardcoded battery symbols - if (percent >= 85) - return "battery_android_6"; - if (percent >= 70) - return "battery_android_5"; - if (percent >= 55) - return "battery_android_4"; - if (percent >= 40) - return "battery_android_3"; - if (percent >= 25) - return "battery_android_2"; - if (percent >= 10) - return "battery_android_1"; - if (percent >= 0) - return "battery_android_0"; + // Hardcoded battery symbols + if (percent >= 85) + return "battery_android_6" + if (percent >= 70) + return "battery_android_5" + if (percent >= 55) + return "battery_android_4" + if (percent >= 40) + return "battery_android_3" + if (percent >= 25) + return "battery_android_2" + if (percent >= 10) + return "battery_android_1" + if (percent >= 0) + return "battery_android_0" + } + + visible: testMode || (isReady && battery.isLaptopBattery) + width: pill.width + height: pill.height + + NPill { + id: pill + icon: root.batteryIcon() + text: Math.round(root.percent) + "%" + pillColor: Colors.surfaceVariant + iconCircleColor: Colors.accentPrimary + iconTextColor: Colors.backgroundPrimary + textColor: charging ? Colors.accentPrimary : Colors.textPrimary + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + pill.showDelayed() + batteryTooltip.show() + } + onExited: { + pill.hide() + batteryTooltip.show() + } } - - visible: testMode || (isReady && battery.isLaptopBattery) - width: pill.width - height: pill.height - - NPill { - id: pill - icon: root.batteryIcon() - text: Math.round(root.percent) + "%" - pillColor: Colors.surfaceVariant - iconCircleColor: Colors.accentPrimary - iconTextColor: Colors.backgroundPrimary - textColor: charging ? Colors.accentPrimary : Colors.textPrimary - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: { - pill.showDelayed(); - batteryTooltip.show(); - } - onExited: { - pill.hide(); - batteryTooltip.show(); - } + NTooltip { + id: batteryTooltip + positionAbove: false + target: pill + delay: Style.tooltipDelayLong + text: { + let lines = [] + if (!root.isReady) { + return "" } - NTooltip { - id: batteryTooltip - positionAbove: false - target: pill - delay: Style.tooltipDelayLong - text: { - let lines = []; - if (!root.isReady) { - return ""; - } - - if (root.battery.timeToEmpty > 0) { - lines.push("Time left: " + Time.formatVagueHumanReadableTime(root.battery.timeToEmpty)); - } - - if (root.battery.timeToFull > 0) { - lines.push("Time until full: " + Time.formatVagueHumanReadableTime(root.battery.timeToFull)); - } - - if (root.battery.changeRate !== undefined) { - const rate = root.battery.changeRate; - if (rate > 0) { - lines.push(root.charging ? "Charging rate: " + rate.toFixed(2) + " W" : "Discharging rate: " + rate.toFixed(2) + " W"); - } - else if (rate < 0) { - lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W"); - } - else { - lines.push("Estimating..."); - } - } - else { - lines.push(root.charging ? "Charging" : "Discharging"); - } - - - if (root.battery.healthPercentage !== undefined && root.battery.healthPercentage > 0) { - lines.push("Health: " + Math.round(root.battery.healthPercentage) + "%"); - } - return lines.join("\n"); - } + if (root.battery.timeToEmpty > 0) { + lines.push("Time left: " + Time.formatVagueHumanReadableTime( + root.battery.timeToEmpty)) } + + if (root.battery.timeToFull > 0) { + lines.push("Time until full: " + Time.formatVagueHumanReadableTime( + root.battery.timeToFull)) + } + + if (root.battery.changeRate !== undefined) { + const rate = root.battery.changeRate + if (rate > 0) { + lines.push( + root.charging ? "Charging rate: " + rate.toFixed( + 2) + " W" : "Discharging rate: " + rate.toFixed( + 2) + " W") + } else if (rate < 0) { + lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W") + } else { + lines.push("Estimating...") + } + } else { + lines.push(root.charging ? "Charging" : "Discharging") + } + + if (root.battery.healthPercentage !== undefined + && root.battery.healthPercentage > 0) { + lines.push("Health: " + Math.round( + root.battery.healthPercentage) + "%") + } + return lines.join("\n") + } } -} \ No newline at end of file + } +} diff --git a/Widgets/NPill.qml b/Widgets/NPill.qml index 71aaa06..8448e17 100644 --- a/Widgets/NPill.qml +++ b/Widgets/NPill.qml @@ -3,200 +3,202 @@ import QtQuick.Controls import qs.Services Item { - id: revealPill + id: root - readonly property real scaling: Scaling.scale(screen) + readonly property real scaling: Scaling.scale(screen) - property string icon: "" - property string text: "" - property color pillColor: Colors.surfaceVariant - property color textColor: Colors.textPrimary - property color iconCircleColor: Colors.accentPrimary - property color iconTextColor: Colors.backgroundPrimary - property color collapsedIconColor: Colors.textPrimary - property real sizeMultiplier: 0.8 - property int pillHeight: Style.baseWidgetSize * sizeMultiplier * scaling - property int iconSize: Style.baseWidgetSize * sizeMultiplier * scaling - property int pillPaddingHorizontal: 14 * scaling - property bool autoHide: false + property string icon: "" + property string text: "" + property color pillColor: Colors.surfaceVariant + property color textColor: Colors.textPrimary + property color iconCircleColor: Colors.accentPrimary + property color iconTextColor: Colors.backgroundPrimary + property color collapsedIconColor: Colors.textPrimary + property real sizeMultiplier: 0.8 + property bool autoHide: false - // Internal state - property bool showPill: false - property bool shouldAnimateHide: false + // Internal state + property bool showPill: false + property bool shouldAnimateHide: false - // Exposed width logic - readonly property int pillOverlap: iconSize / 2 - readonly property int maxPillWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap) + // Exposed width logic + readonly property int pillHeight: Style.baseWidgetSize * sizeMultiplier * scaling + readonly property int iconSize: Style.baseWidgetSize * sizeMultiplier * scaling + readonly property int pillPaddingHorizontal: 14 * scaling + readonly property int pillOverlap: iconSize * 0.5 + readonly property int maxPillWidth: Math.max( + 1, textItem.implicitWidth + + pillPaddingHorizontal * 2 + pillOverlap) - signal shown - signal hidden + signal shown + signal hidden - width: iconSize + (showPill ? maxPillWidth - pillOverlap : 0) + width: iconSize + (showPill ? maxPillWidth - pillOverlap : 0) + height: pillHeight + + Rectangle { + id: pill + width: showPill ? maxPillWidth : 1 height: pillHeight + x: (iconCircle.x + iconCircle.width / 2) - width + opacity: showPill ? 1 : 0 + color: pillColor + topLeftRadius: pillHeight * 0.5 + bottomLeftRadius: pillHeight * 0.5 + anchors.verticalCenter: parent.verticalCenter - Rectangle { - id: pill - width: showPill ? maxPillWidth : 1 - height: pillHeight - x: (iconCircle.x + iconCircle.width / 2) - width - opacity: showPill ? 1 : 0 - color: pillColor - topLeftRadius: pillHeight / 2 - bottomLeftRadius: pillHeight / 2 - anchors.verticalCenter: parent.verticalCenter - - Text { - id: textItem - anchors.centerIn: parent - text: revealPill.text - font.pointSize: Colors.fontSizeSmall * scaling - font.family: Settings.data.ui.fontFamily - font.weight: Font.Bold - color: textColor - visible: showPill - } - - Behavior on width { - enabled: showAnim.running || hideAnim.running - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutCubic - } - } - Behavior on opacity { - enabled: showAnim.running || hideAnim.running - NumberAnimation { - duration: 250 - easing.type: Easing.OutCubic - } - } + Text { + id: textItem + anchors.centerIn: parent + text: root.text + font.pointSize: Colors.fontSizeSmall * scaling + font.family: Settings.data.ui.fontFamily + font.weight: Font.Bold + color: textColor + visible: showPill } - Rectangle { - id: iconCircle - width: iconSize - height: iconSize - radius: width / 2 - color: showPill ? iconCircleColor : "transparent" - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right + Behavior on width { + enabled: showAnim.running || hideAnim.running + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + Behavior on opacity { + enabled: showAnim.running || hideAnim.running + NumberAnimation { + duration: 250 + easing.type: Easing.OutCubic + } + } + } - Behavior on color { - ColorAnimation { - duration: 200 - easing.type: Easing.InOutQuad - } - } + Rectangle { + id: iconCircle + width: iconSize + height: iconSize + radius: width / 2 + color: showPill ? iconCircleColor : "transparent" + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right - Text { - anchors.centerIn: parent - font.family: showPill ? "Material Symbols Rounded" : "Material Symbols Outlined" - font.pointSize: Colors.fontSizeSmall * scaling - text: revealPill.icon - color: showPill ? iconTextColor : collapsedIconColor - } + Behavior on color { + ColorAnimation { + duration: 200 + easing.type: Easing.InOutQuad + } } - ParallelAnimation { - id: showAnim - running: false - NumberAnimation { - target: pill - property: "width" - from: 1 - to: maxPillWidth - duration: Style.animationNormal - easing.type: Easing.OutCubic - } - NumberAnimation { - target: pill - property: "opacity" - from: 0 - to: 1 - duration: Style.animationNormal - easing.type: Easing.OutCubic - } - onStarted: { - showPill = true; - } - onStopped: { - delayedHideAnim.start(); - shown(); - } + Text { + anchors.centerIn: parent + font.family: showPill ? "Material Symbols Rounded" : "Material Symbols Outlined" + font.pointSize: Colors.fontSizeSmall * scaling + text: root.icon + color: showPill ? iconTextColor : collapsedIconColor } + } - SequentialAnimation { - id: delayedHideAnim - running: false - PauseAnimation { - duration: 2500 - } - ScriptAction { - script: if (shouldAnimateHide) + ParallelAnimation { + id: showAnim + running: false + NumberAnimation { + target: pill + property: "width" + from: 1 + to: maxPillWidth + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + NumberAnimation { + target: pill + property: "opacity" + from: 0 + to: 1 + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + onStarted: { + showPill = true + } + onStopped: { + delayedHideAnim.start() + shown() + } + } + + SequentialAnimation { + id: delayedHideAnim + running: false + PauseAnimation { + duration: 2500 + } + ScriptAction { + script: if (shouldAnimateHide) hideAnim.start() - } } + } - ParallelAnimation { - id: hideAnim - running: false - NumberAnimation { - target: pill - property: "width" - from: maxPillWidth - to: 1 - duration: Style.animationNormal - easing.type: Easing.InCubic - } - NumberAnimation { - target: pill - property: "opacity" - from: 1 - to: 0 - duration: Style.animationNormal - easing.type: Easing.InCubic - } - onStopped: { - showPill = false; - shouldAnimateHide = false; - hidden(); - } + ParallelAnimation { + id: hideAnim + running: false + NumberAnimation { + target: pill + property: "width" + from: maxPillWidth + to: 1 + duration: Style.animationNormal + easing.type: Easing.InCubic } + NumberAnimation { + target: pill + property: "opacity" + from: 1 + to: 0 + duration: Style.animationNormal + easing.type: Easing.InCubic + } + onStopped: { + showPill = false + shouldAnimateHide = false + hidden() + } + } - function show() { - if (!showPill) { - shouldAnimateHide = autoHide; - showAnim.start(); - } else { - hideAnim.stop(); - delayedHideAnim.restart(); - } + function show() { + if (!showPill) { + shouldAnimateHide = autoHide + showAnim.start() + } else { + hideAnim.stop() + delayedHideAnim.restart() } + } - function hide() { - if (showPill) { - hideAnim.start(); - } - showTimer.stop(); + function hide() { + if (showPill) { + hideAnim.start() } + showTimer.stop() + } - function showDelayed() { - if (!showPill) { - shouldAnimateHide = autoHide; - showTimer.start(); - } else { - hideAnim.stop(); - delayedHideAnim.restart(); - } + function showDelayed() { + if (!showPill) { + shouldAnimateHide = autoHide + showTimer.start() + } else { + hideAnim.stop() + delayedHideAnim.restart() } + } - Timer { - id: showTimer - interval: Style.pillDelay - onTriggered: { - if (!showPill) { - showAnim.start(); - } - } + Timer { + id: showTimer + interval: Style.pillDelay + onTriggered: { + if (!showPill) { + showAnim.start() + } } -} \ No newline at end of file + } +}