样式 + 对话框加载json
parent
55ca42b24f
commit
c21089e8ea
|
|
@ -6,21 +6,6 @@
|
|||
"h": 1080.0
|
||||
},
|
||||
"widgets": [
|
||||
{
|
||||
"widget_type": "label",
|
||||
"x": 46.0,
|
||||
"y": 378.0,
|
||||
"w": 162.0,
|
||||
"h": 62.0,
|
||||
"z": 1.0,
|
||||
"config": {
|
||||
"fieldName": "减速箱小轴承2",
|
||||
"prefix": "减速箱小轴承温度",
|
||||
"suffix": "℃",
|
||||
"fontSize": 16,
|
||||
"color": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "label",
|
||||
"x": 173.0,
|
||||
|
|
@ -37,15 +22,18 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "web",
|
||||
"x": 649.0,
|
||||
"y": 0.0,
|
||||
"w": 886.0,
|
||||
"h": 863.0,
|
||||
"z": 0.0,
|
||||
"widget_type": "label",
|
||||
"x": 46.0,
|
||||
"y": 378.0,
|
||||
"w": 162.0,
|
||||
"h": 62.0,
|
||||
"z": 1.0,
|
||||
"config": {
|
||||
"url": "http://127.0.0.1:8086/orgs/b2542eeb72a3e614/dashboards/0f8ab8a328fe9000?lower=now%28%29+-+1h",
|
||||
"locked": true
|
||||
"fieldName": "减速箱小轴承2",
|
||||
"prefix": "减速箱小轴承温度",
|
||||
"suffix": "℃",
|
||||
"fontSize": 16,
|
||||
"color": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -58,6 +46,18 @@
|
|||
"config": {
|
||||
"imagePath": "D:/1-2.JPG"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "web",
|
||||
"x": 649.0,
|
||||
"y": 0.0,
|
||||
"w": 886.0,
|
||||
"h": 863.0,
|
||||
"z": 0.0,
|
||||
"config": {
|
||||
"url": "http://127.0.0.1:8086/orgs/b2542eeb72a3e614/dashboards/0f8ab8a328fe9000?lower=now%28%29+-+1h",
|
||||
"locked": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
190
main.py
190
main.py
|
|
@ -16,7 +16,14 @@ from dataclasses import dataclass, asdict, field
|
|||
from typing import Optional, Dict, Any
|
||||
import math
|
||||
|
||||
from PyQt6.QtCore import Qt, QRectF, pyqtSignal, QObject, QTimer, QPointF
|
||||
# 忽略 Windows 显示缩放设置,使用物理像素
|
||||
# 这确保画布 1920x1080 能正确对应屏幕像素,避免 Web 组件被缩放
|
||||
os.environ["QT_SCALE_FACTOR"] = "1"
|
||||
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "0"
|
||||
|
||||
from PyQt6.QtCore import Qt, QRectF, pyqtSignal, QObject, QTimer, QPointF, QSizeF
|
||||
from PyQt6.QtWidgets import QSizePolicy
|
||||
from PyQt6.QtGui import QFontMetrics
|
||||
from PyQt6.QtGui import QBrush, QColor, QPen, QPixmap, QFont, QCursor, QShortcut, QKeySequence
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
|
|
@ -57,6 +64,14 @@ from PyQt6.QtCore import QUrl
|
|||
# 延迟导入:InfluxDB 客户端只在需要时导入(减少启动时间)
|
||||
# from influxdb_wrapper import InfluxDBClient
|
||||
|
||||
# 导入样式模块
|
||||
from styles import MAIN_STYLE, Colors, CANVAS_STYLE, ITEM_STYLE
|
||||
|
||||
# 应用全局样式
|
||||
def apply_styles(app: QApplication):
|
||||
"""应用全局样式表"""
|
||||
app.setStyleSheet(MAIN_STYLE)
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# 数据结构(用于保存/加载布局)
|
||||
|
|
@ -598,12 +613,12 @@ class LabelItem(DashboardItem):
|
|||
# 直接返回,不调用父类的 paint,避免绘制任何边框
|
||||
return
|
||||
|
||||
# 编辑模式:选中时高亮蓝色,否则浅灰色;背景依然透明
|
||||
# 编辑模式:选中时高亮强调色,否则使用边框色;背景依然透明
|
||||
# 先设置 pen,确保父类 paint 使用正确的边框颜色
|
||||
if self.isSelected():
|
||||
self.setPen(QPen(QColor(80, 160, 255), 2))
|
||||
self.setPen(QPen(QColor(Colors.ACCENT), 2))
|
||||
else:
|
||||
self.setPen(QPen(QColor(170, 170, 170), 1))
|
||||
self.setPen(QPen(QColor(Colors.BORDER), 1))
|
||||
# 确保背景透明
|
||||
self.setBrush(QBrush(Qt.GlobalColor.transparent))
|
||||
super().paint(painter, option, widget)
|
||||
|
|
@ -821,7 +836,7 @@ class DashboardScene(QGraphicsScene):
|
|||
def set_canvas_edit_mode(self, edit_mode: bool):
|
||||
# 编辑时显示边界;展示时隐藏边界
|
||||
if edit_mode:
|
||||
self._canvas_item.setPen(QPen(QColor(140, 140, 140), 1, Qt.PenStyle.DashLine))
|
||||
self._canvas_item.setPen(QPen(QColor(Colors.BORDER), 1, Qt.PenStyle.DashLine))
|
||||
else:
|
||||
self._canvas_item.setPen(QPen(Qt.GlobalColor.transparent))
|
||||
|
||||
|
|
@ -831,7 +846,7 @@ class DashboardScene(QGraphicsScene):
|
|||
# 画布边界(用于定义“屏幕/展示区域”的坐标系与尺寸)
|
||||
self._canvas_item = QGraphicsRectItem()
|
||||
self._canvas_item.setZValue(-1e9)
|
||||
# 画布背景色:纯白色
|
||||
# 画布使用白色配色
|
||||
self._canvas_item.setPen(QPen(QColor(200, 200, 200), 1, Qt.PenStyle.DashLine)) # 浅灰色边框,在白色背景下可见
|
||||
self._canvas_item.setBrush(QBrush(QColor(255, 255, 255))) # 纯白色背景
|
||||
self._canvas_item.setFlag(QGraphicsRectItem.GraphicsItemFlag.ItemIsSelectable, False)
|
||||
|
|
@ -849,11 +864,20 @@ class DashboardView(QGraphicsView):
|
|||
self._scene = DashboardScene(self)
|
||||
self.setScene(self._scene)
|
||||
self.setRenderHints(self.renderHints())
|
||||
self.setBackgroundBrush(QColor(255, 255, 255)) # 纯白色背景
|
||||
# 使用深色主题背景
|
||||
self.setBackgroundBrush(QColor(Colors.BG_PRIMARY))
|
||||
# 取消自身的边框,避免全屏时出现白边
|
||||
self.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
||||
self._edit_mode = True
|
||||
|
||||
# 设置视口样式
|
||||
self.setStyleSheet(f"""
|
||||
DashboardView {{
|
||||
background-color: {Colors.BG_PRIMARY};
|
||||
border: none;
|
||||
}}
|
||||
""")
|
||||
|
||||
@property
|
||||
def scene_obj(self) -> DashboardScene:
|
||||
|
|
@ -873,10 +897,21 @@ class DashboardView(QGraphicsView):
|
|||
v.setValue(v.minimum())
|
||||
|
||||
def fit_canvas_to_view(self):
|
||||
"""让画布按1:1显示(用于全屏模式)"""
|
||||
# 直接使用1:1显示,不进行任何缩放
|
||||
"""让画布完全适应视图(用于全屏展示模式)
|
||||
|
||||
关键:保持 1:1 比例,不做任何缩放!
|
||||
只是隐藏滚动条,让画布完整显示。
|
||||
如果视图大小与画布匹配,自然就能全屏显示。
|
||||
"""
|
||||
# 重置变换为 1:1,绝对不能缩放,否则 Web 组件会失真
|
||||
self.resetTransform()
|
||||
|
||||
# 滚动条复位到左上角
|
||||
self.reset_scroll()
|
||||
|
||||
# 隐藏滚动条
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
|
||||
def reset_view_transform(self):
|
||||
"""重置视图变换(用于退出全屏时恢复)"""
|
||||
|
|
@ -967,7 +1002,7 @@ class DashboardView(QGraphicsView):
|
|||
json.dump(payload, f, indent=2, ensure_ascii=False)
|
||||
|
||||
def load_layout(self, path: str):
|
||||
if not os.path.exists(path):
|
||||
if path is None or not os.path.exists(path):
|
||||
return
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
|
@ -1047,6 +1082,9 @@ class PropertyPanel(QWidget):
|
|||
self._current_item: Optional[DashboardItem] = None
|
||||
# mode: "none" | "item" | "canvas"
|
||||
self._mode: str = "none"
|
||||
|
||||
# 注意:不要在这里设置样式表,否则会覆盖全局样式
|
||||
# 背景色通过父窗口或全局样式控制
|
||||
|
||||
self.x_spin = QSpinBox()
|
||||
self.y_spin = QSpinBox()
|
||||
|
|
@ -1084,13 +1122,15 @@ class PropertyPanel(QWidget):
|
|||
self._arrow_color = "#00FF00"
|
||||
|
||||
form = QFormLayout()
|
||||
form.addRow("X:", self.x_spin)
|
||||
form.addRow("Y:", self.y_spin)
|
||||
form.addRow("W:", self.w_spin)
|
||||
form.addRow("H:", self.h_spin)
|
||||
form.addRow("Z:", self.z_spin)
|
||||
form.setSpacing(10)
|
||||
form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
form.addRow("X 坐标:", self.x_spin)
|
||||
form.addRow("Y 坐标:", self.y_spin)
|
||||
form.addRow("宽度:", self.w_spin)
|
||||
form.addRow("高度:", self.h_spin)
|
||||
form.addRow("层级:", self.z_spin)
|
||||
|
||||
img_group = QGroupBox("图片组件")
|
||||
img_group = QGroupBox("🖼 图片组件")
|
||||
img_layout = QVBoxLayout(img_group)
|
||||
img_layout.addWidget(self.image_path_edit)
|
||||
img_layout.addWidget(self.image_browse_btn)
|
||||
|
|
@ -1119,15 +1159,14 @@ class PropertyPanel(QWidget):
|
|||
self.arrow_group = arrow_group
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(QLabel("当前选中组件"))
|
||||
layout.addLayout(form)
|
||||
layout.addSpacing(10)
|
||||
layout.addSpacing(8)
|
||||
layout.addWidget(img_group)
|
||||
layout.addSpacing(10)
|
||||
layout.addSpacing(8)
|
||||
layout.addWidget(label_group)
|
||||
layout.addSpacing(10)
|
||||
layout.addSpacing(8)
|
||||
layout.addWidget(web_group)
|
||||
layout.addSpacing(10)
|
||||
layout.addSpacing(8)
|
||||
layout.addWidget(arrow_group)
|
||||
layout.addStretch(1)
|
||||
|
||||
|
|
@ -1454,11 +1493,19 @@ class MainWindow(QMainWindow):
|
|||
|
||||
# 右侧:属性面板 + 组件列表
|
||||
self.right_panel = QWidget()
|
||||
self.right_panel.setObjectName("right_panel")
|
||||
self.right_panel.setStyleSheet(f"""
|
||||
QWidget#right_panel {{
|
||||
background-color: {Colors.BG_PRIMARY};
|
||||
}}
|
||||
""")
|
||||
right_layout = QVBoxLayout(self.right_panel)
|
||||
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||
right_layout.setContentsMargins(12, 12, 12, 12)
|
||||
right_layout.setSpacing(12)
|
||||
right_layout.addWidget(self.prop)
|
||||
group_list = QGroupBox("当前组件列表")
|
||||
group_list = QGroupBox("📋 当前组件列表")
|
||||
glay = QVBoxLayout(group_list)
|
||||
glay.setContentsMargins(12, 16, 12, 12)
|
||||
glay.addWidget(self.item_list)
|
||||
right_layout.addWidget(group_list)
|
||||
|
||||
|
|
@ -1478,20 +1525,47 @@ class MainWindow(QMainWindow):
|
|||
layout.setSpacing(0)
|
||||
|
||||
self.toolbar_widget = QWidget()
|
||||
self.toolbar_widget.setFixedHeight(40) # 固定工具栏高度,避免与画布之间出现多余间距
|
||||
self.toolbar_widget.setFixedHeight(52) # 增加工具栏高度
|
||||
self.toolbar_widget.setStyleSheet(f"""
|
||||
QWidget#toolbar_widget {{
|
||||
background-color: {Colors.BG_SECONDARY};
|
||||
border-bottom: 1px solid {Colors.BORDER};
|
||||
}}
|
||||
""")
|
||||
self.toolbar_widget.setObjectName("toolbar_widget")
|
||||
toolbar_layout = QHBoxLayout(self.toolbar_widget)
|
||||
toolbar_layout.setContentsMargins(12, 8, 12, 8)
|
||||
toolbar_layout.setSpacing(8)
|
||||
# 保存按钮到成员,方便在其它方法(如键盘事件)中访问 / 控制可用性
|
||||
# 所有按钮使用统一样式(默认灰色样式,与influx配置按钮一致)
|
||||
self.btn_mode = QPushButton("展示模式")
|
||||
|
||||
self.btn_influx = QPushButton("Influx配置")
|
||||
self.btn_add_img = QPushButton("新增图片组件")
|
||||
self.btn_add_label = QPushButton("新增标签组件")
|
||||
self.btn_add_web = QPushButton("新增曲线组件")
|
||||
self.btn_add_arrow = QPushButton("新增箭头组件")
|
||||
|
||||
self.btn_add_img = QPushButton("+ 图片")
|
||||
|
||||
self.btn_add_label = QPushButton("+ 标签")
|
||||
|
||||
self.btn_add_web = QPushButton("+ 曲线")
|
||||
|
||||
self.btn_add_arrow = QPushButton("+ 箭头")
|
||||
|
||||
self.btn_full_edit = QPushButton("全屏编辑")
|
||||
self.btn_delete = QPushButton("删除组件")
|
||||
self.btn_clone = QPushButton("复制组件")
|
||||
self.btn_save = QPushButton("保存布局")
|
||||
self.btn_load = QPushButton("加载布局")
|
||||
|
||||
self.btn_delete = QPushButton("删除")
|
||||
|
||||
self.btn_clone = QPushButton("复制")
|
||||
|
||||
self.btn_save = QPushButton("保存")
|
||||
|
||||
self.btn_load = QPushButton("加载")
|
||||
|
||||
# 当前文件路径显示标签
|
||||
self.lbl_current_file = QLabel("未选择文件")
|
||||
self.lbl_current_file.setStyleSheet(f"color: {Colors.TEXT_SECONDARY}; font-size: 11px;")
|
||||
self.lbl_current_file.setMinimumWidth(200)
|
||||
self.lbl_current_file.setMaximumWidth(400)
|
||||
|
||||
toolbar_layout.addWidget(self.btn_mode)
|
||||
toolbar_layout.addWidget(self.btn_influx)
|
||||
toolbar_layout.addWidget(self.btn_add_img)
|
||||
|
|
@ -1500,6 +1574,8 @@ class MainWindow(QMainWindow):
|
|||
toolbar_layout.addWidget(self.btn_add_arrow)
|
||||
toolbar_layout.addWidget(self.btn_full_edit)
|
||||
toolbar_layout.addStretch(1)
|
||||
toolbar_layout.addWidget(self.lbl_current_file)
|
||||
toolbar_layout.addStretch(1)
|
||||
toolbar_layout.addWidget(self.btn_delete)
|
||||
toolbar_layout.addWidget(self.btn_clone)
|
||||
toolbar_layout.addWidget(self.btn_save)
|
||||
|
|
@ -1530,7 +1606,7 @@ class MainWindow(QMainWindow):
|
|||
self._shortcut_clone = QShortcut(QKeySequence("Ctrl+D"), self)
|
||||
self._shortcut_clone.activated.connect(self._on_clone)
|
||||
|
||||
# 布局 / Influx 配置文件路径
|
||||
# Influx 配置文件路径(固定位置)
|
||||
# 支持打包后的单文件模式:配置文件保存在 exe 同目录
|
||||
if getattr(sys, 'frozen', False):
|
||||
# 打包后的可执行文件模式
|
||||
|
|
@ -1538,7 +1614,7 @@ class MainWindow(QMainWindow):
|
|||
else:
|
||||
# 开发模式:使用脚本所在目录
|
||||
base_dir = os.path.dirname(__file__)
|
||||
self._layout_path = os.path.join(base_dir, "dashboard.json")
|
||||
self._layout_path = None # 初始为 None,等待用户选择文件
|
||||
self._settings_path = os.path.join(base_dir, "influx_settings.json")
|
||||
|
||||
# 先加载全局 Influx 配置,再加载布局
|
||||
|
|
@ -1794,14 +1870,44 @@ class MainWindow(QMainWindow):
|
|||
self._refresh_item_list()
|
||||
|
||||
def _on_save(self):
|
||||
"""保存布局到当前打开的文件,如果没有指定文件则弹出保存对话框"""
|
||||
try:
|
||||
# 如果没有指定文件路径,弹出保存对话框
|
||||
if not self._layout_path or self._layout_path == os.path.join(os.path.dirname(__file__), "dashboard.json"):
|
||||
file_path, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"保存布局文件",
|
||||
"",
|
||||
"JSON Files (*.json);;All Files (*.*)"
|
||||
)
|
||||
if not file_path:
|
||||
return # 用户取消了对话框
|
||||
if not file_path.endswith('.json'):
|
||||
file_path += '.json'
|
||||
self._layout_path = file_path
|
||||
self._update_current_file_label()
|
||||
|
||||
self.view.save_layout(self._layout_path)
|
||||
QMessageBox.information(self, "保存布局", f"已保存到 {self._layout_path}")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "保存失败", str(e))
|
||||
|
||||
def _on_load(self):
|
||||
"""通过文件对话框选择并加载 JSON 文件"""
|
||||
try:
|
||||
# 弹出文件选择对话框
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"选择布局文件",
|
||||
"",
|
||||
"JSON Files (*.json);;All Files (*.*)"
|
||||
)
|
||||
if not file_path:
|
||||
return # 用户取消了对话框
|
||||
|
||||
self._layout_path = file_path
|
||||
self._update_current_file_label()
|
||||
|
||||
self.view.load_layout(self._layout_path)
|
||||
self._refresh_item_list()
|
||||
self.view.reset_scroll()
|
||||
|
|
@ -1810,6 +1916,18 @@ class MainWindow(QMainWindow):
|
|||
except Exception as e:
|
||||
QMessageBox.critical(self, "加载失败", str(e))
|
||||
|
||||
def _update_current_file_label(self):
|
||||
"""更新工具栏上显示当前文件路径的标签"""
|
||||
if self._layout_path:
|
||||
# 显示完整路径,如果太长会用省略号截断
|
||||
self.lbl_current_file.setText(self._layout_path)
|
||||
# 更新窗口标题显示文件名
|
||||
file_name = os.path.basename(self._layout_path)
|
||||
self.setWindowTitle(f"PCM Viewer - {file_name}")
|
||||
else:
|
||||
self.lbl_current_file.setText("未选择文件")
|
||||
self.setWindowTitle("PCM Viewer - Widgets Dashboard")
|
||||
|
||||
def _on_delete(self):
|
||||
# 删除当前选中的组件(支持多选)
|
||||
scene = self.view.scene_obj
|
||||
|
|
@ -1955,6 +2073,10 @@ def main():
|
|||
from PyQt6.QtWebEngineWidgets import QWebEngineView # 确保已导入
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 应用全局样式
|
||||
apply_styles(app)
|
||||
|
||||
win = MainWindow()
|
||||
win.show()
|
||||
sys.exit(app.exec())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<polygon points="1,1 9,1 5,9" fill="#cdd6f4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 139 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<polygon points="5,1 9,9 1,9" fill="#cdd6f4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 139 B |
|
|
@ -0,0 +1,354 @@
|
|||
"""
|
||||
PCM Viewer - 现代化UI样式定义
|
||||
主题:深色专业主题
|
||||
"""
|
||||
|
||||
# ==================== 颜色定义 ====================
|
||||
class Colors:
|
||||
"""颜色常量定义"""
|
||||
# 主背景色
|
||||
BG_PRIMARY = "#1e1e2e" # 主背景
|
||||
BG_SECONDARY = "#252537" # 次级背景(面板)
|
||||
BG_TERTIARY = "#313244" # 第三级背景(输入框、按钮)
|
||||
BG_HOVER = "#3a3a52" # 悬停背景
|
||||
BG_PRESSED = "#45475a" # 按下背景
|
||||
|
||||
# 强调色
|
||||
ACCENT = "#89b4fa" # 主强调色(蓝)
|
||||
ACCENT_HOVER = "#b4befe" # 强调色悬停
|
||||
ACCENT_PRESSED = "#74c7ec" # 强调色按下
|
||||
|
||||
# 功能色
|
||||
SUCCESS = "#a6e3a1" # 成功(绿)
|
||||
WARNING = "#f9e2af" # 警告(黄)
|
||||
ERROR = "#f38ba8" # 错误(红)
|
||||
INFO = "#89b4fa" # 信息(蓝)
|
||||
|
||||
# 文字颜色
|
||||
TEXT_PRIMARY = "#cdd6f4" # 主文字
|
||||
TEXT_SECONDARY = "#a6adc8" # 次级文字
|
||||
TEXT_MUTED = "#6c7086" # 弱化文字
|
||||
TEXT_ON_ACCENT = "#1e1e2e" # 强调色上的文字
|
||||
|
||||
# 边框
|
||||
BORDER = "#45475a" # 普通边框
|
||||
BORDER_FOCUS = "#89b4fa" # 焦点边框
|
||||
BORDER_SUBTLE = "#313244" # 细微边框
|
||||
|
||||
|
||||
# ==================== 样式表 ====================
|
||||
MAIN_STYLE = f"""
|
||||
/* ==================== 全局样式 ==================== */
|
||||
QMainWindow {{
|
||||
background-color: {Colors.BG_PRIMARY};
|
||||
}}
|
||||
|
||||
QWidget {{
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
font-family: "Microsoft YaHei", "Segoe UI", "PingFang SC", sans-serif;
|
||||
font-size: 13px;
|
||||
}}
|
||||
|
||||
/* 特定容器背景 */
|
||||
QMainWindow > QWidget {{
|
||||
background-color: {Colors.BG_PRIMARY};
|
||||
}}
|
||||
|
||||
/* ==================== 按钮样式 ==================== */
|
||||
QPushButton {{
|
||||
background-color: {Colors.BG_TERTIARY};
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
border: 1px solid {Colors.BORDER};
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
font-weight: 500;
|
||||
min-height: 20px;
|
||||
}}
|
||||
|
||||
QPushButton:hover {{
|
||||
background-color: {Colors.BG_HOVER};
|
||||
border-color: {Colors.ACCENT};
|
||||
}}
|
||||
|
||||
QPushButton:pressed {{
|
||||
background-color: {Colors.BG_PRESSED};
|
||||
}}
|
||||
|
||||
QPushButton:disabled {{
|
||||
background-color: {Colors.BG_SECONDARY};
|
||||
color: {Colors.TEXT_MUTED};
|
||||
border-color: {Colors.BORDER_SUBTLE};
|
||||
}}
|
||||
|
||||
/* 主要操作按钮 */
|
||||
QPushButton#primary {{
|
||||
background-color: {Colors.ACCENT};
|
||||
color: {Colors.TEXT_ON_ACCENT};
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
}}
|
||||
|
||||
QPushButton#primary:hover {{
|
||||
background-color: {Colors.ACCENT_HOVER};
|
||||
}}
|
||||
|
||||
QPushButton#primary:pressed {{
|
||||
background-color: {Colors.ACCENT_PRESSED};
|
||||
}}
|
||||
|
||||
/* 危险操作按钮 */
|
||||
QPushButton#danger {{
|
||||
background-color: {Colors.ERROR};
|
||||
color: {Colors.TEXT_ON_ACCENT};
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QPushButton#danger:hover {{
|
||||
background-color: #f5a0b3;
|
||||
}}
|
||||
|
||||
/* ==================== 输入框样式 ==================== */
|
||||
QLineEdit {{
|
||||
background-color: {Colors.BG_TERTIARY};
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
border: 1px solid {Colors.BORDER};
|
||||
border-radius: 6px;
|
||||
padding: 6px 10px;
|
||||
selection-background-color: {Colors.ACCENT};
|
||||
}}
|
||||
|
||||
QLineEdit:focus {{
|
||||
border-color: {Colors.ACCENT};
|
||||
}}
|
||||
|
||||
QLineEdit:disabled {{
|
||||
background-color: {Colors.BG_SECONDARY};
|
||||
color: {Colors.TEXT_MUTED};
|
||||
}}
|
||||
|
||||
/* ==================== 数字输入框样式 ==================== */
|
||||
QSpinBox {{
|
||||
background-color: {Colors.BG_TERTIARY};
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
border: 1px solid {Colors.BORDER};
|
||||
border-radius: 6px;
|
||||
padding-right: 20px;
|
||||
}}
|
||||
|
||||
QSpinBox:focus {{
|
||||
border-color: {Colors.ACCENT};
|
||||
}}
|
||||
|
||||
/* ==================== 标签样式 ==================== */
|
||||
QLabel {{
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
background: transparent;
|
||||
}}
|
||||
|
||||
QLabel#title {{
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
padding: 4px 0;
|
||||
}}
|
||||
|
||||
QLabel#subtitle {{
|
||||
font-size: 12px;
|
||||
color: {Colors.TEXT_SECONDARY};
|
||||
}}
|
||||
|
||||
/* ==================== 分组框样式 ==================== */
|
||||
QGroupBox {{
|
||||
background-color: {Colors.BG_SECONDARY};
|
||||
border: 1px solid {Colors.BORDER};
|
||||
border-radius: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
padding: 12px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
|
||||
QGroupBox::title {{
|
||||
subcontrol-origin: margin;
|
||||
left: 12px;
|
||||
padding: 0 8px;
|
||||
color: {Colors.ACCENT};
|
||||
}}
|
||||
|
||||
/* ==================== 列表样式 ==================== */
|
||||
QListWidget {{
|
||||
background-color: {Colors.BG_TERTIARY};
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
border: 1px solid {Colors.BORDER};
|
||||
border-radius: 6px;
|
||||
padding: 4px;
|
||||
outline: none;
|
||||
}}
|
||||
|
||||
QListWidget::item {{
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
margin: 2px 0;
|
||||
}}
|
||||
|
||||
QListWidget::item:hover {{
|
||||
background-color: {Colors.BG_HOVER};
|
||||
}}
|
||||
|
||||
QListWidget::item:selected {{
|
||||
background-color: {Colors.ACCENT};
|
||||
color: {Colors.TEXT_ON_ACCENT};
|
||||
}}
|
||||
|
||||
/* ==================== 复选框样式 ==================== */
|
||||
QCheckBox {{
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
spacing: 8px;
|
||||
}}
|
||||
|
||||
QCheckBox::indicator {{
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid {Colors.BORDER};
|
||||
background-color: {Colors.BG_TERTIARY};
|
||||
}}
|
||||
|
||||
QCheckBox::indicator:hover {{
|
||||
border-color: {Colors.ACCENT};
|
||||
}}
|
||||
|
||||
QCheckBox::indicator:checked {{
|
||||
background-color: {Colors.ACCENT};
|
||||
border-color: {Colors.ACCENT};
|
||||
image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAgM0w0LjUgOC41TDIgNiIgc3Ryb2tlPSIjMWUxZTJlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==);
|
||||
}}
|
||||
|
||||
/* ==================== 滚动条样式 ==================== */
|
||||
QScrollBar:vertical {{
|
||||
background-color: {Colors.BG_SECONDARY};
|
||||
width: 12px;
|
||||
border-radius: 6px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:vertical {{
|
||||
background-color: {Colors.BG_TERTIARY};
|
||||
border-radius: 6px;
|
||||
min-height: 30px;
|
||||
}}
|
||||
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{
|
||||
height: 0px;
|
||||
}}
|
||||
|
||||
QScrollBar:horizontal {{
|
||||
background-color: {Colors.BG_SECONDARY};
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:horizontal {{
|
||||
background-color: {Colors.BG_TERTIARY};
|
||||
border-radius: 6px;
|
||||
min-width: 30px;
|
||||
}}
|
||||
|
||||
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{
|
||||
width: 0px;
|
||||
}}
|
||||
|
||||
/* ==================== 分割器样式 ==================== */
|
||||
QSplitter::handle {{
|
||||
background-color: {Colors.BORDER};
|
||||
}}
|
||||
|
||||
QSplitter::handle:horizontal {{
|
||||
width: 2px;
|
||||
}}
|
||||
|
||||
QSplitter::handle:vertical {{
|
||||
height: 2px;
|
||||
}}
|
||||
|
||||
QSplitter::handle:hover {{
|
||||
background-color: {Colors.ACCENT};
|
||||
}}
|
||||
|
||||
/* ==================== 对话框样式 ==================== */
|
||||
QDialog {{
|
||||
background-color: {Colors.BG_PRIMARY};
|
||||
}}
|
||||
|
||||
QDialogButtonBox QPushButton {{
|
||||
min-width: 80px;
|
||||
}}
|
||||
|
||||
/* ==================== 消息框样式 ==================== */
|
||||
QMessageBox {{
|
||||
background-color: {Colors.BG_PRIMARY};
|
||||
}}
|
||||
|
||||
QMessageBox QLabel {{
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
}}
|
||||
|
||||
/* ==================== 文件对话框样式 ==================== */
|
||||
QFileDialog {{
|
||||
background-color: {Colors.BG_PRIMARY};
|
||||
}}
|
||||
|
||||
/* ==================== 颜色选择对话框样式 ==================== */
|
||||
QColorDialog {{
|
||||
background-color: {Colors.BG_PRIMARY};
|
||||
}}
|
||||
|
||||
/* ==================== 纯文本编辑框样式 ==================== */
|
||||
QPlainTextEdit {{
|
||||
background-color: {Colors.BG_TERTIARY};
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
border: 1px solid {Colors.BORDER};
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
font-family: "Consolas", "Monaco", "Courier New", monospace;
|
||||
selection-background-color: {Colors.ACCENT};
|
||||
}}
|
||||
|
||||
QPlainTextEdit:focus {{
|
||||
border-color: {Colors.ACCENT};
|
||||
}}
|
||||
|
||||
/* ==================== 表单布局样式 ==================== */
|
||||
QFormLayout QLabel {{
|
||||
color: {Colors.TEXT_SECONDARY};
|
||||
padding-right: 12px;
|
||||
}}
|
||||
|
||||
/* ==================== 工具提示样式 ==================== */
|
||||
QToolTip {{
|
||||
background-color: {Colors.BG_SECONDARY};
|
||||
color: {Colors.TEXT_PRIMARY};
|
||||
border: 1px solid {Colors.BORDER};
|
||||
border-radius: 4px;
|
||||
padding: 6px 10px;
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
# ==================== 画布样式 ====================
|
||||
CANVAS_STYLE = {
|
||||
'background': Colors.BG_SECONDARY,
|
||||
'grid_color': Colors.BORDER_SUBTLE,
|
||||
'selection_border': Colors.ACCENT,
|
||||
'selection_fill': f"{Colors.ACCENT}33", # 20%透明度
|
||||
'canvas_border': Colors.BORDER,
|
||||
}
|
||||
|
||||
|
||||
# ==================== 组件默认样式 ====================
|
||||
ITEM_STYLE = {
|
||||
'default_border': Colors.BORDER,
|
||||
'selected_border': Colors.ACCENT,
|
||||
'hover_border': Colors.ACCENT_HOVER,
|
||||
'resize_handle': Colors.ACCENT,
|
||||
'resize_handle_size': 8,
|
||||
}
|
||||
Loading…
Reference in New Issue