From 061bb2abeb58fb44cfe50b13d35b3b2eb28cf6e5 Mon Sep 17 00:00:00 2001 From: Runze Date: Wed, 6 Aug 2025 18:02:55 +0800 Subject: [PATCH] feat(bar): unify popup handling and improve layouts - Unified popup handling in ClockWidget, Resource, BatteryPopup, and WeatherBar using PanelWindow + LazyLoader for consistent positioning and compositor animations. - Replaced plain text with ColumnLayout and RowLayout where possible, adding MaterialSymbol icons for improved visual consistency with the overall desktop style. - Added Translation.tr() for bilingual (Chinese/English) support to avoid hardcoded strings. - Based on improvements from PR #1771 (mine) and PR #1773 (by @finjener), merged and refined into a more polished and practical solution. --- .../ii/modules/bar/BatteryIndicator.qml | 9 +- .../ii/modules/bar/BatteryPopup.qml | 2 +- .../quickshell/ii/modules/bar/ClockWidget.qml | 106 +++++++++++++---- .../quickshell/ii/modules/bar/Resource.qml | 111 ++++++++++++------ .../ii/modules/bar/weather/WeatherBar.qml | 26 ++-- .config/quickshell/translations/en_US.json | 21 +++- .config/quickshell/translations/zh_CN.json | 21 +++- 7 files changed, 224 insertions(+), 72 deletions(-) diff --git a/.config/quickshell/ii/modules/bar/BatteryIndicator.qml b/.config/quickshell/ii/modules/bar/BatteryIndicator.qml index 3cad9b2a..6815d6d7 100644 --- a/.config/quickshell/ii/modules/bar/BatteryIndicator.qml +++ b/.config/quickshell/ii/modules/bar/BatteryIndicator.qml @@ -95,11 +95,11 @@ MouseArea { } } - Loader { + LazyLoader { id: popupLoader active: root.containsMouse - sourceComponent: PanelWindow { + component: PanelWindow { id: popupWindow visible: true color: "transparent" @@ -112,7 +112,10 @@ MouseArea { implicitHeight: batteryPopup.implicitHeight margins { - left: root.mapToGlobal(Qt.point(0, 0)).x - batteryPopup.implicitWidth / 3 + left: root.mapToGlobal(Qt.point( + (root.width - batteryPopup.implicitWidth) / 2, + 0 + )).x top: root.mapToGlobal(Qt.point(0, root.height)).y - 30 } diff --git a/.config/quickshell/ii/modules/bar/BatteryPopup.qml b/.config/quickshell/ii/modules/bar/BatteryPopup.qml index ae0264c2..075b4bd5 100644 --- a/.config/quickshell/ii/modules/bar/BatteryPopup.qml +++ b/.config/quickshell/ii/modules/bar/BatteryPopup.qml @@ -53,7 +53,7 @@ Rectangle { var h = Math.floor(seconds / 3600); var m = Math.floor((seconds % 3600) / 60); if (h > 0) - return `${h}h ${m}m`; + return `${h}h, ${m}m`; else return `${m}m`; } diff --git a/.config/quickshell/ii/modules/bar/ClockWidget.qml b/.config/quickshell/ii/modules/bar/ClockWidget.qml index a2a70f6c..ac630ad5 100644 --- a/.config/quickshell/ii/modules/bar/ClockWidget.qml +++ b/.config/quickshell/ii/modules/bar/ClockWidget.qml @@ -17,7 +17,7 @@ Item { function getUpcomingTodos() { const unfinishedTodos = Todo.list.filter(function(item) { return !item.done; }) if (unfinishedTodos.length === 0) { - return "No pending tasks" + return Translation.tr("No pending tasks") } // Limit to first 5 todos to keep popup manageable @@ -27,21 +27,17 @@ Item { }).join('\n') if (unfinishedTodos.length > 5) { - todoText += `\n... and ${unfinishedTodos.length - 5} more` + todoText += `\n${Translation.tr("... and %1 more").arg(unfinishedTodos.length - 5)}` } return todoText } - // Generate popup content with date and upcoming todos - property string dateDetails: { - const todosSection = getUpcomingTodos() - return `${Qt.locale().toString(DateTime.clock.date, "dddd, MMMM dd, yyyy")} • ${DateTime.time} -Uptime: ${DateTime.uptime} - -📋 Upcoming Tasks: -${todosSection}` - } + // Popup Data + property string formattedDate: Qt.locale().toString(DateTime.clock.date, "dddd, MMMM dd, yyyy") + property string formattedTime: DateTime.time + property string formattedUptime: DateTime.uptime + property string todosSection: getUpcomingTodos() MouseArea { id: mouseArea @@ -54,37 +50,95 @@ ${todosSection}` id: popupLoader active: mouseArea.containsMouse - component: PopupWindow { + component: PanelWindow { id: popupWindow visible: true implicitWidth: datePopup.implicitWidth implicitHeight: datePopup.implicitHeight - anchor.item: root - anchor.edges: Edges.Top - anchor.rect.x: (root.implicitWidth - popupWindow.implicitWidth) / 2 - anchor.rect.y: Config.options.bar.bottom ? - (-datePopup.implicitHeight - 15) : - (root.implicitHeight + 15) color: "transparent" + exclusiveZone: 0 + + anchors.top: true + anchors.left: true + + margins { + left: root.mapToGlobal(Qt.point( + (root.width - datePopup.implicitWidth) / 2, + 0 + )).x + top: root.mapToGlobal(Qt.point(0, root.height)).y - 30 + } + + mask: Region { + item: datePopup + } Rectangle { id: datePopup readonly property real margin: 12 - implicitWidth: popupText.implicitWidth + margin * 2 - implicitHeight: popupText.implicitHeight + margin * 2 + implicitWidth: columnLayout.implicitWidth + margin * 2 + implicitHeight: columnLayout.implicitHeight + margin * 2 color: Appearance.colors.colLayer0 radius: Appearance.rounding.small border.width: 1 border.color: Appearance.colors.colLayer0Border - - StyledText { - id: popupText + clip: true + + ColumnLayout { + id: columnLayout anchors.centerIn: parent - font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colOnLayer0 - text: dateDetails + spacing: 8 + + // Date + Time row + RowLayout { + spacing: 5 + Layout.fillWidth: true + StyledText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + color: Appearance.colors.colOnLayer1 + text: `${root.formattedDate} • ${root.formattedTime}` + } + } + + // Uptime row + RowLayout { + spacing: 5 + Layout.fillWidth: true + MaterialSymbol { text: "timelapse"; color: Appearance.m3colors.m3onSecondaryContainer } + StyledText { text: Translation.tr("Uptime:"); color: Appearance.colors.colOnLayer1 } + StyledText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + color: Appearance.colors.colOnLayer1 + text: root.formattedUptime + } + } + + // Upcoming tasks row + ColumnLayout { + spacing: 2 + Layout.fillWidth: true + + RowLayout { + spacing: 5 + Layout.fillWidth: true + MaterialSymbol { text: "checklist"; color: Appearance.m3colors.m3onSecondaryContainer } + StyledText { text: Translation.tr("Upcoming Tasks:"); color: Appearance.colors.colOnLayer1 } + } + + StyledText { + Layout.fillWidth: true + topPadding: 5 + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + color: Appearance.colors.colOnLayer1 + text: root.todosSection + } + } } } + } } diff --git a/.config/quickshell/ii/modules/bar/Resource.qml b/.config/quickshell/ii/modules/bar/Resource.qml index e6965dff..cf6a0365 100644 --- a/.config/quickshell/ii/modules/bar/Resource.qml +++ b/.config/quickshell/ii/modules/bar/Resource.qml @@ -7,6 +7,7 @@ import QtQuick.Layouts import Quickshell Item { + id: root required property string iconName required property double percentage property bool shown: true @@ -21,28 +22,41 @@ Item { } // Generate tooltip content based on resource type - property string tooltipContent: { + property var tooltipData: { switch(iconName) { case "memory": - return `Memory Usage -Used: ${formatKB(ResourceUsage.memoryUsed)} -Free: ${formatKB(ResourceUsage.memoryFree)} -Total: ${formatKB(ResourceUsage.memoryTotal)} -Usage: ${Math.round(ResourceUsage.memoryUsedPercentage * 100)}%` + return [ + { icon: "memory", label: Translation.tr("Memory Usage"), value: "" }, + { icon: "storage", label: Translation.tr("Used:"), value: formatKB(ResourceUsage.memoryUsed) }, + { icon: "check_circle", label: Translation.tr("Free:"), value: formatKB(ResourceUsage.memoryFree) }, + { icon: "dns", label: Translation.tr("Total:"), value: formatKB(ResourceUsage.memoryTotal) }, + { icon: "percent", label: Translation.tr("Usage:"), value: `${Math.round(ResourceUsage.memoryUsedPercentage * 100)}%` } + ] case "swap_horiz": - return ResourceUsage.swapTotal > 0 ? - `Swap Usage -Used: ${formatKB(ResourceUsage.swapUsed)} -Free: ${formatKB(ResourceUsage.swapFree)} -Total: ${formatKB(ResourceUsage.swapTotal)} -Usage: ${Math.round(ResourceUsage.swapUsedPercentage * 100)}%` : - "Swap: Not configured" + return ResourceUsage.swapTotal > 0 ? + [ + { icon: "swap_horiz", label: Translation.tr("Swap Usage"), value: "" }, + { icon: "storage", label: Translation.tr("Used:"), value: formatKB(ResourceUsage.swapUsed) }, + { icon: "check_circle", label: Translation.tr("Free:"), value: formatKB(ResourceUsage.swapFree) }, + { icon: "dns", label: Translation.tr("Total:"), value: formatKB(ResourceUsage.swapTotal) }, + { icon: "percent", label: Translation.tr("Usage:"), value: `${Math.round(ResourceUsage.swapUsedPercentage * 100)}%` } + ] : + [ + { icon: "swap_horiz", label: Translation.tr("Swap:"), value: Translation.tr("Not configured") } + ] case "settings_slow_motion": - return `CPU Usage -Current: ${Math.round(ResourceUsage.cpuUsage * 100)}% -Load: ${ResourceUsage.cpuUsage > 0.8 ? "High" : ResourceUsage.cpuUsage > 0.5 ? "Medium" : "Low"}` + return [ + { icon: "settings_slow_motion", label: Translation.tr("CPU Usage"), value: "" }, + { icon: "bolt", label: Translation.tr("Current:"), value: `${Math.round(ResourceUsage.cpuUsage * 100)}%` }, + { icon: "speed", label: Translation.tr("Load:"), value: ResourceUsage.cpuUsage > 0.8 ? + Translation.tr("High") : + ResourceUsage.cpuUsage > 0.5 ? Translation.tr("Medium") : Translation.tr("Low") + } + ] default: - return "System Resource" + return [ + { icon: "info", label: Translation.tr("System Resource"), value: "" } + ] } } @@ -57,35 +71,66 @@ Load: ${ResourceUsage.cpuUsage > 0.8 ? "High" : ResourceUsage.cpuUsage > 0.5 ? " id: popupLoader active: mouseArea.containsMouse - component: PopupWindow { + component: PanelWindow { id: popupWindow visible: true + + color: "transparent" + exclusiveZone: 0 + anchors.top: true + anchors.left: true + implicitWidth: resourcePopup.implicitWidth implicitHeight: resourcePopup.implicitHeight - anchor.item: root - anchor.edges: Edges.Top - anchor.rect.x: (root.implicitWidth - popupWindow.implicitWidth) / 2 - anchor.rect.y: Config.options.bar.bottom ? - (-resourcePopup.implicitHeight - 15) : - (root.implicitHeight + 15) - color: "transparent" + + margins { + left: root.mapToGlobal(Qt.point( + (root.width - resourcePopup.implicitWidth) / 2, + 0 + )).x + top: root.mapToGlobal(Qt.point(0, root.height)).y - 30 + } + Rectangle { id: resourcePopup readonly property real margin: 10 - implicitWidth: popupText.implicitWidth + margin * 2 - implicitHeight: popupText.implicitHeight + margin * 2 + implicitWidth: columnLayout.implicitWidth + margin * 2 + implicitHeight: columnLayout.implicitHeight + margin * 2 color: Appearance.colors.colLayer0 radius: Appearance.rounding.small border.width: 1 border.color: Appearance.colors.colLayer0Border - - StyledText { - id: popupText + clip: true + + ColumnLayout { + id: columnLayout anchors.centerIn: parent - font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colOnLayer0 - text: tooltipContent + spacing: 6 + + Repeater { + model: root.tooltipData + delegate: RowLayout { + spacing: 5 + Layout.fillWidth: true + + MaterialSymbol { + text: modelData.icon + color: Appearance.m3colors.m3onSecondaryContainer + } + StyledText { + text: modelData.label + color: Appearance.colors.colOnLayer1 + } + StyledText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + visible: modelData.value !== "" + color: Appearance.colors.colOnLayer1 + text: modelData.value + } + } + } } } } diff --git a/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml b/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml index 363d9ba5..d3ca95a7 100644 --- a/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml +++ b/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml @@ -40,18 +40,30 @@ MouseArea { id: popupLoader active: root.containsMouse - component: PopupWindow { + component: PanelWindow { id: popupWindow visible: true implicitWidth: weatherPopup.implicitWidth implicitHeight: weatherPopup.implicitHeight - anchor.item: root - anchor.edges: Edges.Top - anchor.rect.x: (root.implicitWidth - popupWindow.implicitWidth) / 2 - anchor.rect.y: Config.options.bar.bottom ? - (-weatherPopup.implicitHeight - 15) : - (root.implicitHeight + 15 ) + color: "transparent" + exclusiveZone: 0 + + anchors.top: true + anchors.left: true + + margins { + left: root.mapToGlobal(Qt.point( + (root.width - weatherPopup.implicitWidth) / 2, + 0 + )).x + top: root.mapToGlobal(Qt.point(0, root.height)).y - 25 + } + + mask: Region { + item: weatherPopup + } + WeatherPopup { id: weatherPopup } diff --git a/.config/quickshell/translations/en_US.json b/.config/quickshell/translations/en_US.json index 474d4a4f..9bfaeb9e 100644 --- a/.config/quickshell/translations/en_US.json +++ b/.config/quickshell/translations/en_US.json @@ -316,5 +316,24 @@ "Time to empty:": "Time to empty:", "Fully charged": "Fully charged", "Charging:": "Charging:", - "Discharging:": "Discharging:" + "Discharging:": "Discharging:", + "Uptime:": "Uptime:", + "Upcoming Tasks:": "Upcoming Tasks:", + "No pending tasks": "No pending tasks", + "... and %1 more": "... and %1 more", + "Memory Usage": "Memory Usage", + "Used:": "Used:", + "Free:": "Free:", + "Total:": "Total:", + "Usage:": "Usage:", + "Swap Usage": "Swap Usage", + "Swap:": "Swap:", + "Not configured": "Not configured", + "CPU Usage": "CPU Usage", + "Current:": "Current:", + "Load:": "Load:", + "High": "High", + "Medium": "Medium", + "Low": "Low", + "System Resource": "System Resource" } \ No newline at end of file diff --git a/.config/quickshell/translations/zh_CN.json b/.config/quickshell/translations/zh_CN.json index 216e3f94..02540dd7 100644 --- a/.config/quickshell/translations/zh_CN.json +++ b/.config/quickshell/translations/zh_CN.json @@ -316,5 +316,24 @@ "Time to empty:": "距离耗尽:", "Fully charged": "已充满电", "Charging:": "充电功率:", - "Discharging:": "放电功率:" + "Discharging:": "放电功率:", + "Uptime:": "运行时间:", + "Upcoming Tasks:": "待办任务:", + "No pending tasks": "没有待办任务", + "... and %1 more": "... 还有 %1 个", + "Memory Usage": "内存使用情况", + "Used:": "已用:", + "Free:": "可用:", + "Total:": "总计:", + "Usage:": "占比:", + "Swap Usage": "交换区使用情况", + "Swap:": "交换区:", + "Not configured": "未配置", + "CPU Usage": "CPU 使用情况", + "Current:": "当前占比:", + "Load:": "负载:", + "High": "高", + "Medium": "中", + "Low": "低", + "System Resource": "系统资源" }