MaterialIcon opsz clamp, UPower popout and widget changes, improved SysTray hover detection logic
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 32s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m13s

This commit is contained in:
2026-06-14 12:47:20 +02:00
parent 6034c4816d
commit 3f5cc2de07
15 changed files with 221 additions and 94 deletions
+3 -1
View File
@@ -13,6 +13,8 @@ StackView {
id: root
property int biggestWidth: 0
readonly property int itemHeight: 30
readonly property int panelRadius: ((itemHeight / 2) + Appearance.padding.small) * Appearance.rounding.scale
required property PopoutState popouts
property int rootWidth: 0
required property QsMenuHandle trayItem
@@ -100,7 +102,7 @@ StackView {
asynchronous: true
sourceComponent: Item {
implicitHeight: 30
implicitHeight: root.itemHeight
StateLayer {
enabled: item.modelData.enabled
+101 -57
View File
@@ -3,16 +3,97 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Services.UPower
import qs.Config
import qs.Helpers
import qs.Components
import qs.Modules
Item {
Column {
id: root
required property var wrapper
readonly property int panelRadius: ((profiles.height / 2) + Appearance.padding.small) * Appearance.rounding.scale
implicitHeight: profiles.implicitHeight
implicitWidth: profiles.implicitWidth
spacing: Appearance.spacing.normal
Loader {
active: Battery.isLaptop
CustomText {
text: qsTr("Remaining: %1%").arg(Math.round(UPower.displayDevice.percentage * 100))
}
}
CustomText {
function formatSeconds(s: int, fallback: string): string {
const day = Math.floor(s / 86400);
const hr = Math.floor(s / 3600) % 60;
const min = Math.floor(s / 60) % 60;
let comps = [];
if (day > 0)
comps.push(`${day} days`);
if (hr > 0)
comps.push(`${hr} hours`);
if (min > 0)
comps.push(`${min} mins`);
return comps.join(", ") || fallback;
}
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
text: Battery.isLaptop ? qsTr("Time %1: %2").arg(Battery.onBattery ? "remaining" : "until charged").arg(Battery.onBattery ? formatSeconds(Battery.timeToEmpty, "Calculating...") : formatSeconds(Battery.timeToFull, "Fully charged!")) : qsTr("Power profile: %1").arg(PowerProfile.toString(PowerProfiles.profile))
}
Loader {
active: PowerProfiles.degradationReason !== PerformanceDegradationReason.None
anchors.horizontalCenter: parent.horizontalCenter
asynchronous: true
height: active ? ((item as Item)?.implicitHeight ?? 0) : 0
sourceComponent: CustomRect {
color: DynamicColors.palette.m3error
implicitHeight: child.implicitHeight + Appearance.padding.large
implicitWidth: child.implicitWidth + Appearance.padding.larger * 2
radius: Appearance.rounding.large
Column {
id: child
anchors.centerIn: parent
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Appearance.spacing.small
MaterialIcon {
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -font.pointSize / 10
color: DynamicColors.palette.m3onError
text: "warning"
}
CustomText {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onError
font.family: Appearance.font.family.mono
text: qsTr("Performance Degraded")
}
MaterialIcon {
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -font.pointSize / 10
color: DynamicColors.palette.m3onError
text: "warning"
}
}
CustomText {
anchors.horizontalCenter: parent.horizontalCenter
color: DynamicColors.palette.m3onError
text: qsTr("Reason: %1").arg(PerformanceDegradationReason.toString(PowerProfiles.degradationReason))
}
}
}
}
CustomRect {
id: profiles
@@ -28,10 +109,9 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
// color: "transparent"
radius: (20 - Appearance.padding.small) * Appearance.rounding.scale
implicitHeight: indicator.height + Appearance.padding.extraSmall * 2
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + Appearance.padding.larger * 2 + Appearance.spacing.small * 8
radius: Appearance.rounding.full
CustomRect {
id: indicator
@@ -64,10 +144,7 @@ Item {
}
]
transitions: Transition {
AnchorAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline
AnchorAnim {
}
}
}
@@ -76,62 +153,28 @@ Item {
id: saver
anchors.left: parent.left
anchors.leftMargin: 25
anchors.top: parent.top
anchors.topMargin: 8
icon: "nest_eco_leaf"
anchors.leftMargin: Appearance.padding.extraSmall
anchors.verticalCenter: parent.verticalCenter
icon: "energy_savings_leaf"
profile: PowerProfile.PowerSaver
text: "Power Saver"
}
CustomText {
id: saverLabel
anchors.horizontalCenter: saver.horizontalCenter
anchors.top: saver.bottom
font.bold: true
text: saver.text
}
Profile {
id: balance
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 8
icon: "power_settings_new"
anchors.centerIn: parent
icon: "balance"
profile: PowerProfile.Balanced
text: "Balanced"
}
CustomText {
id: balanceLabel
anchors.horizontalCenter: balance.horizontalCenter
anchors.top: balance.bottom
font.bold: true
text: balance.text
}
Profile {
id: perf
anchors.right: parent.right
anchors.rightMargin: 25
anchors.top: parent.top
anchors.topMargin: 8
anchors.rightMargin: Appearance.padding.extraSmall
anchors.verticalCenter: parent.verticalCenter
icon: "bolt"
profile: PowerProfile.Performance
text: "Performance"
}
CustomText {
id: perfLabel
anchors.horizontalCenter: perf.horizontalCenter
anchors.top: perf.bottom
font.bold: true
text: perf.text
}
}
@@ -147,10 +190,9 @@ Item {
component Profile: Item {
required property string icon
required property int profile
required property string text
implicitHeight: icon.implicitHeight + 5 * 2
implicitWidth: icon.implicitHeight + 5 * 2
implicitHeight: icon.implicitHeight + Appearance.padding.small
implicitWidth: icon.implicitHeight + Appearance.padding.small
StateLayer {
color: profiles.current === parent.icon ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
@@ -168,10 +210,12 @@ Item {
color: profiles.current === text ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: profiles.current === text ? 1 : 0
font.pointSize: Appearance.font.size.large * 2
grade: DynamicColors.light ? 0 : (text === "balance" ? -25 : 0)
text: parent.icon
Behavior on fill {
Anim {
type: Anim.DefaultEffects
}
}
}
+27 -3
View File
@@ -2,12 +2,10 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.SystemTray
import qs.Components
import qs.Config
import qs.Modules.SysTray.Widgets
import qs.Modules.SysTray.Popouts
import qs.Modules
RowLayout {
@@ -17,11 +15,37 @@ RowLayout {
required property RowLayout loader
required property Wrapper popouts
function closestRowChild(row, x) {
let child = row.childAt(x, row.height / 2);
if (child)
return child;
let closest = null;
let closestDistance = Infinity;
for (let i = 0; i < row.children.length; ++i) {
let c = row.children[i];
if (!c.visible || c.width <= 0)
continue;
let centerX = c.x + c.width / 2;
let dist = Math.abs(x - centerX);
if (dist < closestDistance) {
closestDistance = dist;
closest = c;
}
}
return closest;
}
function getHoveredSubItem(localX, localY) {
let modPos = mapToItem(sysTrayMod, localX, localY);
if (sysTrayMod.contains(Qt.point(modPos.x, modPos.y))) {
let modRowPos = sysTrayMod.mapToItem(sysModRow, modPos.x, modPos.y);
let child = sysModRow.childAt(modRowPos.x, modRowPos.y);
let child = closestRowChild(sysModRow, modRowPos.x);
if (child) {
if (child.objectName === "audioWidget" && Config.barConfig.popouts.audio)
return {
+2
View File
@@ -14,6 +14,8 @@ RowLayout {
property color textColor: DynamicColors.palette.m3onSurface
MaterialIcon {
id: mic
Layout.alignment: Qt.AlignVCenter
animate: true
color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.textColor
+10 -14
View File
@@ -102,20 +102,16 @@ Item {
active: !Battery.isLaptop
anchors.centerIn: parent
sourceComponent: RowLayout {
id: upowerIcon
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
fill: 1
text: {
if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "nest_eco_leaf";
if (PowerProfiles.profile === PowerProfile.Performance)
return "bolt";
return "power_settings_new";
}
sourceComponent: MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
fill: 1
text: {
if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "energy_savings_leaf";
if (PowerProfiles.profile === PowerProfile.Performance)
return "bolt";
return "balance";
}
}
}