feat: GPU切换确认弹窗、按钮选中状态、文字对比度优化

- GPU切换按钮:核显厂商自动识别(AMD/Intel),当前模式高亮选中
- 切换确认覆盖层:15秒倒计时圆环,确认/取消按钮,超时自动取消
- 文字对比度提升:primaryText 0.95, secondaryText 0.8, tertiaryText 0.65
- 图表刻度文字 0.25->0.5,网格线 0.06->0.1
- 删除卡片内频率/功耗/风扇行,驱动名版本号两端对齐
- 卡片高度78,进度条宽度180,弹窗宽度480
- 翻译更新:切换确认相关8条新增
This commit is contained in:
2026-07-14 21:50:35 +08:00
parent f7c6801793
commit 64408bf842
3 changed files with 329 additions and 38 deletions
+249 -38
View File
@@ -26,6 +26,55 @@ AppletItem {
readonly property string currentDriver: applet ? (applet.currentDriver || qsTr("Unknown")) : qsTr("Unknown") readonly property string currentDriver: applet ? (applet.currentDriver || qsTr("Unknown")) : qsTr("Unknown")
readonly property string gpuMode: applet ? (applet.gpuMode || "") : "" readonly property string gpuMode: applet ? (applet.gpuMode || "") : ""
// 当前 PRIME 模式(nvidia/on-demand/intel
readonly property string currentPrimeMode: {
if (root.gpuMode !== "PRIME" || !root.applet || !root.applet.gpuPrimary) return ""
var lines = root.deviceInfo.split("\n").filter(function(l) { return l.trim().length > 0 })
for (var i = 0; i < lines.length; i++) {
if (/nvidia/i.test(lines[i]) && root.applet.gpuPrimary[i] === "true") {
return "nvidia"
}
}
return "on-demand"
}
// GPU 切换确认
property string pendingSwitchMode: ""
property int switchCountdown: 15
// 切换模式描述
function switchModeDesc(mode) {
if (mode === "nvidia") return qsTr("Switch to NVIDIA discrete GPU for maximum performance. Higher power consumption.")
if (mode === "intel") return qsTr("Switch to integrated GPU for power saving. NVIDIA GPU will be disabled.")
if (mode === "on-demand") return qsTr("Hybrid mode: integrated GPU for display, NVIDIA for on-demand rendering.")
return ""
}
function switchModeLabel(mode) {
if (mode === "nvidia") return "NVIDIA"
if (mode === "intel") return getIgpuVendorName()
if (mode === "on-demand") return qsTr("On-Demand")
return mode
}
function requestSwitch(mode) {
root.pendingSwitchMode = mode
root.switchCountdown = 15
switchConfirmTimer.start()
}
function confirmSwitch() {
if (root.pendingSwitchMode && root.applet) {
root.applet.switchGpu(root.pendingSwitchMode)
}
cancelSwitch()
}
function cancelSwitch() {
root.pendingSwitchMode = ""
switchConfirmTimer.stop()
}
// 所有 GPU 中的最高温度,用于任务栏图标变色 // 所有 GPU 中的最高温度,用于任务栏图标变色
readonly property int maxTemp: { readonly property int maxTemp: {
if (!applet || !applet.gpuStats) return -1 if (!applet || !applet.gpuStats) return -1
@@ -45,9 +94,9 @@ AppletItem {
} }
property Palette basePalette: DockPalette.iconTextPalette property Palette basePalette: DockPalette.iconTextPalette
readonly property color primaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.9) readonly property color primaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.95)
readonly property color secondaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.65) readonly property color secondaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.8)
readonly property color tertiaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.5) readonly property color tertiaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.65)
readonly property color cardBackground: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.06) readonly property color cardBackground: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.06)
readonly property color cardBorder: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.1) readonly property color cardBorder: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.1)
readonly property color accentBlue: Qt.rgba(20 / 255, 80 / 255, 160 / 255, 1) readonly property color accentBlue: Qt.rgba(20 / 255, 80 / 255, 160 / 255, 1)
@@ -92,6 +141,19 @@ AppletItem {
} }
} }
// 切换确认倒计时
Timer {
id: switchConfirmTimer
interval: 1000
repeat: true
onTriggered: {
root.switchCountdown--
if (root.switchCountdown <= 0) {
root.cancelSwitch()
}
}
}
Timer { Timer {
id: toolTipShowTimer id: toolTipShowTimer
interval: 50 interval: 50
@@ -522,7 +584,7 @@ AppletItem {
} }
Rectangle { Rectangle {
Layout.preferredWidth: 140 Layout.preferredWidth: 180
height: 4 height: 4
color: root.cardBorder color: root.cardBorder
radius: 2 radius: 2
@@ -667,12 +729,12 @@ AppletItem {
// 1. 绘制网格线和刻度 // 1. 绘制网格线和刻度
ctx.font = "9px sans-serif" ctx.font = "9px sans-serif"
ctx.fillStyle = Qt.rgba(1, 1, 1, 0.25) ctx.fillStyle = Qt.rgba(1, 1, 1, 0.5)
var temps = [40, 60, 80] var temps = [40, 60, 80]
for (var ti = 0; ti < temps.length; ti++) { for (var ti = 0; ti < temps.length; ti++) {
var t = temps[ti] var t = temps[ti]
var y = h - ((t - minT) / (maxT - minT)) * (h - pad * 2) - pad var y = h - ((t - minT) / (maxT - minT)) * (h - pad * 2) - pad
ctx.strokeStyle = Qt.rgba(1, 1, 1, 0.06) ctx.strokeStyle = Qt.rgba(1, 1, 1, 0.1)
ctx.lineWidth = 1 ctx.lineWidth = 1
ctx.setLineDash(t === 80 ? [3, 3] : []) ctx.setLineDash(t === 80 ? [3, 3] : [])
ctx.beginPath() ctx.beginPath()
@@ -806,67 +868,204 @@ AppletItem {
spacing: 8 spacing: 8
visible: root.gpuMode === "PRIME" visible: root.gpuMode === "PRIME"
// 核显按钮
Button { Button {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 30 Layout.preferredHeight: 32
font.pixelSize: 11 font.pixelSize: 11
text: qsTr("NVIDIA") text: getIgpuVendorName()
background: Rectangle { background: Rectangle {
color: parent.hovered ? root.accentBlueLight : root.cardBackground color: root.currentPrimeMode === "intel" ? root.accentBlue : (parent.hovered ? root.accentBlueLight : root.cardBackground)
radius: 6 radius: 6
border.width: 1 border.width: 1
border.color: root.cardBorder border.color: root.currentPrimeMode === "intel" ? root.accentBlue : root.cardBorder
} }
contentItem: Text { contentItem: Text {
text: parent.text text: parent.text
color: root.accentBlue color: root.currentPrimeMode === "intel" ? "white" : root.accentBlue
font.pixelSize: 11 font.pixelSize: 11
font.bold: root.currentPrimeMode === "intel"
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
onClicked: if (root.applet) root.applet.switchGpu("nvidia") onClicked: root.requestSwitch("intel")
} }
// 按需按钮
Button { Button {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 30 Layout.preferredHeight: 32
font.pixelSize: 11
text: qsTr("Intel")
background: Rectangle {
color: parent.hovered ? root.accentBlueLight : root.cardBackground
radius: 6
border.width: 1
border.color: root.cardBorder
}
contentItem: Text {
text: parent.text
color: root.accentBlue
font.pixelSize: 11
horizontalAlignment: Text.AlignHCenter
}
onClicked: if (root.applet) root.applet.switchGpu("intel")
}
Button {
Layout.fillWidth: true
Layout.preferredHeight: 30
font.pixelSize: 11 font.pixelSize: 11
text: qsTr("On-Demand") text: qsTr("On-Demand")
background: Rectangle { background: Rectangle {
color: parent.hovered ? root.accentBlueLight : root.cardBackground color: root.currentPrimeMode === "on-demand" ? root.accentBlue : (parent.hovered ? root.accentBlueLight : root.cardBackground)
radius: 6 radius: 6
border.width: 1 border.width: 1
border.color: root.cardBorder border.color: root.currentPrimeMode === "on-demand" ? root.accentBlue : root.cardBorder
} }
contentItem: Text { contentItem: Text {
text: parent.text text: parent.text
color: root.accentBlue color: root.currentPrimeMode === "on-demand" ? "white" : root.accentBlue
font.pixelSize: 11 font.pixelSize: 11
font.bold: root.currentPrimeMode === "on-demand"
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
onClicked: if (root.applet) root.applet.switchGpu("on-demand") onClicked: root.requestSwitch("on-demand")
}
// NVIDIA 按钮
Button {
Layout.fillWidth: true
Layout.preferredHeight: 32
font.pixelSize: 11
text: qsTr("NVIDIA")
background: Rectangle {
color: root.currentPrimeMode === "nvidia" ? root.accentBlue : (parent.hovered ? root.accentBlueLight : root.cardBackground)
radius: 6
border.width: 1
border.color: root.currentPrimeMode === "nvidia" ? root.accentBlue : root.cardBorder
}
contentItem: Text {
text: parent.text
color: root.currentPrimeMode === "nvidia" ? "white" : root.accentBlue
font.pixelSize: 11
font.bold: root.currentPrimeMode === "nvidia"
horizontalAlignment: Text.AlignHCenter
}
onClicked: root.requestSwitch("nvidia")
}
}
}
}
// GPU 切换确认覆盖层
Rectangle {
id: switchOverlay
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.7)
radius: 12
visible: root.pendingSwitchMode.length > 0
z: 100
ColumnLayout {
anchors.centerIn: parent
width: parent.width - 48
spacing: 16
// 模式标题
Text {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Switch to") + " " + switchModeLabel(root.pendingSwitchMode)
font.pixelSize: 16
font.bold: true
color: "#e8e8e8"
}
// 模式描述
Text {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
text: switchModeDesc(root.pendingSwitchMode)
font.pixelSize: 12
color: "#a0a0a0"
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
// 倒计时圆环
Item {
Layout.alignment: Qt.AlignHCenter
width: 64
height: 64
// 背景圆环
Rectangle {
anchors.fill: parent
radius: 32
color: "transparent"
border.width: 3
border.color: Qt.rgba(1, 1, 1, 0.1)
}
// 进度圆环
Rectangle {
anchors.fill: parent
radius: 32
color: "transparent"
border.width: 3
border.color: root.accentBlue
// 用 ConicGradient 效果模拟进度(简化用 opacity)
opacity: root.switchCountdown / 15
Behavior on opacity {
NumberAnimation { duration: 1000 }
}
}
// 倒计时数字
Text {
anchors.centerIn: parent
text: root.switchCountdown
font.pixelSize: 24
font.bold: true
color: "#e8e8e8"
}
}
// 提示文字
Text {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Auto-cancel if not confirmed")
font.pixelSize: 10
color: "#666666"
}
// 按钮行
RowLayout {
Layout.fillWidth: true
spacing: 12
// 取消按钮
Button {
Layout.fillWidth: true
Layout.preferredHeight: 34
font.pixelSize: 12
background: Rectangle {
color: parent.hovered ? Qt.rgba(1, 1, 1, 0.1) : Qt.rgba(1, 1, 1, 0.05)
radius: 8
border.width: 1
border.color: Qt.rgba(1, 1, 1, 0.15)
}
contentItem: Text {
text: qsTr("Cancel")
color: "#cccccc"
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
}
onClicked: root.cancelSwitch()
}
// 确认按钮
Button {
Layout.fillWidth: true
Layout.preferredHeight: 34
font.pixelSize: 12
background: Rectangle {
color: parent.hovered ? Qt.darker(root.accentBlue, 1.2) : root.accentBlue
radius: 8
}
contentItem: Text {
text: qsTr("Confirm")
color: "white"
font.pixelSize: 12
font.bold: true
horizontalAlignment: Text.AlignHCenter
}
onClicked: root.confirmSwitch()
} }
} }
} }
@@ -928,6 +1127,18 @@ AppletItem {
return "GPU" return "GPU"
} }
// 获取核显厂商简称
function getIgpuVendorName() {
if (!root.ready) return qsTr("Integrated")
var lines = root.deviceInfo.split("\n").filter(function(l) { return l.trim().length > 0 })
for (var i = 0; i < lines.length; i++) {
if (!/nvidia/i.test(lines[i])) {
return parseGpuVendorShort(lines[i])
}
}
return qsTr("Integrated")
}
// 构建 tooltip 文本 // 构建 tooltip 文本
function buildToolTipText() { function buildToolTipText() {
if (!root.applet || !root.applet.gpuStats) { if (!root.applet || !root.applet.gpuStats) {
@@ -153,5 +153,45 @@
<source>Temperature Trend</source> <source>Temperature Trend</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../package/driverview.qml"/>
<source>Switch to</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Switch to NVIDIA discrete GPU for maximum performance. Higher power consumption.</source>
<translation> NVIDIA </translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Switch to integrated GPU for power saving. NVIDIA GPU will be disabled.</source>
<translation>NVIDIA </translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Hybrid mode: integrated GPU for display, NVIDIA for on-demand rendering.</source>
<translation>NVIDIA </translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Auto-cancel if not confirmed</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Confirm</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Integrated</source>
<translation></translation>
</message>
</context> </context>
</TS> </TS>
@@ -153,5 +153,45 @@
<source>Temperature Trend</source> <source>Temperature Trend</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../package/driverview.qml"/>
<source>Switch to</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Switch to NVIDIA discrete GPU for maximum performance. Higher power consumption.</source>
<translation> NVIDIA </translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Switch to integrated GPU for power saving. NVIDIA GPU will be disabled.</source>
<translation>NVIDIA </translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Hybrid mode: integrated GPU for display, NVIDIA for on-demand rendering.</source>
<translation>NVIDIA </translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Auto-cancel if not confirmed</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Confirm</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Integrated</source>
<translation></translation>
</message>
</context> </context>
</TS> </TS>