Files
JSwitchDisplayApplet/package/driverview.qml
T
Jokul f7c6801793 feat: 设置窗口、合并温度趋势图、UI优化
- 新增设置窗口(右键打开):无边框毛玻璃风格,温度告警开关,QSettings持久化
- 合并温度趋势图:多GPU多色折线,图例,Y轴刻度,填充区域,最新温度标注
- 显卡排序:核显在前独显在后(std::stable_sort by boot_vga)
- 卡片优化:删除温度行和频率/功耗行,驱动名+版本两端对齐,高度压缩至78
- 弹窗优化:宽度480,右上角刷新图标替代底部按钮
- tooltip修复:删除自定义Rectangle tooltip改用标准ToolTip
- 翻译更新:设置/常规设置/温度告警/温度趋势等
2026-07-14 21:31:00 +08:00

1153 lines
47 KiB
QML

// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import org.deepin.ds 1.0
import org.deepin.ds.dock 1.0
import org.deepin.dtk 1.0
AppletItem {
id: root
objectName: "graphics driver applet"
property int dockOrder: 21
property int dockSize: Panel.rootObject.dockItemMaxSize || 48
implicitWidth: dockSize
implicitHeight: dockSize
readonly property var applet: Applet
readonly property bool ready: applet ? applet.ready : false
readonly property string gpuSummary: applet ? (applet.gpuSummary || qsTr("No GPU detected")) : qsTr("No GPU detected")
readonly property string deviceInfo: applet ? (applet.deviceInfo || qsTr("Unknown")) : qsTr("Unknown")
readonly property string currentDriver: applet ? (applet.currentDriver || qsTr("Unknown")) : qsTr("Unknown")
readonly property string gpuMode: applet ? (applet.gpuMode || "") : ""
// 所有 GPU 中的最高温度,用于任务栏图标变色
readonly property int maxTemp: {
if (!applet || !applet.gpuStats) return -1
var max = -1
for (var i = 0; i < applet.gpuStats.length; i++) {
var parts = applet.gpuStats[i].split("|")
var temp = parseInt(parts[0])
if (!isNaN(temp) && temp > max) max = temp
}
return max
}
// 图标颜色:温度 >=80 红色,>=60 橙色,否则正常
readonly property color iconColor: {
if (maxTemp >= 80) return Qt.rgba(220/255, 38/255, 38/255, 1)
if (maxTemp >= 60) return Qt.rgba(245/255, 158/255, 11/255, 1)
return root.primaryText
}
property Palette basePalette: DockPalette.iconTextPalette
readonly property color primaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.9)
readonly property color secondaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.65)
readonly property color tertiaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.5)
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 accentBlue: Qt.rgba(20 / 255, 80 / 255, 160 / 255, 1)
readonly property color accentBlueLight: Qt.rgba(20 / 255, 80 / 255, 160 / 255, 0.12)
// 图标区域(根据最高温度变色)
Rectangle {
anchors.centerIn: parent
width: dockSize * 0.7
height: dockSize * 0.7
color: "transparent"
radius: width * 0.45
border.width: 1
border.color: root.iconColor
Text {
anchors.centerIn: parent
text: "GPU"
font.pixelSize: root.dockSize * 0.22
font.bold: true
color: root.iconColor
}
}
// 悬停提示
PanelToolTip {
id: toolTip
text: root.ready ? buildToolTipText() : qsTr("No GPU detected")
toolTipX: DockPanelPositioner.x
toolTipY: DockPanelPositioner.y
}
// 统计数据刷新定时器
Timer {
id: statsRefreshTimer
interval: 1000
repeat: true
onTriggered: {
if (root.applet) {
root.applet.refreshStats()
}
}
}
Timer {
id: toolTipShowTimer
interval: 50
onTriggered: {
const point = root.mapToItem(null, root.width / 2, root.height / 2)
toolTip.DockPanelPositioner.bounding = Qt.rect(point.x, point.y, toolTip.width, toolTip.height)
toolTip.open()
}
}
HoverHandler {
onHoveredChanged: {
if (hovered && !driverPopup.popupVisible) {
toolTipShowTimer.start()
if (root.applet) {
root.applet.refreshStats()
}
statsRefreshTimer.start()
} else {
if (toolTipShowTimer.running) {
toolTipShowTimer.stop()
}
toolTip.close()
if (!driverPopup.popupVisible) {
statsRefreshTimer.stop()
}
}
}
}
// 弹出窗口
PanelPopup {
id: driverPopup
width: 480
height: 440
popupX: DockPanelPositioner.x
popupY: DockPanelPositioner.y
onPopupVisibleChanged: {
if (popupVisible) {
toolTip.close()
statsRefreshTimer.start()
if (root.applet) {
root.applet.refreshStats()
}
} else {
statsRefreshTimer.stop()
}
}
Control {
id: popupContainer
anchors.fill: parent
padding: 16
contentItem: ColumnLayout {
spacing: 14
// 标题区域
RowLayout {
Layout.fillWidth: true
spacing: 10
Item { Layout.fillWidth: true }
Rectangle {
width: 28
height: 28
color: accentBlueLight
radius: 8
Text {
anchors.centerIn: parent
text: "GPU"
font.pixelSize: 12
font.bold: true
color: accentBlue
}
}
Text {
text: qsTr("Graphics Driver Manager")
font.pixelSize: 16
font.bold: true
color: root.primaryText
}
Item { Layout.fillWidth: true }
// 刷新图标
Rectangle {
width: 28
height: 28
radius: 6
color: refreshIconArea.containsMouse ? root.accentBlueLight : "transparent"
Text {
anchors.centerIn: parent
text: "⟳"
font.pixelSize: 18
color: refreshIconArea.containsMouse ? root.accentBlue : root.secondaryText
}
MouseArea {
id: refreshIconArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.applet) {
root.applet.refreshDeviceInfo()
}
}
}
// 旋转动画
RotationAnimator on rotation {
id: refreshSpin
running: false
from: 0
to: 360
duration: 500
}
Connections {
target: refreshIconArea
function onClicked() { refreshSpin.start() }
}
}
}
// 分隔线
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: root.cardBorder
}
// 当前驱动信息
RowLayout {
Layout.fillWidth: true
spacing: 8
visible: root.ready
Text {
text: qsTr("Current Driver:")
font.pixelSize: 12
color: root.secondaryText
}
Text {
text: root.currentDriver
font.pixelSize: 12
font.bold: true
color: root.primaryText
Layout.fillWidth: true
elide: Text.ElideRight
}
// GPU 模式标签
Rectangle {
visible: root.gpuMode.length > 0
width: modeText.implicitWidth + 16
height: 20
color: root.accentBlueLight
radius: 10
Text {
id: modeText
anchors.centerIn: parent
text: root.gpuMode
font.pixelSize: 11
font.bold: true
color: root.accentBlue
}
}
// 帮助图标
Item {
visible: root.gpuMode.length > 0
width: 16
height: 16
Rectangle {
anchors.fill: parent
radius: width / 2
color: helpHover.hovered ? root.accentBlue : root.tertiaryText
}
Text {
anchors.centerIn: parent
text: "?"
font.pixelSize: 10
font.bold: true
color: "white"
}
HoverHandler {
id: helpHover
}
ToolTip {
visible: helpHover.hovered
delay: 200
text: {
if (root.gpuMode === "PRIME")
return qsTr("PRIME mode: the integrated GPU handles display, the discrete GPU renders on demand. 3D apps use PRIME Render Offload to invoke the discrete GPU.")
if (root.gpuMode === "Bumblebee")
return qsTr("Bumblebee mode: a legacy dual-GPU solution, now deprecated.")
if (root.gpuMode === "Solo")
return qsTr("Solo mode: only one GPU detected.")
if (root.gpuMode === "Hybrid")
return qsTr("Hybrid mode: dual GPU detected, but no known switching solution found.")
return ""
}
}
}
}
// GPU 列表区域
Flickable {
id: gpuListFlick
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
contentWidth: width
contentHeight: gpuColumn.height
boundsBehavior: Flickable.StopAtBounds
Column {
id: gpuColumn
width: gpuListFlick.width
spacing: 10
visible: root.ready
Repeater {
model: root.ready ? root.deviceInfo.split("\n").filter(function(line) { return line.trim().length > 0 }) : []
delegate: Rectangle {
id: gpuCard
width: gpuColumn.width
height: 78
color: root.cardBackground
radius: 10
border.width: 1
border.color: root.cardBorder
property string statsLine: root.applet && root.applet.gpuStats ? (root.applet.gpuStats[index] || "") : ""
property bool isPrimaryGpu: root.applet && root.applet.gpuPrimary ? (root.applet.gpuPrimary[index] === "true") : false
property string tempStr: {
var parts = statsLine.split("|")
var temp = parts[0]
return temp === "-1" || temp === "" ? "—" : temp
}
property string gpuUsageStr: {
var parts = statsLine.split("|")
var usage = parts[1]
return usage === "-1" || usage === "" ? "—" : usage
}
property string memUsageStr: {
var parts = statsLine.split("|")
var usage = parts[2]
return usage === "-1" || usage === "" ? "—" : usage
}
property string memUsedStr: {
var parts = statsLine.split("|")
var used = parts[3]
return used === "-1" || used === "" ? "—" : used
}
property string memTotalStr: {
var parts = statsLine.split("|")
var total = parts[4]
return total === "-1" || total === "" ? "—" : total
}
property real gpuUsagePercent: {
var parts = statsLine.split("|")
var usage = parts[1]
if (usage === "-1" || usage === "") return 0
return parseFloat(usage)
}
property color tempColor: {
var parts = statsLine.split("|")
var temp = parseInt(parts[0])
if (isNaN(temp) || temp === -1) return root.primaryText
if (temp >= 80) return Qt.rgba(220/255, 38/255, 38/255, 1)
if (temp >= 60) return Qt.rgba(245/255, 158/255, 11/255, 1)
return Qt.rgba(22/255, 163/255, 74/255, 1)
}
property string gpuClockStr: {
var parts = statsLine.split("|")
var v = parts[5]
return v === "-1" || v === "" ? "-" : v
}
property string memClockStr: {
var parts = statsLine.split("|")
var v = parts[6]
return v === "-1" || v === "" ? "-" : v
}
property string powerStr: {
var parts = statsLine.split("|")
var v = parts[7]
return v === "-1.0" || v === "" ? "-" : v
}
property string fanStr: {
var parts = statsLine.split("|")
var v = parts[8]
return v === "-1" || v === "" ? "-" : v
}
HoverHandler {
id: cardHover
}
ToolTip.text: qsTr("GPU: ") + parseGpuName(modelData) + "\n"
+ qsTr("Driver: ") + parseDriverName(modelData) + "\n"
+ qsTr("Version: ") + (parseDriverVersion(modelData) || qsTr("Unknown"))
ToolTip.visible: cardHover.hovered
ToolTip.delay: 300
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 6
RowLayout {
Layout.fillWidth: true
spacing: 12
// GPU 图标
Rectangle {
width: 36
height: 36
color: root.accentBlueLight
radius: 8
Text {
anchors.centerIn: parent
text: parseGpuVendorShort(modelData)
font.pixelSize: 14
font.bold: true
color: root.accentBlue
}
}
// GPU 信息
ColumnLayout {
spacing: 4
Layout.fillWidth: true
RowLayout {
spacing: 6
Layout.fillWidth: true
Text {
text: parseGpuName(modelData)
font.pixelSize: 13
font.bold: true
color: root.primaryText
Layout.fillWidth: true
elide: Text.ElideRight
}
// 主/副显标识
Rectangle {
visible: gpuCard.isPrimaryGpu
width: badgeText.implicitWidth + 12
height: 16
color: Qt.rgba(22/255, 163/255, 74/255, 0.15)
radius: 8
Text {
id: badgeText
anchors.centerIn: parent
text: qsTr("Primary")
font.pixelSize: 10
font.bold: true
color: Qt.rgba(22/255, 163/255, 74/255, 1)
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: parseDriverName(modelData)
font.pixelSize: 11
color: root.secondaryText
}
Item { Layout.fillWidth: true }
Text {
text: parseDriverVersion(modelData) || qsTr("Unknown")
font.pixelSize: 11
color: root.tertiaryText
}
}
}
}
// 统计数据区域
ColumnLayout {
Layout.fillWidth: true
spacing: 6
RowLayout {
Layout.fillWidth: true
spacing: 12
// 显存信息
Text {
text: (memUsedStr !== "-" && memTotalStr !== "-") ? memUsedStr + "/" + memTotalStr + " MB" : "-"
font.pixelSize: 11
color: root.secondaryText
}
Item { Layout.fillWidth: true }
// GPU 使用率进度条
RowLayout {
spacing: 8
Text {
text: "GPU"
font.pixelSize: 11
color: root.tertiaryText
}
Rectangle {
Layout.preferredWidth: 140
height: 4
color: root.cardBorder
radius: 2
Rectangle {
width: Math.min(gpuCard.gpuUsagePercent, 100) * parent.width / 100
height: parent.height
color: root.accentBlue
radius: 2
}
}
Text {
text: gpuUsageStr !== "-" ? gpuUsageStr + "%" : "-"
font.pixelSize: 11
color: root.secondaryText
font.bold: gpuUsageStr !== "-"
}
}
}
}
}
}
}
}
// 未检测到 GPU 提示
Rectangle {
anchors.centerIn: parent
width: gpuListFlick.width - 32
height: 80
color: root.cardBackground
radius: 10
border.width: 1
border.color: root.cardBorder
visible: !root.ready
ColumnLayout {
anchors.centerIn: parent
spacing: 6
Text {
text: "⚠"
font.pixelSize: 24
}
Text {
text: qsTr("No GPU detected")
font.pixelSize: 13
color: root.secondaryText
}
}
}
}
// 合并温度趋势图
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 90
color: root.cardBackground
radius: 10
border.width: 1
border.color: root.cardBorder
visible: {
if (!root.applet || !root.applet.tempHistory) return false
var th = root.applet.tempHistory
for (var i = 0; i < th.length; i++) {
if (th[i] && th[i].length > 1) return true
}
return false
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 6
// 图例
RowLayout {
Layout.fillWidth: true
spacing: 12
Repeater {
model: root.ready ? root.deviceInfo.split("\n").filter(function(line) { return line.trim().length > 0 }) : []
RowLayout {
spacing: 4
Rectangle {
width: 10
height: 10
radius: 2
color: {
var colors = [
Qt.rgba(22/255, 163/255, 74/255, 1),
Qt.rgba(20/255, 80/255, 160/255, 1),
Qt.rgba(245/255, 158/255, 11/255, 1)
]
return colors[index] || colors[0]
}
}
Text {
text: parseGpuVendorShort(modelData)
font.pixelSize: 10
color: root.secondaryText
}
}
}
Item { Layout.fillWidth: true }
Text {
text: qsTr("Temperature Trend")
font.pixelSize: 10
color: root.tertiaryText
}
}
// 趋势图 Canvas
Canvas {
id: mergedTempCanvas
Layout.fillWidth: true
Layout.fillHeight: true
onPaint: {
var ctx = getContext("2d")
ctx.reset()
var w = width
var h = height
if (!root.applet || !root.applet.tempHistory) return
var th = root.applet.tempHistory
var minT = 20, maxT = 100
var pad = 4
var colors = [
Qt.rgba(22/255, 163/255, 74/255, 1),
Qt.rgba(20/255, 80/255, 160/255, 1),
Qt.rgba(245/255, 158/255, 11/255, 1)
]
// 1. 绘制网格线和刻度
ctx.font = "9px sans-serif"
ctx.fillStyle = Qt.rgba(1, 1, 1, 0.25)
var temps = [40, 60, 80]
for (var ti = 0; ti < temps.length; ti++) {
var t = temps[ti]
var y = h - ((t - minT) / (maxT - minT)) * (h - pad * 2) - pad
ctx.strokeStyle = Qt.rgba(1, 1, 1, 0.06)
ctx.lineWidth = 1
ctx.setLineDash(t === 80 ? [3, 3] : [])
ctx.beginPath()
ctx.moveTo(24, y)
ctx.lineTo(w, y)
ctx.stroke()
ctx.setLineDash([])
ctx.fillText(t + "°C", 2, y + 3)
}
// 2. 绘制每个 GPU 的温度线
for (var g = 0; g < th.length; g++) {
var history = th[g]
if (!history || history.length < 2) continue
var len = history.length
var color = colors[g] || colors[0]
// 填充区域
ctx.beginPath()
for (var i = 0; i < len; i++) {
var x = 24 + (i / (len - 1)) * (w - 24)
var temp = history[i]
if (temp < 0) temp = minT
var y2 = h - ((temp - minT) / (maxT - minT)) * (h - pad * 2) - pad
if (i === 0) ctx.moveTo(x, y2)
else ctx.lineTo(x, y2)
}
ctx.lineTo(24 + (w - 24), h)
ctx.lineTo(24, h)
ctx.closePath()
var grad = ctx.createLinearGradient(0, 0, 0, h)
grad.addColorStop(0, Qt.rgba(color.r, color.g, color.b, 0.15))
grad.addColorStop(1, Qt.rgba(color.r, color.g, color.b, 0.01))
ctx.fillStyle = grad
ctx.fill()
// 折线
ctx.strokeStyle = color
ctx.lineWidth = 2
ctx.beginPath()
for (var j = 0; j < len; j++) {
var x2 = 24 + (j / (len - 1)) * (w - 24)
var t2 = history[j]
if (t2 < 0) t2 = minT
var y3 = h - ((t2 - minT) / (maxT - minT)) * (h - pad * 2) - pad
if (j === 0) ctx.moveTo(x2, y3)
else ctx.lineTo(x2, y3)
}
ctx.stroke()
// 最新数据点
var lastIdx = len - 1
var lastX = 24 + (lastIdx / (len - 1)) * (w - 24)
var lastT = history[lastIdx]
if (lastT < 0) lastT = minT
var lastY = h - ((lastT - minT) / (maxT - minT)) * (h - pad * 2) - pad
ctx.fillStyle = color
ctx.beginPath()
ctx.arc(lastX, lastY, 3, 0, Math.PI * 2)
ctx.fill()
// 最新温度标注
ctx.font = "bold 10px sans-serif"
ctx.fillStyle = color
var label = lastT + "°C"
var labelX = lastX + 6
if (labelX + 30 > w) labelX = lastX - 30
ctx.fillText(label, labelX, lastY + 3)
}
}
Connections {
target: root.applet
function onTempHistoryChanged() { mergedTempCanvas.requestPaint() }
}
}
}
}
// NVIDIA 进程列表(仅有进程时显示)
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: processList.implicitHeight + 24
color: root.cardBackground
radius: 10
border.width: 1
border.color: root.cardBorder
visible: root.applet && root.applet.gpuProcesses && root.applet.gpuProcesses.length > 0
ColumnLayout {
id: processList
anchors.fill: parent
anchors.margins: 10
spacing: 4
Text {
text: qsTr("GPU Processes")
font.pixelSize: 11
font.bold: true
color: root.secondaryText
}
Repeater {
model: root.applet ? (root.applet.gpuProcesses || []) : []
RowLayout {
Layout.fillWidth: true
spacing: 8
Text {
text: modelData.split("|")[1] || "?"
font.pixelSize: 10
color: root.primaryText
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
text: (modelData.split("|")[2] || "?") + " MB"
font.pixelSize: 10
color: root.tertiaryText
}
}
}
}
}
// GPU 切换按钮(仅 PRIME 模式下显示)
RowLayout {
Layout.fillWidth: true
spacing: 8
visible: root.gpuMode === "PRIME"
Button {
Layout.fillWidth: true
Layout.preferredHeight: 30
font.pixelSize: 11
text: qsTr("NVIDIA")
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("nvidia")
}
Button {
Layout.fillWidth: true
Layout.preferredHeight: 30
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
text: qsTr("On-Demand")
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("on-demand")
}
}
}
}
Component.onCompleted: {
DockPanelPositioner.bounding = Qt.binding(function () {
const point = root.mapToItem(null, root.width / 2, root.height / 2)
return Qt.rect(point.x, point.y, driverPopup.width, driverPopup.height)
})
}
}
// 解析 GPU 名称的辅助函数
function parseGpuName(info) {
var parts = info.split("(")
if (parts.length > 0) {
return parts[0].trim()
}
return info
}
// 解析驱动信息的辅助函数
function parseDriverInfo(info) {
var startIndex = info.indexOf("(")
var endIndex = info.lastIndexOf(")")
if (startIndex !== -1 && endIndex !== -1) {
return info.substring(startIndex + 1, endIndex)
}
return info
}
// 解析驱动名称
function parseDriverName(info) {
var driver = parseDriverInfo(info)
var parts = driver.split(" ")
return parts[0] || qsTr("Unknown")
}
// 解析驱动版本号
function parseDriverVersion(info) {
var driver = parseDriverInfo(info)
var parts = driver.split(" ")
if (parts.length > 1) {
return parts[1]
}
return ""
}
// 识别 GPU 厂商简称
// 注意:用单词边界匹配,避免 "corporation" 中的 "ati" 等子串误匹配
function parseGpuVendorShort(info) {
var name = parseGpuName(info).toLowerCase()
if (/nvidia/.test(name)) return "NV"
if (/\bamd\b/.test(name) || /\bati\b/.test(name) || /advanced micro/.test(name)) return "AMD"
if (/intel/.test(name)) return "Intel"
if (/qualcomm/.test(name)) return "QC"
if (/microsoft/.test(name)) return "MS"
return "GPU"
}
// 构建 tooltip 文本
function buildToolTipText() {
if (!root.applet || !root.applet.gpuStats) {
return root.currentDriver || qsTr("No GPU detected")
}
var lines = root.deviceInfo.split("\n").filter(function(line) { return line.trim().length > 0 })
var result = []
for (var i = 0; i < lines.length; i++) {
var vendor = parseGpuVendorShort(lines[i])
var statsLine = root.applet.gpuStats[i] || ""
var parts = statsLine.split("|")
var temp = parts[0]
var gpuUsage = parts[1]
var tempStr = (temp === "-1" || temp === "") ? "" : temp + "°C"
var gpuUsageStr = (gpuUsage === "-1" || gpuUsage === "") ? "" : gpuUsage + "%"
result.push(vendor + " " + tempStr + " " + gpuUsageStr)
}
return result.join(" | ")
}
// 点击处理
TapHandler {
acceptedButtons: Qt.LeftButton
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: {
if (driverPopup.popupVisible) {
driverPopup.close()
} else {
Panel.requestClosePopup()
const point = root.mapToItem(null, root.width / 2, root.height / 2)
driverPopup.DockPanelPositioner.bounding = Qt.rect(point.x, point.y, driverPopup.width, driverPopup.height)
driverPopup.open()
}
toolTip.close()
}
}
// 设置窗口(无边框 + 毛玻璃风格)
Window {
id: settingsWindow
title: qsTr("Settings")
width: 650
height: 350
flags: Qt.FramelessWindowHint | Qt.Window
color: "transparent"
// 居中屏幕
x: (Screen.width - width) / 2
y: (Screen.height - height) / 2
// 毛玻璃风格背景
Rectangle {
anchors.fill: parent
radius: 16
color: Qt.rgba(0.12, 0.12, 0.14, 0.88)
border.width: 1
border.color: Qt.rgba(1, 1, 1, 0.1)
ColumnLayout {
anchors.fill: parent
anchors.margins: 0
spacing: 0
// 自定义标题栏
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 48
color: "transparent"
radius: 16
Rectangle {
anchors.fill: parent
anchors.bottomMargin: 0
color: Qt.rgba(1, 1, 1, 0.04)
radius: 16
RowLayout {
anchors.fill: parent
anchors.leftMargin: 20
anchors.rightMargin: 16
spacing: 10
Text {
text: qsTr("General Settings")
font.pixelSize: 15
font.bold: true
color: "#e8e8e8"
}
Item { Layout.fillWidth: true }
// 关闭按钮
Rectangle {
width: 28
height: 28
radius: 14
color: closeBtnArea.containsMouse ? Qt.rgba(255/255, 95/255, 86/255, 0.8) : "transparent"
Text {
anchors.centerIn: parent
text: "✕"
font.pixelSize: 12
color: closeBtnArea.containsMouse ? "white" : "#999999"
}
MouseArea {
id: closeBtnArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: settingsWindow.close()
}
}
}
}
// 窗口拖拽(使用系统移动,避免幻影)
MouseArea {
anchors.fill: parent
anchors.rightMargin: 44
onPressed: settingsWindow.startSystemMove()
}
}
// 分隔线
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Qt.rgba(1, 1, 1, 0.08)
}
// 内容区域
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 24
Layout.rightMargin: 24
Layout.topMargin: 20
spacing: 20
// 温度告警通知开关
RowLayout {
Layout.fillWidth: true
spacing: 12
ColumnLayout {
spacing: 2
Text {
text: qsTr("Temperature Alert")
font.pixelSize: 14
color: "#e0e0e0"
}
Text {
text: qsTr("Show notification when GPU temperature exceeds threshold")
font.pixelSize: 11
color: "#777777"
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
}
Item { Layout.fillWidth: true }
Switch {
checked: root.applet ? root.applet.tempAlertEnabled : false
onToggled: {
if (root.applet) {
root.applet.tempAlertEnabled = checked
}
}
}
}
// 分隔线
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Qt.rgba(1, 1, 1, 0.06)
}
// 温度告警阈值
RowLayout {
Layout.fillWidth: true
spacing: 12
Text {
text: qsTr("Alert Temperature Threshold")
font.pixelSize: 14
color: "#e0e0e0"
}
Item { Layout.fillWidth: true }
Text {
text: "80°C"
font.pixelSize: 14
font.bold: true
color: Qt.rgba(245/255, 158/255, 11/255, 1)
}
}
Item { Layout.fillHeight: true }
}
}
}
}
// 右键点击显示设置窗口
TapHandler {
acceptedButtons: Qt.RightButton
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: {
settingsWindow.show()
}
}
}