样式 + 对话框加载json

main
risingLee 2026-02-25 15:05:50 +08:00
parent 55ca42b24f
commit c21089e8ea
5 changed files with 539 additions and 57 deletions

View File

@ -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
View File

@ -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())

3
resources/down_arrow.svg Normal file
View File

@ -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

3
resources/up_arrow.svg Normal file
View File

@ -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

354
styles.py Normal file
View File

@ -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,
}