From c7a4ce7963509f84f6dff504756499c24a2830db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Fri, 10 Oct 2025 16:38:25 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E9=A6=83=E5=B8=B9=20UI=E7=92=81=E6=8D=90?= =?UTF-8?q?=EE=85=B8=E6=B5=BC=E6=A8=BA=E5=AF=B2:=20=E9=8F=8D=E8=A7=84?= =?UTF-8?q?=E5=B5=81Figma=E7=92=81=E6=8D=90=EE=85=B8=E9=8D=9A=E5=B1=BE?= =?UTF-8?q?=EE=84=9E=E9=8F=81=E7=BF=A0=E7=B6=8B=E7=94=AF=E5=86=A8=E7=9C=AC?= =?UTF-8?q?=E9=8D=9C=E5=B2=83=E5=A3=8A=E7=92=8B=3F=20-=20=E7=BC=81?= =?UTF-8?q?=E7=86=B6=E7=AB=B4=E5=A8=A3=E8=BE=AB=E5=A3=8A=E6=B6=93=E5=A9=9A?= =?UTF-8?q?=EE=95=BD=E9=96=B0=E5=B6=88=E5=A3=8A=E9=8F=82=E8=A7=84=EE=94=8D?= =?UTF-8?q?=20(#19191b=E9=91=B3=E5=B1=BE=E6=AB=99,=20#3067c0=E9=92=83?= =?UTF-8?q?=E6=BF=8A=E5=A3=8A=E6=A5=82=E6=A8=B9=E5=AF=92)=20-=20=E6=B5=BC?= =?UTF-8?q?=E6=A8=BA=E5=AF=B2=E7=80=9B=E6=A4=BE=E7=B6=8B=E7=81=9E=E5=82=9C?= =?UTF-8?q?=E9=AA=87=E7=BB=AF=E8=8D=A4=E7=B2=BA=20(16/14/12/10px)=20-=20?= =?UTF-8?q?=E9=8F=87=E5=AD=98=E6=9F=8A=E9=8E=B5=E2=82=AC=E9=8F=88=E5=A4=8A?= =?UTF-8?q?=E4=BB=A0=E9=97=88=E7=8A=B5=E7=8D=A5=E9=8D=99=EF=BD=86=E7=89=B1?= =?UTF-8?q?=E5=AF=AE=3F(=E7=81=9E=E5=82=9C=E9=AA=87/=E7=81=9E=E7=82=B4?= =?UTF-8?q?=E2=82=AC=3F=E9=91=B4=E6=B0=AD=E6=B9=B0/=E7=92=A7=E5=8B=AC?= =?UTF-8?q?=E7=B0=AE/=E9=8E=BA=D1=83=E5=9F=97=E9=8D=99=3F=20-=20=E6=B5=BC?= =?UTF-8?q?=E6=A8=BA=E5=AF=B2QGroupBox=E9=8F=8D=E5=B3=B0=E7=B4=A1=20(?= =?UTF-8?q?=E9=96=AB=E5=BF=94=E6=A7=91=E9=91=B3=E5=B1=BE=E6=AB=99,=2080%?= =?UTF-8?q?=E9=96=AB=E5=BF=94=E6=A7=91=E6=90=B4=EF=B8=BD=E7=88=A3=E6=A3=B0?= =?UTF-8?q?=3F=20-=20=E7=BC=81=E7=86=B6=E7=AB=B4=E9=8D=92=E6=A5=84?= =?UTF-8?q?=E3=80=83=E9=8D=9C=E5=B1=BE=E7=88=B2=E8=A4=B0=E3=88=A1=E5=B8=B6?= =?UTF-8?q?=E6=B5=A0=E8=88=B5=E7=89=B1=E5=AF=AE=3F-=20=E6=B5=BC=E6=A8=BA?= =?UTF-8?q?=E5=AF=B2=E9=8F=8D=E5=9B=A9=EE=84=B7=E6=A4=A4=E5=9E=AB=E7=89=B1?= =?UTF-8?q?=E5=AF=AE=3F(=E9=92=83=E6=BF=8A=E5=A3=8A=E9=96=AB=E5=A4=89?= =?UTF-8?q?=E8=85=91,=20=E9=90=8F=E6=8B=8C=E5=A3=8A=E9=8F=88=EE=81=88?= =?UTF-8?q?=E2=82=AC=E5=A4=89=E8=85=91)=20-=20=E7=BB=89=E5=A9=9A=E6=AB=8E?= =?UTF-8?q?=E6=B6=93=E5=B6=85=E7=B9=80=E7=91=95=E4=BD=BA=E6=AE=91=E6=9D=88?= =?UTF-8?q?=E8=A7=84=EE=94=8B,=20=E7=80=B9=E7=82=B5=E5=B9=87=E9=8F=83?= =?UTF-8?q?=E7=8A=B5=E7=B4=B3=E9=8F=81=E6=9D=91=E6=82=8E=20-=20=E7=92=8B?= =?UTF-8?q?=E5=86=A9=E6=9A=A3=E6=9D=88=E6=92=B3=E5=8F=86=E5=A6=97=E5=97=97?= =?UTF-8?q?=E6=8B=B0=E9=8E=B8=E5=A4=90=E6=8C=B3=E9=90=A8=E5=8B=AA=E6=B0=A6?= =?UTF-8?q?=E6=B5=9C=E6=8E=93=E6=99=A5=E9=8F=8B=3F=20=E9=8F=89=E3=83=A6?= =?UTF-8?q?=E7=B0=AE:=20Figma=E6=A3=B0=E6=88=A6=E4=BA=BE=206glyi82d=20?= =?UTF-8?q?=E9=90=98=E8=88=B5=E2=82=AC=3F=20=E9=89=81=3F=E7=92=81=E6=8D=90?= =?UTF-8?q?=EE=85=B8=E9=8D=9A=E5=B1=BE=EE=84=9E=E7=80=B9=E5=B1=BE=E5=9E=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/main_window.py | 570 +++++++++++++++++++++++++++------------------- 1 file changed, 340 insertions(+), 230 deletions(-) diff --git a/ui/main_window.py b/ui/main_window.py index 01dc5b6c..641e4ab9 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -49,106 +49,124 @@ class MainWindow(QMainWindow): self.setStyleSheet(""" QMainWindow { - background-color: #1e1e2e; + background-color: #000000; } QMenuBar { - background-color: #252538; - color: #e0e0ff; - border-bottom: 1px solid #3a3a4a; + background-color: #000000; + color: #ffffff; + border-bottom: 1px solid #4c5c6e; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 14px; + font-weight: 400; } QMenuBar::item { background-color: transparent; - padding: 4px 8px; + padding: 6px 12px; + color: #ffffff; } QMenuBar::item:selected { - background-color: rgba(139, 92, 246, 100); + background-color: rgba(48, 103, 192, 0.4); } QMenuBar::item:pressed { - background-color: rgba(139, 92, 246, 150); + background-color: rgba(48, 103, 192, 0.6); } QMenu { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; + background-color: #19191b; + color: #ebebeb; + border: 1px solid #4c5c6e; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 14px; } QMenu::item { - padding: 4px 20px; + padding: 6px 20px; } QMenu::item:selected { - background-color: rgba(139, 92, 246, 100); + background-color: rgba(48, 103, 192, 0.4); } QPushButton { - background-color: #8b5cf6; - color: white; + background-color: rgba(89, 98, 118, 0.4); + color: #ebebeb; border: none; padding: 6px 12px; - border-radius: 4px; - font-weight: 500; + border-radius: 2px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-weight: 300; + font-size: 10px; } QPushButton:hover { - background-color: #7c3aed; + background-color: #3067c0; + color: #ffffff; } QPushButton:pressed { - background-color: #6d28d9; + background-color: #2556a0; } QPushButton:disabled { - background-color: #4c4c6e; - color: #8888aa; + background-color: #394560; + color: rgba(235, 235, 235, 0.5); } QComboBox { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; padding: 4px 8px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; } QComboBox::drop-down { border: none; } QComboBox QAbstractItemView { - background-color: #2d2d44; - color: #e0e0ff; - selection-background-color: rgba(139, 92, 246, 100); + background-color: #596471; + color: #ebebeb; + selection-background-color: rgba(48, 103, 192, 0.4); } QLineEdit { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; padding: 4px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; } QSpinBox, QDoubleSpinBox { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; padding: 4px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; } QScrollBar:vertical { - background-color: #252538; - width: 15px; + background-color: #19191b; + width: 12px; border: none; } QScrollBar::handle:vertical { - background-color: #3a3a4a; - border-radius: 4px; + background-color: #596471; + border-radius: 2px; min-height: 20px; } QScrollBar::handle:vertical:hover { - background-color: #8b5cf6; + background-color: #3067c0; } QScrollBar:horizontal { - background-color: #252538; - width: 15px; + background-color: #19191b; + height: 12px; border: none; } QScrollBar::handle:horizontal { - background-color: #3a3a4a; - border-radius: 4px; - min-height: 20px; + background-color: #596471; + border-radius: 2px; + min-width: 20px; } QScrollBar::handle:horizontal:hover { - background-color: #8b5cf6; + background-color: #3067c0; } """) # 设置 QMessageBox 样式表 @@ -177,31 +195,37 @@ class MainWindow(QMainWindow): # 设置 QMessageBox 的样式表 msg_box_style = """ QMessageBox { - background-color: #252538; - color: #e0e0ff; - border: 1px solid #3a3a4a; + background-color: #19191b; + color: #ebebeb; + border: 1px solid #4c5c6e; } QMessageBox QLabel { - color: #e0e0ff; + color: #ebebeb; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; + font-weight: 300; } QMessageBox QPushButton { - background-color: #8b5cf6; - color: white; + background-color: rgba(89, 98, 118, 0.4); + color: #ebebeb; border: none; padding: 6px 12px; - border-radius: 4px; - font-weight: 500; + border-radius: 2px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-weight: 300; + font-size: 10px; min-width: 80px; } QMessageBox QPushButton:hover { - background-color: #7c3aed; + background-color: #3067c0; + color: #ffffff; } QMessageBox QPushButton:pressed { - background-color: #6d28d9; + background-color: #2556a0; } QMessageBox QPushButton:disabled { - background-color: #4c4c6e; - color: #8888aa; + background-color: #394560; + color: rgba(235, 235, 235, 0.5); } """ @@ -219,38 +243,45 @@ class MainWindow(QMainWindow): # 设置样式表 dialog.setStyleSheet(""" QInputDialog { - background-color: #252538; - color: #e0e0ff; + background-color: #19191b; + color: #ebebeb; } QLabel { - color: #e0e0ff; + color: #ffffff; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-weight: 500; + font-size: 14px; } QLineEdit { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; padding: 6px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; } QPushButton { - background-color: #8b5cf6; - color: white; + background-color: rgba(89, 98, 118, 0.4); + color: #ebebeb; border: none; padding: 6px 12px; - border-radius: 4px; - font-weight: 500; + border-radius: 2px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-weight: 300; + font-size: 10px; min-width: 80px; } QPushButton:hover { - background-color: #7c3aed; + background-color: #3067c0; + color: #ffffff; } QPushButton:pressed { - background-color: #6d28d9; + background-color: #2556a0; } QPushButton:disabled { - background-color: #4c4c6e; - color: #8888aa; + background-color: #394560; + color: rgba(235, 235, 235, 0.5); } """) @@ -264,77 +295,91 @@ class MainWindow(QMainWindow): # 设置样式表 dialog.setStyleSheet(""" QFileDialog { - background-color: #252538; - color: #e0e0ff; + background-color: #19191b; + color: #ebebeb; } QLabel { - color: #e0e0ff; + color: #ebebeb; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; } QListView { - background-color: #1e1e2e; - color: #e0e0ff; - border: 1px solid #3a3a4a; - alternate-background-color: #252538; + background-color: #19191b; + color: #ebebeb; + border: 1px solid #4c5c6e; + alternate-background-color: #19191b; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; } QListView::item:hover { - background-color: #3a3a4a; + background-color: #394560; } QListView::item:selected { - background-color: rgba(139, 92, 246, 100); - color: white; + background-color: rgba(48, 103, 192, 0.4); + color: #ffffff; } QTreeView { - background-color: #1e1e2e; - color: #e0e0ff; - border: 1px solid #3a3a4a; - alternate-background-color: #252538; + background-color: #19191b; + color: #ebebeb; + border: 1px solid #4c5c6e; + alternate-background-color: #19191b; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; } QTreeView::item:hover { - background-color: #3a3a4a; + background-color: #394560; } QTreeView::item:selected { - background-color: rgba(139, 92, 246, 100); - color: white; + background-color: rgba(48, 103, 192, 0.4); + color: #ffffff; } QComboBox { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; padding: 4px 8px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; } QComboBox::drop-down { border: none; } QComboBox QAbstractItemView { - background-color: #2d2d44; - color: #e0e0ff; - selection-background-color: rgba(139, 92, 246, 100); + background-color: #596471; + color: #ebebeb; + selection-background-color: rgba(48, 103, 192, 0.4); } QPushButton { - background-color: #8b5cf6; - color: white; + background-color: rgba(89, 98, 118, 0.4); + color: #ebebeb; border: none; padding: 6px 12px; - border-radius: 4px; - font-weight: 500; + border-radius: 2px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-weight: 300; + font-size: 10px; } QPushButton:hover { - background-color: #7c3aed; + background-color: #3067c0; + color: #ffffff; } QPushButton:pressed { - background-color: #6d28d9; + background-color: #2556a0; } QPushButton:disabled { - background-color: #4c4c6e; - color: #8888aa; + background-color: #394560; + color: rgba(235, 235, 235, 0.5); } QLineEdit { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; padding: 4px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; } """) @@ -1104,14 +1149,18 @@ class MainWindow(QMainWindow): self.leftDock = QDockWidget("层级", self) self.leftDock.setStyleSheet(""" QDockWidget { - background-color: #252538; - color: #e0e0ff; - border: 1px solid #3a3a4a; + background-color: #19191b; + color: #ffffff; + border: none; } QDockWidget::title { - background-color: #2d2d44; - padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ - border-bottom: 0px solid #3a3a4a; + background-color: #19191b; + padding: 8px 10px; + border-bottom: none; + text-align: left; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 14px; + font-weight: 500; } QDockWidget::close-button { background-color: #8b5cf6; @@ -1136,17 +1185,28 @@ class MainWindow(QMainWindow): background-color: #8b5cf6; /* 按下时显示紫色高亮 */ } QTreeView { - background-color: #1e1e2e; - color: #e0e0ff; - border: 1px solid #3a3a4a; - alternate-background-color: #252538; + background-color: #19191b; + color: #ebebeb; + border: none; + alternate-background-color: #19191b; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; + outline: none; + } + QTreeView::item { + padding: 3px 0px; + border: none; } QTreeView::item:hover { - background-color: #3a3a4a; + background-color: rgba(89, 100, 113, 0.3); } QTreeView::item:selected { - background-color: rgba(139, 92, 246, 100); - color: white; + background-color: rgba(48, 103, 192, 0.4); + color: #ffffff; + } + QTreeView::branch { + background-color: #19191b; } """) self.treeWidget = CustomTreeWidget(self.world) @@ -1158,14 +1218,18 @@ class MainWindow(QMainWindow): self.rightDock = QDockWidget("属性", self) self.rightDock.setStyleSheet(""" QDockWidget { - background-color: #252538; - color: #e0e0ff; - border: 1px solid #3a3a4a; + background-color: #19191b; + color: #ffffff; + border: none; } QDockWidget::title { - background-color: #2d2d44; - padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ - border-bottom: 0px solid #3a3a4a; + background-color: #19191b; + padding: 8px 10px; + border-bottom: none; + text-align: left; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 16px; + font-weight: 500; } QDockWidget::close-button { background-color: #8b5cf6; @@ -1190,28 +1254,37 @@ class MainWindow(QMainWindow): background-color: #8b5cf6; /* 按下时显示紫色高亮 */ } QScrollArea { - background-color: #1e1e2e; + background-color: #19191b; border: none; } QWidget#PropertyContainer { - background-color: #1e1e2e; + background-color: #19191b; } QLabel { - color: #e0e0ff; + color: #ebebeb; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; } QGroupBox { - background-color: #252538; - border: 1px solid #3a3a4a; - border-radius: 6px; - margin-top: 1ex; - color: #e0e0ff; + background-color: transparent; + border: none; + margin-top: 20px; + margin-bottom: 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; font-weight: 500; - padding-top: 10px; /* 增加顶部内边距,使标题和内容分离 */ + color: rgba(255, 255, 255, 0.8); + padding-top: 0px; } QGroupBox::title { - padding: 0 8px; - color: #c0c0e0; + padding: 0 0 10px 0; + color: rgba(255, 255, 255, 0.8); + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; font-weight: 500; + subcontrol-origin: margin; + subcontrol-position: top left; } """) @@ -1264,14 +1337,18 @@ class MainWindow(QMainWindow): self.scriptDock = QDockWidget("脚本管理", self) self.scriptDock.setStyleSheet(""" QDockWidget { - background-color: #252538; - color: #e0e0ff; - border: 1px solid #3a3a4a; + background-color: #19191b; + color: #ffffff; + border: none; } QDockWidget::title { - background-color: #2d2d44; - padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ - border-bottom: 0px solid #3a3a4a; + background-color: #19191b; + padding: 8px 10px; + border-bottom: none; + text-align: left; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 14px; + font-weight: 500; } QDockWidget::close-button { background-color: #8b5cf6; @@ -1296,28 +1373,37 @@ class MainWindow(QMainWindow): background-color: #8b5cf6; /* 按下时显示紫色高亮 */ } QScrollArea { - background-color: #1e1e2e; + background-color: #19191b; border: none; } - QWidget#PropertyContainer { - background-color: #1e1e2e; + QWidget#ScriptContainer { + background-color: #19191b; } QLabel { - color: #e0e0ff; + color: #ebebeb; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; } QGroupBox { - background-color: #252538; - border: 1px solid #3a3a4a; - border-radius: 6px; - margin-top: 1ex; - color: #e0e0ff; + background-color: transparent; + border: none; + margin-top: 20px; + margin-bottom: 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; font-weight: 500; - padding-top: 10px; /* 增加顶部内边距,使标题和内容分离 */ + color: rgba(255, 255, 255, 0.8); + padding-top: 0px; } QGroupBox::title { - padding: 0 8px; - color: #c0c0e0; + padding: 0 0 10px 0; + color: rgba(255, 255, 255, 0.8); + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; font-weight: 500; + subcontrol-origin: margin; + subcontrol-position: top left; } """) @@ -1326,7 +1412,7 @@ class MainWindow(QMainWindow): # 设置脚本容器的背景色 self.scriptContainer.setStyleSheet(""" QWidget#ScriptContainer { - background-color: #1e1e2e; + background-color: #19191b; } """) self.scriptContainer.setObjectName("ScriptContainer") @@ -1375,14 +1461,18 @@ class MainWindow(QMainWindow): self.bottomDock = QDockWidget("资源", self) self.bottomDock.setStyleSheet(""" QDockWidget { - background-color: #252538; - color: #e0e0ff; - border: 1px solid #3a3a4a; + background-color: #19191b; + color: #ffffff; + border: none; } QDockWidget::title { - background-color: #2d2d44; - padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ - border-bottom: 0px solid #3a3a4a; + background-color: #19191b; + padding: 8px 10px; + border-bottom: none; + text-align: left; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 14px; + font-weight: 500; } QDockWidget::close-button { background-color: #8b5cf6; @@ -1412,23 +1502,35 @@ class MainWindow(QMainWindow): # 为资源树添加样式 self.fileView.setStyleSheet(""" QTreeWidget { - background-color: #1e1e2e; - color: #e0e0ff; - border: 1px solid #3a3a4a; - alternate-background-color: #252538; + background-color: #19191b; + color: #ebebeb; + border: none; + alternate-background-color: #19191b; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; + outline: none; + } + QTreeWidget::item { + padding: 3px 0px; + border: none; } QTreeWidget::item:hover { - background-color: #3a3a4a; + background-color: rgba(89, 100, 113, 0.3); } QTreeWidget::item:selected { - background-color: rgba(139, 92, 246, 100); - color: white; + background-color: rgba(48, 103, 192, 0.4); + color: #ffffff; } QHeaderView::section { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - padding: 4px; + background-color: #19191b; + color: #ebebeb; + border: none; + border-bottom: 1px solid #4c5c6e; + padding: 6px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; + font-weight: 500; } """) self.bottomDock.setWidget(self.fileView) @@ -1438,14 +1540,18 @@ class MainWindow(QMainWindow): self.consoleDock = QDockWidget("控制台", self) self.consoleDock.setStyleSheet(""" QDockWidget { - background-color: #252538; - color: #e0e0ff; - border: 1px solid #3a3a4a; + background-color: #19191b; + color: #ffffff; + border: none; } QDockWidget::title { - background-color: #2d2d44; - padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ - border-bottom: 0px solid #3a3a4a; + background-color: #19191b; + padding: 8px 10px; + border-bottom: none; + text-align: left; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 14px; + font-weight: 500; } QDockWidget::close-button { background-color: #8b5cf6; @@ -1474,10 +1580,12 @@ class MainWindow(QMainWindow): # 为控制台添加样式 self.consoleView.setStyleSheet(""" QTextEdit { - background-color: #1e1e2e; - color: #e0e0ff; - border: 1px solid #3a3a4a; - font-family: 'Consolas', 'Monaco', monospace; + background-color: #19191b; + color: #ebebeb; + border: none; + font-family: 'Consolas', 'Monaco', 'Microsoft YaHei', monospace; + font-size: 10px; + font-weight: 300; } """) self.consoleDock.setWidget(self.consoleView) @@ -1500,44 +1608,46 @@ class MainWindow(QMainWindow): tab_bar_style = """ /* QTabBar 的整体样式 */ QTabBar { - qproperty-drawBase: 0; /* 移除标签栏底部的线条 */ + qproperty-drawBase: 0; } QTabBar::tab { - background-color: #2d2d44; /* 未选中标签的背景色,与标题栏一致 */ - color: #c0c0e0; /* 未选中标签的文字颜色 */ - border: 1px solid #3a3a4a; /* 边框颜色 */ - border-bottom: none; /* 移除底部边框 */ - border-top-left-radius: 6px; - border-top-right-radius: 6px; - padding: 8px 16px; /* 内边距,让文字不拥挤 */ + background-color: #394560; + color: rgba(255, 255, 255, 0.7); + border: 1px solid #4c5c6e; + border-bottom: none; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + padding: 8px 16px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-weight: 500; - margin-right: 2px; /* 标签之间的间距 */ + font-size: 14px; + margin-right: 2px; } /* 鼠标悬停在标签上时的样式 */ QTabBar::tab:hover { - background-color: #3a3a4a; /* 悬停时使用稍亮的背景色 */ + background-color: #596276; } /* 当前选中的标签页的样式 */ QTabBar::tab:selected { - background-color: #252538; /* 选中时使用 Dock 背景色 */ - color: #ffffff; /* 选中时使用更亮的文字颜色 */ - font-weight: bold; /* 字体加粗 */ - border-color: #3a3a4a; - border-bottom: 2px solid #8b5cf6; /* 选中标签的底部高亮线 */ + background-color: #3067c0; + color: #ffffff; + font-weight: 500; + border-color: #4c5c6e; + border-bottom: 2px solid #3067c0; } /* 未选中的标签 */ QTabBar::tab:!selected { - margin-top: 2px; /* 未选中标签稍微下移,创建层次感 */ + margin-top: 2px; } /* 标签栏底部的线条 */ QTabBar::tab-bar { alignment: left; - border: none; /* 移除标签栏边框 */ + border: none; } """ @@ -1689,27 +1799,27 @@ class MainWindow(QMainWindow): self.scriptsList = QListWidget() self.scriptsList.setStyleSheet(""" QListWidget { - background-color: #1e1e2e; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; - alternate-background-color: #252538; - selection-background-color: rgba(139, 92, 246, 100); - selection-color: white; + background-color: #19191b; + color: #ebebeb; + border: none; + alternate-background-color: #19191b; + selection-background-color: rgba(48, 103, 192, 0.4); + selection-color: #ffffff; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; + outline: none; } QListWidget::item { padding: 6px 8px; - border-bottom: 1px solid #2d2d44; - } - QListWidget::item:last-child { - border-bottom: none; + border: none; } QListWidget::item:hover { - background-color: #3a3a4a; + background-color: rgba(89, 100, 113, 0.3); } QListWidget::item:selected { - background-color: rgba(139, 92, 246, 120); - color: white; + background-color: rgba(48, 103, 192, 0.4); + color: #ffffff; } """) self.scriptsList.itemDoubleClicked.connect(self.onScriptDoubleClick) @@ -1757,28 +1867,28 @@ class MainWindow(QMainWindow): self.mountedScriptsList = QListWidget() self.mountedScriptsList.setStyleSheet(""" QListWidget { - background-color: #1e1e2e; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; - alternate-background-color: #252538; - selection-background-color: rgba(139, 92, 246, 100); - selection-color: white; + background-color: #19191b; + color: #ebebeb; + border: none; + alternate-background-color: #19191b; + selection-background-color: rgba(48, 103, 192, 0.4); + selection-color: #ffffff; max-height: 120px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; + outline: none; } QListWidget::item { padding: 4px 8px; - border-bottom: 1px solid #2d2d44; - } - QListWidget::item:last-child { - border-bottom: none; + border: none; } QListWidget::item:hover { - background-color: #3a3a4a; + background-color: rgba(89, 100, 113, 0.3); } QListWidget::item:selected { - background-color: rgba(139, 92, 246, 120); - color: white; + background-color: rgba(48, 103, 192, 0.4); + color: #ffffff; } """) self.mountedScriptsList.setMaximumHeight(100) From ae4fbb8b703657d9623bd1f130c4d0488c9b46d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Wed, 15 Oct 2025 11:48:58 +0800 Subject: [PATCH 2/7] =?UTF-8?q?1.UI=E9=A3=8E=E6=A0=BC=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/icon_manager.py | 12 + ui/main_window.py | 1179 +++++++++++++++++++++++++++++++++++------- ui/property_panel.py | 776 ++++++++++++++++++++++----- ui/widgets.py | 809 +++++++++++++++++++++++------ 4 files changed, 2308 insertions(+), 468 deletions(-) diff --git a/ui/icon_manager.py b/ui/icon_manager.py index 944c3aa6..f3a9d375 100644 --- a/ui/icon_manager.py +++ b/ui/icon_manager.py @@ -52,6 +52,18 @@ class IconManager: 'warning': 'warning.png', 'error': 'error.png', 'info': 'info.png', + + 'up_arrow': 'up_arrows.png', + 'down_arrow': 'down_arrows.png', + 'left_arrow': 'left_arrows.png', + 'right_arrow': 'right_arrows.png', + + 'solid_down_arrows': 'solid_down_arrows.png', + 'solid_right_arrows': 'solid_right_arrows.png', + + 'minimize_icon': 'minimize_icon.png', + 'windowing_icon': 'windowing_icon.png', + 'close_icon': 'close_icon.png', } # 初始化默认图标 diff --git a/ui/main_window.py b/ui/main_window.py index 641e4ab9..e5233ddc 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -16,7 +16,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea, QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout, QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QDialog, - QSpinBox, QFrame, QRadioButton, QTextEdit) + QSpinBox, QFrame, QRadioButton, QTextEdit, QTabWidget) from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint, QUrl, QRect from direct.showbase.ShowBaseGlobal import aspect2d from panda3d.core import OrthographicLens @@ -56,13 +56,13 @@ class MainWindow(QMainWindow): color: #ffffff; border-bottom: 1px solid #4c5c6e; font-family: 'Microsoft YaHei', 'Inter', sans-serif; - font-size: 14px; + font-size: 12px; font-weight: 400; } QMenuBar::item { background-color: transparent; padding: 6px 12px; - color: #ffffff; + color: #D4D4D4; } QMenuBar::item:selected { background-color: rgba(48, 103, 192, 0.4); @@ -71,11 +71,11 @@ class MainWindow(QMainWindow): background-color: rgba(48, 103, 192, 0.6); } QMenu { - background-color: #19191b; + background-color: #2E3035; color: #ebebeb; border: 1px solid #4c5c6e; font-family: 'Microsoft YaHei', 'Inter', sans-serif; - font-size: 14px; + font-size: 12px; } QMenu::item { padding: 6px 20px; @@ -116,6 +116,14 @@ class MainWindow(QMainWindow): } QComboBox::drop-down { border: none; + border-left: 1px solid rgba(76, 92, 110, 0.6); + background-color: rgba(89, 100, 113, 0.3); + width: 16px; + } + QComboBox::down-arrow { + image: url(icons/down_arrows.png); + width: 10px; + height: 10px; } QComboBox QAbstractItemView { background-color: #596471; @@ -457,48 +465,53 @@ class MainWindow(QMainWindow): # """) self.embeddedToolbar.setStyleSheet(""" QFrame#UnityToolbar { - background-color: rgba(40, 40, 60, 200); /* 深蓝灰色背景 */ - border: 1px solid rgba(80, 80, 120, 200); /* 深蓝灰边框 */ - border-radius: 6px; - padding: 4px; + background-color: transparent; + border: none; + padding: 0px; } QToolButton { - background-color: rgba(60, 60, 90, 180); /* 稍亮的深蓝灰 */ - border: 1px solid rgba(100, 100, 150, 150); - color: #e0e0ff; /* 浅蓝白色文字 */ - padding: 6px 8px; - border-radius: 4px; - font-weight: 500; + background-color: #1e1e1f; + border: 1px solid rgba(184, 211, 241, 0.2); + color: #ebebeb; + padding: 0px; + border-radius: 2px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; + min-width: 31px; + max-width: 31px; + min-height: 30px; + max-height: 30px; } QToolButton:hover { - background-color: rgba(80, 80, 130, 200); /* 悬停时更亮 */ - border: 1px solid rgba(139, 92, 246, 150); /* 紫色边框 */ + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.3); } QToolButton:checked { - background-color: rgba(139, 92, 246, 180); /* 紫色背景 */ - color: white; - border: 1px solid rgba(180, 140, 255, 200); + background-color: #3067c0; + color: #ffffff; + border: 1px solid rgba(48, 103, 192, 0.6); } QToolButton:pressed { - background-color: rgba(120, 75, 220, 200); /* 按下时稍暗的紫色 */ + background-color: #2556a0; } """) # 水平布局 self.toolbarLayout = QHBoxLayout(self.embeddedToolbar) - self.toolbarLayout.setContentsMargins(5, 5, 5, 5) - self.toolbarLayout.setSpacing(2) + self.toolbarLayout.setContentsMargins(0, 0, 0, 0) + self.toolbarLayout.setSpacing(4) # 创建工具按钮组 self.toolGroup = QButtonGroup() # 选择工具 self.selectTool = QToolButton() - select_icon = get_icon('select_tool', QSize(16, 16)) + select_icon = get_icon('select_tool', QSize(20, 20)) if not select_icon.isNull(): self.selectTool.setIcon(select_icon) self.selectTool.setText('选择') # 如果没有图标则显示文字 - self.selectTool.setIconSize(QSize(16, 16)) + self.selectTool.setIconSize(QSize(20, 20)) self.selectTool.setCheckable(True) self.selectTool.setToolTip("选择工具 (Q)") # self.selectTool.setShortcut(QKeySequence("Q")) @@ -511,7 +524,7 @@ class MainWindow(QMainWindow): if icon_path and os.path.exists(icon_path): self.moveTool.setIcon(QIcon(icon_path)) self.moveTool.setText("移动") - self.moveTool.setIconSize(QSize(16, 16)) + self.moveTool.setIconSize(QSize(20, 20)) self.moveTool.setCheckable(True) self.moveTool.setToolTip("移动工具 (W)") # self.moveTool.setShortcut(QKeySequence("W")) @@ -520,11 +533,11 @@ class MainWindow(QMainWindow): # 旋转工具 self.rotateTool = QToolButton() - rotate_icon = get_icon('rotate_tool', QSize(16, 16)) + rotate_icon = get_icon('rotate_tool', QSize(20, 20)) if not rotate_icon.isNull(): self.rotateTool.setIcon(rotate_icon) self.rotateTool.setText("旋转") - self.rotateTool.setIconSize(QSize(16, 16)) + self.rotateTool.setIconSize(QSize(20, 20)) self.rotateTool.setCheckable(True) self.rotateTool.setToolTip("旋转工具 (E)") # self.rotateTool.setShortcut(QKeySequence("E")) @@ -533,11 +546,11 @@ class MainWindow(QMainWindow): # 缩放工具 self.scaleTool = QToolButton() - scale_icon = get_icon('scale_tool', QSize(16, 16)) + scale_icon = get_icon('scale_tool', QSize(20, 20)) if not scale_icon.isNull(): self.scaleTool.setIcon(scale_icon) self.scaleTool.setText("缩放") - self.scaleTool.setIconSize(QSize(16, 16)) + self.scaleTool.setIconSize(QSize(20, 20)) self.scaleTool.setCheckable(True) self.scaleTool.setToolTip("缩放工具 (R)") # self.scaleTool.setShortcut(QKeySequence("R")) @@ -545,12 +558,12 @@ class MainWindow(QMainWindow): self.toolbarLayout.addWidget(self.scaleTool) # 添加分隔符 - separator = QFrame() - separator.setFrameShape(QFrame.VLine) - separator.setFrameShadow(QFrame.Sunken) - separator.setStyleSheet("background-color: rgba(240, 240, 240, 255);") - separator.setFixedWidth(1) - self.toolbarLayout.addWidget(separator) + # separator = QFrame() + # separator.setFrameShape(QFrame.VLine) + # separator.setFrameShadow(QFrame.Sunken) + # separator.setStyleSheet("background-color: rgba(240, 240, 240, 255);") + # separator.setFixedWidth(1) + # self.toolbarLayout.addWidget(separator) # 设置位置到左上角 self.embeddedToolbar.move(10, 10) @@ -1163,26 +1176,32 @@ class MainWindow(QMainWindow): font-weight: 500; } QDockWidget::close-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 5px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 6px; + top: 6px; } QDockWidget::float-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 25px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 26px; + top: 6px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { - background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + background-color: rgba(89, 100, 113, 0.5); + border: 1px solid rgba(184, 211, 241, 0.3); } QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { - background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + background-color: #3067c0; + border: 1px solid rgba(48, 103, 192, 0.6); } QTreeView { background-color: #19191b; @@ -1208,10 +1227,46 @@ class MainWindow(QMainWindow): QTreeView::branch { background-color: #19191b; } + QTreeView::branch:has-children:!has-siblings:closed, + QTreeView::branch:closed:has-children:has-siblings { + image: url(icons/solid_right_arrows.png); + width: 12px; + height: 12px; + } + QTreeView::branch:open:has-children:!has-siblings, + QTreeView::branch:open:has-children:has-siblings { + image: url(icons/solid_down_arrows.png); + width: 12px; + height: 12px; + } """) self.treeWidget = CustomTreeWidget(self.world) self.world.setTreeWidget(self.treeWidget) # 设置树形控件引用 - self.leftDock.setWidget(self.treeWidget) + + # 创建包装容器,添加标题下方的分隔线 + leftDockContainer = QWidget() + leftDockContainer.setStyleSheet("QWidget { background-color: #19191b; }") + leftDockLayout = QVBoxLayout(leftDockContainer) + leftDockLayout.setContentsMargins(0, 0, 0, 0) + leftDockLayout.setSpacing(0) + + # 添加带左右间距的分隔线 + leftSeparator = QFrame() + leftSeparator.setFrameShape(QFrame.HLine) + leftSeparator.setFrameShadow(QFrame.Plain) + leftSeparator.setStyleSheet(""" + QFrame { + background-color: rgba(77, 116, 189, 0.4); + max-height: 1px; + margin-left: 8px; + margin-right: 8px; + border: none; + } + """) + leftDockLayout.addWidget(leftSeparator) + leftDockLayout.addWidget(self.treeWidget) + + self.leftDock.setWidget(leftDockContainer) self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.leftDock) # 创建右侧停靠窗口(属性窗口) @@ -1228,30 +1283,36 @@ class MainWindow(QMainWindow): border-bottom: none; text-align: left; font-family: 'Microsoft YaHei', 'Inter', sans-serif; - font-size: 16px; + font-size: 14px; font-weight: 500; } QDockWidget::close-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 5px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 6px; + top: 6px; } QDockWidget::float-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 25px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 26px; + top: 6px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { - background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + background-color: rgba(89, 100, 113, 0.5); + border: 1px solid rgba(184, 211, 241, 0.3); } QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { - background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + background-color: #3067c0; + border: 1px solid rgba(48, 103, 192, 0.6); } QScrollArea { background-color: #19191b; @@ -1266,25 +1327,220 @@ class MainWindow(QMainWindow): font-size: 10px; font-weight: 300; } - QGroupBox { - background-color: transparent; - border: none; - margin-top: 20px; - margin-bottom: 10px; + + /* 现代化的输入控件样式 */ + QLineEdit { + background-color: rgba(89, 100, 113, 0.15); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 4px; + padding: 6px 8px; font-family: 'Microsoft YaHei', 'Inter', sans-serif; - font-size: 12px; - font-weight: 500; - color: rgba(255, 255, 255, 0.8); - padding-top: 0px; + font-size: 10px; + font-weight: 300; + min-height: 16px; } + QLineEdit:focus { + border: 1px solid #4d74bd; + background-color: rgba(77, 116, 189, 0.1); + } + QLineEdit:hover { + border: 1px solid rgba(77, 116, 189, 0.6); + background-color: rgba(89, 100, 113, 0.2); + } + + QDoubleSpinBox, QSpinBox { + background-color: rgba(89, 100, 113, 0.15); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 4px; + padding: 6px 8px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; + min-height: 16px; + } + QDoubleSpinBox:focus, QSpinBox:focus { + border: 1px solid #4d74bd; + background-color: rgba(77, 116, 189, 0.1); + } + QDoubleSpinBox:hover, QSpinBox:hover { + border: 1px solid rgba(77, 116, 189, 0.6); + background-color: rgba(89, 100, 113, 0.2); + } + QDoubleSpinBox::up-button, QSpinBox::up-button { + background-color: rgba(77, 116, 189, 0.2); + border: none; + border-radius: 2px; + width: 16px; + subcontrol-origin: border; + subcontrol-position: top right; + } + QDoubleSpinBox::down-button, QSpinBox::down-button { + background-color: rgba(77, 116, 189, 0.2); + border: none; + border-radius: 2px; + width: 16px; + subcontrol-origin: border; + subcontrol-position: bottom right; + } + QDoubleSpinBox::up-button:hover, QSpinBox::up-button:hover, + QDoubleSpinBox::down-button:hover, QSpinBox::down-button:hover { + background-color: rgba(77, 116, 189, 0.4); + } + QDoubleSpinBox::up-arrow, QSpinBox::up-arrow { + image: url(icons/up_arrows.png); + width: 12px; + height: 12px; + } + QDoubleSpinBox::down-arrow, QSpinBox::down-arrow { + image: url(icons/down_arrows.png); + width: 12px; + height: 12px; + } + QDoubleSpinBox::up-arrow:hover, QSpinBox::up-arrow:hover { + image: url(icons/up_arrows.png); + } + QDoubleSpinBox::down-arrow:hover, QSpinBox::down-arrow:hover { + image: url(icons/down_arrows.png); + } + + QComboBox { + background-color: rgba(89, 100, 113, 0.15); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 4px; + padding: 6px 8px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; + min-height: 16px; + } + QComboBox:focus { + border: 1px solid #4d74bd; + background-color: rgba(77, 116, 189, 0.1); + } + QComboBox:hover { + border: 1px solid rgba(77, 116, 189, 0.6); + background-color: rgba(89, 100, 113, 0.2); + } + QComboBox::drop-down { + border: none; + border-left: 1px solid rgba(76, 92, 110, 0.4); + background-color: rgba(77, 116, 189, 0.2); + border-radius: 0 4px 4px 0; + width: 20px; + } + QComboBox::drop-down:hover { + background-color: rgba(77, 116, 189, 0.4); + } + QComboBox::down-arrow { + image: url(icons/down_arrows.png); + width: 12px; + height: 12px; + } + QComboBox QAbstractItemView { + background-color: #2a2a2e; + color: #ebebeb; + selection-background-color: rgba(77, 116, 189, 0.4); + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 4px; + padding: 4px; + } + + QPushButton { + background-color: rgba(89, 100, 113, 0.4); + color: rgba(255, 255, 255, 0.8); + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 400; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(89, 100, 113, 0.6); + border: 1px solid rgba(77, 116, 189, 0.6); + color: #ffffff; + } + QPushButton:pressed, QPushButton:checked { + background-color: rgba(77, 116, 189, 0.8); + border: 1px solid #4d74bd; + color: #ffffff; + } + QPushButton:disabled { + background-color: rgba(89, 100, 113, 0.2); + color: rgba(235, 235, 235, 0.4); + border: 1px solid rgba(76, 92, 110, 0.2); + } + + QCheckBox { + color: #ebebeb; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 300; + spacing: 8px; + } + QCheckBox::indicator { + width: 16px; + height: 16px; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 3px; + background-color: rgba(89, 100, 113, 0.15); + } + QCheckBox::indicator:hover { + border: 1px solid rgba(77, 116, 189, 0.6); + background-color: rgba(89, 100, 113, 0.2); + } + QCheckBox::indicator:checked { + background-color: #4d74bd; + border: 1px solid #4d74bd; + image: none; + } + QCheckBox::indicator:checked:hover { + background-color: rgba(77, 116, 189, 1.0); + } + + QGroupBox { + /* 减少上边距,使相邻组更紧凑 */ + margin-top: 0px; + + /* 移除边框 */ + border: none; + + /* 设置内边距,为标题和内容留出空间,左右内边距与主标题对齐 */ + padding-top: 20px; /* 为标题留出足够空间 */ + padding-left: 8px; /* 左内边距与主标题分隔线对齐 */ + padding-right: 8px; /* 右内边距与主标题分隔线对齐 */ + + /* 减少下边距,使相邻组更紧凑 */ + background-color: transparent; + margin-bottom: 6px; + + /* 使用边框创建样式线,与主标题分隔线保持相同的8px左右间距 */ + border-top: 1px solid rgba(77, 116, 189, 0.4); + margin-left: 8px; + margin-right: 8px; + } + QGroupBox::title { - padding: 0 0 10px 0; + /* 将标题定位到控件内部,在样条线下方 */ + subcontrol-origin: padding; + subcontrol-position: top left; + + /* 标题样式,左边距为0因为已经通过padding-left处理了对齐 */ + padding: 5px 3px 8px 0px; /* 左边距设为0,与内容对齐 */ + margin-top: 0px; /* 标题紧贴内边距顶部 */ + + /* 移除标题上的边框 */ + border: none; + + /* 字体和颜色样式 */ color: rgba(255, 255, 255, 0.8); font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 12px; font-weight: 500; - subcontrol-origin: margin; - subcontrol-position: top left; } """) @@ -1294,11 +1550,12 @@ class MainWindow(QMainWindow): try: self.propertyLayout = QVBoxLayout(self.propertyContainer) + self.propertyLayout.setContentsMargins(0, 0, 0, 0) print(f"✓ 属性布局创建完成: {self.propertyLayout}") # 添加初始提示信息 tipLabel = QLabel("选择对象以查看属性") - tipLabel.setStyleSheet("color: gray; padding: 10px;") # 使用灰色字体 + tipLabel.setStyleSheet("color: gray; padding: 10px 0px;") # 使用灰色字体 self.propertyLayout.addWidget(tipLabel) print("✓ 提示标签添加完成") @@ -1308,8 +1565,31 @@ class MainWindow(QMainWindow): self.scrollArea.setWidget(self.propertyContainer) print("✓ 滚动区域设置完成") - # 设置滚动区域为停靠窗口的主部件 - self.rightDock.setWidget(self.scrollArea) + # 创建包装容器,添加标题下方的分隔线 + rightDockContainer = QWidget() + rightDockContainer.setStyleSheet("QWidget { background-color: #19191b; }") + rightDockLayout = QVBoxLayout(rightDockContainer) + rightDockLayout.setContentsMargins(0, 0, 0, 0) + rightDockLayout.setSpacing(0) + + # 添加带左右间距的分隔线 + rightSeparator = QFrame() + rightSeparator.setFrameShape(QFrame.HLine) + rightSeparator.setFrameShadow(QFrame.Plain) + rightSeparator.setStyleSheet(""" + QFrame { + background-color: rgba(77, 116, 189, 0.4); + max-height: 1px; + margin-left: 8px; + margin-right: 8px; + border: none; + } + """) + rightDockLayout.addWidget(rightSeparator) + rightDockLayout.addWidget(self.scrollArea) + + # 设置包装容器为停靠窗口的主部件 + self.rightDock.setWidget(rightDockContainer) self.rightDock.setMinimumWidth(300) self.addDockWidget(Qt.RightDockWidgetArea, self.rightDock) print("✓ 右侧停靠窗口添加完成") @@ -1351,26 +1631,32 @@ class MainWindow(QMainWindow): font-weight: 500; } QDockWidget::close-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 5px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 6px; + top: 6px; } QDockWidget::float-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 25px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 26px; + top: 6px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { - background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + background-color: rgba(89, 100, 113, 0.5); + border: 1px solid rgba(184, 211, 241, 0.3); } QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { - background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + background-color: #3067c0; + border: 1px solid rgba(48, 103, 192, 0.6); } QScrollArea { background-color: #19191b; @@ -1385,25 +1671,45 @@ class MainWindow(QMainWindow): font-size: 10px; font-weight: 300; } - QGroupBox { - background-color: transparent; + QGroupBox { + /* 减少上边距,使相邻组更紧凑 */ + margin-top: 0px; + + /* 移除边框 */ border: none; - margin-top: 20px; - margin-bottom: 10px; - font-family: 'Microsoft YaHei', 'Inter', sans-serif; - font-size: 12px; - font-weight: 500; - color: rgba(255, 255, 255, 0.8); - padding-top: 0px; + + /* 设置内边距,为标题和内容留出空间,同时与主标题左侧对齐 */ + padding-top: 20px; /* 为标题留出足够空间 */ + padding-left: 0px; + padding-right: 8px; /* 右内边距与主标题分隔线对齐 */ + + /* 减少下边距,使相邻组更紧凑 */ + background-color: transparent; + margin-bottom: 6px; + + /* 使用边框创建样式线,与主标题分隔线保持相同的8px右侧间距 */ + border-top: 1px solid rgba(77, 116, 189, 0.4); + margin-left: 0px; + margin-right: 8px; } + QGroupBox::title { - padding: 0 0 10px 0; + /* 将标题定位到控件内部,在样条线下方 */ + subcontrol-origin: padding; + subcontrol-position: top left; + + /* 标题样式,左边距为0因为已经通过padding-left处理了对齐 */ + padding: 5px 3px 8px 0px; /* 左边距设为0,与内容对齐 */ + margin-top: 0px; /* 标题紧贴内边距顶部 */ + + /* 移除标题上的边框 */ + border: none; + + /* 字体和颜色样式 */ color: rgba(255, 255, 255, 0.8); font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 12px; font-weight: 500; - subcontrol-origin: margin; - subcontrol-position: top left; } """) @@ -1423,8 +1729,31 @@ class MainWindow(QMainWindow): self.scriptScrollArea.setWidgetResizable(True) self.scriptScrollArea.setWidget(self.scriptContainer) - # 设置滚动区域为停靠窗口的主部件 - self.scriptDock.setWidget(self.scriptScrollArea) + # 创建包装容器,添加标题下方的分隔线 + scriptDockContainer = QWidget() + scriptDockContainer.setStyleSheet("QWidget { background-color: #19191b; }") + scriptDockLayout = QVBoxLayout(scriptDockContainer) + scriptDockLayout.setContentsMargins(0, 0, 0, 0) + scriptDockLayout.setSpacing(0) + + # 添加带左右间距的分隔线 + scriptSeparator = QFrame() + scriptSeparator.setFrameShape(QFrame.HLine) + scriptSeparator.setFrameShadow(QFrame.Plain) + scriptSeparator.setStyleSheet(""" + QFrame { + background-color: rgba(77, 116, 189, 0.4); + max-height: 1px; + margin-left: 8px; + margin-right: 8px; + border: none; + } + """) + scriptDockLayout.addWidget(scriptSeparator) + scriptDockLayout.addWidget(self.scriptScrollArea) + + # 设置包装容器为停靠窗口的主部件 + self.scriptDock.setWidget(scriptDockContainer) self.scriptDock.setMinimumWidth(300) # 设置脚本面板内容 - 这里添加调用 @@ -1475,26 +1804,32 @@ class MainWindow(QMainWindow): font-weight: 500; } QDockWidget::close-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 5px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 6px; + top: 6px; } QDockWidget::float-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 25px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 26px; + top: 6px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { - background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + background-color: rgba(89, 100, 113, 0.5); + border: 1px solid rgba(184, 211, 241, 0.3); } QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { - background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + background-color: #3067c0; + border: 1px solid rgba(48, 103, 192, 0.6); } """) @@ -1522,6 +1857,21 @@ class MainWindow(QMainWindow): background-color: rgba(48, 103, 192, 0.4); color: #ffffff; } + QTreeWidget::branch { + background-color: #19191b; + } + QTreeWidget::branch:has-children:!has-siblings:closed, + QTreeWidget::branch:closed:has-children:has-siblings { + image: url(icons/solid_right_arrows.png); + width: 12px; + height: 12px; + } + QTreeWidget::branch:open:has-children:!has-siblings, + QTreeWidget::branch:open:has-children:has-siblings { + image: url(icons/solid_down_arrows.png); + width: 12px; + height: 12px; + } QHeaderView::section { background-color: #19191b; color: #ebebeb; @@ -1533,7 +1883,31 @@ class MainWindow(QMainWindow): font-weight: 500; } """) - self.bottomDock.setWidget(self.fileView) + + # 创建包装容器,添加标题下方的分隔线 + bottomDockContainer = QWidget() + bottomDockContainer.setStyleSheet("QWidget { background-color: #19191b; }") + bottomDockLayout = QVBoxLayout(bottomDockContainer) + bottomDockLayout.setContentsMargins(0, 0, 0, 0) + bottomDockLayout.setSpacing(0) + + # 添加带左右间距的分隔线 + bottomSeparator = QFrame() + bottomSeparator.setFrameShape(QFrame.HLine) + bottomSeparator.setFrameShadow(QFrame.Plain) + bottomSeparator.setStyleSheet(""" + QFrame { + background-color: rgba(77, 116, 189, 0.4); + max-height: 1px; + margin-left: 8px; + margin-right: 8px; + border: none; + } + """) + bottomDockLayout.addWidget(bottomSeparator) + bottomDockLayout.addWidget(self.fileView) + + self.bottomDock.setWidget(bottomDockContainer) self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock) # 创建底部停靠控制台 @@ -1554,26 +1928,32 @@ class MainWindow(QMainWindow): font-weight: 500; } QDockWidget::close-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 5px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 6px; + top: 6px; } QDockWidget::float-button { - background-color: #8b5cf6; - border: none; - icon-size: 8px; /* 调整图标大小 */ - border-radius: 4px; /* 增加圆角 */ - right: 25px; - top: 2px; + background-color: rgba(89, 100, 113, 0.3); + border: 1px solid rgba(184, 211, 241, 0.2); + icon-size: 10px; + border-radius: 2px; + width: 16px; + height: 16px; + right: 26px; + top: 6px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { - background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + background-color: rgba(89, 100, 113, 0.5); + border: 1px solid rgba(184, 211, 241, 0.3); } QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { - background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + background-color: #3067c0; + border: 1px solid rgba(48, 103, 192, 0.6); } """) self.consoleView = CustomConsoleDockWidget(self.world) @@ -1588,46 +1968,89 @@ class MainWindow(QMainWindow): font-weight: 300; } """) - self.consoleDock.setWidget(self.consoleView) + + # 创建包装容器,添加标题下方的分隔线 + consoleDockContainer = QWidget() + consoleDockContainer.setStyleSheet("QWidget { background-color: #19191b; }") + consoleDockLayout = QVBoxLayout(consoleDockContainer) + consoleDockLayout.setContentsMargins(0, 0, 0, 0) + consoleDockLayout.setSpacing(0) + + # 添加带左右间距的分隔线 + consoleSeparator = QFrame() + consoleSeparator.setFrameShape(QFrame.HLine) + consoleSeparator.setFrameShadow(QFrame.Plain) + consoleSeparator.setStyleSheet(""" + QFrame { + background-color: rgba(77, 116, 189, 0.4); + max-height: 1px; + margin-left: 8px; + margin-right: 8px; + border: none; + } + """) + consoleDockLayout.addWidget(consoleSeparator) + consoleDockLayout.addWidget(self.consoleView) + + self.consoleDock.setWidget(consoleDockContainer) self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock) - # 将右侧停靠窗口设为标签形式 + # 配置Dock区域的角落归属 + # 使右侧和下侧的Dock面板可以占据右下角区域 + # setCorner(角落位置, 归属的Dock区域) + self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea) + self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) + # self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) + # self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) + + # 设置Dock标签栏位置为上方(North) + self.setTabPosition(Qt.RightDockWidgetArea, QTabWidget.North) + self.setTabPosition(Qt.LeftDockWidgetArea, QTabWidget.North) + self.setTabPosition(Qt.TopDockWidgetArea, QTabWidget.North) + self.setTabPosition(Qt.BottomDockWidgetArea, QTabWidget.North) + + # 将右侧的属性面板和脚本管理面板合并为标签页 # self.tabifyDockWidget(self.rightDock, self.scriptDock) - # # 将底部的两个窗口也标签化 - # self.tabifyDockWidget(self.bottomDock, self.consoleDock) - # 设定默认显示的标签 - self.bottomDock.raise_() + + # 设定默认显示的标签(属性面板在前) self.rightDock.raise_() - self.scriptDock.raise_() + + # 确保其他面板也正常显示 + self.bottomDock.raise_() self.consoleDock.raise_() self.leftDock.raise_() # ========================================================================= - # ↓↓↓ 新增代码:为停靠窗口的标签栏(QTabBar)设置统一样式 ↓↓↓ + # ↓↓↓ 为停靠窗口的标签栏(QTabBar)设置统一样式(根据Figma设计)↓↓↓ # ========================================================================= # 这段样式会应用到主窗口内的所有 QTabBar,特别是停靠区域的标签栏。 tab_bar_style = """ /* QTabBar 的整体样式 */ QTabBar { qproperty-drawBase: 0; + qproperty-expanding: 0; + background-color: #19191b; + border: none; } + /* 标签的基础样式 - 未选中状态 */ QTabBar::tab { background-color: #394560; color: rgba(255, 255, 255, 0.7); - border: 1px solid #4c5c6e; - border-bottom: none; - border-top-left-radius: 2px; - border-top-right-radius: 2px; - padding: 8px 16px; + border: none; + border-radius: 2px; + padding: 7px 16px; font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-weight: 500; - font-size: 14px; - margin-right: 2px; + font-size: 12px; + margin-right: 4px; + min-height: 20px; + max-height: 20px; + min-width: 80px; /* 增加一个最小宽度,根据您的标签文字长度调整 */ } /* 鼠标悬停在标签上时的样式 */ QTabBar::tab:hover { - background-color: #596276; + background-color: #4a5568; } /* 当前选中的标签页的样式 */ @@ -1635,19 +2058,27 @@ class MainWindow(QMainWindow): background-color: #3067c0; color: #ffffff; font-weight: 500; - border-color: #4c5c6e; - border-bottom: 2px solid #3067c0; } /* 未选中的标签 */ QTabBar::tab:!selected { - margin-top: 2px; + margin-top: 0px; } - /* 标签栏底部的线条 */ + /* 标签栏的对齐方式 */ QTabBar::tab-bar { alignment: left; - border: none; + } + + /* 标签栏的关闭按钮(如果有) */ + QTabBar::close-button { + image: none; + subcontrol-position: right; + } + + /* 移除标签栏的滚动按钮样式冲突 */ + QTabBar::scroller { + width: 0px; } """ @@ -1752,13 +2183,69 @@ class MainWindow(QMainWindow): statusGroup = QGroupBox("脚本系统状态") statusLayout = QVBoxLayout() - self.scriptStatusLabel = QLabel("脚本系统: 已启动") - self.scriptStatusLabel.setStyleSheet("color: green; font-weight: bold;") - statusLayout.addWidget(self.scriptStatusLabel) + # 脚本系统状态行 + scriptSystemLayout = QHBoxLayout() + scriptSystemLabel = QLabel("脚本系统:") + scriptSystemLabel.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + scriptSystemLayout.addWidget(scriptSystemLabel) + + self.scriptStatusLabel = QLabel("已启动") + self.scriptStatusLabel.setStyleSheet(""" + QLabel { + background-color: rgba(45, 255, 196, 0.17); + border: 1px solid #2dffc4; + color: #2dffc4; + border-radius: 2px; + padding: 2px 8px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + scriptSystemLayout.addWidget(self.scriptStatusLabel) + scriptSystemLayout.addStretch() + statusLayout.addLayout(scriptSystemLayout) - self.hotReloadLabel = QLabel("热重载: 已启用") - self.hotReloadLabel.setStyleSheet("color: blue;") - statusLayout.addWidget(self.hotReloadLabel) + # 热重载状态行 + hotReloadLayout = QHBoxLayout() + hotReloadLabel = QLabel("热重载:") + hotReloadLabel.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + hotReloadLayout.addWidget(hotReloadLabel) + + self.hotReloadLabel = QLabel("已启用") + self.hotReloadLabel.setStyleSheet(""" + QLabel { + background-color: rgba(45, 136, 255, 0.17); + border: 1px solid #289eff; + color: #289eff; + border-radius: 2px; + padding: 2px 8px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + hotReloadLayout.addWidget(self.hotReloadLabel) + hotReloadLayout.addStretch() + statusLayout.addLayout(hotReloadLayout) statusGroup.setLayout(statusLayout) layout.addWidget(statusGroup) @@ -1769,23 +2256,123 @@ class MainWindow(QMainWindow): # 脚本名称输入 nameLayout = QHBoxLayout() - nameLayout.addWidget(QLabel("脚本名称:")) + nameLabel = QLabel("脚本名称:") + nameLabel.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + min-width: 60px; + } + """) + nameLayout.addWidget(nameLabel) + self.scriptNameEdit = QLineEdit() self.scriptNameEdit.setPlaceholderText("输入脚本名称...") + self.scriptNameEdit.setStyleSheet(""" + QLineEdit { + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; + padding: 4px 8px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + min-height: 18px; + } + QLineEdit::placeholder { + color: rgba(235, 235, 235, 0.7); + } + QLineEdit:focus { + border: 1px solid #289eff; + } + """) nameLayout.addWidget(self.scriptNameEdit) createLayout.addLayout(nameLayout) # 模板选择 templateLayout = QHBoxLayout() - templateLayout.addWidget(QLabel("模板:")) + templateLabel = QLabel("模板:") + templateLabel.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + min-width: 60px; + } + """) + templateLayout.addWidget(templateLabel) + self.templateCombo = QComboBox() self.templateCombo.addItems(["basic", "movement", "animation"]) + self.templateCombo.setStyleSheet(""" + QComboBox { + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; + padding: 4px 8px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + min-height: 18px; + } + QComboBox::drop-down { + border: none; + border-left: 1px solid rgba(76, 92, 110, 0.6); + background-color: rgba(89, 100, 113, 0.3); + width: 16px; + } + QComboBox::down-arrow { + image: url(icons/down_arrows.png); + width: 10px; + height: 10px; + } + QComboBox QAbstractItemView { + background-color: #596471; + color: #ebebeb; + selection-background-color: rgba(48, 103, 192, 0.4); + border: 1px solid rgba(76, 92, 110, 0.6); + } + """) templateLayout.addWidget(self.templateCombo) createLayout.addLayout(templateLayout) # 创建按钮 self.createScriptBtn = QPushButton("创建脚本") self.createScriptBtn.clicked.connect(self.onCreateScript) + self.createScriptBtn.setStyleSheet(""" + QPushButton { + background-color: rgba(89, 98, 118, 0.4); + color: #ebebeb; + border: none; + padding: 6px 12px; + border-radius: 2px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-height: 18px; + } + QPushButton:hover { + background-color: #3067c0; + color: #ffffff; + } + QPushButton:pressed { + background-color: #2556a0; + } + QPushButton:disabled { + background-color: #394560; + color: rgba(235, 235, 235, 0.5); + } + """) createLayout.addWidget(self.createScriptBtn) createGroup.setLayout(createLayout) @@ -1799,20 +2386,28 @@ class MainWindow(QMainWindow): self.scriptsList = QListWidget() self.scriptsList.setStyleSheet(""" QListWidget { - background-color: #19191b; + background-color: rgba(89, 100, 113, 0.15); color: #ebebeb; - border: none; - alternate-background-color: #19191b; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; + alternate-background-color: rgba(89, 100, 113, 0.15); selection-background-color: rgba(48, 103, 192, 0.4); selection-color: #ffffff; - font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; font-size: 10px; font-weight: 300; + letter-spacing: 0.5px; outline: none; + min-height: 120px; + max-height: 120px; } QListWidget::item { padding: 6px 8px; border: none; + border-bottom: 1px solid rgba(76, 92, 110, 0.3); + } + QListWidget::item:last { + border-bottom: none; } QListWidget::item:hover { background-color: rgba(89, 100, 113, 0.3); @@ -1830,10 +2425,52 @@ class MainWindow(QMainWindow): self.loadScriptBtn = QPushButton("加载脚本") self.loadScriptBtn.clicked.connect(self.onLoadScript) + self.loadScriptBtn.setStyleSheet(""" + QPushButton { + background-color: rgba(89, 98, 118, 0.4); + color: #ebebeb; + border: none; + padding: 6px 12px; + border-radius: 2px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-height: 18px; + } + QPushButton:hover { + background-color: #3067c0; + color: #ffffff; + } + QPushButton:pressed { + background-color: #2556a0; + } + """) scriptButtonsLayout.addWidget(self.loadScriptBtn) self.reloadAllBtn = QPushButton("重载全部") self.reloadAllBtn.clicked.connect(self.onReloadAllScripts) + self.reloadAllBtn.setStyleSheet(""" + QPushButton { + background-color: rgba(89, 98, 118, 0.4); + color: #ebebeb; + border: none; + padding: 6px 12px; + border-radius: 2px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-height: 18px; + } + QPushButton:hover { + background-color: #3067c0; + color: #ffffff; + } + QPushButton:pressed { + background-color: #2556a0; + } + """) scriptButtonsLayout.addWidget(self.reloadAllBtn) scriptsLayout.addLayout(scriptButtonsLayout) @@ -1846,7 +2483,16 @@ class MainWindow(QMainWindow): # 当前选中对象显示 self.selectedObjectLabel = QLabel("未选择对象") - self.selectedObjectLabel.setStyleSheet("color: gray; font-style: italic;") + self.selectedObjectLabel.setStyleSheet(""" + QLabel { + color: #2dffc4; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + padding: 2px 0px; + } + """) mountLayout.addWidget(self.selectedObjectLabel) # 脚本选择和挂载 @@ -1854,11 +2500,71 @@ class MainWindow(QMainWindow): self.mountScriptCombo = QComboBox() self.mountScriptCombo.setEnabled(False) + self.mountScriptCombo.setStyleSheet(""" + QComboBox { + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; + padding: 4px 8px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + min-height: 18px; + } + QComboBox:disabled { + background-color: rgba(89, 100, 113, 0.1); + color: rgba(235, 235, 235, 0.5); + } + QComboBox::drop-down { + border: none; + border-left: 1px solid rgba(76, 92, 110, 0.6); + background-color: rgba(89, 100, 113, 0.3); + width: 16px; + } + QComboBox::down-arrow { + image: url(icons/down_arrows.png); + width: 10px; + height: 10px; + } + QComboBox QAbstractItemView { + background-color: #596471; + color: #ebebeb; + selection-background-color: rgba(48, 103, 192, 0.4); + border: 1px solid rgba(76, 92, 110, 0.6); + } + """) mountControlLayout.addWidget(self.mountScriptCombo) self.mountBtn = QPushButton("挂载") self.mountBtn.setEnabled(False) self.mountBtn.clicked.connect(self.onMountScript) + self.mountBtn.setStyleSheet(""" + QPushButton { + background-color: rgba(89, 98, 118, 0.4); + color: #ebebeb; + border: none; + padding: 6px 12px; + border-radius: 2px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-height: 18px; + } + QPushButton:hover { + background-color: #3067c0; + color: #ffffff; + } + QPushButton:pressed { + background-color: #2556a0; + } + QPushButton:disabled { + background-color: #394560; + color: rgba(235, 235, 235, 0.5); + } + """) mountControlLayout.addWidget(self.mountBtn) mountLayout.addLayout(mountControlLayout) @@ -1867,21 +2573,28 @@ class MainWindow(QMainWindow): self.mountedScriptsList = QListWidget() self.mountedScriptsList.setStyleSheet(""" QListWidget { - background-color: #19191b; + background-color: rgba(89, 100, 113, 0.15); color: #ebebeb; - border: none; - alternate-background-color: #19191b; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; + alternate-background-color: rgba(89, 100, 113, 0.15); selection-background-color: rgba(48, 103, 192, 0.4); selection-color: #ffffff; - max-height: 120px; - font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; font-size: 10px; font-weight: 300; + letter-spacing: 0.5px; outline: none; + min-height: 80px; + max-height: 80px; } QListWidget::item { padding: 4px 8px; border: none; + border-bottom: 1px solid rgba(76, 92, 110, 0.3); + } + QListWidget::item:last { + border-bottom: none; } QListWidget::item:hover { background-color: rgba(89, 100, 113, 0.3); @@ -1892,12 +2605,45 @@ class MainWindow(QMainWindow): } """) self.mountedScriptsList.setMaximumHeight(100) - mountLayout.addWidget(QLabel("已挂载脚本:")) + + mountedLabel = QLabel("已挂载脚本:") + mountedLabel.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + padding: 2px 0px; + } + """) + mountLayout.addWidget(mountedLabel) mountLayout.addWidget(self.mountedScriptsList) # 卸载按钮 self.unmountBtn = QPushButton("卸载选中脚本") self.unmountBtn.clicked.connect(self.onUnmountScript) + self.unmountBtn.setStyleSheet(""" + QPushButton { + background-color: rgba(89, 98, 118, 0.4); + color: #ebebeb; + border: none; + padding: 6px 12px; + border-radius: 2px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-height: 18px; + } + QPushButton:hover { + background-color: #3067c0; + color: #ffffff; + } + QPushButton:pressed { + background-color: #2556a0; + } + """) mountLayout.addWidget(self.unmountBtn) mountGroup.setLayout(mountLayout) @@ -3121,17 +3867,72 @@ class MainWindow(QMainWindow): """更新脚本面板状态""" # 更新热重载状态 hot_reload_enabled = self.world.script_manager.hot_reload_enabled - self.hotReloadLabel.setText(f"热重载: {'已启用' if hot_reload_enabled else '已禁用'}") - self.hotReloadLabel.setStyleSheet(f"color: {'blue' if hot_reload_enabled else 'gray'};") + + # 更新热重载标签文本和样式 + if hot_reload_enabled: + self.hotReloadLabel.setText("已启用") + self.hotReloadLabel.setStyleSheet(""" + QLabel { + background-color: rgba(243, 157, 120, 0.15); + border: 1px solid #f39d78 ; + color: #f39d78 ; + border-radius: 2px; + padding: 2px 8px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + else: + self.hotReloadLabel.setText("已禁用") + self.hotReloadLabel.setStyleSheet(""" + QLabel { + background-color: rgba(45, 136, 255, 0.17) ; + border: 1px solid #289eff ; + color: #289eff ; + border-radius: 2px; + padding: 2px 8px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) # 更新热重载菜单状态 self.toggleHotReloadAction.setChecked(hot_reload_enabled) + + # 更新脚本系统状态(确保使用正确的样式) + self.scriptStatusLabel.setText("已启动") + self.scriptStatusLabel.setStyleSheet(""" + QLabel { + background-color: rgba(45, 255, 196, 0.17); + border: 1px solid #2dffc4; + color: #2dffc4; + border-radius: 2px; + padding: 2px 8px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) # 更新选中对象信息 selected_object = getattr(self.world.selection, 'selectedObject', None) if selected_object: - self.selectedObjectLabel.setText(f"选中对象: {selected_object.getName()}") - self.selectedObjectLabel.setStyleSheet("color: green; font-weight: bold;") + self.selectedObjectLabel.setText(f"选中对象:{selected_object.getName()}") + self.selectedObjectLabel.setStyleSheet(""" + QLabel { + color: #2dffc4; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + padding: 2px 0px; + } + """) self.mountScriptCombo.setEnabled(True) self.mountBtn.setEnabled(True) @@ -3139,7 +3940,17 @@ class MainWindow(QMainWindow): self.updateMountedScriptsList(selected_object) else: self.selectedObjectLabel.setText("未选择对象") - self.selectedObjectLabel.setStyleSheet("color: gray; font-style: italic;") + self.selectedObjectLabel.setStyleSheet(""" + QLabel { + color: rgba(235, 235, 235, 0.5); + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + padding: 2px 0px; + font-style: italic; + } + """) self.mountScriptCombo.setEnabled(False) self.mountBtn.setEnabled(False) self.mountedScriptsList.clear() @@ -3786,4 +4597,4 @@ def setup_main_window(world,path = None): if path: openProjectForPath(path,main_window) - return app, main_window \ No newline at end of file + return app, main_window diff --git a/ui/property_panel.py b/ui/property_panel.py index eba770b4..ccab840d 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -42,24 +42,464 @@ class PropertyPanelManager: # 初始化碰撞参数加载标志位 self._loading_collision_params = False - # 定义紧凑样式 + # 定义现代化紧凑样式 self.compact_style = """ - QDoubleSpinBox { - min-width: 45px; + QDoubleSpinBox, QSpinBox { + min-width: 60px; + background-color: rgba(89, 100, 113, 0.15); + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 4px; + padding: 4px 6px; + font-size: 10px; + } + QDoubleSpinBox:focus, QSpinBox:focus { + border: 1px solid #4d74bd; + background-color: rgba(77, 116, 189, 0.1); + } + QDoubleSpinBox::up-button, QSpinBox::up-button { + background-color: rgba(77, 116, 189, 0.2); + border: none; + border-radius: 2px; + width: 14px; + subcontrol-origin: border; + subcontrol-position: top right; + } + QDoubleSpinBox::down-button, QSpinBox::down-button { + background-color: rgba(77, 116, 189, 0.2); + border: none; + border-radius: 2px; + width: 14px; + subcontrol-origin: border; + subcontrol-position: bottom right; + } + QDoubleSpinBox::up-button:hover, QSpinBox::up-button:hover, + QDoubleSpinBox::down-button:hover, QSpinBox::down-button:hover { + background-color: rgba(77, 116, 189, 0.4); + } + QDoubleSpinBox::up-arrow, QSpinBox::up-arrow { + image: url(icons/up_arrows.png); + width: 10px; + height: 10px; + } + QDoubleSpinBox::down-arrow, QSpinBox::down-arrow { + image: url(icons/down_arrows.png); + width: 10px; + height: 10px; + } + QDoubleSpinBox::up-arrow:hover, QSpinBox::up-arrow:hover { + image: url(icons/up_arrows.png); + } + QDoubleSpinBox::down-arrow:hover, QSpinBox::down-arrow:hover { + image: url(icons/down_arrows.png); } QPushButton { - min-width: 10px; + min-width: 60px; + background-color: rgba(89, 100, 113, 0.4); + color: rgba(255, 255, 255, 0.8); + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 4px; + padding: 6px 10px; + font-size: 10px; + font-weight: 400; + } + QPushButton:hover { + background-color: rgba(89, 100, 113, 0.6); + border: 1px solid rgba(77, 116, 189, 0.6); + color: #ffffff; + } + QPushButton:pressed, QPushButton:checked { + background-color: rgba(77, 116, 189, 0.8); + border: 1px solid #4d74bd; + color: #ffffff; + } + QPushButton:disabled { + background-color: rgba(89, 100, 113, 0.2); + color: rgba(235, 235, 235, 0.4); + border: 1px solid rgba(76, 92, 110, 0.2); } QComboBox { - min-width: 60px; + min-width: 80px; + background-color: rgba(89, 100, 113, 0.15); + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 4px; + padding: 4px 6px; + font-size: 10px; + } + QComboBox:focus { + border: 1px solid #4d74bd; + } + QComboBox::drop-down { + border: none; + border-left: 1px solid rgba(76, 92, 110, 0.4); + background-color: rgba(77, 116, 189, 0.2); + border-radius: 0 4px 4px 0; + width: 16px; + } + QComboBox::drop-down:hover { + background-color: rgba(77, 116, 189, 0.4); + } + QComboBox::down-arrow { + image: url(icons/down_arrows.png); + width: 10px; + height: 10px; } QLineEdit { - min-width: 60px; + min-width: 80px; + background-color: rgba(89, 100, 113, 0.15); + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 4px; + padding: 4px 6px; + font-size: 10px; + } + QLineEdit:focus { + border: 1px solid #4d74bd; + background-color: rgba(77, 116, 189, 0.1); } QCheckBox { min-width: 20px; + font-size: 10px; + spacing: 6px; + } + QCheckBox::indicator { + width: 14px; + height: 14px; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 3px; + background-color: rgba(89, 100, 113, 0.15); + } + QCheckBox::indicator:checked { + background-color: #4d74bd; + border: 1px solid #4d74bd; } """ + self.compact_style += """ + QGroupBox { + margin-left: 8px; + padding-left: 0px; + } + QGroupBox::title { + left: 0px; + padding: 0 5px 5px 0; + margin-top: 0px; + } + """ + + # 定义现代化状态徽章样式 + self.badge_style_blue = """ + QLabel { + background-color: rgba(77, 116, 189, 0.15); + border: 1px solid rgba(77, 116, 189, 0.6); + color: #4d74bd; + border-radius: 6px; + padding: 4px 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 9px; + font-weight: 500; + letter-spacing: 0.5px; + } + """ + + self.badge_style_green = """ + QLabel { + background-color: rgba(45, 255, 196, 0.15); + border: 1px solid rgba(45, 255, 196, 0.6); + color: #2dffc4; + border-radius: 6px; + padding: 4px 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 9px; + font-weight: 500; + letter-spacing: 0.5px; + } + """ + + # 添加新的徽章样式 + self.badge_style_orange = """ + QLabel { + background-color: rgba(255, 165, 0, 0.15); + border: 1px solid rgba(255, 165, 0, 0.6); + color: #ffa500; + border-radius: 6px; + padding: 4px 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 9px; + font-weight: 500; + letter-spacing: 0.5px; + } + """ + + self.badge_style_red = """ + QLabel { + background-color: rgba(255, 99, 99, 0.15); + border: 1px solid rgba(255, 99, 99, 0.6); + color: #ff6363; + border-radius: 6px; + padding: 4px 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 9px; + font-weight: 500; + letter-spacing: 0.5px; + } + """ + + def createStatusBadge(self, text, badge_type="green"): + """创建现代化状态徽章 + + Args: + text: 徽章文字 + badge_type: 徽章类型,支持 "green", "blue", "orange", "red" + + Returns: + QLabel: 配置好样式的标签 + """ + badge = QLabel(text) + if badge_type == "green": + badge.setStyleSheet(self.badge_style_green) + elif badge_type == "blue": + badge.setStyleSheet(self.badge_style_blue) + elif badge_type == "orange": + badge.setStyleSheet(self.badge_style_orange) + elif badge_type == "red": + badge.setStyleSheet(self.badge_style_red) + else: + badge.setStyleSheet(self.badge_style_blue) # 默认蓝色 + return badge + + def createModernButton(self, text, button_type="default"): + """创建现代化按钮 + + Args: + text: 按钮文字 + button_type: 按钮类型 - "default", "primary", "success", "warning", "danger" + + Returns: + QPushButton: 配置好样式的按钮 + """ + button = QPushButton(text) + + if button_type == "primary": + button.setStyleSheet(""" + QPushButton { + background-color: rgba(77, 116, 189, 0.8); + color: #ffffff; + border: 1px solid #4d74bd; + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 500; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(77, 116, 189, 1.0); + border: 1px solid rgba(77, 116, 189, 1.0); + } + QPushButton:pressed { + background-color: rgba(61, 96, 169, 1.0); + border: 1px solid rgba(61, 96, 169, 1.0); + } + """) + elif button_type == "success": + button.setStyleSheet(""" + QPushButton { + background-color: rgba(45, 255, 196, 0.15); + color: #2dffc4; + border: 1px solid rgba(45, 255, 196, 0.6); + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 400; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(45, 255, 196, 0.25); + border: 1px solid #2dffc4; + } + QPushButton:pressed { + background-color: rgba(45, 255, 196, 0.4); + color: #ffffff; + } + """) + elif button_type == "warning": + button.setStyleSheet(""" + QPushButton { + background-color: rgba(255, 165, 0, 0.15); + color: #ffa500; + border: 1px solid rgba(255, 165, 0, 0.6); + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 400; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(255, 165, 0, 0.25); + border: 1px solid #ffa500; + } + QPushButton:pressed { + background-color: rgba(255, 165, 0, 0.4); + color: #ffffff; + } + """) + elif button_type == "danger": + button.setStyleSheet(""" + QPushButton { + background-color: rgba(255, 99, 99, 0.15); + color: #ff6363; + border: 1px solid rgba(255, 99, 99, 0.6); + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 400; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(255, 99, 99, 0.25); + border: 1px solid #ff6363; + } + QPushButton:pressed { + background-color: rgba(255, 99, 99, 0.4); + color: #ffffff; + } + """) + else: # default + # 使用全局样式,不需要额外设置 + pass + + return button + + def createModernGroupBox(self, title, parent_layout): + """创建现代化的分组框 + + Args: + title: 分组框标题 + parent_layout: 父布局 + + Returns: + tuple: (QGroupBox, QVBoxLayout) 分组框和其内部布局 + """ + group_box = QGroupBox(title) + group_layout = QVBoxLayout(group_box) + group_layout.setSpacing(0) # 减少内部间距 + group_layout.setContentsMargins(0, 3, 0, 6) # 减少内容边距 + + # 应用现代化样式,使用更紧凑的间距 + group_box.setStyleSheet(""" + QGroupBox { + font-weight: 500; + font-size: 11px; + color: rgba(255, 255, 255, 0.9); + margin-top: 12px; + margin-bottom: 4px; + padding-top: 15px; + border-top: 1px solid rgba(77, 116, 189, 0.3); + } + QGroupBox::title { + subcontrol-origin: padding; + subcontrol-position: top left; + left: 0px; + padding: 0 5px 5px 0; + margin-top: 0px; + } + """) + + parent_layout.addWidget(group_box) + return group_box, group_layout + + def _getFigmaSpinBoxStyle(self): + """获取符合Figma设计的SpinBox样式""" + return """ + QDoubleSpinBox, QSpinBox { + background-color: rgba(89, 100, 113, 0.2); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; + padding: 3px 6px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + min-width: 70px; + max-width: 70px; + min-height: 18px; + max-height: 18px; + } + QDoubleSpinBox:focus, QSpinBox:focus { + border: 1px solid #4d74bd; + background-color: rgba(77, 116, 189, 0.1); + } + QDoubleSpinBox:hover, QSpinBox:hover { + border: 1px solid rgba(77, 116, 189, 0.6); + background-color: rgba(89, 100, 113, 0.25); + } + QDoubleSpinBox::up-button, QSpinBox::up-button { + background-color: rgba(77, 116, 189, 0.2); + border: none; + border-radius: 2px; + width: 14px; + subcontrol-origin: border; + subcontrol-position: top right; + } + QDoubleSpinBox::down-button, QSpinBox::down-button { + background-color: rgba(77, 116, 189, 0.2); + border: none; + border-radius: 2px; + width: 14px; + subcontrol-origin: border; + subcontrol-position: bottom right; + } + QDoubleSpinBox::up-button:hover, QSpinBox::up-button:hover, + QDoubleSpinBox::down-button:hover, QSpinBox::down-button:hover { + background-color: rgba(77, 116, 189, 0.4); + } + QDoubleSpinBox::up-arrow, QSpinBox::up-arrow { + image: url(icons/up_arrows.png); + width: 10px; + height: 10px; + } + QDoubleSpinBox::down-arrow, QSpinBox::down-arrow { + image: url(icons/down_arrows.png); + width: 10px; + height: 10px; + } + """ + + def createPropertyRow(self, label_text, widget, layout, tooltip=None): + """创建属性行 + + Args: + label_text: 标签文字 + widget: 控件 + layout: 布局 + tooltip: 工具提示 + """ + row_layout = QHBoxLayout() + row_layout.setContentsMargins(0, 0, 0, 0) + row_layout.setSpacing(8) + + # 创建标签 + label = QLabel(label_text) + label.setMinimumWidth(60) + label.setStyleSheet(""" + QLabel { + color: rgba(255, 255, 255, 0.8); + font-size: 10px; + font-weight: 300; + } + """) + + if tooltip: + label.setToolTip(tooltip) + widget.setToolTip(tooltip) + + row_layout.addWidget(label) + row_layout.addWidget(widget) + row_layout.addStretch() + + layout.addLayout(row_layout) def setPropertyLayout(self, layout): """设置属性面板布局引用""" @@ -139,6 +579,7 @@ class PropertyPanelManager: self.name_group = QGroupBox("物体名称") name_layout = QHBoxLayout() + name_layout.setContentsMargins(0, 8, 0, 8) self.active_check = QCheckBox() # 根据模型的实际可见性状态设置复选框 self.active_check.setChecked(user_visible) @@ -1124,161 +1565,234 @@ class PropertyPanelManager: # 获取父节点 parent = model.getParent() - # 变换属性部分 (Transform) + # 变换属性部分 (Transform) - 根据Figma设计优化 self.transform_group = QGroupBox("变换 Transform") - transform_layout = QGridLayout() + # 应用Figma设计的GroupBox样式 + self.transform_group.setStyleSheet(""" + QGroupBox { + margin-top: 0px; + border: none; + padding-top: 20px; + padding-left: 8px; + padding-right: 8px; + background-color: transparent; + margin-bottom: 6px; + border-top: 1px solid rgba(77, 116, 189, 0.4); + margin-left: 8px; + margin-right: 8px; + } + QGroupBox::title { + subcontrol-origin: padding; + subcontrol-position: top left; + padding: 5px 3px 8px 0px; + margin-top: 0px; + border: none; + color: rgba(255, 255, 255, 0.8); + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.6px; + } + """) + self._propertyLayout.addWidget(self.transform_group) + transform_layout = QVBoxLayout() + transform_layout.setSpacing(8) # 设置行间距 + transform_layout.setContentsMargins(0, 8, 0, 8) # 获取当前值 relativePos = model.getPos(parent) if parent else model.getPos() - worldPos = model.getPos(self.world.render) + current_scale = model.getScale() + + # 位置行 (Position) + position_container = QWidget() + position_layout = QHBoxLayout(position_container) + position_layout.setContentsMargins(0, 0, 0, 0) + position_layout.setSpacing(6) + + # 位置标签 + pos_label = QLabel("位置:") + pos_label.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + min-width: 31px; + } + """) + position_layout.addWidget(pos_label) + + # 添加弹性空间 + position_layout.addStretch() + + # X位置 + x_pos_label = QLabel("X:") + x_pos_label.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + position_layout.addWidget(x_pos_label) - # 位置 (Position) - transform_layout.addWidget(QLabel("相对位置"), 0, 0) self.pos_x = QDoubleSpinBox() - self.pos_y = QDoubleSpinBox() - self.pos_z = QDoubleSpinBox() - - # 设置位置控件属性 - for pos_widget in [self.pos_x, self.pos_y, self.pos_z]: - pos_widget.setRange(-1000000.0, 1000000.0) - + self.pos_x.setRange(-1000000.0, 1000000.0) self.pos_x.setValue(relativePos.getX()) + self.pos_x.setStyleSheet(self._getFigmaSpinBoxStyle()) + position_layout.addWidget(self.pos_x) + + # Y位置 + y_pos_label = QLabel("Y:") + y_pos_label.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + position_layout.addWidget(y_pos_label) + + self.pos_y = QDoubleSpinBox() + self.pos_y.setRange(-1000000.0, 1000000.0) self.pos_y.setValue(relativePos.getY()) + self.pos_y.setStyleSheet(self._getFigmaSpinBoxStyle()) + position_layout.addWidget(self.pos_y) + + # Z位置 + z_pos_label = QLabel("Z:") + z_pos_label.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + position_layout.addWidget(z_pos_label) + + self.pos_z = QDoubleSpinBox() + self.pos_z.setRange(-1000000.0, 1000000.0) self.pos_z.setValue(relativePos.getZ()) + self.pos_z.setStyleSheet(self._getFigmaSpinBoxStyle()) + position_layout.addWidget(self.pos_z) - # 连接位置变化事件 - # self.pos_x.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v)) - # self.pos_y.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v)) - # self.pos_z.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v)) + transform_layout.addWidget(position_container) + # 缩放行 (Scale) + scale_container = QWidget() + scale_layout = QHBoxLayout(scale_container) + scale_layout.setContentsMargins(0, 0, 0, 0) + scale_layout.setSpacing(6) + + # 缩放标签 + scale_label = QLabel("缩放:") + scale_label.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + min-width: 31px; + } + """) + scale_layout.addWidget(scale_label) + + # 添加弹性空间 + scale_layout.addStretch() + + # X缩放 + x_scale_label = QLabel("X:") + x_scale_label.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + scale_layout.addWidget(x_scale_label) + + self.scale_x = QDoubleSpinBox() + self.scale_x.setRange(-1000, 1000) + self.scale_x.setSingleStep(0.1) + self.scale_x.setValue(current_scale.getX()) + self.scale_x.setStyleSheet(self._getFigmaSpinBoxStyle()) + scale_layout.addWidget(self.scale_x) + + # Y缩放 + y_scale_label = QLabel("Y:") + y_scale_label.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + scale_layout.addWidget(y_scale_label) + + self.scale_y = QDoubleSpinBox() + self.scale_y.setRange(-1000, 1000) + self.scale_y.setSingleStep(0.1) + self.scale_y.setValue(current_scale.getY()) + self.scale_y.setStyleSheet(self._getFigmaSpinBoxStyle()) + scale_layout.addWidget(self.scale_y) + + # Z缩放 + z_scale_label = QLabel("Z:") + z_scale_label.setStyleSheet(""" + QLabel { + color: #ebebeb; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + } + """) + scale_layout.addWidget(z_scale_label) + + self.scale_z = QDoubleSpinBox() + self.scale_z.setRange(-1000, 1000) + self.scale_z.setSingleStep(0.1) + self.scale_z.setValue(current_scale.getZ()) + self.scale_z.setStyleSheet(self._getFigmaSpinBoxStyle()) + scale_layout.addWidget(self.scale_z) + + transform_layout.addWidget(scale_container) + + # 连接事件 def updateXPosition(value): model.setX(value) self.refreshModelValues(model) - self.pos_x.valueChanged.connect(updateXPosition) - def updateYPosition(value): model.setY(value) self.refreshModelValues(model) - self.pos_y.valueChanged.connect(updateYPosition) - def updateZPosition(value): model.setZ(value) self.refreshModelValues(model) + self.pos_x.valueChanged.connect(updateXPosition) + self.pos_y.valueChanged.connect(updateYPosition) self.pos_z.valueChanged.connect(updateZPosition) - # 创建并设置 X, Y, Z 标签居中 - x_label1 = QLabel("X") - y_label1 = QLabel("Y") - z_label1 = QLabel("Z") - x_label1.setAlignment(Qt.AlignCenter) - y_label1.setAlignment(Qt.AlignCenter) - z_label1.setAlignment(Qt.AlignCenter) - - transform_layout.addWidget(x_label1, 0, 1) - transform_layout.addWidget(y_label1, 0, 2) - transform_layout.addWidget(z_label1, 0, 3) - transform_layout.addWidget(self.pos_x, 1, 1) - transform_layout.addWidget(self.pos_y, 1, 2) - transform_layout.addWidget(self.pos_z, 1, 3) - - # 世界位置 (只读) - transform_layout.addWidget(QLabel("世界位置"), 2, 0) - self.world_pos_x = QDoubleSpinBox() - self.world_pos_y = QDoubleSpinBox() - self.world_pos_z = QDoubleSpinBox() - - # 设置世界位置控件属性 - for world_pos_widget in [self.world_pos_x, self.world_pos_y, self.world_pos_z]: - world_pos_widget.setRange(-1000000.0, 1000000.0) - world_pos_widget.setReadOnly(True) - - self.world_pos_x.setValue(worldPos.getX()) - self.world_pos_y.setValue(worldPos.getY()) - self.world_pos_z.setValue(worldPos.getZ()) - - transform_layout.addWidget(self.world_pos_x, 3, 1) - transform_layout.addWidget(self.world_pos_y, 3, 2) - transform_layout.addWidget(self.world_pos_z, 3, 3) - - # 旋转 (Rotation) - transform_layout.addWidget(QLabel("旋转"), 4, 0) - self.rot_x = QDoubleSpinBox() - self.rot_y = QDoubleSpinBox() - self.rot_z = QDoubleSpinBox() - - # 设置旋转控件属性 - for rot_widget in [self.rot_x, self.rot_y, self.rot_z]: - rot_widget.setRange(-360, 360) - - self.rot_x.setValue(model.getH()) - self.rot_y.setValue(model.getP()) - self.rot_z.setValue(model.getR()) - - # 连接旋转变化事件 - self.rot_x.valueChanged.connect(lambda v: model.setH(v)) - self.rot_y.valueChanged.connect(lambda v: model.setP(v)) - self.rot_z.valueChanged.connect(lambda v: model.setR(v)) - - # 创建并设置 H, P, R 标签居中 - h_label = QLabel("H") - p_label = QLabel("P") - r_label = QLabel("R") - h_label.setAlignment(Qt.AlignCenter) - p_label.setAlignment(Qt.AlignCenter) - r_label.setAlignment(Qt.AlignCenter) - - transform_layout.addWidget(h_label, 4, 1) - transform_layout.addWidget(p_label, 4, 2) - transform_layout.addWidget(r_label, 4, 3) - transform_layout.addWidget(self.rot_x, 5, 1) - transform_layout.addWidget(self.rot_y, 5, 2) - transform_layout.addWidget(self.rot_z, 5, 3) - - # 缩放 (Scale) - transform_layout.addWidget(QLabel("缩放"), 6, 0) - self.scale_x = QDoubleSpinBox() - self.scale_y = QDoubleSpinBox() - self.scale_z = QDoubleSpinBox() - - current_scale = model.getScale() - - # 设置缩放控件属性 - for i, (scale_widget, scale_value) in enumerate(zip([self.scale_x, self.scale_y, self.scale_z], - [current_scale.getX(), current_scale.getY(), - current_scale.getZ()])): - scale_widget.setRange(-1000, 1000) - scale_widget.setSingleStep(0.1) - scale_widget.setValue(scale_value) - - self.scale_x.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_x, value)) - self.scale_y.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_y, value)) - self.scale_z.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_z, value)) - # 连接缩放变化事件 self.scale_x.valueChanged.connect(lambda value: self._updateXScale(model, value)) self.scale_y.valueChanged.connect(lambda value: self._updateYScale(model, value)) self.scale_z.valueChanged.connect(lambda value: self._updateZScale(model, value)) - # 创建并设置 X, Y, Z 标签居中 - x_label3 = QLabel("X") - y_label3 = QLabel("Y") - z_label3 = QLabel("Z") - x_label3.setAlignment(Qt.AlignCenter) - y_label3.setAlignment(Qt.AlignCenter) - z_label3.setAlignment(Qt.AlignCenter) - - transform_layout.addWidget(x_label3, 6, 1) - transform_layout.addWidget(y_label3, 6, 2) - transform_layout.addWidget(z_label3, 6, 3) - transform_layout.addWidget(self.scale_x, 7, 1) - transform_layout.addWidget(self.scale_y, 7, 2) - transform_layout.addWidget(self.scale_z, 7, 3) - self.transform_group.setLayout(transform_layout) - self._propertyLayout.addWidget(self.transform_group) # 碰撞检测面板 self._addCollisionPanel(model) @@ -10107,4 +10621,4 @@ except Exception as e: except Exception as e: print(f"隐藏碰撞参数控件失败: {e}") import traceback - traceback.print_exc() \ No newline at end of file + traceback.print_exc() diff --git a/ui/widgets.py b/ui/widgets.py index d5978703..842baec9 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -10,11 +10,11 @@ import os import re -from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QGroupBox, QHBoxLayout, - QLineEdit, QPushButton, QLabel, QDialogButtonBox, +from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, + QLineEdit, QPushButton, QLabel, QTreeView, QTreeWidget, QTreeWidgetItem, QWidget, - QFileDialog, QMessageBox, QAbstractItemView, QMenu, QDockWidget, QButtonGroup, QToolButton) -from PyQt5.QtCore import Qt, QUrl, QMimeData + QFileDialog, QMessageBox, QAbstractItemView, QMenu, QDockWidget, QButtonGroup, QToolButton, QFrame, QSizePolicy) +from PyQt5.QtCore import Qt, QUrl, QMimeData, QPoint, QSize from PyQt5.QtGui import QDrag, QPainter, QPixmap, QPen, QBrush from PyQt5.sip import wrapinstance from direct.showbase.ShowBaseGlobal import aspect2d @@ -22,6 +22,7 @@ from panda3d.core import ModelRoot, NodePath, CollisionNode from QPanda3D.QPanda3DWidget import QPanda3DWidget from scene import util +from ui.icon_manager import get_icon_manager class NewProjectDialog(QDialog): """新建项目对话框""" @@ -29,109 +30,391 @@ class NewProjectDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("新建项目") - self.setMinimumWidth(500) + self.setObjectName("newProjectDialog") + self.resize(508, 274) + self.setMinimumSize(508, 274) + self.setModal(True) + + # 移除默认窗口装饰,使用自定义顶部栏 + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_TranslucentBackground, True) + + # 拖拽和窗口状态 + self.dragging = False + self.drag_position = QPoint() + self.icon_manager = get_icon_manager() + self._title_icon_size = QSize(14, 14) + self._icon_close = self.icon_manager.get_icon('close_icon', self._title_icon_size) - # 设置对话框样式与主窗口保持一致 + # 设置严格按照Figma设计的样式 self.setStyleSheet(""" - QDialog { - background-color: #252538; - color: #e0e0ff; + QDialog#newProjectDialog { + background-color: transparent; + color: #EBEBEB; + border: none; } - QGroupBox { - background-color: #2d2d44; - border: 1px solid #3a3a4a; - border-radius: 6px; - margin-top: 1ex; /* 保持这个设置 */ - color: #e0e0ff; - font-weight: 500; - padding-top: 10px; /* 增加顶部内边距,为标题留出空间 */ + QFrame#baseFrame { + background-color: #000000; + border: 1px solid #3E3E42; + border-radius: 5px; } - QGroupBox::title { - padding: 0 8px; - color: #c0c0e0; + QWidget#titleBar { + background-color: transparent; + border-radius: 5px 5px 0px 0px; + min-height: 32px; + max-height: 32px; + } + QLabel#titleLabel { + color: #FFFFFF; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 14px; font-weight: 500; + letter-spacing: 0.7px; + } + QWidget#controlButtons QPushButton { + background-color: transparent; + border: none; + color: #EBEBEB; + font-size: 14px; + min-width: 32px; + max-width: 32px; + min-height: 32px; + max-height: 32px; + } + QWidget#controlButtons QPushButton:hover { + background-color: #2A2D2E; + color: #FFFFFF; + } + QPushButton#closeButton { + border-radius: 0px 5px 0px 0px; + } + QPushButton#closeButton:hover { + background-color: #E74C3C; + color: #FFFFFF; + } + QWidget#contentWidget { + background-color: transparent; + border-radius: 0px 0px 5px 5px; + } + QFrame#contentContainer { + background-color: #19191B; + border: 1px solid #2C2F36; + border-radius: 5px; + } + QLabel[role="sectionTitle"] { + color: #EBEBEB; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.6px; + padding: 0px; + margin-bottom: 0px; + } + QLabel[role="hint"] { + color: rgba(235, 235, 235, 0.6); + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 11px; + font-weight: 300; + letter-spacing: 0.55px; + margin-top: 0px; + padding: 0px; } QLineEdit { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; - padding: 6px; + background-color: rgba(89, 100, 113, 0.2); + color: #EBEBEB; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; + padding: 6px 10px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 11px; + font-weight: 300; + letter-spacing: 0.55px; + min-height: 14px; + max-height: 30px; + } + QLineEdit:focus { + border: 1px solid #3067C0; + background-color: rgba(48, 103, 192, 0.1); + } + QLineEdit:hover { + border: 1px solid #3067C0; + background-color: rgba(89, 100, 113, 0.3); } QLineEdit:disabled { - background-color: #1e1e2e; - color: #8888aa; + background-color: rgba(89, 100, 113, 0.1); + color: rgba(235, 235, 235, 0.4); + border: 1px solid rgba(76, 92, 110, 0.2); } QPushButton { - background-color: #8b5cf6; - color: white; + background-color: rgba(89, 98, 118, 0.5); + color: #EBEBEB; border: none; - padding: 6px 12px; - border-radius: 4px; - font-weight: 500; + border-radius: 2px; + padding: 0px 0px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-width: 90px; + min-height: 30px; + max-height: 30px; } QPushButton:hover { - background-color: #7c3aed; + background-color: #3067C0; + color: #FFFFFF; } QPushButton:pressed { - background-color: #6d28d9; + background-color: #2556A0; + color: #FFFFFF; } QPushButton:disabled { - background-color: #4c4c6e; - color: #8888aa; + background-color: rgba(89, 98, 118, 0.3); + color: rgba(235, 235, 235, 0.4); } - QLabel { - color: #e0e0ff; + QPushButton#primaryButton { + background-color: rgba(89, 98, 118, 0.5); + border: none; + color: #EBEBEB; + font-weight: 300; + min-width: 120px; + max-width: 120px; } - QLabel:disabled { - color: #8888aa; + QPushButton#primaryButton:hover { + background-color: #2556A0; } - QDialogButtonBox QPushButton { - min-width: 80px; + QPushButton#primaryButton:pressed { + background-color: #1E4A8C; + } + QPushButton#secondaryButton { + background-color: rgba(89, 98, 118, 0.5); + border: none; + color: #EBEBEB; + min-width: 120px; + max-width: 120px; + } + QPushButton#secondaryButton:hover { + background-color: #3067C0; + color: #FFFFFF; + } + QPushButton#secondaryButton:pressed { + background-color: #2556A0; + color: #FFFFFF; + } + QPushButton#browseButton { + min-width: 90px; + max-width: 90px; + min-height: 28px; + max-height: 28px; } """) - # 创建布局 - layout = QVBoxLayout(self) - - # 创建路径选择部分 - pathGroup = QGroupBox("项目路径") - pathLayout = QHBoxLayout() + # 创建主布局 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # 创建基础容器,负责绘制圆角和边框 + base_frame = QFrame() + base_frame.setObjectName('baseFrame') + base_frame.setFrameShape(QFrame.NoFrame) + base_frame.setAttribute(Qt.WA_StyledBackground, True) + + base_layout = QVBoxLayout(base_frame) + base_layout.setContentsMargins(0, 0, 0, 0) + base_layout.setSpacing(0) + + # 创建自定义顶部栏 + self.createTitleBar() + base_layout.addWidget(self.title_bar) + + # 创建内容区域 + content_widget = QWidget() + content_widget.setObjectName('contentWidget') + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(15, 10, 15, 10) + content_layout.setSpacing(0) + + content_container = QFrame() + content_container.setObjectName('contentContainer') + content_container.setFrameShape(QFrame.NoFrame) + content_container.setAttribute(Qt.WA_StyledBackground, True) + content_container.setFixedWidth(488) + content_container.setMinimumHeight(213) + + container_layout = QVBoxLayout(content_container) + container_layout.setContentsMargins(15, 10, 15, 10) + container_layout.setSpacing(10) + + pathLabel = QLabel('项目路径') + pathLabel.setProperty('role', 'sectionTitle') + container_layout.addWidget(pathLabel) + + path_row_widget = QWidget() + path_row_layout = QHBoxLayout(path_row_widget) + path_row_layout.setContentsMargins(0, 0, 0, 0) + path_row_layout.setSpacing(10) + path_row_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.pathEdit = QLineEdit() self.pathEdit.setReadOnly(True) - browseButton = QPushButton("浏览...") + self.pathEdit.setPlaceholderText('请选择项目路径') + self.pathEdit.setMinimumWidth(358) + self.pathEdit.setFixedHeight(30) + path_row_layout.addWidget(self.pathEdit) + + browseButton = QPushButton('浏览...') + browseButton.setObjectName('browseButton') browseButton.clicked.connect(self.browsePath) - pathLayout.addWidget(self.pathEdit) - pathLayout.addWidget(browseButton) - pathGroup.setLayout(pathLayout) - layout.addWidget(pathGroup) - - # 创建项目名称部分 - nameGroup = QGroupBox("项目名称") - nameLayout = QVBoxLayout() + path_row_layout.addWidget(browseButton) + container_layout.addWidget(path_row_widget) + + nameLabel = QLabel('项目名称') + nameLabel.setProperty('role', 'sectionTitle') + container_layout.addWidget(nameLabel) + self.nameEdit = QLineEdit() - self.nameEdit.setText("新项目") + self.nameEdit.setText('新项目') + self.nameEdit.setPlaceholderText('请输入项目名称') self.nameEdit.selectAll() - nameLayout.addWidget(self.nameEdit) - # 添加提示标签 - self.tipLabel = QLabel("项目名称只能包含字母、数字、下划线、中划线和中文") - self.tipLabel.setStyleSheet("color: gray;") - nameLayout.addWidget(self.tipLabel) - nameGroup.setLayout(nameLayout) - layout.addWidget(nameGroup) - - # 添加按钮 - buttonBox = QDialogButtonBox( - QDialogButtonBox.Ok | QDialogButtonBox.Cancel - ) - buttonBox.accepted.connect(self.validate) - buttonBox.rejected.connect(self.reject) - layout.addWidget(buttonBox) - - # 存储结果 + self.nameEdit.setFixedHeight(30) + container_layout.addWidget(self.nameEdit) + + self.tipLabel = QLabel('项目名称只能包含字母、数字、下划线、中划线和中文') + self.tipLabel.setProperty('role', 'hint') + container_layout.addWidget(self.tipLabel) + + separator = QFrame() + separator.setFrameShape(QFrame.HLine) + separator.setFrameShadow(QFrame.Plain) + separator.setFixedHeight(1) + separator.setStyleSheet('background-color: #2C2F36; border: none;') + container_layout.addWidget(separator) + + button_row_widget = QWidget() + button_row_layout = QHBoxLayout(button_row_widget) + button_row_layout.setContentsMargins(0, 0, 0, 0) + button_row_layout.setSpacing(10) + button_row_layout.addStretch() + button_row_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + self.cancelButton = QPushButton('取消') + self.cancelButton.setObjectName('secondaryButton') + self.cancelButton.clicked.connect(self.reject) + self.cancelButton.setFixedSize(120, 30) + button_row_layout.addWidget(self.cancelButton) + + self.confirmButton = QPushButton('确认') + self.confirmButton.setObjectName('primaryButton') + self.confirmButton.clicked.connect(self.validate) + self.confirmButton.setFixedSize(120, 30) + button_row_layout.addWidget(self.confirmButton) + + container_layout.addWidget(button_row_widget) + + self.confirmButton.setDefault(True) + self.confirmButton.setAutoDefault(True) + self.cancelButton.setAutoDefault(False) + + content_layout.addWidget(content_container, 0, Qt.AlignTop) + base_layout.addWidget(content_widget) + main_layout.addWidget(base_frame) + self.projectPath = "" self.projectName = "" + def createTitleBar(self): + """创建自定义顶部栏""" + self.title_bar = QFrame() + self.title_bar.setObjectName("titleBar") + + title_layout = QHBoxLayout(self.title_bar) + title_layout.setContentsMargins(12, 0, 12, 0) + title_layout.setSpacing(0) + + # Control buttons area + controls = QWidget() + controls.setObjectName("controlButtons") + controls_layout = QHBoxLayout(controls) + controls_layout.setContentsMargins(0, 0, 0, 0) + controls_layout.setSpacing(0) + + self.close_button = QPushButton() + self.close_button.setObjectName("closeButton") + self.close_button.clicked.connect(self.reject) + self.close_button.setFocusPolicy(Qt.NoFocus) + controls_layout.addWidget(self.close_button) + + self._applyTitleBarIcons() + + # Reserve left space equal to control buttons to keep title centered + left_placeholder = QWidget() + left_placeholder.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + title_layout.addWidget(left_placeholder) + + self.title_label = QLabel(self.windowTitle()) + self.title_label.setObjectName("titleLabel") + self.title_label.setAlignment(Qt.AlignCenter) + title_layout.addWidget(self.title_label, 1) + + title_layout.addWidget(controls) + + left_placeholder.setFixedWidth(controls.sizeHint().width()) + + + def _applyTitleBarIcons(self): + """为标题栏按钮应用图标""" + if self._icon_close: + self.close_button.setIcon(self._icon_close) + self.close_button.setIconSize(self._title_icon_size) + self.close_button.setText("") + self.close_button.setToolTip("关闭") + + + def toggleMaximize(self): + """切换窗口最大化/还原""" + if self.isMaximized(): + self.showNormal() + else: + self.showMaximized() + + + def setWindowTitle(self, title): + super().setWindowTitle(title) + if hasattr(self, "title_label"): + self.title_label.setText(title) + + + def mousePressEvent(self, event): + """鼠标按下事件 - 用于拖拽窗口""" + if event.button() == Qt.LeftButton and self.title_bar.geometry().contains(event.pos()): + if self.isMaximized(): + self.showNormal() + self.dragging = True + self.drag_position = event.globalPos() - self.frameGeometry().topLeft() + event.accept() + super().mousePressEvent(event) + + def mouseDoubleClickEvent(self, event): + """双击标题栏切换最大化""" + if event.button() == Qt.LeftButton and self.title_bar.geometry().contains(event.pos()): + self.toggleMaximize() + event.accept() + return + super().mouseDoubleClickEvent(event) + + + def mouseMoveEvent(self, event): + """鼠标移动事件 - 用于拖拽窗口""" + if event.buttons() == Qt.LeftButton and self.dragging: + self.move(event.globalPos() - self.drag_position) + event.accept() + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + """鼠标释放事件 - 停止拖拽""" + if event.button() == Qt.LeftButton: + self.dragging = False + super().mouseReleaseEvent(event) + def browsePath(self): """浏览选择项目路径""" path = QFileDialog.getExistingDirectory(self, "选择项目路径") @@ -143,25 +426,25 @@ class NewProjectDialog(QDialog): # 获取并验证路径 self.projectPath = self.pathEdit.text() if not self.projectPath: - QMessageBox.warning(self, "错误", "请选择项目路径!") + StyledMessageBox.warning(self, "错误", "请选择项目路径!") return # 获取并验证项目名称 self.projectName = self.nameEdit.text().strip() if not self.projectName: - QMessageBox.warning(self, "错误", "请输入项目名称!") + StyledMessageBox.warning(self, "错误", "请输入项目名称!") return # 验证项目名称格式 if not re.match(r'^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$', self.projectName): - QMessageBox.warning(self, "错误", + StyledMessageBox.warning(self, "错误", "项目名称只能包含字母、数字、下划线、中划线和中文!") return # 检查项目是否已存在 full_path = os.path.join(self.projectPath, self.projectName) if os.path.exists(full_path): - QMessageBox.warning(self, "错误", "项目已存在!") + StyledMessageBox.warning(self, "错误", "项目已存在!") return self.accept() @@ -363,7 +646,7 @@ class CustomFileView(QTreeView): drag.exec_(supportedActions) def mouseDoubleClickEvent(self, event): - """处理双击事件""" + """双击标题栏切换最大化""" index = self.indexAt(event.pos()) if index.isValid(): model = self.model() @@ -488,10 +771,9 @@ class CustomAssetsTreeWidget(QTreeWidget): def createNewFolder(self, parent_item): """创建新文件夹""" import os - from PyQt5.QtWidgets import QInputDialog parent_path = parent_item.data(0, Qt.UserRole) - folder_name, ok = QInputDialog.getText(self, "新建文件夹", "文件夹名称:") + folder_name, ok = StyledMessageBox.getText(self, "新建文件夹", "文件夹名称:") if ok and folder_name: new_folder_path = os.path.join(parent_path, folder_name) @@ -505,10 +787,9 @@ class CustomAssetsTreeWidget(QTreeWidget): def createNewFile(self, parent_item): """创建新文件""" import os - from PyQt5.QtWidgets import QInputDialog parent_path = parent_item.data(0, Qt.UserRole) - file_name, ok = QInputDialog.getText(self, "新建文件", "文件名称:") + file_name, ok = StyledMessageBox.getText(self, "新建文件", "文件名称:") if ok and file_name: new_file_path = os.path.join(parent_path, file_name) @@ -523,12 +804,11 @@ class CustomAssetsTreeWidget(QTreeWidget): def renameItem(self, item): """重命名文件或文件夹""" import os - from PyQt5.QtWidgets import QInputDialog old_path = item.data(0, Qt.UserRole) old_name = os.path.basename(old_path) - new_name, ok = QInputDialog.getText(self, "重命名", "新名称:", text=old_name) + new_name, ok = StyledMessageBox.getText(self, "重命名", "新名称:", text=old_name) if ok and new_name and new_name != old_name: parent_dir = os.path.dirname(old_path) @@ -551,7 +831,7 @@ class CustomAssetsTreeWidget(QTreeWidget): is_folder = item.data(0, Qt.UserRole + 1) item_type = "文件夹" if is_folder else "文件" - reply = QMessageBox.question( + reply = StyledMessageBox.question( self, "确认删除", f"确定要删除这个{item_type}吗?\n{filepath}", @@ -622,10 +902,10 @@ class CustomAssetsTreeWidget(QTreeWidget): 修改时间: {modified_str} """ - QMessageBox.information(self, "属性", properties.strip()) + StyledMessageBox.information(self, "属性", properties.strip()) except OSError as e: - QMessageBox.warning(self, "错误", f"无法获取属性: {e}") + StyledMessageBox.warning(self, "错误", f"无法获取属性: {e}") # def mouseDoubleClickEvent(self, event): # """处理双击事件""" @@ -1254,7 +1534,7 @@ class CustomAssetsTreeWidget(QTreeWidget): self._refreshWithStatePreservation() if failed_files: - QMessageBox.warning( + StyledMessageBox.warning( self, "移动失败", f"以下文件移动失败:\n" + "\n".join(failed_files) @@ -1304,6 +1584,25 @@ class CustomConsoleDockWidget(QWidget): # 清空按钮 self.clearBtn = QPushButton("清空") + self.clearBtn.setStyleSheet(""" + QPushButton { + background-color: rgba(84, 89, 98, 0.5); + color: #ffffff; + border: none; + padding: 4px 12px; + border-radius: 2px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.7px; + } + QPushButton:hover { + background-color: rgba(84, 89, 98, 0.7); + } + QPushButton:pressed { + background-color: rgba(84, 89, 98, 0.9); + } + """) self.clearBtn.clicked.connect(self.clearConsole) toolbar.addWidget(self.clearBtn) @@ -1313,26 +1612,25 @@ class CustomConsoleDockWidget(QWidget): self.autoScrollBtn.setChecked(True) self.autoScrollBtn.setStyleSheet(""" QPushButton { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - padding: 6px 12px; - border-radius: 4px; - font-weight: 500; + background-color: rgba(84, 89, 98, 0.5); + color: #ffffff; + border: none; + padding: 4px 12px; + border-radius: 2px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.7px; } QPushButton:checked { - background-color: #8b5cf6; - color: white; - border: 1px solid #7c3aed; + background-color: #3067c0; + color: #ffffff; } QPushButton:hover { - background-color: #3a3a4a; - } - QPushButton:checked:hover { - background-color: #7c3aed; + background-color: rgba(84, 89, 98, 0.7); } QPushButton:pressed { - background-color: #6d28d9; + background-color: rgba(84, 89, 98, 0.9); } """) toolbar.addWidget(self.autoScrollBtn) @@ -1343,26 +1641,25 @@ class CustomConsoleDockWidget(QWidget): self.timestampBtn.setChecked(True) self.timestampBtn.setStyleSheet(""" QPushButton { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - padding: 6px 12px; - border-radius: 4px; - font-weight: 500; + background-color: rgba(84, 89, 98, 0.5); + color: #ffffff; + border: none; + padding: 4px 12px; + border-radius: 2px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.7px; } QPushButton:checked { - background-color: #8b5cf6; - color: white; - border: 1px solid #7c3aed; + background-color: #3067c0; + color: #ffffff; } QPushButton:hover { - background-color: #3a3a4a; - } - QPushButton:checked:hover { - background-color: #7c3aed; + background-color: rgba(84, 89, 98, 0.7); } QPushButton:pressed { - background-color: #6d28d9; + background-color: rgba(84, 89, 98, 0.9); } """) toolbar.addWidget(self.timestampBtn) @@ -1370,13 +1667,15 @@ class CustomConsoleDockWidget(QWidget): self.fpsLabel = QLabel("FPS:0.0") self.fpsLabel.setStyleSheet(""" QLabel { - background-color: #2d2d44; - color: #80ff80; - border: 1px solid #3a3a4a; - padding: 6px 12px; - border-radius: 4px; - font-weight: 500; - font-family: 'Consolas', 'Monaco', monospace; + background-color: #3067c0; + color: #ffffff; + border: none; + padding: 4px 12px; + border-radius: 2px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.7px; } """) self.fpsLabel.setMinimumWidth(100) @@ -1397,11 +1696,14 @@ class CustomConsoleDockWidget(QWidget): self.consoleText.setReadOnly(True) self.consoleText.setStyleSheet(""" QTextEdit { - background-color: #1e1e1e; - color: #ffffff; - font-family: 'Consolas', 'Monaco', monospace; - font-size: 10pt; - border: 1px solid #3e3e3e; + background-color: #19191b; + color: #ebebeb; + font-family: 'Consolas', 'Monaco', 'Microsoft YaHei', monospace; + font-size: 10px; + font-weight: 300; + border: none; + letter-spacing: 0.5px; + line-height: 12px; } """) layout.addWidget(self.consoleText) @@ -1445,7 +1747,7 @@ class CustomConsoleDockWidget(QWidget): self.fpsLabel.setStyleSheet(f""" QLabel {{ - background-color: #2d2d44; + background-color: #3067c0; color: {color}; border: 1px solid #3a3a4a; padding: 6px 12px; @@ -1559,6 +1861,226 @@ class ConsoleRedirect: """刷新缓冲区""" pass +class StyledMessageBox: + """统一样式的消息框辅助类""" + + MESSAGEBOX_STYLE = """ + QMessageBox { + background-color: #1e1e1f; + color: #ebebeb; + border: 1px solid rgba(77, 116, 189, 0.3); + border-radius: 8px; + padding: 20px; + min-width: 300px; + } + QMessageBox QLabel { + color: #ffffff; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 13px; + font-weight: 400; + line-height: 1.4; + padding: 10px 0px; + } + QMessageBox QPushButton { + background-color: rgba(89, 100, 113, 0.4); + color: rgba(255, 255, 255, 0.8); + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 6px; + padding: 8px 16px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-weight: 400; + font-size: 11px; + min-width: 80px; + min-height: 28px; + } + QMessageBox QPushButton:hover { + background-color: rgba(89, 100, 113, 0.6); + border: 1px solid rgba(77, 116, 189, 0.6); + color: #ffffff; + } + QMessageBox QPushButton:pressed, QMessageBox QPushButton:checked { + background-color: rgba(77, 116, 189, 0.8); + border: 1px solid #4d74bd; + color: #ffffff; + } + QMessageBox QPushButton:disabled { + background-color: rgba(89, 100, 113, 0.2); + color: rgba(235, 235, 235, 0.4); + border: 1px solid rgba(76, 92, 110, 0.2); + } + QMessageBox QPushButton:default { + background-color: rgba(77, 116, 189, 0.8); + border: 1px solid #4d74bd; + color: #ffffff; + font-weight: 500; + } + QMessageBox QPushButton:default:hover { + background-color: rgba(77, 116, 189, 1.0); + border: 1px solid rgba(77, 116, 189, 1.0); + } + QMessageBox QIcon { + padding: 0px 10px 0px 0px; + } + """ + + INPUTDIALOG_STYLE = """ + QInputDialog { + background-color: #1e1e1f; + color: #ebebeb; + border: 1px solid rgba(77, 116, 189, 0.3); + border-radius: 8px; + padding: 20px; + min-width: 350px; + } + QLabel { + color: #ffffff; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-weight: 500; + font-size: 13px; + padding: 0px 0px 10px 0px; + } + QLineEdit { + background-color: rgba(89, 100, 113, 0.15); + color: #ebebeb; + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 6px; + padding: 10px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 11px; + font-weight: 400; + min-height: 16px; + } + QLineEdit:focus { + border: 1px solid #4d74bd; + background-color: rgba(77, 116, 189, 0.1); + } + QLineEdit:hover { + border: 1px solid rgba(77, 116, 189, 0.6); + background-color: rgba(89, 100, 113, 0.2); + } + QPushButton { + background-color: rgba(89, 100, 113, 0.4); + color: rgba(255, 255, 255, 0.8); + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 6px; + padding: 8px 16px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-weight: 400; + font-size: 11px; + min-width: 80px; + min-height: 28px; + } + QPushButton:hover { + background-color: rgba(89, 100, 113, 0.6); + border: 1px solid rgba(77, 116, 189, 0.6); + color: #ffffff; + } + QPushButton:pressed, QPushButton:checked { + background-color: rgba(77, 116, 189, 0.8); + border: 1px solid #4d74bd; + color: #ffffff; + } + QPushButton:disabled { + background-color: rgba(89, 100, 113, 0.2); + color: rgba(235, 235, 235, 0.4); + border: 1px solid rgba(76, 92, 110, 0.2); + } + QPushButton:default { + background-color: rgba(77, 116, 189, 0.8); + border: 1px solid #4d74bd; + color: #ffffff; + font-weight: 500; + } + """ + + BUTTON_STYLE = """ + QPushButton { + background-color: rgba(89, 100, 113, 0.4); + color: rgba(255, 255, 255, 0.8); + border: 1px solid rgba(76, 92, 110, 0.4); + border-radius: 6px; + padding: 8px 16px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-weight: 400; + font-size: 11px; + min-width: 80px; + min-height: 28px; + } + QPushButton:hover { + background-color: rgba(89, 100, 113, 0.6); + border: 1px solid rgba(77, 116, 189, 0.6); + color: #ffffff; + } + QPushButton:pressed, QPushButton:checked { + background-color: rgba(77, 116, 189, 0.8); + border: 1px solid #4d74bd; + color: #ffffff; + } + QPushButton:disabled { + background-color: rgba(89, 100, 113, 0.2); + color: rgba(235, 235, 235, 0.4); + border: 1px solid rgba(76, 92, 110, 0.2); + } + """ + + @staticmethod + def information(parent, title, message): + """显示信息提示框""" + msg = QMessageBox(QMessageBox.Information, title, message, QMessageBox.Ok, parent) + msg.setStyleSheet(StyledMessageBox.MESSAGEBOX_STYLE) + # 强制设置所有按钮的样式 + for button in msg.buttons(): + button.setStyleSheet(StyledMessageBox.BUTTON_STYLE) + return msg.exec_() + + @staticmethod + def question(parent, title, message, buttons=QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.No): + """显示确认对话框""" + msg = QMessageBox(QMessageBox.Question, title, message, buttons, parent) + msg.setDefaultButton(defaultButton) + msg.setStyleSheet(StyledMessageBox.MESSAGEBOX_STYLE) + # 强制设置所有按钮的样式 + for button in msg.buttons(): + button.setStyleSheet(StyledMessageBox.BUTTON_STYLE) + return msg.exec_() + + @staticmethod + def warning(parent, title, message): + """显示警告对话框""" + msg = QMessageBox(QMessageBox.Warning, title, message, QMessageBox.Ok, parent) + msg.setStyleSheet(StyledMessageBox.MESSAGEBOX_STYLE) + # 强制设置所有按钮的样式 + for button in msg.buttons(): + button.setStyleSheet(StyledMessageBox.BUTTON_STYLE) + return msg.exec_() + + @staticmethod + def critical(parent, title, message): + """显示错误对话框""" + msg = QMessageBox(QMessageBox.Critical, title, message, QMessageBox.Ok, parent) + msg.setStyleSheet(StyledMessageBox.MESSAGEBOX_STYLE) + # 强制设置所有按钮的样式 + for button in msg.buttons(): + button.setStyleSheet(StyledMessageBox.BUTTON_STYLE) + return msg.exec_() + + @staticmethod + def getText(parent, title, label, text=""): + """显示样式统一的文本输入对话框""" + from PyQt5.QtWidgets import QInputDialog + dialog = QInputDialog(parent) + dialog.setWindowTitle(title) + dialog.setLabelText(label) + dialog.setTextValue(text) + dialog.setStyleSheet(StyledMessageBox.INPUTDIALOG_STYLE) + + # 强制设置所有按钮的样式 + for button in dialog.findChildren(QPushButton): + button.setStyleSheet(StyledMessageBox.BUTTON_STYLE) + + ok = dialog.exec_() + return dialog.textValue(), ok + class CustomTreeWidget(QTreeWidget): """自定义场景树部件""" @@ -1573,24 +2095,7 @@ class CustomTreeWidget(QTreeWidget): self.setupContextMenu() # 初始化右键菜单 self.setupDragDrop() # 设置拖拽功能 self.original_scales={} - - self.setStyleSheet(""" - /* 设置折叠状态下,带子节点的箭头颜色 */ - QTreeWidget::branch:has-children:!open { - color: #8b5cf6; /* 紫色 */ - } - - /* 设置展开状态下,带子节点的箭头颜色 */ - QTreeWidget::branch:has-children:open { - color: #9ca3af; /* 灰色,提供状态变化反馈 */ - } - - /* 鼠标悬停在任意箭头上时,颜色变亮 */ - QTreeWidget::branch:hover { - color: #a78bfa; /* 亮紫色 */ - } - """) - + def initData(self): """初始化变量""" # 定义2D GUI元素类型 @@ -2119,7 +2624,7 @@ class CustomTreeWidget(QTreeWidget): deletable_items.append(item) if not deletable_items: - QMessageBox.information(self, "提示", "没有可删除的节点") + StyledMessageBox.information(self, "提示", "没有可删除的节点") return # 2. 确认删除 @@ -2129,7 +2634,7 @@ class CustomTreeWidget(QTreeWidget): else: message = f"确定要删除 {item_count} 个节点吗?" - reply = QMessageBox.question( + reply = StyledMessageBox.question( self, "确认删除", message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) @@ -3036,5 +3541,3 @@ class CustomTreeWidget(QTreeWidget): - - From 54fca099149832af6c8c0d8b142df52d7d6d5075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Wed, 15 Oct 2025 11:50:03 +0800 Subject: [PATCH 3/7] =?UTF-8?q?1.Icons=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- icons/close_icon.png | Bin 0 -> 370 bytes icons/delete_fail_icon.png | Bin 0 -> 404 bytes icons/down_arrows.png | Bin 0 -> 2805 bytes icons/left_arrows.png | Bin 0 -> 2691 bytes icons/minimize_icon.png | Bin 0 -> 282 bytes icons/right_arrows.png | Bin 0 -> 2680 bytes icons/solid_down_arrows.png | Bin 0 -> 2790 bytes icons/solid_right_arrows.png | Bin 0 -> 2746 bytes icons/sueccess_icon.png | Bin 0 -> 546 bytes icons/sure_delete_icon.png | Bin 0 -> 482 bytes icons/up_arrows.png | Bin 0 -> 2791 bytes icons/windowing_icon.png | Bin 0 -> 323 bytes 12 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 icons/close_icon.png create mode 100644 icons/delete_fail_icon.png create mode 100644 icons/down_arrows.png create mode 100644 icons/left_arrows.png create mode 100644 icons/minimize_icon.png create mode 100644 icons/right_arrows.png create mode 100644 icons/solid_down_arrows.png create mode 100644 icons/solid_right_arrows.png create mode 100644 icons/sueccess_icon.png create mode 100644 icons/sure_delete_icon.png create mode 100644 icons/up_arrows.png create mode 100644 icons/windowing_icon.png diff --git a/icons/close_icon.png b/icons/close_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b5c923a1387a10282eb5bdac4b2616b4e91d01ac GIT binary patch literal 370 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|&H|6fVg?3oVGw3ym^DWND9BhG z5UT-ycZmr7WO{D zSG#<{SDC0H_UvpRN0+xnXbr=E2~VC4YYzCEc~85!-MBu2#j$eU?->binh^M$OZHuVU9FpXNdu8WbQZtjklo5xnIvgv!d zp5MXXebn_*6Siayw)&a{&u+#q*r&dU&8I2l*s28PfXHbJ&)nc+JKy)Wym_5vU-2@|TK*k9 zX)VG!QJUv89J`tC%ojf^QfDje!@kd`N&J$@-C4_aE>87IaR0<6%D2A3efEWaz#wAq MboFyt=akR{044~Da{vGU literal 0 HcmV?d00001 diff --git a/icons/delete_fail_icon.png b/icons/delete_fail_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..90ba204d53b2ac64748e3bbbc6885e8115aad138 GIT binary patch literal 404 zcmeAS@N?(olHy`uVBq!ia0vp^B0wy}!3HFwFZ>e#Qk(@Ik;M!Q+`=Ht$S`Y;1W=H% zILO_JVcj{Imp~3nx}&cn1H;CC?mvmFKsilM7srqa#6W$GD?nJ+TS z@zt-3Vs%b?U8*;qL*Ti=%oOLZe$Myuo;yw3c2(=~QKdb%1p1D5tqawd@-wupjZeYy zzT%YGXa8m#p0#e$wfRPM=EaMS2e$2rS}G>{x&6xkGk^O`FnV> zcX70-7*4u%CNo3oq4V`w&+baxY;oxnk6pb%x-9jgVf?b$e$F$_zSCPb)i6qLv9YtZ zTCCt+hrm6K@=JrxPfL`&vgEqJ#y!jHk5>sV?%JRccTjcF-Z=^H*Ic|c$KXSkr@#_} tC6kQ$giZ4A?l||Y`=|R<<~x4-)gP>iH(DTdP#qYE44$rjF6*2UngCorq^1A> literal 0 HcmV?d00001 diff --git a/icons/down_arrows.png b/icons/down_arrows.png new file mode 100644 index 0000000000000000000000000000000000000000..86bdb1ee41b6e1334ff9a5e178d4297e6a468355 GIT binary patch literal 2805 zcmZvedpuNI8^`xdn=^x98<)^vGu!nluIbos7~dQ*(o#0GuW#nEFmG_)p?>^`6_m92y`mFW&JkRg3TqZUREBGVT@XX+Gh z!2I2U6ZLMw#8{ZY_{y zB~uz%fN%*}M~O9db{n+U9Y71UPwx0?k`AZd?fPAdOc}c5r*=z&1dcZqZ7+g8TZy#p zs&%=Nz?pD%v|kQ@ZOZV8TLhuzqqIEKgG>p~w7v2}%T2nCc9jt zMmA*+AtG0K66)4;lNv%s?tC8rt#zEJJoCsHx0OaS((q z8SXwJ-~uQ^cY0ph6OaX9uS|XVttfD-c2p$&HU;L|R4a-w09)_xY3~2;_FA9*$D=|x zs~A|ncpktQUv_*N0-?wmt@LqYLRy5&)V4wZTb@RqD}0doK(m>BRxiSdOnFILXP6A9 z!$wz|_3jZ06UN57mk>t%%>K~xDe0b}1gM#bqmgvjA^0pyhnWsP_QPX5$rSsDpu9jk z!ur44pF^ekyV?q2DWRGEsB;gE@a|cZ7iCHUd7#&=RfKSZ1JSu+Fkc0C_RL31mJzi* zaY3>Ua)UI2cAk1iIC7fXKP3dPLlyLhDNnvGDkohUt|MyKHy?E{4}=gc_}Qp=I-K(*?M$wmDDCKZ7@=&iTokpKAU3wC&03ROlS=oP>`lGKE>R_|HJ&f7?RcBI z_@C<*-Z+e2W19s}t*v*-W%wJ0FB518ad2wukDUu&7jt?qzt8FjGW?R<)afmSa|evG zlC#!$;>oh^@azT;$wrdl*jAeJ)P5Bm#IaPu#^Jf&y2QgLHAPZ(>V)`SN6>X+&kFdV z`mOrDE0P?AnU+xy0$H_u;r8E8Ub^2mQ)Tvj(wooA5bCbSIr?CL;>O1Zp`ZSpNj>~3Ns@|1qOvj5Hm5K` z#EsUM`}5b0yx`md{mF=?Qkk-_Ul8NlgIQ8>5!oMapa^1IJV5_4GCpRIR&J_mvGOFt znr(;5mIvY+m6ds*VDLs#Nw3yySa+66nYxstEz%|v2hMIQWoa+v@XW-=Z7Dtrle(@v zaNkY6X^R$}qdI71TaLmT`-@4w_@vZsvku1)7F$AzntEH*v+LyW1j5F>mb7a9iWaWC zR@=00v^yffRJ3M;jI$w!qivqWVa>TnYTCW%3#+s^!)&@gY_IsfXVmY_?Vy`;1uA8a zdceT{%F8eRO&A&-#F1k`c4#xnC{pT#wtJmGh??MIQ6M*=H5Dpvm`s0ligAJ=^`6<$l7`5=T?)D1hA9ugpbvUS07i#GjJ8> z1RYjJ<5}3?vj={>uG^1s}ViCFdwe9^#Lj1YV;InLx zgEk^_;*$EU2+n11dZ${(BTiLz`t=CZX$5w2h^z5LNl>`OmnphTR7+~yI+HoL35yBs zFzeKoD+}tIU5ns+BV6R30=Mjr4pb?NH5FTC{3Pg$UUePL|LBQ#`!MEAhwQ7)z?mJn zy>_?Z{8Wvp!&7daiM8hK zaq-z6R-R;Ho~z06BlP%MD4dfhb=$llQV$oOrPY5tiW9N1&mq20X&ra=*_(4X;x94& zqkZ0NGI&%G#X~vmCo6qMk`QrfVupNLS6?ue9osT|;n1gQtm1MW{!2ISTgfjMGqW8= zsC8+AdwGz8L-WS7@vV(k2k0@!%x0s>oK~5_mMPuS>o^&{%`{`P+2aqAU zR^fryci%a&aA3DNYxT%@^-0+2MshC5O3-$U(8B6TE7*9#`vMUoQQINH0b?}y$T<21 zJ{Y5My9=aH57fZnmLk*WDNiYIh)FDylg8zq;;R?p9)jNP-X=i)ucRA5QE5DDY>?d0Jk>h&d zn+X)qaVF;Z9Bw%82$YK635K_1>B_RZ3EZ>@P zIjtDL`gS#I&VZL=g>aV)7r!ECVpRp6WI#mTV3o%uSRmk-HS0HmuDWD`v*MZ}3#?{j zLt+O77Tv<^`f3T>u}M6V-dHlFkBID4 za)U}H#0tzFtpgJXGt2zCLE+EB0Bq}*eHy|m1Zivk) zX72sq3dI?UwC-R3hdzN`!7ESn<`U@j|FrPq2{BoNwS@WUysnE(0tqWZ)NZdMfw9jE zjSMy2Ag{+eE2fDoW*;sU9cx%5n00aS$F+kiiHY(*)Vc&{uW5hKS=szMu>$Zsd^wfw GeC&Uqkew+2 literal 0 HcmV?d00001 diff --git a/icons/left_arrows.png b/icons/left_arrows.png new file mode 100644 index 0000000000000000000000000000000000000000..7e782f69c4bb1b188b3f6fd25884088ac58baf41 GIT binary patch literal 2691 zcmX9=dpK0<7oMh#JyY1TmFtu}Gh2#uQy5*?W-v1eLn<97Q>1Zi z(IG04TXZUtj_H_6qEeDNr8yxfN#|E8e$#XQ_||%!?~m_U-@D#-y(`_<+f$!xP9_it z`dkj%Pn*?052&k+7q``v5C~)^E*sl?VAi|h%-Cm6kZVO1I)YZBu~vyT+#0yqDA>B? zSHtzC5quRfbLK8o$|25qY4>+`p55Eib7l~Nh(!d2GRbV{N)VKGi9k#O5%mHms+b^- zF>rlpAj%RCjO4SR;WUCcUb$EjC?$v=z4ERthFo!P_|}S1ZFU}Lewz?j(6Ku(W%=ES zmZh1k(+_$9SohgEgk*F!!b!9{u$7yfZ|vij!uz_&J^03*l;;Ka>P-N%wKsY$8O_=( z2_wbCH+9?I=~)@!@Rt|~(exx3TQIu@HzwWPRIc*z%?QV-iWW(i+GT^gUiT*K)6|_a#NKsY zixN>|BUrf8{$HEi9FIZePTZQKzJ7Q^^LS zUlwW{HU8h>6~J6^n(y1{E*ohI-0dvX^if#&0{n%eqTTTrIC%F`WaCod=3@SjE=qZd zL6Ao(b(yJ)4u<=1rk;tYUxgCnYoBp?7BzV#gR`SG@C?IqOH1@kibkJxbS4||G<-xU zmo6RqOGaIbm^o5V#sIZc!O-h{T?7g{&t7Gy`5UZ2zmnw5IcmyryQ>s2%ILHtFgvrJ znc@XAZa9giAPdM_#O8hRhu_2whFO-P&tJ|e~q(hGy;0Tr5Oy4iA z6@H&Stcf?YOmub+tsM`94$a-}MCntP1|Qv%owy z4IJ~{iy+m4N;^c@cObte8>vW-o?^-Mvpj;0=a=(!10=_(0d*(Wxu`a! z*AX&?GhWR-(K0eFMmjWM2L>ml#sCw(Z}3~N8!!SJ3FA*(extj-T>HpC^5d~oi4nlO zw%+6<^^30<@ys3C_T-qk2c%%#=;dZw(X=Z3oKrC?VTkS<2yN|~>J2MMRqrfa6Y-n| zD5^S4LZ(Zu2u;&BZ9f8Hn$Vg1MK?}U{LGqvp8^316uK$R9lk!ej&YkO@1=dp- z#_d-TH$_Rrc98C}=ZBK}<9deLqfLy?!uQmy%0hl)D>PNr3N}*r1v2G9?qZZD8gv&p zVe`Di=#8tfTg7=vL>9R$H#3iY)F=lf;}aeNEt3+jqCI_iOmME9>@391BU%--jf<-V zt#|vU-W$8@CpA7TT%s*lc86kn+i?HDP;V$1TYa6RT*p0xQb7A<55cnAW3KRmMHMun zKdEd$!CvC)M)I9$63c%(n7NV>dmjqdI!qW_$-%3@Er z*zOija1fhks?^RpJ0jTNhbjMSaOkm+d<~X368&QL{FZgGK5*xSstmAUVSe4Ph>;?VXF`1Ji7V~PTkEXrazf@h0r+EG~?Jk6y4LmM)C;SXUtFq6FIjimHf&t|X4OIE&dR z#HIh9&<|)e@P|TWF0%ZiT4nRt69>Ljp;Nwkd*|h|PcMz$=Xu11h$$cxd>C5+aCa2$ z#*`tAo2VEhU5%vAR(Y@xng1?a^`F{S(l~?QC510IcnU;gb+u>j-z2|cPu?2lQ$_CuF9$KM1haL4^tVK z((-VL%F#?~2M_K0lSq4xByF_B(oka8UJP2U?26UclnIe^`>lJOfO^<=nmf$L#kQfLcdGva_FX#53;5RccJTWdVDU>5L$$5qWv8=!ikWv#SN4}bc zjqTKSv!Lw8^7_fos^(vYu0Wbo{7-AxN)t0w2?8H*wGP`V}O7e1W6-OmX?g(Xus4)j5*ZRT6qv@!=y2W43D!B#yTvjf9cwW5wPM6JL0%m+8+_*{n8yN9qhMBF4>WICs+lNYo;#m$nIv zA|dJm-C9~5BNbVTM7aqz60pmw59o^)%mSXy`q++a32aTAFD59`}_rvLx| literal 0 HcmV?d00001 diff --git a/icons/minimize_icon.png b/icons/minimize_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5c8155ece0de881752c4c56b273b40d859550798 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|&H|6fVg?3oVGw3ym^DWND9BhG zu%t>B( zB;(#n53lPck4v>ON|H*{r7NTOmmVll^q0Gwm5vAW!M-Q9X~k4iB=;+1wSPoFTsi=J)@2qT^87 z;T-CP@J2G2+m-jt_FV63+VnWO@%jZwMhOeB3=Lr`TWeu}6-#{I|p`80GV~vOeBD-(q8i?jBCWlMYXJ5Mb?Jy$mblNR?>&(7t zOj^;m*=TNVmD@oMFNi$DiLyxV*2c1E%d!$3kOj=2>mW4;w^{`I=+^LQ2kAa5&4Fi!Qwz%V#RX%zr zFelQnqNXAPTIwqzjjcL9@KkphiLh~K!2vg-5a+kCxR&*7_4!UzN>P112r#Txkbuz6 z2#1nVT}h!gCu~&;4;7syU|P1q8<5$+q=rEh)moZ z({8vKd}^nd@W?_*M%2gmpya#|l~7)tw`ME7!w4l?)zSr_EjWZBxyedr;#R7Vyd`w_ zGfrPQo?^p14eMWyanc6rq*k-WSO$NTWFt$$#bid5ahV%&@4P~3_ust{j?ymF^EB)p z*3rF%5OB`sts%1pdhF-mt6@1jH_X6D7tu{(dM3jq%Dd?<1S?udvO4FV6cM7Rx;_HV z2|YKv71-la{?Q7REQ(A$0!pZRJ=|eg$8f>}*Pw5{nRQ<8pi52Vr} ziptThGqck@U{%M^u6-}+(XQ;tN0FdZbn*5q6H5rTHJGt0W>wb;V4S(_nFwoA4L~xX z)n5>5O)eaRF#1J>H3!!7e;Wnw@%*0P2-LlKbaYwNYM24|nlq6l!mA2BY_GRdhYV`X zOp^)8FJ(gFOMA7Oh_s1+RLFsi0)DX2u4~$0mpuBBLJs#X%KUL=e`4?ROeip7agQAS zd@2R{Z0Kh~#V)^cGDm}(JgfMaQ#&X*ec*9BD$!YoKN)y#v5$}4`e_;Mjo~c+x^d#F_5u*0Y$hr8Jq#y#4io0HJ>amZkRf*Wl@-BU$*s+8lUr zAq}}(j(svT$Lu8lwtJoLW*A#)fGZE3t1=+( zRl6C9+T*0(~HZuJzG+J9#nNc?72JDf4j}fZZ?|QO? zhHO)?xMFqZ_03*WmjAt&ihqqe5{D!G^P!Sn*(qLy>XYT=$7}qDqQ3R|`T@}ieAMLH zqW}fpDBuhW>;lGTAywtWKi`A^BiQ!4ZmgsgA92X!R5I6OK!@d2040qVZP>5i5A|L? z7F(M!XUuIk1z$}&;n2=I+0=^_cQy7!AK5;%;zwhmn7!@2n8>c#dBs>TVUkp35rfD> zw+zuZO78vJ9;!p>kvAy7o-KOnNZ{tj6-9t`rmcNp$l;H>eOK2unXr}A-(n{^cb5~T z)VEzO5W45WyeVRks){HFC6%EnAI`zjC!mC}TDpbyV*N6F0X3l*5WJ6VZ%Bhu%X6$+ zTQ$fxs#Ev*@J#Tm^EcEgVsMghW0(ne zNuH*1h(oVgEWT4xKi8FL?&6Cfs~usWp0~GR(Dl}h*KDv~9}rx;p(+V)Fip8SI@>~@ zGRm&2HCK5x>I)`D{w=)tG&sI*Y)SUiTAs@70kK2Nqi5GDAFxW|uJ%uQb!0y7xWH6k znz(+uD{*eVP~ExNbRd5)O*UB%RuxcBai>2y-p~He*jAP7^S~e-lB`m*!N)cSEdLk6 zR!7F&dayQsA42S?ef-xIqhIyctCT{f*D?u11jv2L1nCs%ff8-LW7x$<_UDIklT5VCGt#@#E4Wy2H6&k6nz z)2_cY8Jd=*iFbY5i86Vsw!I=OmL{Pf$;r7F6HXllB$ViN^<*PYE@Ds286qEpCQYpFnG z@OHWF844ajQ`R4#f#LN{+~CDjpbbEey#Uxs$q8&vDE@z_(ubh{ literal 0 HcmV?d00001 diff --git a/icons/solid_down_arrows.png b/icons/solid_down_arrows.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc740f51d52cade8864834ae7efb0ed5bc1e221 GIT binary patch literal 2790 zcmZ`*c|4SB8-BfB8iW~xBz+BrP?CL1&9OB@jO_bPmTaj+(Y)D`JqcNg)Z{dFO(+s$ ztB^I65K)sQb%-4Kp3eFE{4w*)@BTf{b6xlK+;d-dl7;zkeqM222!i-2WJ61^cK!Zf zxWRY1eMJ!j@#a$u|FFKmu~5jxl+eT)Eix1Xd1A*dVpQ{Xk6?X$6ZGQni3N7dj-}m# zXMbQCOTS2O$(Bvsblo1Q?pra_UFYP$Fo-mgZOJqTa(;nCV89rLB#kuEGL1v9m_P^) z#1MtM#4C?nq2XZ+i&BiK-N}JiCg{2rVo3yZUZY$e3?W11-3mL7Mn$zy)7)P?1njmt zF*XjtlJ)Cz*QgM>UMabo2vCGt+v_gF81Lc)XZ<^-5PH@>Q~>1y0S-(K_LC5a;38gN z;EqDD1Jo*mG=d9qjK{+ny5OjUXlllH9Eq@DKhrJAfqba)iz`Tn;OPw`j z;z@*5C1}76aAnN54R-JlGCFpVy8qH!Hzxe=Ps5aUkfZ0PumwPE#X`pOpfQ9B8>D*) zBG{{m3b+{yxW7X>!%t4xS{i8MWz#J$&VdN=%OnH<<^c=^Z6j$Ksic@?k%|NhlT#h8 z;}K(!t@Lx{a()<7nyQoakqlHIYTbYI2ZYw0JXIq>Av`1*cXX*?gu&5k#hukVfcgg6 zSF6PU!cM(3J)r)-%!Z^gJfSdWO%RWW?hZBu7n#ydROW-6pEP?vmf=8ZWJvelm_g`i zh7zZvEREz?akL?XIAu=AQF3c`D4fe`>Dgm9%i^xbcl8$P-rm&~%T{wk1QNy@u8kLx zQYuK3^ckabw*=u`20fPy-zE**BkBMBn7tm`I5}cGYb*UQ`{n5gUc->meMavTFSNfm zd7?h_!lyZy%d?K_7uR0F2ZQ%1Q=|v|YIncjvI9~37=@NC9ajswS#nFfMZUk0`eE@O zFwe#~edNbG)MbB*t!(o7#zx9)J@qMnvoE8I|o-U$Xw4k`>xVFnd+X`zJ+eooT^y1 zk;gu~%DYBOn;-J`=PBT03`WU&H#wq6(OTJRkk&l*HmzE{V?~k5@*IZy^A~r8RW#CG zRC)tu%7=CvSWepTvAQTkr7!i=LeZR-<`#{L;UtHLu7nP!_)8X@wd^`Aq?h}C7{jGj zr724m5vqDiAJLpHXuAM+ytYq!Jmu%B3it0>wi~{e*Q)Twx@T=~hf4MqWh}^;VC*6l zY1G-ot_wrfxD#f4bqj`XkQhf5z8dJ%?8C*OzxM`DsZF-!Sw6m??C&A%LPvEN-A zbC<3a;C;f3*YKH93|?tX`pWCtvF5hiRSDO=Dp@Yik6C2b;jna_AU#wM9sYST`)H6Y z!ImoW%VTffgTb44ExefGfoz}t>)@!?8)3eRV^EVazCZQ73E$a2l1ENLCR^7m1?d+qCbr%irg zW_c3=k1bfeY;LNY@LOP=(d16O*QDcu40zntoL_D{{Y{B=sVFE->?ZO6+p!Vhe$_2N;6vCrfOw~83&9{kJx zGv%_yxk-c!5w%t0ErWDNdoAX2m2>u?piIyhhyx8b$)}H<37NMIqW@6n)C@3H@I8w0 z<}_2h)2TLF8WptY-Br$fS~6!%31jwY-n1nt7Jl?yD?&&ayTP zsWCdY>if%dkp2`3n)FasI592;`pC{ly%I^YiY^`F>0Q=!zMBHcinpGN@=Io5Y;t}p zbMQ);y=+>1uIE_bhya{q7qvO`Q?00>|K>sNWS=??S=aqEQbp}3`$WjXGaq+ezQMJ< zqRFkrL7?@YHmy>1lZtf8i73knh=><6*{JMQf|Hyr6<_c7`Su`c>Ac#y>vYarFVK0F zhcx;CWvYf?vPSErygol}<+Irk-0(t@O>OGl&U{|LH?p>O&MMen88q%(HC-n0dc@gC zdKCNosju1N)T}iexwR&6`l!tJ<3m=Dq@N#G*z9?0MEIOTiw=k`Xh|JYd&_e|P|aZ_ z6uDksAJw*fV3)jT&7$rr=Bq(VU^-=8=NC+f%j|CgtM-d&?y5KS78pQuTUt`I6W>q& zv6Sk4)zOJ#*R>r3U<^@xI&yQ(?Z73q%Ym=6d(`*D$|_$t!hxt1O-$weRQz^ZHmLTW z?HPF*j-L_WSF zh;|>=QPMtuCn(dQ1`0&YDN1PB07CyNX65>VXZ1Cueu&1@5i^b+5#vO)L3Il3*oDmn z6*dxHpI^;%1eZA;Fm*i%DoajG+42B*n$;IVZYKe@ zTucGA5Q6if;;&Z9fgFX8_0r8D^hA?B%kBmQ%U^rO2!9#^^3+iEG;9M|wYBeG?Ln|z zzQ!qjxe)AMzh2s$2VU>hRa_g7wJIlSZDk@v0j6Gh=UPtyKnyUUNXdg$*t*(*A0U{? zJ5h)sMHr$|$Wv|59128ILcfFAATn~MXE&I0818ZTIbhCtxhKR*wu-2M)F8Z%35UT> zU!s=2I}dR8{|I1B391(*BrQY$ij6i!Rs)PA`EqN20`dP;*x&tc-L$*fAQG5Th4;jn z6bKHpJ>1#@w4eQ$s(A{`Hnmr9!LFd9|KawU3CJafeg}oMkvG$i1LPjq$KARO$n{C2 zr(p=#4d#k){PKjqM<`7zKl4_c5W(TLxoRYW9;faduaZ1s)*J+X>Lgkp8 zo{SEJ^x%2qtP)G_LTMo#Fi)un@2}7E$9?}kpZos(uJ85xUf1=#F0rc%OLe`@dI*A4 zIc$bISi65durhepcuNW)Xni<`LF2|EJ{AOB(Pe2cyZl`i(ZRvx+Y|Hi{Z;*ggUP|j)lTG9)UScwEVZajBVf;=nEx8m}6| zw4%3_h7sTmmsfy(!^OGu>JB2Y6G z?Z3iKh63D>VBQe5q=IAHs#5HZ>VPfEiTYcg9)M^|A&DzabIK~X)b>*1WGukaJI*Ar zA=*Z*3){NDQH8f<77YpW>W*1f^yj#O4H`a6+eMH_!;PthNrOa*29B-vXzC?{-fjar zhI+|0&~fJeLKKcYc?7!h$_1jyzMs0!N5bykHwz~3t5YvUQ$}m9Ln7{~61(Z_h5*CV zBr{@HATYcoPPu^!(JZ*)y~Y5!=c#3#5vj`+$S0 zFCM!cGUq(z8sqB3kmv-RSYJVToP&=sS4VpTN}dP2cvHJ<)N`VK?0#eKCJ#XD)SaT= z`)0eNl1XkvVWmi0-jX{-K0sDs5rmai=)l;?vCI!x@tv3Rtkb2tkTB_x{ga^pu)mq) zhC!gVJbZEH!{B!rXV%@=Y^wk#-@Jt2EHua@^44D6=Ww^ZZ`D2^&7Mc1K~elPjIyc=Br6=Xaq{()gLdF(m&y2 zOx2S>AWD*cSr(??#bo{`o5gD_m#_$w%E7Gh%n#W+$A*qufV9c@obWy_Gy3w&9F-e@ z_p=jvSMcH05pUN&kuAgU8*E5G%I5d)bvXPxB>z-M)M5U7RVVZ|zRCHt-F(a;y&a}-OWp7Rh@@C{ItuxIJrBouXrOYYg;%P^QPdVIDbxZOZU4{zaIfO4}#0oR^hcl{Ok)K(%S*<8OU%|+P`=YAHgDo+S1)v!d-N0 z8(unft~=`9?bECxf{4K?91x@&H~zi;+TjYPjW_{rT^l#@`xefENOmjjxy^z*CNPj| zZyTHAIi1iKl}#FC0nIqdT~)v}uU0mzj+m+Is^GN(~><|@zdqazOcJr zkM8ClrIKbt>7k&~i;(DZd*x^O7nzmBQ_hyoD^V{goBl=6Z&g1K>)o%jDXS9HK7jwr|$~t1s&K&da?F9H?se#Otsl9&5Ag zdU`$#6dd`d>To^T-A_^=ucfXGozw>n;lG*RyPoW7RX1WLu*{rq4O7v&Iq}Y*GO8zg z89-XqFwtElVS|#0d|46p)pWfINGsGlvURCx$(F;jDRIIZ2yo)IOMOsJ&l!7<`n{?u%#P5 zg5b8ie}$XZ9tlV(V7C;t4!fE-rgvqFoj|@{{j;UYe=IY!ijp zWYNjbdYBIVg<7dJL{07=REh>&r{dB>L#cu7*q|c;B=Ca6=*^$uPsndEkL$ibjTXIC zt_-5QdN)3kCEprU>WYM))zrHo?W zI{>PFo0xughwMJ!Wo%|Fz*(N3%ivCjOn-}*+AbLe%Dda^$C-E=M;_oF=B-1Z{?|6) zXB1O*wPJK@>`gA<-YTj#`oeG01k?KNTfdWqGNrw~ywO)!{qQR;pt<6^MxSlyUotjy zt;bz1k$=*GaqL---~d}Jf!#U*oHuH(u&%D1M^6eSL$~oBLR-1sK8zy zOabj+B0aX02Re6z!e{9efV_@5z;%LXW(10(J{S>_E#HV0DjI()i!=^8XsLneBC*(^ z=n*JNogU2ueGZx(`g1%TG*-JTbA z800~&^^HlVV*fDs8Ie`4B>LHGZJ}(*C z6`KMK?p~kta1iKgrYoBE5G~Ej&;z zonTnIEh3IJg62X9XIe6g!)WQ6=Vv+r8;X3((yd^Qsq}WT0@IoJ$#sk>hKja(#H&^S v4slbFzRJQi1O%4u#gD()r-i0Bd9~J!{cdwl0 literal 0 HcmV?d00001 diff --git a/icons/sueccess_icon.png b/icons/sueccess_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4dda99f9762fba5f01c070e6c5e25728b677b5ce GIT binary patch literal 546 zcmV+-0^R+IP)( z!axwmcUQt#$OB002v{>2ob`Y~@_Nvk93eXSC3rgyxrs)^vxI)S$OYiBq!(?gL znkU|;7+@SEJ4?!)AD9G~f~}$g&76fMHl;~cGzB}#fmud=KNvI>$Zbc$I5BW|2i5}Y z8>g12Yn>62(~T^(G56&&+6rT;>uXiI4bw`HZ8xon$m>uYwl43|;c_4b0yjp`I9PSXy5dyk9dcjZhKNp30oTrZx-zh4>L%8Ru!nqhzqiy%-Q7-7lOR; z5P8G}nNx_FBs5;Pr4@iliJGaNoWG|~M9-5@oS9KXl<7e04`Ul)M$Zq*6j6w-W0kH) z+L@-IuuKhYQiiWwL3$NlmLi_tC=`273rM0BE~uiL25B3FjWRpWBU`VhqV=|oZPSKb z(=9Yz9E)*;-2?4w!@BdpF%b=qy!huXEp{fO-_wD4V1 literal 0 HcmV?d00001 diff --git a/icons/up_arrows.png b/icons/up_arrows.png new file mode 100644 index 0000000000000000000000000000000000000000..7250437607e98bcc64ae715577e59ece0e96afca GIT binary patch literal 2791 zcmZ{m`#)6qAIE1n>WmmOqb+5!XT}+~m2%lMOKXl9%#2(bmngYTvPc=aNEdSyqXuJg zS+}I9EpqGPR@g{GiA0IUYzlp0Lf5iiVO0!naNg`IE^R%_<09L^$=75NcL#T@~X^)IkpqT*W zNj9cqZB#Zp(%oDG6dbqwmx$nyG|>2@nF|i#=TttUVI4?eLP{kFam^dfUU8UnBnhX0 zQ1xwqr211Y+0--zI%$eiI$;WKAAIb(eV>_MrPK-nMODGXi#_qsFK(h`*HxpClDz!*xk>b#xGN-SBtj5#d~Di}7( zfeS*rM#->-fAm8a;s$fCo#9{C!%ryYi9aLS;;WHM6k#65898PFbS~1QHCDIjN8p=9 z=h7VC_C+c_S~qNlGk|42tB~+0t)V{IXjwr)!J5Io>dmkwuJMuQexgqvn4ln(&JZ7>VR#y>jt_>d zWn>1{y5Zt#Rb3lq!vk|NEdNv~;>_-Rh=sXp$S@jzhI-)shmqghIE}4-Il|?4#tg|Y z?trV4~ln9v9O;GG7?&69hrhk~Cv2|k#8bKYLPd8N+hy8xzm-(uSk|rm{(nd|h|N1hfJou8l zOX|_<*Z-)dH$bMMSAY#EL8^f@IYXUQ+he>xvMCLQ%-Aj)6WH2d1p@`I=TSv#CWb1h z>r$QN0iS$wl}l_cQuA+M<_`8=(CU$m7Q?~&(*61ctnS{CA=~w)PtX4Z`(z#QY}(ss z$QyLFsf6h1!7m2%rvjqYcSk&l#lmy2&v$q@5w#SJyEo_CmsiFxW92L42T6O6G3qzX z`gk%VKtLJuTwdoOktgj+VrD{$kLy;*RI9Uhd_G44n6sVP@YkTBt?A1F=FrIDAUC<2 z$>eJG5|ldW?G#exEPtbP8n&&56!8Y1(@VKiDXO+GE+QK>dOGgJosz_^M{HDI=!93Z z^p_D)%4=ZigT_joq*9?7VlyV0-+umzPU3%N@SJ`{7(ixDyTu5sG$w2pi@x<_d|fFY zwjOo{=KL;?wx6J?A3D~bI;!POOJN`4eBl`5-BN2HGPj~0vL2c9+pHQm8NcoDPH>^BpQ<3= zi)oBZgKH@c`^rq?84{eo_4L^AvzM!4v5xEOTPckDal^b>k&I~k9>VH0is(MF$*K8?b zm8HJjCJ)EvC?h->{p&z}g#;UuSL;ux&OJYDG;SvbsTO9lDTAkxak^lRZjmh z%Z6xfYimAQ@V~X5R0$A&QJ3q7;tnJ$AX;;%z;T=}zXSB-MyP+Ti7%a8uGePDvmygb zt9zuP))_kQ8l*th70?G#;lLk0Un6gzWpA%|L#6uP5){`Rj^W9*0#ySAqKM8kYJHbU z?F)e%Mr09F(o5?PO_n(e4yWP!!9)HSZ5Z^C^~+ zjsZOf9(m8`QAh3Nd2E7^Px)5bkD!Xn>E9Fm-z8T~+bfdH?d@JYIAJ2AWxP)CK7ycJ zcXRAYx*ea&+N6;o2i_zQkm*8A=J9h{nYDxpF5S%qkssBjsOvYY0^G7@qJeNm;6z#0 z8eiFh=hl{IveEALd?3F;3x)-txXLC?rt# zd)Olgxw8CifT5p5`*5872Gt;beBJ!1VK4jd$Z35Ic~w!dT6>~Or$^-H-sktss<<=m zZ!{|5pL$ERZu&nthZ)&5F}GWawKSfQWR4HBcOQ`tiu~@32W3(!il5k$Kwo+al*dwL zdSQ#)Hcr|?5x)m6*zIZ&vq*S=aFbIRNPra8IXj*s00{>5^CB53&}G+CNsGyU@?;R? zi*^*@(CR0Bxd$g^w86>ZeHUY%K7=ieDSMv?-ST>KL!TvkZrt^yh{BSc`||esJ`Hd%`KUpN4|Xm+^7P)@d29!l z*q-U`!f58zOWT-Vq44%%TS$0D?D)FPVw^+GK;hWAJMc2qk(PD zr-y9CrS&ugap^jw1B4_wr;m0JvDw8pO+W)YU`@#-J;o@_!TT|QH{P8G4H+?jG|Xl8 zv6DSP9yp}NgrCeNvt;ErdxSL@^=#I!yipFWCkf4-Z|ufsCw|%5w5(Dom)`-{F7#z!utT tJBLN$1w0P7un^~4%f@hZ(GO15#PuyMYLgvm`~~}h;y|IF+H>>d=b! z56--K;rxT+oL$G}b#~3`N~W=13N?QfE-ci%c3DZm$LTSBF)y$0el72|AafRv-)!SK zpS?v~!_}6~*^;|E$ZM`dvU$<+23e*KoiFZ_RGQcVu5a~Ic>KgO-1S4^(yh5Xxigk# z?yBwG%enBQ^bR34T`S8?7T=qf^_;Tk>rY|imVA8A_80eGhZ9@=?#%7pT`1FkH(c?u zzim(Yn@B0WcO5ntB{}zKvYy&GS$Ris@($H!?`QIbgqrdmF7P`nkkMYjuzQA6X0hZ| RUZ5WtJYD@<);T3K0RXO1e4zjU literal 0 HcmV?d00001 From 64c7a30bc264e37db1e1294e6dc739a3ad56ef39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Wed, 15 Oct 2025 15:29:01 +0800 Subject: [PATCH 4/7] =?UTF-8?q?1.UI=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/main_window.py | 553 ++++++++++++++++++++++++++++++++++------------ ui/widgets.py | 383 ++++++++++++++++++++++++++++---- 2 files changed, 750 insertions(+), 186 deletions(-) diff --git a/ui/main_window.py b/ui/main_window.py index e5233ddc..0afd939b 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -16,13 +16,336 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea, QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout, QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QDialog, - QSpinBox, QFrame, QRadioButton, QTextEdit, QTabWidget) + QSpinBox, QFrame, QRadioButton, QTextEdit, QTabWidget, QSizePolicy) from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint, QUrl, QRect from direct.showbase.ShowBaseGlobal import aspect2d from panda3d.core import OrthographicLens from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget,CustomAssetsTreeWidget, CustomConsoleDockWidget from ui.icon_manager import get_icon_manager, get_icon + + +class StyledTerrainDialog(QDialog): + """与新建项目对话框风格一致的参数输入对话框""" + + def __init__(self, parent, title, fields, primary_text="确认", secondary_text="取消"): + super().__init__(parent) + self.setWindowTitle(title) + self.setObjectName("styledTerrainDialog") + self.setModal(True) + self.resize(508, 241) + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_TranslucentBackground, True) + + self.dragging = False + self.drag_position = QPoint() + self.icon_manager = get_icon_manager() + self._title_icon_size = QSize(18, 18) + self._icon_close = self.icon_manager.get_icon('close_icon', self._title_icon_size) + self.field_widgets = {} + + self.setStyleSheet(""" + QDialog#styledTerrainDialog { + background-color: transparent; + border: none; + } + QFrame#baseFrame { + background-color: #000000; + border: 1px solid #3E3E42; + border-radius: 5px; + } + QWidget#titleBar { + background-color: transparent; + border-radius: 5px 5px 0px 0px; + min-height: 32px; + max-height: 32px; + } + QLabel#titleLabel { + color: #FFFFFF; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + } + QWidget#controlButtons QPushButton { + background-color: transparent; + border: none; + color: #EBEBEB; + font-size: 14px; + min-width: 18px; + max-width: 18px; + min-height: 18px; + max-height: 18px; + padding: 0px; + border-radius: 3px; + } + QWidget#controlButtons QPushButton:hover { + background-color: #2A2D2E; + color: #FFFFFF; + } + QPushButton#closeButton { + border-radius: 0px 5px 0px 0px; + } + QPushButton#closeButton:hover { + background-color: #2A2D2E; + color: #FFFFFF; + } + QWidget#contentWidget { + background-color: transparent; + border-radius: 0px 0px 5px 5px; + } + QFrame#contentContainer { + background-color: #19191B; + border: 1px solid #2C2F36; + border-radius: 5px; + } + QLabel[role="fieldLabel"] { + color: #EBEBEB; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 12px; + font-weight: 400; + letter-spacing: 0.6px; + } + QLabel[role="hint"] { + color: rgba(235, 235, 235, 0.6); + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 11px; + font-weight: 300; + letter-spacing: 0.55px; + padding: 0px; + } + QDoubleSpinBox, QSpinBox { + background-color: rgba(89, 100, 113, 0.2); + color: #EBEBEB; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; + padding: 0px 10px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 11px; + font-weight: 300; + letter-spacing: 0.55px; + min-height: 30px; + max-height: 30px; + } + QDoubleSpinBox:focus, QSpinBox:focus { + border: 1px solid #3067C0; + background-color: rgba(48, 103, 192, 0.1); + } + QDoubleSpinBox:hover, QSpinBox:hover { + border: 1px solid #3067C0; + background-color: rgba(89, 100, 113, 0.3); + } + QDoubleSpinBox:disabled, QSpinBox:disabled { + background-color: rgba(89, 100, 113, 0.1); + color: rgba(235, 235, 235, 0.4); + border: 1px solid rgba(76, 92, 110, 0.2); + } + QPushButton { + background-color: rgba(89, 98, 118, 0.5); + color: #EBEBEB; + border: none; + border-radius: 2px; + padding: 0px 12px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-width: 90px; + min-height: 30px; + max-height: 30px; + } + QPushButton:hover { + background-color: #3067C0; + color: #FFFFFF; + } + QPushButton:pressed { + background-color: #2556A0; + color: #FFFFFF; + } + QPushButton:disabled { + background-color: rgba(89, 98, 118, 0.3); + color: rgba(235, 235, 235, 0.4); + } + QPushButton#primaryButton { + min-width: 120px; + max-width: 120px; + } + QPushButton#secondaryButton { + min-width: 120px; + max-width: 120px; + } + """) + + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + base_frame = QFrame() + base_frame.setObjectName('baseFrame') + base_frame.setFrameShape(QFrame.NoFrame) + base_frame.setAttribute(Qt.WA_StyledBackground, True) + + base_layout = QVBoxLayout(base_frame) + base_layout.setContentsMargins(0, 0, 0, 0) + base_layout.setSpacing(0) + + self.createTitleBar() + base_layout.addWidget(self.title_bar) + + content_widget = QWidget() + content_widget.setObjectName('contentWidget') + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(15, 10, 15, 10) + content_layout.setSpacing(0) + + content_container = QFrame() + content_container.setObjectName('contentContainer') + content_container.setFrameShape(QFrame.NoFrame) + content_container.setAttribute(Qt.WA_StyledBackground, True) + + container_layout = QVBoxLayout(content_container) + container_layout.setContentsMargins(15, 10, 15, 10) + container_layout.setSpacing(10) + + for field in fields: + row_widget = QWidget() + row_layout = QHBoxLayout(row_widget) + row_layout.setContentsMargins(0, 0, 0, 0) + row_layout.setSpacing(10) + + label = QLabel(field.get("label", "")) + label.setProperty('role', 'fieldLabel') + label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + label.setMinimumWidth(field.get("label_width", 80)) + label.setMaximumWidth(field.get("label_width", 120)) + row_layout.addWidget(label) + + widget_type = field.get("type", "double") + if widget_type == "int": + widget = QSpinBox() + widget.setRange(field.get("min", 0), field.get("max", 1000)) + widget.setSingleStep(field.get("step", 1)) + widget.setValue(field.get("value", field.get("default", 0))) + else: + widget = QDoubleSpinBox() + widget.setRange(field.get("min", 0.0), field.get("max", 1000.0)) + widget.setSingleStep(field.get("step", 0.1)) + widget.setDecimals(field.get("decimals", 2)) + widget.setValue(field.get("value", field.get("default", 0.0))) + + widget.setFixedHeight(30) + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + row_layout.addWidget(widget, 1) + + if field.get("suffix"): + suffix_label = QLabel(field["suffix"]) + suffix_label.setProperty('role', 'fieldLabel') + row_layout.addWidget(suffix_label) + + self.field_widgets[field.get("name", field.get("label", ""))] = widget + container_layout.addWidget(row_widget) + + separator = QFrame() + separator.setFrameShape(QFrame.HLine) + separator.setFrameShadow(QFrame.Plain) + separator.setFixedHeight(1) + separator.setStyleSheet("background-color: #2C2F36; border: none;") + container_layout.addWidget(separator) + + button_row_widget = QWidget() + button_row_layout = QHBoxLayout(button_row_widget) + button_row_layout.setContentsMargins(0, 0, 0, 0) + button_row_layout.setSpacing(10) + button_row_layout.addStretch() + + self.confirmButton = QPushButton(primary_text) + self.confirmButton.setObjectName('primaryButton') + self.confirmButton.setFixedSize(120, 30) + self.confirmButton.clicked.connect(self.accept) + button_row_layout.addWidget(self.confirmButton) + + self.cancelButton = QPushButton(secondary_text) + self.cancelButton.setObjectName('secondaryButton') + self.cancelButton.setFixedSize(120, 30) + self.cancelButton.clicked.connect(self.reject) + button_row_layout.addWidget(self.cancelButton) + + container_layout.addWidget(button_row_widget) + + content_layout.addWidget(content_container, 0, Qt.AlignTop) + base_layout.addWidget(content_widget) + main_layout.addWidget(base_frame) + + def createTitleBar(self): + self.title_bar = QFrame() + self.title_bar.setObjectName("titleBar") + + title_layout = QHBoxLayout(self.title_bar) + title_layout.setContentsMargins(12, 0, 12, 0) + title_layout.setSpacing(0) + + controls = QWidget() + controls.setObjectName("controlButtons") + controls_layout = QHBoxLayout(controls) + controls_layout.setContentsMargins(0, 0, 0, 0) + controls_layout.setSpacing(0) + + self.close_button = QPushButton() + self.close_button.setObjectName("closeButton") + self.close_button.clicked.connect(self.reject) + self.close_button.setFocusPolicy(Qt.NoFocus) + controls_layout.addWidget(self.close_button) + + self._applyTitleBarIcons() + + left_placeholder = QWidget() + left_placeholder.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + title_layout.addWidget(left_placeholder) + + self.title_label = QLabel(self.windowTitle()) + self.title_label.setObjectName("titleLabel") + self.title_label.setAlignment(Qt.AlignCenter) + title_layout.addWidget(self.title_label, 1) + + title_layout.addWidget(controls) + left_placeholder.setFixedWidth(controls.sizeHint().width()) + + def _applyTitleBarIcons(self): + if self._icon_close: + self.close_button.setIcon(self._icon_close) + self.close_button.setIconSize(self._title_icon_size) + self.close_button.setText("") + self.close_button.setToolTip("关闭") + + def setWindowTitle(self, title): + super().setWindowTitle(title) + if hasattr(self, "title_label"): + self.title_label.setText(title) + + def mousePressEvent(self, event): + if event.button() == Qt.LeftButton and hasattr(self, "title_bar"): + if self.title_bar.geometry().contains(event.pos()): + self.dragging = True + self.drag_position = event.globalPos() - self.frameGeometry().topLeft() + event.accept() + super().mousePressEvent(event) + + def mouseMoveEvent(self, event): + if event.buttons() == Qt.LeftButton and self.dragging: + self.move(event.globalPos() - self.drag_position) + event.accept() + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + if event.button() == Qt.LeftButton: + self.dragging = False + super().mouseReleaseEvent(event) + + def get_value(self, name): + widget = self.field_widgets.get(name) + if isinstance(widget, (QDoubleSpinBox, QSpinBox)): + return widget.value() + return None try: from PyQt5.QtWebEngineWidgets import QWebEngineView WEB_ENGINE_AVAILABLE = True @@ -4194,170 +4517,108 @@ class MainWindow(QMainWindow): def onCreateFlatTerrain(self): """创建平面地形""" - dialog = QDialog(self) - dialog.setWindowTitle("创建平面地形") - dialog.setModal(True) - dialog.resize(300,200) - # 设置对话框样式 - dialog.setStyleSheet(""" - QDialog { - background-color: #252538; - color: #e0e0ff; - } - QLabel { - color: #e0e0ff; - font-weight: 500; - } - QPushButton { - background-color: #8b5cf6; - color: white; - border: none; - padding: 6px 12px; - border-radius: 4px; - font-weight: 500; - min-width: 80px; - } - QPushButton:hover { - background-color: #7c3aed; - } - QPushButton:pressed { - background-color: #6d28d9; - } - QPushButton:disabled { - background-color: #4c4c6e; - color: #8888aa; - } - QDoubleSpinBox, QSpinBox { - background-color: #2d2d44; - color: #e0e0ff; - border: 1px solid #3a3a4a; - border-radius: 4px; - padding: 4px; - } - """) + fields = [ + { + "name": "width", + "label": "宽度:", + "type": "double", + "min": 0.0, + "max": 10000.0, + "step": 0.1, + "decimals": 2, + "value": 0.30, + "label_width": 60 + }, + { + "name": "height", + "label": "高度:", + "type": "double", + "min": 0.0, + "max": 10000.0, + "step": 0.1, + "decimals": 2, + "value": 0.30, + "label_width": 60 + }, + { + "name": "resolution", + "label": "分辨率:", + "type": "int", + "min": 16, + "max": 2048, + "step": 16, + "value": 256, + "label_width": 60 + }, + ] + dialog = StyledTerrainDialog(self, "创建平面地形", fields, primary_text="创建", secondary_text="取消") - layout = QVBoxLayout(dialog) - - width_layout = QHBoxLayout() - width_layout.addWidget(QLabel("宽度:")) - width_spin = QDoubleSpinBox() - width_spin.setRange(0,10000) - width_spin.setValue(0.3) - width_layout.addWidget(width_spin) - layout.addLayout(width_layout) - - # 高度 - height_layout = QHBoxLayout() - height_layout.addWidget(QLabel("高度:")) - height_spin = QDoubleSpinBox() - height_spin.setRange(0, 10000) - height_spin.setValue(0.3) - height_layout.addWidget(height_spin) - layout.addLayout(height_layout) - - # 分辨率 - resolution_layout = QHBoxLayout() - resolution_layout.addWidget(QLabel("分辨率:")) - resolution_spin = QSpinBox() - resolution_spin.setRange(16, 2048) - resolution_spin.setValue(256) - resolution_spin.setSingleStep(16) - resolution_layout.addWidget(resolution_spin) - layout.addLayout(resolution_layout) - - # 按钮 - button_layout = QHBoxLayout() - ok_button = QPushButton("创建") - cancel_button = QPushButton("取消") - button_layout.addWidget(ok_button) - button_layout.addWidget(cancel_button) - layout.addLayout(button_layout) - - # 连接信号 - ok_button.clicked.connect(dialog.accept) - cancel_button.clicked.connect(dialog.reject) - - # 显示对话框 if dialog.exec_() == QDialog.Accepted: - width = width_spin.value() - height = height_spin.value() - resolution = resolution_spin.value() + width = dialog.get_value("width") + height = dialog.get_value("height") + resolution = int(dialog.get_value("resolution")) - # 调用世界对象创建地形 terrain_info = self.world.createFlatTerrain((width, height), resolution) if terrain_info: QMessageBox.information(self, "成功", "平面地形创建成功!") else: - QMessageBox.warning(self, "错误", "平面地形创建失败!") + QMessageBox.warning(self, "警告", "平面地形创建失败!") def onCreateHeightmapTerrain(self): """从高度图创建地形""" - dialog = self.createStyledFileDialog( + file_dialog = self.createStyledFileDialog( self, "选择高度图文件", "", "图像文件 (*.png *.jpg *.jpeg *.bmp *.tga);;所有文件 (*)" ) - if dialog.exec_() == QDialog.Accepted: - file_path = dialog.selectedFiles()[0] + if file_dialog.exec_() == QDialog.Accepted: + file_path = file_dialog.selectedFiles()[0] if file_path: - #创建对话框获取地形参数 - dialog = QDialog(self) - dialog.setWindowTitle("设置地形参数") - dialog.setModal(True) - dialog.resize(300,250) + fields = [ + { + "name": "x_scale", + "label": "X缩放:", + "type": "double", + "min": 0.1, + "max": 1000.0, + "step": 0.1, + "decimals": 2, + "value": 0.30, + "label_width": 70 + }, + { + "name": "y_scale", + "label": "Y缩放:", + "type": "double", + "min": 0.1, + "max": 1000.0, + "step": 0.1, + "decimals": 2, + "value": 0.30, + "label_width": 70 + }, + { + "name": "z_scale", + "label": "Z缩放:", + "type": "double", + "min": 0.1, + "max": 1000.0, + "step": 1.0, + "decimals": 2, + "value": 50.0, + "label_width": 70 + }, + ] - layout = QVBoxLayout(dialog) + params_dialog = StyledTerrainDialog(self, "设置地形参数", fields, primary_text="创建", secondary_text="取消") - x_scale_layout = QHBoxLayout() - x_scale_layout.addWidget(QLabel("X缩放:")) - x_scale_spin = QDoubleSpinBox() - x_scale_spin.setRange(0.1,1000) - x_scale_spin.setValue(0.3) - x_scale_spin.setSingleStep(10) - x_scale_layout.addWidget(x_scale_spin) - layout.addLayout(x_scale_layout) + if params_dialog.exec_() == QDialog.Accepted: + x_scale = params_dialog.get_value("x_scale") + y_scale = params_dialog.get_value("y_scale") + z_scale = params_dialog.get_value("z_scale") - # Y缩放 - y_scale_layout = QHBoxLayout() - y_scale_layout.addWidget(QLabel("Y缩放:")) - y_scale_spin = QDoubleSpinBox() - y_scale_spin.setRange(0.1, 1000) - y_scale_spin.setValue(0.3) - y_scale_spin.setSingleStep(10) - y_scale_layout.addWidget(y_scale_spin) - layout.addLayout(y_scale_layout) - - # Z缩放 - z_scale_layout = QHBoxLayout() - z_scale_layout.addWidget(QLabel("Z缩放:")) - z_scale_spin = QDoubleSpinBox() - z_scale_spin.setRange(0.1, 1000) - z_scale_spin.setValue(50) - z_scale_spin.setSingleStep(5) - z_scale_layout.addWidget(z_scale_spin) - layout.addLayout(z_scale_layout) - - # 按钮 - button_layout = QHBoxLayout() - ok_button = QPushButton("创建") - cancel_button = QPushButton("取消") - button_layout.addWidget(ok_button) - button_layout.addWidget(cancel_button) - layout.addLayout(button_layout) - - # 连接信号 - ok_button.clicked.connect(dialog.accept) - cancel_button.clicked.connect(dialog.reject) - - # 显示对话框 - if dialog.exec_() == QDialog.Accepted: - x_scale = x_scale_spin.value() - y_scale = y_scale_spin.value() - z_scale = z_scale_spin.value() - - # 调用世界对象创建地形 terrain_info = self.world.createTerrainFromHeightMap( file_path, (x_scale, y_scale, z_scale) @@ -4365,7 +4626,7 @@ class MainWindow(QMainWindow): if terrain_info: QMessageBox.information(self, "成功", "高度图地形创建成功!") else: - QMessageBox.warning(self, "错误", "高度图地形创建失败!") + QMessageBox.warning(self, "警告", "高度图地形创建失败!") def onOpenAssemblyDisassemblyConfig(self): """打开拆装配置界面""" diff --git a/ui/widgets.py b/ui/widgets.py index 842baec9..a9692cc8 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -60,10 +60,15 @@ class NewProjectDialog(QDialog): } QWidget#titleBar { background-color: transparent; + border: none; border-radius: 5px 5px 0px 0px; min-height: 32px; max-height: 32px; } + QWidget#titleBar QWidget { + background-color: transparent; + border: none; + } QLabel#titleLabel { color: #FFFFFF; font-family: 'Inter', 'Microsoft YaHei', sans-serif; @@ -231,7 +236,7 @@ class NewProjectDialog(QDialog): content_widget = QWidget() content_widget.setObjectName('contentWidget') content_layout = QVBoxLayout(content_widget) - content_layout.setContentsMargins(15, 10, 15, 10) + content_layout.setContentsMargins(10, 10, 10, 10) content_layout.setSpacing(0) content_container = QFrame() @@ -769,57 +774,76 @@ class CustomAssetsTreeWidget(QTreeWidget): self._restoreExpandedState(expanded_paths) def createNewFolder(self, parent_item): - """创建新文件夹""" + """新建文件夹""" import os - + parent_path = parent_item.data(0, Qt.UserRole) - folder_name, ok = StyledMessageBox.getText(self, "新建文件夹", "文件夹名称:") - - if ok and folder_name: - new_folder_path = os.path.join(parent_path, folder_name) - try: - os.makedirs(new_folder_path, exist_ok=True) - self._refreshWithStatePreservation() - print(f"创建文件夹: {new_folder_path}") - except OSError as e: - print(f"创建文件夹失败: {e}") + dialog = StyledTextInputDialog(self, "新建文件夹", "文件夹名称", "请输入文件夹名称") + if dialog.exec_() != QDialog.Accepted: + return + + folder_name = dialog.text() + if not folder_name: + return + + new_folder_path = os.path.join(parent_path, folder_name) + try: + os.makedirs(new_folder_path, exist_ok=True) + self._refreshWithStatePreservation() + print(f"新建文件夹: {new_folder_path}") + except OSError as e: + print(f"新建文件夹失败: {e}") + def createNewFile(self, parent_item): - """创建新文件""" + """新建文件""" import os - + parent_path = parent_item.data(0, Qt.UserRole) - file_name, ok = StyledMessageBox.getText(self, "新建文件", "文件名称:") - - if ok and file_name: - new_file_path = os.path.join(parent_path, file_name) - try: - with open(new_file_path, 'w', encoding='utf-8') as f: - f.write("") - self._refreshWithStatePreservation() - print(f"创建文件: {new_file_path}") - except OSError as e: - print(f"创建文件失败: {e}") + dialog = StyledTextInputDialog(self, "新建文件", "文件名称", "请输入文件名称") + if dialog.exec_() != QDialog.Accepted: + return + + file_name = dialog.text() + if not file_name: + return + + new_file_path = os.path.join(parent_path, file_name) + try: + with open(new_file_path, "w", encoding="utf-8") as f: + f.write("") + self._refreshWithStatePreservation() + print(f"新建文件: {new_file_path}") + except OSError as e: + print(f"新建文件失败: {e}") + def renameItem(self, item): """重命名文件或文件夹""" import os - + old_path = item.data(0, Qt.UserRole) old_name = os.path.basename(old_path) - - new_name, ok = StyledMessageBox.getText(self, "重命名", "新名称:", text=old_name) - - if ok and new_name and new_name != old_name: - parent_dir = os.path.dirname(old_path) - new_path = os.path.join(parent_dir, new_name) - - try: - os.rename(old_path, new_path) - self._refreshWithStatePreservation() - print(f"重命名: {old_path} -> {new_path}") - except OSError as e: - print(f"重命名失败: {e}") + + dialog = StyledTextInputDialog(self, "重命名", "新名称", "请输入新名称", old_name) + if dialog.exec_() != QDialog.Accepted: + return + + new_name = dialog.text() + if not new_name or new_name == old_name: + return + + parent_dir = os.path.dirname(old_path) + new_path = os.path.join(parent_dir, new_name) + + try: + os.rename(old_path, new_path) + item.setText(0, new_name) + item.setData(0, Qt.UserRole, new_path) + self._refreshWithStatePreservation() + except OSError as e: + print(f"重命名失败: {e}") + def deleteItem(self, item): """删除文件或文件夹""" @@ -2081,6 +2105,285 @@ class StyledMessageBox: ok = dialog.exec_() return dialog.textValue(), ok +class StyledTextInputDialog(QDialog): + """与新建项目样式一致的文本输入对话框""" + + def __init__(self, parent, title, label_text="", placeholder="", default_text=""): + super().__init__(parent) + self.setWindowTitle(title) + self.setObjectName("styledTextInputDialog") + self.setModal(True) + self.resize(420, 186) + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_TranslucentBackground, True) + + self.dragging = False + self.drag_position = QPoint() + self.icon_manager = get_icon_manager() + self._title_icon_size = QSize(18, 18) + self._icon_close = self.icon_manager.get_icon('close_icon', self._title_icon_size) + + self.setStyleSheet(""" + QDialog#styledTextInputDialog { + background-color: transparent; + border: none; + } + QFrame#baseFrame { + background-color: #000000; + border: 1px solid #3E3E42; + border-radius: 5px; + } + QWidget#titleBar { + background-color: transparent; + border: none; + border-radius: 5px 5px 0px 0px; + min-height: 32px; + max-height: 32px; + } + QWidget#titleBar QWidget { + background-color: transparent; + border: none; + } + QLabel#titleLabel { + color: #FFFFFF; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + } + QWidget#controlButtons QPushButton { + background-color: transparent; + border: none; + color: #EBEBEB; + font-size: 14px; + min-width: 18px; + max-width: 18px; + min-height: 18px; + max-height: 18px; + padding: 0px; + border-radius: 3px; + } + QWidget#controlButtons QPushButton:hover { + background-color: #2A2D2E; + color: #FFFFFF; + } + QPushButton#closeButton { + border-radius: 0px 5px 0px 0px; + } + QPushButton#closeButton:hover { + background-color: #2A2D2E; + color: #FFFFFF; + } + QWidget#contentWidget { + background-color: transparent; + border-radius: 0px 0px 5px 5px; + } + QFrame#contentContainer { + background-color: #19191B; + border: 1px solid #2C2F36; + border-radius: 5px; + } + QLabel[role="fieldLabel"] { + color: #EBEBEB; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 12px; + font-weight: 400; + letter-spacing: 0.6px; + } + QLineEdit { + background-color: rgba(89, 100, 113, 0.2); + color: #EBEBEB; + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 2px; + padding: 0px 10px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 11px; + font-weight: 300; + letter-spacing: 0.55px; + min-height: 30px; + max-height: 30px; + } + QLineEdit:focus { + border: 1px solid #3067C0; + background-color: rgba(48, 103, 192, 0.1); + } + QLineEdit:hover { + border: 1px solid #3067C0; + background-color: rgba(89, 100, 113, 0.3); + } + QPushButton { + background-color: rgba(89, 98, 118, 0.5); + color: #EBEBEB; + border: none; + border-radius: 2px; + padding: 0px 12px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-width: 120px; + min-height: 30px; + max-height: 30px; + } + QPushButton:hover { + background-color: #3067C0; + color: #FFFFFF; + } + QPushButton:pressed { + background-color: #2556A0; + color: #FFFFFF; + } + """) + + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + base_frame = QFrame() + base_frame.setObjectName('baseFrame') + base_frame.setFrameShape(QFrame.NoFrame) + base_frame.setAttribute(Qt.WA_StyledBackground, True) + + base_layout = QVBoxLayout(base_frame) + base_layout.setContentsMargins(0, 0, 0, 0) + base_layout.setSpacing(0) + + self._createTitleBar() + base_layout.addWidget(self.title_bar) + base_layout.addSpacing(10) + + content_widget = QWidget() + content_widget.setObjectName('contentWidget') + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(10, 0, 10, 10) + content_layout.setSpacing(0) + + content_container = QFrame() + content_container.setObjectName('contentContainer') + content_container.setFrameShape(QFrame.NoFrame) + content_container.setAttribute(Qt.WA_StyledBackground, True) + content_container.setFixedWidth(400) + + container_layout = QVBoxLayout(content_container) + container_layout.setContentsMargins(15, 10, 15, 10) + container_layout.setSpacing(10) + + if label_text: + label = QLabel(label_text) + label.setProperty('role', 'fieldLabel') + container_layout.addWidget(label) + + self.line_edit = QLineEdit() + self.line_edit.setPlaceholderText(placeholder) + self.line_edit.setText(default_text) + container_layout.addWidget(self.line_edit) + + separator = QFrame() + separator.setFrameShape(QFrame.HLine) + separator.setFrameShadow(QFrame.Plain) + separator.setFixedHeight(1) + separator.setStyleSheet("background-color: #2C2F36; border: none;") + container_layout.addWidget(separator) + + button_row = QHBoxLayout() + button_row.setContentsMargins(0, 0, 0, 0) + button_row.setSpacing(10) + button_row.addStretch() + + self.confirmButton = QPushButton("确认") + self.confirmButton.setObjectName("primaryButton") + self.confirmButton.clicked.connect(self._onAccept) + button_row.addWidget(self.confirmButton) + + self.cancelButton = QPushButton("取消") + self.cancelButton.setObjectName("secondaryButton") + self.cancelButton.clicked.connect(self.reject) + button_row.addWidget(self.cancelButton) + + container_layout.addLayout(button_row) + + content_layout.addWidget(content_container, 0, Qt.AlignTop) + base_layout.addWidget(content_widget) + main_layout.addWidget(base_frame) + + self.confirmButton.setDefault(True) + self.confirmButton.setAutoDefault(True) + self.line_edit.selectAll() + self.line_edit.setFocus() + + def _createTitleBar(self): + self.title_bar = QFrame() + self.title_bar.setObjectName("titleBar") + + title_layout = QHBoxLayout(self.title_bar) + title_layout.setContentsMargins(12, 6, 12, 6) + title_layout.setSpacing(0) + + controls = QWidget() + controls.setObjectName("controlButtons") + controls_layout = QHBoxLayout(controls) + controls_layout.setContentsMargins(0, 0, 0, 0) + controls_layout.setSpacing(0) + + self.close_button = QPushButton() + self.close_button.setObjectName("closeButton") + self.close_button.clicked.connect(self.reject) + self.close_button.setFocusPolicy(Qt.NoFocus) + self.close_button.setFixedSize(18, 18) + controls_layout.addWidget(self.close_button) + + self._applyTitleBarIcons() + + left_placeholder = QWidget() + left_placeholder.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + title_layout.addWidget(left_placeholder) + + self.title_label = QLabel(self.windowTitle()) + self.title_label.setObjectName("titleLabel") + self.title_label.setAlignment(Qt.AlignCenter) + title_layout.addWidget(self.title_label, 1) + + title_layout.addWidget(controls) + left_placeholder.setFixedWidth(controls.sizeHint().width()) + + def _applyTitleBarIcons(self): + if self._icon_close: + self.close_button.setIcon(self._icon_close) + self.close_button.setIconSize(self._title_icon_size) + self.close_button.setText("") + self.close_button.setToolTip("关闭") + + def setWindowTitle(self, title): + super().setWindowTitle(title) + if hasattr(self, "title_label"): + self.title_label.setText(title) + + def _onAccept(self): + if self.line_edit.text().strip(): + self.accept() + + def text(self): + return self.line_edit.text().strip() + + def mousePressEvent(self, event): + if event.button() == Qt.LeftButton and self.title_bar.geometry().contains(event.pos()): + self.dragging = True + self.drag_position = event.globalPos() - self.frameGeometry().topLeft() + event.accept() + super().mousePressEvent(event) + + def mouseMoveEvent(self, event): + if event.buttons() == Qt.LeftButton and self.dragging: + self.move(event.globalPos() - self.drag_position) + event.accept() + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + if event.button() == Qt.LeftButton: + self.dragging = False + super().mouseReleaseEvent(event) + + class CustomTreeWidget(QTreeWidget): """自定义场景树部件""" From b386818f7689c4fc70372f6b13fbdb68141bd328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Thu, 16 Oct 2025 00:54:47 +0800 Subject: [PATCH 5/7] =?UTF-8?q?1.UI=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../toolkit/plugin_configurator/main.py | 12 +- icons/sueccess_icon.png | Bin 546 -> 0 bytes icons/sure_delete_icon.png | Bin 482 -> 0 bytes project/project_manager.py | 108 +++- ui/icon_manager.py | 5 + ui/main_window.py | 575 ++++++++++++++---- ui/property_panel.py | 236 ++++++- ui/widgets.py | 462 +++++++++++++- 8 files changed, 1210 insertions(+), 188 deletions(-) delete mode 100644 icons/sueccess_icon.png delete mode 100644 icons/sure_delete_icon.png diff --git a/RenderPipelineFile/toolkit/plugin_configurator/main.py b/RenderPipelineFile/toolkit/plugin_configurator/main.py index 9410f080..fd393ae8 100644 --- a/RenderPipelineFile/toolkit/plugin_configurator/main.py +++ b/RenderPipelineFile/toolkit/plugin_configurator/main.py @@ -49,6 +49,8 @@ from rplibs.pyqt_imports import * #noqa # Load the generated UI Layout from ui.main_window_generated import Ui_MainWindow # noqa +from ui.widgets import UniversalMessageDialog + from rpcore.pluginbase.manager import PluginManager # noqa from rpcore.util.network_communication import NetworkCommunication # noqa from rpcore.mount_manager import MountManager # noqa @@ -119,9 +121,13 @@ class PluginConfigurator(QMainWindow, Ui_MainWindow): self, "Warning", msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: - QMessageBox.information( - self, "Success", - "Settings have been reset! You may have to restart the pipeline.") + UniversalMessageDialog.show_success( + self, + "Success", + "Settings have been reset! You may have to restart the pipeline.", + show_cancel=False, + confirm_text="OK" + ) self._plugin_mgr.reset_plugin_settings(self._current_plugin) # Save config diff --git a/icons/sueccess_icon.png b/icons/sueccess_icon.png deleted file mode 100644 index 4dda99f9762fba5f01c070e6c5e25728b677b5ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 546 zcmV+-0^R+IP)( z!axwmcUQt#$OB002v{>2ob`Y~@_Nvk93eXSC3rgyxrs)^vxI)S$OYiBq!(?gL znkU|;7+@SEJ4?!)AD9G~f~}$g&76fMHl;~cGzB}#fmud=KNvI>$Zbc$I5BW|2i5}Y z8>g12Yn>62(~T^(G56&&+6rT;>uXiI4bw`HZ8xon$m>uYwl43|;c_4b0yjp`I9PSXy5dyk9dcjZhKNp30oTrZx-zh4>L%8Ru!nqhzqiy%-Q7-7lOR; z5P8G}nNx_FBs5;Pr4@iliJGaNoWG|~M9-5@oS9KXl<7e04`Ul)M$Zq*6j6w-W0kH) z+L@-IuuKhYQiiWwL3$NlmLi_tC=`273rM0BE~uiL25B3FjWRpWBU`VhqV=|oZPSKb z(=9Yz9E)*;-2?4w!@BdpF%b=qy!huXEp{fO-_wD4V1 diff --git a/project/project_manager.py b/project/project_manager.py index f4613fbd..24562bd8 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -19,7 +19,7 @@ from PyQt5.QtWidgets import ( from PyQt5.QtCore import Qt # 导入自定义对话框 -from ui.widgets import NewProjectDialog +from ui.widgets import NewProjectDialog, UniversalMessageDialog class ProjectManager: """项目管理器 - 统一管理项目的生命周期""" @@ -103,11 +103,23 @@ class ProjectManager: parent_window.current_project_path = full_project_path # 显示成功消息 - QMessageBox.information(parent_window, "成功", f"项目 '{project_name}' 创建成功!") + UniversalMessageDialog.show_success( + parent_window, + "成功", + f"项目 '{project_name}' 创建成功!", + show_cancel=False, + confirm_text="确认" + ) return True except Exception as e: - QMessageBox.critical(parent_window, "错误", f"创建项目失败: {str(e)}") + UniversalMessageDialog.show_error( + parent_window, + "错误", + f"创建项目失败: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) return False def openProject(self, parent_window): @@ -127,7 +139,13 @@ class ProjectManager: # 检查是否是有效的项目文件夹 config_file = os.path.join(project_path, "project.json") if not os.path.exists(config_file): - QMessageBox.warning(parent_window, "警告", "选择的不是有效的项目文件夹!") + UniversalMessageDialog.show_warning( + parent_window, + "警告", + "选择的不是有效的项目文件夹!", + show_cancel=False, + confirm_text="确认" + ) return False # 读取项目配置 @@ -161,7 +179,13 @@ class ProjectManager: if hasattr(parent_window, 'fileView') and hasattr(parent_window, 'fileModel'): parent_window.fileView.setRootIndex(parent_window.fileModel.index(project_path)) - QMessageBox.information(parent_window, "成功", "项目加载成功!") + UniversalMessageDialog.show_success( + parent_window, + "成功", + "项目加载成功!", + show_cancel=False, + confirm_text="确认" + ) return True # 检查场景文件 # scene_file = os.path.join(project_path, "scenes", "scene.bam") @@ -200,7 +224,13 @@ class ProjectManager: # return False except Exception as e: - QMessageBox.critical(parent_window, "错误", f"加载项目时发生错误:{str(e)}") + UniversalMessageDialog.show_error( + parent_window, + "错误", + f"加载项目时发生错误:{str(e)}", + show_cancel=False, + confirm_text="确认" + ) return False def openProjectForPath(self, project_path, parent_window=None): @@ -217,7 +247,13 @@ class ProjectManager: config_file = os.path.join(project_path, "project.json") if not os.path.exists(config_file): if parent_window: - QMessageBox.warning(parent_window, "警告", f"选择的不是有效的项目文件夹!{project_path}") + UniversalMessageDialog.show_warning( + parent_window, + "警告", + f"选择的不是有效的项目文件夹!{project_path}", + show_cancel=False, + confirm_text="确认" + ) else: print("警告: 选择的不是有效的项目文件夹!") return False @@ -255,13 +291,25 @@ class ProjectManager: if hasattr(parent_window, 'fileView') and hasattr(parent_window, 'fileModel'): parent_window.fileView.setRootIndex(parent_window.fileModel.index(project_path)) - QMessageBox.information(parent_window, "成功", "项目加载成功!") + UniversalMessageDialog.show_success( + parent_window, + "成功", + "项目加载成功!", + show_cancel=False, + confirm_text="确认" + ) print(f"项目 '{project_path}' 加载成功!") return True else: if parent_window: - QMessageBox.warning(parent_window, "错误", "加载场景失败!") + UniversalMessageDialog.show_warning( + parent_window, + "错误", + "加载场景失败!", + show_cancel=False, + confirm_text="确认" + ) else: print("错误: 加载场景失败!") return False @@ -269,7 +317,13 @@ class ProjectManager: except Exception as e: error_msg = f"加载项目时发生错误:{str(e)}" if parent_window: - QMessageBox.critical(parent_window, "错误", error_msg) + UniversalMessageDialog.show_error( + parent_window, + "错误", + error_msg, + show_cancel=False, + confirm_text="确认" + ) else: print(error_msg) return False @@ -283,7 +337,13 @@ class ProjectManager: if hasattr(parent_window, 'current_project_path'): self.current_project_path = parent_window.current_project_path else: - QMessageBox.warning(parent_window, "警告", "请先创建或打开一个项目!") + UniversalMessageDialog.show_warning( + parent_window, + "警告", + "请先创建或打开一个项目!", + show_cancel=False, + confirm_text="确认" + ) return False project_path = self.current_project_path @@ -324,14 +384,32 @@ class ProjectManager: project_name = os.path.basename(project_path) self.updateWindowTitle(parent_window, project_name) - QMessageBox.information(parent_window, "成功", "项目保存成功!") + UniversalMessageDialog.show_success( + parent_window, + "成功", + "项目保存成功!", + show_cancel=False, + confirm_text="确认" + ) return True else: - QMessageBox.warning(parent_window, "错误", "保存场景失败!") + UniversalMessageDialog.show_warning( + parent_window, + "错误", + "保存场景失败!", + show_cancel=False, + confirm_text="确认" + ) return False except Exception as e: - QMessageBox.critical(parent_window, "错误", f"保存项目时发生错误:{str(e)}") + UniversalMessageDialog.show_error( + parent_window, + "错误", + f"保存项目时发生错误:{str(e)}", + show_cancel=False, + confirm_text="确认" + ) return False # ==================== 项目打包功能 ==================== @@ -1099,4 +1177,4 @@ def updateWindowTitle(window, project_name=None): if project_name: window.setWindowTitle(f"{base_title} - {project_name}") else: - window.setWindowTitle(base_title) \ No newline at end of file + window.setWindowTitle(base_title) diff --git a/ui/icon_manager.py b/ui/icon_manager.py index f3a9d375..8bfebceb 100644 --- a/ui/icon_manager.py +++ b/ui/icon_manager.py @@ -64,6 +64,11 @@ class IconManager: 'minimize_icon': 'minimize_icon.png', 'windowing_icon': 'windowing_icon.png', 'close_icon': 'close_icon.png', + + # 弹窗图标 + 'success_icon': 'success_icon.png', + 'warning_icon': 'warning_icon.png', + 'fail_icon': 'delete_fail_icon.png', } # 初始化默认图标 diff --git a/ui/main_window.py b/ui/main_window.py index 0afd939b..73e8c3bc 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -1,4 +1,4 @@ -""" +""" 主窗口设置模块 负责主窗口的界面构建和事件绑定: @@ -21,7 +21,9 @@ from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint, QUrl, QRect from direct.showbase.ShowBaseGlobal import aspect2d from panda3d.core import OrthographicLens -from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget,CustomAssetsTreeWidget, CustomConsoleDockWidget +from ui.widgets import (CustomPanda3DWidget, CustomFileView, CustomTreeWidget, + CustomAssetsTreeWidget, CustomConsoleDockWidget, + UniversalMessageDialog) from ui.icon_manager import get_icon_manager, get_icon @@ -226,6 +228,12 @@ class StyledTerrainDialog(QDialog): widget.setRange(field.get("min", 0), field.get("max", 1000)) widget.setSingleStep(field.get("step", 1)) widget.setValue(field.get("value", field.get("default", 0))) + elif widget_type == "text": + widget = QLineEdit() + widget.setText(field.get("value", field.get("default", ""))) + if field.get("placeholder"): + widget.setPlaceholderText(field.get("placeholder")) + widget.setClearButtonEnabled(True) else: widget = QDoubleSpinBox() widget.setRange(field.get("min", 0.0), field.get("max", 1000.0)) @@ -345,6 +353,8 @@ class StyledTerrainDialog(QDialog): widget = self.field_widgets.get(name) if isinstance(widget, (QDoubleSpinBox, QSpinBox)): return widget.value() + if isinstance(widget, QLineEdit): + return widget.text() return None try: from PyQt5.QtWebEngineWidgets import QWebEngineView @@ -3052,7 +3062,13 @@ class MainWindow(QMainWindow): try: selected_item = self.treeWidget.currentItem() if not selected_item: - QMessageBox.information(self, "提示", "请先选择要复制的节点") + UniversalMessageDialog.show_warning( + self, + "提示", + "请先选择要复制的节点", + show_cancel=False, + confirm_text="确认" + ) return # 获取选中的节点 @@ -3064,35 +3080,71 @@ class MainWindow(QMainWindow): selected_node = getattr(self.world.selection, 'selectedNode', None) if not selected_node or selected_node.isEmpty(): - QMessageBox.warning(self, "错误", "无法获取选中节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法获取选中节点", + show_cancel=False, + confirm_text="确认" + ) return # 检查是否是根节点 if selected_node.getName() == "render": - QMessageBox.warning(self, "错误", "不能复制根节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "不能复制根节点", + show_cancel=False, + confirm_text="确认" + ) return # 序列化节点数据 node_data = self.world.scene_manager.serializeNodeForCopy(selected_node) if not node_data: - QMessageBox.warning(self, "错误", "无法序列化选中节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法序列化选中节点", + show_cancel=False, + confirm_text="确认" + ) return # 存储到剪切板 self.clipboard = [node_data] self.clipboard_mode = "copy" - QMessageBox.information(self, "成功", "节点已复制到剪切板") + UniversalMessageDialog.show_success( + self, + "成功", + "节点已复制到剪切板", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"复制操作失败: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"复制操作失败: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onCut(self): """剪切操作""" try: selected_item = self.treeWidget.currentItem() if not selected_item: - QMessageBox.information(self, "提示", "请先选择要剪切的节点") + UniversalMessageDialog.show_warning( + self, + "提示", + "请先选择要剪切的节点", + show_cancel=False, + confirm_text="确认" + ) return # 获取选中的节点 @@ -3104,18 +3156,36 @@ class MainWindow(QMainWindow): selected_node = getattr(self.world.selection, 'selectedNode', None) if not selected_node or selected_node.isEmpty(): - QMessageBox.warning(self, "错误", "无法获取选中节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法获取选中节点", + show_cancel=False, + confirm_text="确认" + ) return # 检查是否是根节点或特殊节点 if selected_node.getName() in ["render", "camera", "ambientLight", "directionalLight"]: - QMessageBox.warning(self, "错误", "不能剪切根节点或系统节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "不能剪切根节点或系统节点", + show_cancel=False, + confirm_text="确认" + ) return # 序列化节点数据 node_data = self.world.scene_manager.serializeNodeForCopy(selected_node) if not node_data: - QMessageBox.warning(self, "错误", "无法序列化选中节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法序列化选中节点", + show_cancel=False, + confirm_text="确认" + ) return # 存储到剪切板 @@ -3125,16 +3195,34 @@ class MainWindow(QMainWindow): # 删除原节点 self.treeWidget.delete_items([selected_item]) - QMessageBox.information(self, "成功", "节点已剪切到剪切板") + UniversalMessageDialog.show_success( + self, + "成功", + "节点已剪切到剪切板", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"剪切操作失败: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"剪切操作失败: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onPaste(self): """粘贴操作""" try: if not self.clipboard: - QMessageBox.information(self, "提示", "剪切板为空") + UniversalMessageDialog.show_warning( + self, + "提示", + "剪切板为空", + show_cancel=False, + confirm_text="确认" + ) return # 获取粘贴目标节点 @@ -3175,13 +3263,25 @@ class MainWindow(QMainWindow): # 检查父节点有效性 if not parent_node or parent_node.isEmpty(): - QMessageBox.warning(self, "错误", "无法获取有效的父节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法获取有效的父节点", + show_cancel=False, + confirm_text="确认" + ) return # 检查目标节点是否为允许的父节点类型 parent_name = parent_node.getName() if parent_name in ["camera", "ambientLight", "directionalLight"]: - QMessageBox.warning(self, "错误", "不能粘贴到该类型节点下") + UniversalMessageDialog.show_warning( + self, + "错误", + "不能粘贴到该类型节点下", + show_cancel=False, + confirm_text="确认" + ) return # 粘贴节点 @@ -3200,10 +3300,22 @@ class MainWindow(QMainWindow): self.clipboard.clear() self.clipboard_mode = None - QMessageBox.information(self, "成功", f"已粘贴 {len(pasted_nodes)} 个节点") + UniversalMessageDialog.show_success( + self, + "成功", + f"已粘贴 {len(pasted_nodes)} 个节点", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"粘贴操作失败: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"粘贴操作失败: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def _serializeNode(self, node): """序列化节点数据""" @@ -3330,13 +3442,31 @@ class MainWindow(QMainWindow): print("成功操作") else: print("撤销失败") - QMessageBox.information(self,"提示","撤销操作失败") + UniversalMessageDialog.show_warning( + self, + "提示", + "撤销操作失败", + show_cancel=False, + confirm_text="确认" + ) else: print("没有可撤销的操作") - QMessageBox.information(self,"提示","没有可撤销的操作") + UniversalMessageDialog.show_warning( + self, + "提示", + "没有可撤销的操作", + show_cancel=False, + confirm_text="确认" + ) else: print("命令管理器未初始化") - QMessageBox.information(self,"提示","命令系统未初始化") + UniversalMessageDialog.show_warning( + self, + "提示", + "命令系统未初始化", + show_cancel=False, + confirm_text="确认" + ) def onRedo(self): """重做操作""" @@ -3347,13 +3477,31 @@ class MainWindow(QMainWindow): print("成功重做") else: print("重做失败") - QMessageBox.information(self,"提示","重做操作失败") + UniversalMessageDialog.show_warning( + self, + "提示", + "重做操作失败", + show_cancel=False, + confirm_text="确认" + ) else: print("没有可重做的操作") - QMessageBox.information(self,"提示","没有可重做的操作") + UniversalMessageDialog.show_warning( + self, + "提示", + "没有可重做的操作", + show_cancel=False, + confirm_text="确认" + ) else: print("命令管理器未初始化") - QMessageBox.information(self,"提示","命令系统未初始化") + UniversalMessageDialog.show_warning( + self, + "提示", + "命令系统未初始化", + show_cancel=False, + confirm_text="确认" + ) def onCreateCesiumView(self): if hasattr(self.world,'gui_manager') and self.world.gui_manager: @@ -3404,15 +3552,16 @@ class MainWindow(QMainWindow): break if not cesium_view_exists: - reply = QMessageBox.question( + result = UniversalMessageDialog.show_info( self, - '提示', - 'Cesium 地图视图尚未打开,是否先打开地图视图?', - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes + "提示", + "Cesium 地图视图尚未打开,是否先打开地图视图?", + show_cancel=True, + confirm_text="打开视图", + cancel_text="取消" ) - if reply == QMessageBox.Yes: + if result == QDialog.Accepted: self.onCreateCesiumView() # 给一点时间让 Cesium 视图加载 QTimer.singleShot(1000, self.showAddModelDialog) @@ -3457,22 +3606,28 @@ class MainWindow(QMainWindow): ) if success: - QMessageBox.information( + UniversalMessageDialog.show_success( self, "成功", - f"模型已成功添加到地图!\n模型ID: {model_id}" + f"模型已成功添加到地图!\n模型ID: {model_id}", + show_cancel=False, + confirm_text="确认" ) else: - QMessageBox.warning( + UniversalMessageDialog.show_warning( self, "失败", - "添加模型失败,请检查控制台输出" + "添加模型失败,请检查控制台输出", + show_cancel=False, + confirm_text="确认" ) except Exception as e: - QMessageBox.critical( + UniversalMessageDialog.show_error( self, "错误", - f"添加模型时发生错误:\n{str(e)}" + f"添加模型时发生错误:\n{str(e)}", + show_cancel=False, + confirm_text="确认" ) @@ -3547,43 +3702,103 @@ class MainWindow(QMainWindow): return None, False def onLoadCesiumTileset(self): - dialog = self.createStyledInputDialog( - self, - "加载 Cesium 3D Tiles", - "输入 tileset.json URL:", - QLineEdit.Normal, - "https://assets.ion.cesium.com/96128/tileset.json" - ) + """加载 Cesium 3D Tiles""" + fields = [ + { + "name": "url", + "label": "Tileset URL", + "type": "text", + "value": "https://assets.ion.cesium.com/96128/tileset.json", + "placeholder": "输入 tileset.json 地址", + "label_width": 110 + }, + { + "name": "longitude", + "label": "经度(°)", + "type": "double", + "min": -180.0, + "max": 180.0, + "step": 0.1, + "decimals": 6, + "value": 0.0, + "label_width": 110 + }, + { + "name": "latitude", + "label": "纬度(°)", + "type": "double", + "min": -90.0, + "max": 90.0, + "step": 0.1, + "decimals": 6, + "value": 0.0, + "label_width": 110 + }, + { + "name": "height", + "label": "高度(米)", + "type": "double", + "min": -10000.0, + "max": 10000.0, + "step": 1.0, + "decimals": 2, + "value": 0.0, + "label_width": 110 + } + ] + + dialog = StyledTerrainDialog(self, "加载 Cesium 3D Tiles", fields, primary_text="加载", secondary_text="取消") if dialog.exec_() == QDialog.Accepted: - url = dialog.textValue() - if url: - try: - # 生成唯一的 tileset 名称 - import uuid - tileset_name = f"tileset_{uuid.uuid4().hex[:8]}" + url = dialog.get_value("url") + if not url or not url.strip(): + UniversalMessageDialog.show_warning( + self, + "提示", + "请输入有效的 tileset URL", + show_cancel=False, + confirm_text="确认" + ) + return - # 加载 tileset - if hasattr(self.world, 'addCesiumTileset'): - success = self.world.addCesiumTileset(tileset_name, url, (0, 0, 0)) - if success: - QMessageBox.information( - self, - "成功", - f"Cesium 3D Tiles 已加载到场景中!\n名称: {tileset_name}" - ) - else: - QMessageBox.warning( - self, - "失败", - "加载 Cesium 3D Tiles 失败" - ) - except Exception as e: - QMessageBox.critical( - self, - "错误", - f"加载 Cesium 3D Tiles 时发生错误:\n{str(e)}" + longitude = dialog.get_value("longitude") + latitude = dialog.get_value("latitude") + height = dialog.get_value("height") + + try: + import uuid + tileset_name = f"tileset_{uuid.uuid4().hex[:8]}" + + if hasattr(self.world, 'addCesiumTileset'): + success = self.world.addCesiumTileset( + tileset_name, + url.strip(), + (longitude, latitude, height) ) + if success: + UniversalMessageDialog.show_success( + self, + "成功", + f"Cesium 3D Tiles 已加载到场景中!\n名称: {tileset_name}", + show_cancel=False, + confirm_text="确认" + ) + else: + UniversalMessageDialog.show_warning( + self, + "失败", + "加载 Cesium 3D Tiles 失败", + show_cancel=False, + confirm_text="确认" + ) + except Exception as e: + UniversalMessageDialog.show_error( + self, + "错误", + f"加载 Cesium 3D Tiles 时发生错误:\n{str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onToolChanged(self, button): """工具切换事件处理""" @@ -4310,7 +4525,9 @@ class MainWindow(QMainWindow): """创建脚本按钮事件""" script_name = self.scriptNameEdit.text().strip() if not script_name: - QMessageBox.warning(self, "错误", "请输入脚本名称!") + UniversalMessageDialog.show_warning( + self, "警告", "请输入脚本名称!", show_cancel=False, confirm_text="确认" + ) return template = self.templateCombo.currentText() @@ -4318,17 +4535,35 @@ class MainWindow(QMainWindow): try: success = self.world.createScript(script_name, template) if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 创建成功!") + UniversalMessageDialog.show_success( + self, + "成功", + f"脚本 '{script_name}' 创建成功!", + show_cancel=False, + confirm_text="确认" + ) self.scriptNameEdit.clear() self.refreshScriptsList() else: - QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 创建失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + f"脚本 '{script_name}' 创建失败!", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"创建脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"创建脚本时发生错误: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onCreateScriptDialog(self): """菜单创建脚本事件""" - dialog = self.createStyledInputDialog(self, "创建脚本", "输入脚本名称:") + dialog = self.createStyledInputDialog(self, "创建脚本", "请输入脚本名称:") if dialog.exec_() == QDialog.Accepted: script_name = dialog.textValue() @@ -4336,30 +4571,55 @@ class MainWindow(QMainWindow): try: success = self.world.createScript(script_name.strip(), "basic") if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 创建成功!") + UniversalMessageDialog.show_success( + self, + "成功", + f"脚本 '{script_name}' 创建成功!", + show_cancel=False, + confirm_text="确认" + ) self.refreshScriptsList() else: - QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 创建失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + f"脚本 '{script_name}' 创建失败!", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"创建脚本时出错: {str(e)}") - + UniversalMessageDialog.show_error( + self, + "错误", + f"创建脚本时发生错误: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onLoadScript(self): """加载脚本按钮事件""" current_item = self.scriptsList.currentItem() if not current_item: - QMessageBox.warning(self, "错误", "请选择要加载的脚本!") + UniversalMessageDialog.show_warning( + self, "警告", "请选择要加载的脚本!", show_cancel=False, confirm_text="确认" + ) return script_name = current_item.text() try: success = self.world.reloadScript(script_name) if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 重载成功!") + UniversalMessageDialog.show_success( + self, "成功", f"脚本 '{script_name}' 加载成功!", show_cancel=False, confirm_text="确认" + ) else: - QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 重载失败!") + UniversalMessageDialog.show_warning( + self, "警告", f"脚本 '{script_name}' 加载失败!", show_cancel=False, confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"重载脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, "错误", f"加载脚本时发生错误: {str(e)}", show_cancel=False, confirm_text="确认" + ) def onLoadScriptFile(self): """加载脚本文件菜单事件""" @@ -4376,29 +4636,52 @@ class MainWindow(QMainWindow): try: success = self.world.loadScript(file_path) if success: - QMessageBox.information(self, "成功", "脚本文件加载成功!") + UniversalMessageDialog.show_success( + self, "成功", "脚本文件加载成功!", show_cancel=False, confirm_text="确认" + ) self.refreshScriptsList() else: - QMessageBox.warning(self, "错误", "脚本文件加载失败!") + UniversalMessageDialog.show_warning( + self, "警告", "脚本文件加载失败!", show_cancel=False, confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"加载脚本文件时出错: {str(e)}") - + UniversalMessageDialog.show_error( + self, "错误", f"加载脚本文件时发生错误: {str(e)}", show_cancel=False, confirm_text="确认" + ) def onReloadAllScripts(self): - """重载所有脚本事件""" + """重新加载所有脚本事件""" try: scripts_loaded = self.world.loadAllScripts() - QMessageBox.information(self, "成功", f"重载完成,共加载 {len(scripts_loaded)} 个脚本!") + UniversalMessageDialog.show_success( + self, + "成功", + f"更新完成,共加载 {len(scripts_loaded)} 个脚本!", + show_cancel=False, + confirm_text="确认" + ) self.refreshScriptsList() except Exception as e: - QMessageBox.critical(self, "错误", f"重载脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"加载脚本时发生错误: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onToggleHotReload(self): - """切换热重载状态""" + """切换热更新状态""" enabled = self.toggleHotReloadAction.isChecked() self.world.enableHotReload(enabled) - status = "启用" if enabled else "禁用" - QMessageBox.information(self, "热重载", f"热重载已{status}") + status = "已开启" if enabled else "已关闭" + UniversalMessageDialog.show_info( + self, + "脚本热更", + f"脚本热更{status}", + show_cancel=False, + confirm_text="确认" + ) def onOpenScriptsManager(self): """打开脚本管理器""" @@ -4408,65 +4691,107 @@ class MainWindow(QMainWindow): def onScriptDoubleClick(self, item): """脚本列表双击事件""" - # 可以在这里添加打开外部编辑器的功能 script_name = item.text() - QMessageBox.information(self, "提示", f"双击了脚本: {script_name}\n\n可以使用外部编辑器编辑脚本文件。") + UniversalMessageDialog.show_info( + self, + "提示", + f"双击脚本: {script_name}\n\n请使用外部编辑器编辑脚本文件。", + show_cancel=False, + confirm_text="确认" + ) def onMountScript(self): """挂载脚本事件""" selected_object = getattr(self.world.selection, 'selectedObject', None) if not selected_object: - QMessageBox.warning(self, "错误", "请先选择一个对象!") + UniversalMessageDialog.show_warning( + self, "警告", "请选择一个对象!", show_cancel=False, confirm_text="确认" + ) return script_name = self.mountScriptCombo.currentText() if not script_name: - QMessageBox.warning(self, "错误", "请选择要挂载的脚本!") + UniversalMessageDialog.show_warning( + self, "警告", "请选择要挂载的脚本!", show_cancel=False, confirm_text="确认" + ) return try: success = self.world.addScript(selected_object, script_name) if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 已挂载到对象!") + UniversalMessageDialog.show_success( + self, "成功", f"脚本 '{script_name}' 已挂载到对象!", show_cancel=False, confirm_text="确认" + ) self.updateMountedScriptsList(selected_object) - # 同时更新属性面板 if self.treeWidget and self.treeWidget.currentItem(): self.world.updatePropertyPanel(self.treeWidget.currentItem()) else: - QMessageBox.warning(self, "错误", f"挂载脚本失败!") + UniversalMessageDialog.show_warning( + self, "警告", "挂载脚本失败!", show_cancel=False, confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"挂载脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, "错误", f"挂载脚本时发生错误: {str(e)}", show_cancel=False, confirm_text="确认" + ) def onUnmountScript(self): """卸载脚本事件""" selected_object = getattr(self.world.selection, 'selectedObject', None) if not selected_object: - QMessageBox.warning(self, "错误", "请先选择一个对象!") + UniversalMessageDialog.show_warning( + self, + "警告", + "请选择一个对象!", + show_cancel=False, + confirm_text="确认" + ) return current_item = self.mountedScriptsList.currentItem() if not current_item: - QMessageBox.warning(self, "错误", "请选择要卸载的脚本!") + UniversalMessageDialog.show_warning( + self, + "警告", + "请选择要卸载的脚本!", + show_cancel=False, + confirm_text="确认" + ) return - # 解析脚本名称(移除状态标记) item_text = current_item.text() - script_name = item_text[2:] # 移除 "✓ " 或 "✗ " 前缀 + script_name = item_text[2:] try: success = self.world.removeScript(selected_object, script_name) if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 已从对象卸载!") + UniversalMessageDialog.show_success( + self, + "成功", + f"脚本 '{script_name}' 已从对象卸载!", + show_cancel=False, + confirm_text="确认" + ) self.updateMountedScriptsList(selected_object) - # 同时更新属性面板 if self.treeWidget and self.treeWidget.currentItem(): self.world.updatePropertyPanel(self.treeWidget.currentItem()) else: - QMessageBox.warning(self, "错误", f"卸载脚本失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + "卸载脚本失败!", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"卸载脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"卸载脚本时发生错误: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onOpenIconManager(self): """打开图标管理器""" @@ -4560,9 +4885,21 @@ class MainWindow(QMainWindow): terrain_info = self.world.createFlatTerrain((width, height), resolution) if terrain_info: - QMessageBox.information(self, "成功", "平面地形创建成功!") + UniversalMessageDialog.show_success( + self, + "成功", + "平面地形创建成功!", + show_cancel=False, + confirm_text="确认" + ) else: - QMessageBox.warning(self, "警告", "平面地形创建失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + "平面地形创建失败!", + show_cancel=False, + confirm_text="确认" + ) def onCreateHeightmapTerrain(self): """从高度图创建地形""" @@ -4624,9 +4961,21 @@ class MainWindow(QMainWindow): (x_scale, y_scale, z_scale) ) if terrain_info: - QMessageBox.information(self, "成功", "高度图地形创建成功!") + UniversalMessageDialog.show_success( + self, + "成功", + "高度图地形创建成功!", + show_cancel=False, + confirm_text="确认" + ) else: - QMessageBox.warning(self, "警告", "高度图地形创建失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + "高度图地形创建失败!", + show_cancel=False, + confirm_text="确认" + ) def onOpenAssemblyDisassemblyConfig(self): """打开拆装配置界面""" diff --git a/ui/property_panel.py b/ui/property_panel.py index ccab840d..b815ee94 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -241,6 +241,41 @@ class PropertyPanelManager: letter-spacing: 0.5px; } """ + + # 固定宽度的徽章样式(用于碰撞检测状态) + self.badge_style_green_fixed = """ + QLabel { + background-color: rgba(45, 255, 196, 0.15); + border: 1px solid rgba(45, 255, 196, 0.6); + color: #2dffc4; + border-radius: 6px; + padding: 4px 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 9px; + font-weight: 500; + letter-spacing: 0.5px; + min-width: 50px; + max-width: 50px; + text-align: center; + } + """ + + self.badge_style_red_fixed = """ + QLabel { + background-color: rgba(255, 99, 99, 0.15); + border: 1px solid rgba(255, 99, 99, 0.6); + color: #ff6363; + border-radius: 6px; + padding: 4px 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 9px; + font-weight: 500; + letter-spacing: 0.5px; + min-width: 50px; + max-width: 50px; + text-align: center; + } + """ def createStatusBadge(self, text, badge_type="green"): """创建现代化状态徽章 @@ -265,6 +300,28 @@ class PropertyPanelManager: badge.setStyleSheet(self.badge_style_blue) # 默认蓝色 return badge + def createFixedStatusBadge(self, text, badge_type="green"): + """创建固定宽度的状态徽章(用于碰撞检测等需要保持一致宽度的场景) + + Args: + text: 徽章文字 + badge_type: 徽章类型,支持 "green", "red" + + Returns: + QLabel: 配置好固定宽度样式的标签 + """ + badge = QLabel(text) + badge.setAlignment(Qt.AlignCenter) # 确保文字居中 + + if badge_type == "green": + badge.setStyleSheet(self.badge_style_green_fixed) + elif badge_type == "red": + badge.setStyleSheet(self.badge_style_red_fixed) + else: + badge.setStyleSheet(self.badge_style_green_fixed) # 默认绿色 + + return badge + def createModernButton(self, text, button_type="default"): """创建现代化按钮 @@ -1012,7 +1069,7 @@ class PropertyPanelManager: # 清理碰撞相关控件引用 collision_controls = [ - 'collision_status_text', 'collision_shape_combo', 'collision_shape_label', + 'collision_status_badge', 'collision_shape_combo', 'collision_shape_label', 'collision_visibility_button', 'collision_button', 'collision_layout', 'collision_group', 'collision_pos_x', 'collision_pos_y', 'collision_pos_z', 'collision_radius', 'collision_width', 'collision_length', 'collision_height', @@ -9300,14 +9357,28 @@ except Exception as e: # 检查模型是否已有碰撞 has_collision = self._hasCollision(model) - # 碰撞状态标签 + # 创建主容器 + main_container = QWidget() + main_layout = QVBoxLayout(main_container) + main_layout.setContentsMargins(8, 8, 8, 8) + main_layout.setSpacing(12) + + # 状态和形状选择区域 + header_container = QWidget() + header_layout = QGridLayout(header_container) + header_layout.setContentsMargins(0, 0, 0, 0) + header_layout.setSpacing(8) + + # 碰撞状态行 status_label = QLabel("状态:") collision_layout.addWidget(status_label, 0, 0) - # 状态文本(需要保存引用以便更新) - self.collision_status_text = QLabel("已启用" if has_collision else "未启用") - self.collision_status_text.setStyleSheet("color: green;" if has_collision else "color: red;") - collision_layout.addWidget(self.collision_status_text, 0, 1) + # 状态徽章(使用固定宽度样式) + if has_collision: + self.collision_status_badge = self.createFixedStatusBadge("已启用", "green") + else: + self.collision_status_badge = self.createFixedStatusBadge("未启用", "red") + collision_layout.addWidget(self.collision_status_badge, 0, 1) # 形状选择标签(始终显示) self.collision_shape_label = QLabel("碰撞形状:") @@ -9343,14 +9414,15 @@ except Exception as e: # 添加碰撞参数调整控件 current_row = self._addCollisionParameterControls(model, collision_layout, current_row, current_shape) - # 显示/隐藏切换按钮 - self.collision_visibility_button = QPushButton("隐藏碰撞" if is_collision_visible else "显示碰撞") + # 显示/隐藏切换按钮(使用现代化样式) + visibility_text = "隐藏碰撞" if is_collision_visible else "显示碰撞" + self.collision_visibility_button = self.createModernButton(visibility_text, "primary") self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model)) collision_layout.addWidget(self.collision_visibility_button, current_row, 0, 1, 2) current_row += 1 - # 移除碰撞按钮 - self.collision_button = QPushButton("移除碰撞") + # 移除碰撞按钮(使用现代化样式) + self.collision_button = self.createModernButton("移除碰撞", "danger") self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) collision_layout.addWidget(self.collision_button, current_row, 0, 1, 2) else: @@ -9365,8 +9437,8 @@ except Exception as e: if hasattr(self, 'collision_visibility_button'): self.collision_visibility_button.setVisible(False) - # 添加碰撞按钮 - self.collision_button = QPushButton("添加碰撞") + # 添加碰撞按钮(使用现代化样式) + self.collision_button = self.createModernButton("添加碰撞", "success") self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) collision_layout.addWidget(self.collision_button, current_row, 0, 1, 2) collision_group.setLayout(collision_layout) @@ -9677,7 +9749,32 @@ except Exception as e: try: if hasattr(self, 'collision_visibility_button'): is_visible = self._isCollisionVisible(model) - self.collision_visibility_button.setText("隐藏碰撞" if is_visible else "显示碰撞") + button_text = "隐藏碰撞" if is_visible else "显示碰撞" + self.collision_visibility_button.setText(button_text) + + # 应用主要按钮样式 + self.collision_visibility_button.setStyleSheet(""" + QPushButton { + background-color: rgba(77, 116, 189, 0.8); + color: #ffffff; + border: 1px solid #4d74bd; + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 500; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(77, 116, 189, 1.0); + border: 1px solid rgba(77, 116, 189, 1.0); + } + QPushButton:pressed { + background-color: rgba(61, 96, 169, 1.0); + border: 1px solid rgba(61, 96, 169, 1.0); + } + """) + print(f"更新可见性按钮:{model.getName()} - {'可见' if is_visible else '隐藏'}") except Exception as e: print(f"更新碰撞可见性按钮失败: {e}") @@ -9845,6 +9942,17 @@ except Exception as e: except Exception as e: print(f"移除碰撞失败: {e}") + def _findButtonRow(self, layout): + """查找按钮应该在的行数""" + # 简单返回一个合适的行数,基于布局中现有的项目数 + row_count = 0 + for i in range(layout.count()): + item = layout.itemAt(i) + if item and item.widget(): + row, col, rowspan, colspan = layout.getItemPosition(i) + row_count = max(row_count, row + 1) + return row_count + def _updateCollisionPanelState(self, model): """更新碰撞面板状态""" try: @@ -9854,24 +9962,65 @@ except Exception as e: self._updating_collision_panel = True - if hasattr(self, 'collision_button') and hasattr(self, 'collision_status_text') and hasattr(self, + if hasattr(self, 'collision_button') and hasattr(self, 'collision_status_badge') and hasattr(self, 'collision_shape_combo'): has_collision = self._hasCollision(model) - # 更新状态文本和颜色 - self.collision_status_text.setText("已启用" if has_collision else "未启用") - self.collision_status_text.setStyleSheet("color: green;" if has_collision else "color: red;") + # 更新状态徽章(使用固定宽度) + if has_collision: + new_badge = self.createFixedStatusBadge("已启用", "green") + else: + new_badge = self.createFixedStatusBadge("未启用", "red") + + # 替换旧的徽章 + old_badge = self.collision_status_badge + parent_layout = old_badge.parent().layout() + if parent_layout: + # 找到旧徽章在布局中的位置 + for i in range(parent_layout.count()): + item = parent_layout.itemAt(i) + if item and item.widget() == old_badge: + # 移除旧徽章并添加新徽章 + parent_layout.removeWidget(old_badge) + old_badge.deleteLater() + parent_layout.addWidget(new_badge, 0, 1) # 状态徽章在第0行第1列 + self.collision_status_badge = new_badge + break if has_collision: # 有碰撞:显示移除按钮,下拉框变为只读并显示当前类型 - self.collision_button.setText("移除碰撞") - - # 先断开所有连接,再重新连接 - try: - self.collision_button.clicked.disconnect() - except: - pass - self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) + if hasattr(self, 'collision_button'): + # 更新按钮文本和样式(简单方法:直接设置文本和样式) + self.collision_button.setText("移除碰撞") + # 应用危险按钮样式 + self.collision_button.setStyleSheet(""" + QPushButton { + background-color: rgba(255, 99, 99, 0.15); + color: #ff6363; + border: 1px solid rgba(255, 99, 99, 0.6); + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 400; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(255, 99, 99, 0.25); + border: 1px solid #ff6363; + } + QPushButton:pressed { + background-color: rgba(255, 99, 99, 0.4); + color: #ffffff; + } + """) + + # 重新连接信号 + try: + self.collision_button.clicked.disconnect() + except: + pass + self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) # 获取并显示当前碰撞类型,设置为只读 current_shape = self._getCurrentCollisionShape(model) @@ -9895,14 +10044,37 @@ except Exception as e: else: # 无碰撞:显示添加按钮,下拉框变为可编辑 - self.collision_button.setText("添加碰撞") + if hasattr(self, 'collision_button'): + self.collision_button.setText("添加碰撞") + # 应用成功按钮样式 + self.collision_button.setStyleSheet(""" + QPushButton { + background-color: rgba(45, 255, 196, 0.15); + color: #2dffc4; + border: 1px solid rgba(45, 255, 196, 0.6); + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 400; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(45, 255, 196, 0.25); + border: 1px solid #2dffc4; + } + QPushButton:pressed { + background-color: rgba(45, 255, 196, 0.4); + color: #ffffff; + } + """) - # 先断开所有连接,再重新连接 - try: - self.collision_button.clicked.disconnect() - except: - pass - self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) + # 先断开所有连接,再重新连接 + try: + self.collision_button.clicked.disconnect() + except: + pass + self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) # 恢复为可编辑状态 self.collision_shape_combo.setEnabled(True) diff --git a/ui/widgets.py b/ui/widgets.py index a9692cc8..a4280eb8 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -43,7 +43,7 @@ class NewProjectDialog(QDialog): self.dragging = False self.drag_position = QPoint() self.icon_manager = get_icon_manager() - self._title_icon_size = QSize(14, 14) + self._title_icon_size = QSize(18, 18) self._icon_close = self.icon_manager.get_icon('close_icon', self._title_icon_size) # 设置严格按照Figma设计的样式 @@ -81,10 +81,12 @@ class NewProjectDialog(QDialog): border: none; color: #EBEBEB; font-size: 14px; - min-width: 32px; - max-width: 32px; - min-height: 32px; - max-height: 32px; + min-width: 18px; + max-width: 18px; + min-height: 18px; + max-height: 18px; + padding: 0px; + border-radius: 3px; } QWidget#controlButtons QPushButton:hover { background-color: #2A2D2E; @@ -94,7 +96,7 @@ class NewProjectDialog(QDialog): border-radius: 0px 5px 0px 0px; } QPushButton#closeButton:hover { - background-color: #E74C3C; + background-color: #2A2D2E; color: #FFFFFF; } QWidget#contentWidget { @@ -431,25 +433,25 @@ class NewProjectDialog(QDialog): # 获取并验证路径 self.projectPath = self.pathEdit.text() if not self.projectPath: - StyledMessageBox.warning(self, "错误", "请选择项目路径!") + UniversalMessageDialog.show_warning(self, "错误", "请选择项目路径!", show_cancel=False, confirm_text="确认") return # 获取并验证项目名称 self.projectName = self.nameEdit.text().strip() if not self.projectName: - StyledMessageBox.warning(self, "错误", "请输入项目名称!") + UniversalMessageDialog.show_warning(self, "错误", "请输入项目名称!", show_cancel=False, confirm_text="确认") return # 验证项目名称格式 if not re.match(r'^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$', self.projectName): - StyledMessageBox.warning(self, "错误", - "项目名称只能包含字母、数字、下划线、中划线和中文!") + UniversalMessageDialog.show_warning(self, "错误", + "项目名称只能包含字母、数字、下划线、中划线和中文!", show_cancel=False, confirm_text="确认") return # 检查项目是否已存在 full_path = os.path.join(self.projectPath, self.projectName) if os.path.exists(full_path): - StyledMessageBox.warning(self, "错误", "项目已存在!") + UniversalMessageDialog.show_warning(self, "错误", "项目已存在!", show_cancel=False, confirm_text="确认") return self.accept() @@ -849,21 +851,21 @@ class CustomAssetsTreeWidget(QTreeWidget): """删除文件或文件夹""" import os import shutil - from PyQt5.QtWidgets import QMessageBox filepath = item.data(0, Qt.UserRole) is_folder = item.data(0, Qt.UserRole + 1) item_type = "文件夹" if is_folder else "文件" - reply = StyledMessageBox.question( - self, - "确认删除", + result = UniversalMessageDialog.show_warning( + self, + "确认删除", f"确定要删除这个{item_type}吗?\n{filepath}", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No + show_cancel=True, + confirm_text="删除", + cancel_text="取消" ) - if reply == QMessageBox.Yes: + if result == QDialog.Accepted: try: if is_folder: shutil.rmtree(filepath) @@ -873,6 +875,13 @@ class CustomAssetsTreeWidget(QTreeWidget): print(f"删除{item_type}: {filepath}") except OSError as e: print(f"删除{item_type}失败: {e}") + UniversalMessageDialog.show_error( + self, + "错误", + f"删除{item_type}失败: {e}", + show_cancel=False, + confirm_text="确认" + ) def copyPath(self, filepath): """复制路径到剪贴板""" @@ -903,7 +912,6 @@ class CustomAssetsTreeWidget(QTreeWidget): def showProperties(self, item): """显示属性面板""" import os - from PyQt5.QtWidgets import QMessageBox filepath = item.data(0, Qt.UserRole) is_folder = item.data(0, Qt.UserRole + 1) @@ -926,10 +934,23 @@ class CustomAssetsTreeWidget(QTreeWidget): 修改时间: {modified_str} """ - StyledMessageBox.information(self, "属性", properties.strip()) + UniversalMessageDialog.show_info( + self, + "属性", + properties.strip(), + show_cancel=False, + confirm_text="确认" + ) except OSError as e: - StyledMessageBox.warning(self, "错误", f"无法获取属性: {e}") + UniversalMessageDialog.show_warning( + self, + "错误", + f"无法获取属性: {e}", + show_cancel=False, + confirm_text="确认" + ) + # def mouseDoubleClickEvent(self, event): # """处理双击事件""" @@ -2105,6 +2126,392 @@ class StyledMessageBox: ok = dialog.exec_() return dialog.textValue(), ok +class UniversalMessageDialog(QDialog): + """通用消息对话框类 - 支持不同图标和按钮配置""" + + # 消息类型枚举 + SUCCESS = "success_icon" + WARNING = "warning_icon" + ERROR = "fail_icon" + INFO = "info" + + def __init__(self, parent, title, message, message_type=INFO, show_cancel=True, + confirm_text="确认", cancel_text="取消", icon_size=QSize(48, 48)): + """ + 初始化通用消息对话框 + + Args: + parent: 父窗口 + title: 对话框标题 + message: 消息内容 + message_type: 消息类型 (SUCCESS, WARNING, ERROR, INFO) + show_cancel: 是否显示取消按钮 + confirm_text: 确认按钮文字 + cancel_text: 取消按钮文字 + icon_size: 图标尺寸 + """ + super().__init__(parent) + self.setWindowTitle(title) + self.setObjectName("universalMessageDialog") + self.setModal(True) + self.resize(508, 134) + self.setMinimumSize(508, 134) + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_TranslucentBackground, True) + + # 对话框配置 + self.message_type = message_type + self.show_cancel = show_cancel + self.confirm_text = confirm_text + self.cancel_text = cancel_text + self.icon_size = icon_size + + # 拖拽相关 + self.dragging = False + self.drag_position = QPoint() + + # 图标管理 + self.icon_manager = get_icon_manager() + self._title_icon_size = QSize(18, 18) + self._icon_close = self.icon_manager.get_icon('close_icon', self._title_icon_size) + + # 根据消息类型获取对应图标 + self._message_icon = self._get_message_icon() + + # 设置样式 + self._setup_styles() + + # 创建UI + self._create_ui(message) + + def _get_message_icon(self): + """根据消息类型获取对应图标""" + icon_map = { + self.SUCCESS: 'success_icon', + self.WARNING: 'warning_icon', + self.ERROR: 'fail_icon', + self.INFO: 'success_icon' # 默认使用成功图标 + } + + icon_name = icon_map.get(self.message_type, 'success_icon') + return self.icon_manager.get_icon(icon_name, self.icon_size) + + def _setup_styles(self): + """设置对话框样式""" + self.setStyleSheet(""" + QDialog#universalMessageDialog { + background-color: transparent; + border: none; + } + QFrame#baseFrame { + background-color: #19191B; + border: 1px solid #3E3E42; + border-radius: 5px; + } + QWidget#titleBar { + background-color: transparent; + border: none; + border-radius: 5px 5px 0px 0px; + min-height: 32px; + max-height: 32px; + } + QWidget#titleBar QWidget { + background-color: transparent; + border: none; + } + QLabel#titleLabel { + color: #FFFFFF; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + } + QWidget#controlButtons QPushButton { + background-color: transparent; + border: none; + color: #EBEBEB; + font-size: 14px; + min-width: 18px; + max-width: 18px; + min-height: 18px; + max-height: 18px; + padding: 0px; + border-radius: 3px; + } + QWidget#controlButtons QPushButton:hover { + background-color: #2A2D2E; + color: #FFFFFF; + } + QPushButton#closeButton { + border-radius: 0px 5px 0px 0px; + } + QPushButton#closeButton:hover { + background-color: #2A2D2E; + color: #FFFFFF; + } + QFrame#titleSeparator { + background-color: #3E3E42; + border: none; + min-height: 1px; + max-height: 1px; + } + QWidget#contentWidget { + background-color: transparent; + border: none; + } + QLabel#messageLabel { + color: #EBEBEB; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 13px; + font-weight: 400; + letter-spacing: 0.6px; + line-height: 1.4; + padding: 0px; + margin: 0px; + } + QPushButton { + background-color: rgba(89, 98, 118, 0.5); + color: #EBEBEB; + border: none; + border-radius: 2px; + padding: 0px 12px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-width: 90px; + max-width: 90px; + min-height: 28px; + max-height: 28px; + } + QPushButton:hover { + background-color: #3067C0; + color: #FFFFFF; + } + QPushButton:pressed { + background-color: #2556A0; + color: #FFFFFF; + } + QPushButton#primaryButton { + background-color: rgba(89, 98, 118, 0.5); + border: none; + color: #EBEBEB; + font-weight: 300; + } + QPushButton#primaryButton:hover { + background-color: #2556A0; + } + QPushButton#primaryButton:pressed { + background-color: #1E4A8C; + } + QPushButton#secondaryButton { + background-color: rgba(89, 98, 118, 0.5); + border: none; + color: #EBEBEB; + } + QPushButton#secondaryButton:hover { + background-color: #3067C0; + color: #FFFFFF; + } + QPushButton#secondaryButton:pressed { + background-color: #2556A0; + color: #FFFFFF; + } + """) + + def _create_ui(self, message): + """构建用户界面""" + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + base_frame = QFrame() + base_frame.setObjectName('baseFrame') + base_frame.setFrameShape(QFrame.NoFrame) + base_frame.setAttribute(Qt.WA_StyledBackground, True) + + base_layout = QVBoxLayout(base_frame) + base_layout.setContentsMargins(25, 14, 25, 14) + base_layout.setSpacing(0) + + self._create_title_bar() + base_layout.addWidget(self.title_bar) + base_layout.addSpacing(4) + + title_separator = QFrame() + title_separator.setObjectName('titleSeparator') + title_separator.setFrameShape(QFrame.HLine) + title_separator.setFrameShadow(QFrame.Plain) + base_layout.addWidget(title_separator) + base_layout.addSpacing(10) + + content_widget = QWidget() + content_widget.setObjectName('contentWidget') + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(10) + + message_area = QHBoxLayout() + message_area.setContentsMargins(0, 0, 0, 0) + message_area.setSpacing(15) + + icon_label = QLabel() + if self._message_icon and not self._message_icon.isNull(): + icon_label.setPixmap(self._message_icon.pixmap(self.icon_size)) + icon_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) + icon_label.setFixedSize(self.icon_size) + message_area.addWidget(icon_label) + + self.message_label = QLabel(message) + self.message_label.setObjectName('messageLabel') + self.message_label.setWordWrap(True) + self.message_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) + self.message_label.setMinimumHeight(self.icon_size.height()) + self.message_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) + message_area.addWidget(self.message_label, 1) + message_area.addStretch() + + content_layout.addLayout(message_area) + base_layout.addWidget(content_widget) + base_layout.addSpacing(10) + + button_widget = QWidget() + button_layout = QHBoxLayout(button_widget) + button_layout.setContentsMargins(0, 0, 0, 0) + button_layout.setSpacing(10) + button_layout.addStretch() + + if self.show_cancel: + self.cancel_button = QPushButton(self.cancel_text) + self.cancel_button.setObjectName("secondaryButton") + self.cancel_button.clicked.connect(self.reject) + self.cancel_button.setFixedSize(90, 28) + button_layout.addWidget(self.cancel_button) + + self.confirm_button = QPushButton(self.confirm_text) + self.confirm_button.setObjectName("primaryButton") + self.confirm_button.clicked.connect(self.accept) + self.confirm_button.setFixedSize(90, 28) + button_layout.addWidget(self.confirm_button) + + self.confirm_button.setDefault(True) + self.confirm_button.setAutoDefault(True) + if self.show_cancel: + self.cancel_button.setAutoDefault(False) + + base_layout.addWidget(button_widget) + + main_layout.addWidget(base_frame) + + def _create_title_bar(self): + """创建自定义标题栏""" + self.title_bar = QFrame() + self.title_bar.setObjectName("titleBar") + + title_layout = QHBoxLayout(self.title_bar) + title_layout.setContentsMargins(0, 0, 0, 0) + title_layout.setSpacing(0) + + self.title_label = QLabel(self.windowTitle()) + self.title_label.setObjectName("titleLabel") + self.title_label.setAlignment(Qt.AlignVCenter | Qt.AlignLeft) + title_layout.addWidget(self.title_label, 1) + + title_layout.addStretch() + + controls = QWidget() + controls.setObjectName("controlButtons") + controls_layout = QHBoxLayout(controls) + controls_layout.setContentsMargins(0, 0, 0, 0) + controls_layout.setSpacing(0) + + self.close_button = QPushButton() + self.close_button.setObjectName("closeButton") + self.close_button.clicked.connect(self.reject) + self.close_button.setFocusPolicy(Qt.NoFocus) + controls_layout.addWidget(self.close_button) + + self._apply_title_bar_icons() + + title_layout.addWidget(controls) + + + def _apply_title_bar_icons(self): + """应用标题栏图标""" + if self._icon_close: + self.close_button.setIcon(self._icon_close) + self.close_button.setIconSize(self._title_icon_size) + self.close_button.setText("") + self.close_button.setToolTip("关闭") + + def setWindowTitle(self, title): + """设置窗口标题""" + super().setWindowTitle(title) + if hasattr(self, "title_label"): + self.title_label.setText(title) + + def mousePressEvent(self, event): + """鼠标按下事件 - 用于拖拽窗口""" + if event.button() == Qt.LeftButton and self.title_bar.geometry().contains(event.pos()): + self.dragging = True + self.drag_position = event.globalPos() - self.frameGeometry().topLeft() + event.accept() + super().mousePressEvent(event) + + def mouseMoveEvent(self, event): + """鼠标移动事件 - 用于拖拽窗口""" + if event.buttons() == Qt.LeftButton and self.dragging: + self.move(event.globalPos() - self.drag_position) + event.accept() + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + """鼠标释放事件 - 停止拖拽""" + if event.button() == Qt.LeftButton: + self.dragging = False + super().mouseReleaseEvent(event) + + @staticmethod + def show_success(parent, title, message, show_cancel=False, confirm_text="确认"): + """显示成功消息对话框""" + dialog = UniversalMessageDialog( + parent, title, message, + UniversalMessageDialog.SUCCESS, + show_cancel, confirm_text + ) + return dialog.exec_() + + @staticmethod + def show_warning(parent, title, message, show_cancel=True, confirm_text="确认", cancel_text="取消"): + """显示警告消息对话框""" + dialog = UniversalMessageDialog( + parent, title, message, + UniversalMessageDialog.WARNING, + show_cancel, confirm_text, cancel_text + ) + return dialog.exec_() + + @staticmethod + def show_error(parent, title, message, show_cancel=False, confirm_text="确认"): + """显示错误消息对话框""" + dialog = UniversalMessageDialog( + parent, title, message, + UniversalMessageDialog.ERROR, + show_cancel, confirm_text + ) + return dialog.exec_() + + @staticmethod + def show_info(parent, title, message, show_cancel=True, confirm_text="确认", cancel_text="取消"): + """显示信息消息对话框""" + dialog = UniversalMessageDialog( + parent, title, message, + UniversalMessageDialog.INFO, + show_cancel, confirm_text, cancel_text + ) + return dialog.exec_() + + class StyledTextInputDialog(QDialog): """与新建项目样式一致的文本输入对话框""" @@ -2937,12 +3344,17 @@ class CustomTreeWidget(QTreeWidget): else: message = f"确定要删除 {item_count} 个节点吗?" - reply = StyledMessageBox.question( - self, "确认删除", message, - QMessageBox.Yes | QMessageBox.No, QMessageBox.No + dialog = UniversalMessageDialog( + self, + "确认删除", + message, + message_type=UniversalMessageDialog.WARNING, + show_cancel=True, + confirm_text="确认", + cancel_text="取消" ) - if reply != QMessageBox.Yes: + if dialog.exec_() != QDialog.Accepted: return # 默认选中场景根节点,通常是第一个顶级节点 From a4e5d834db3462e345d34138d823cd00a7b09a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Sun, 19 Oct 2025 23:05:56 +0800 Subject: [PATCH 6/7] =?UTF-8?q?1.UI=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 54 + RenderPipelineFile/config/daytime.yaml | 4 +- Resources/a | 0 Resources/icons/heightmap.png | Bin 0 -> 8583 bytes icons/property_select_image.png | Bin 0 -> 400 bytes icons/success_icon.png | Bin 0 -> 546 bytes icons/warning_icon.png | Bin 0 -> 482 bytes new/project.json | 8 + quick_menu_fix.py | 277 ++++ scripts/a.py | 28 + ui/icon_manager.py | 3 + ui/main_window.py | 57 +- ui/property_panel.py | 1711 ++++++++++++++---------- ui/widgets.py | 57 +- 14 files changed, 1483 insertions(+), 716 deletions(-) create mode 100644 Resources/a create mode 100644 Resources/icons/heightmap.png create mode 100644 icons/property_select_image.png create mode 100644 icons/success_icon.png create mode 100644 icons/warning_icon.png create mode 100644 new/project.json create mode 100644 quick_menu_fix.py create mode 100644 scripts/a.py diff --git a/.gitignore b/.gitignore index aa6cef21..efd048cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,57 @@ __pycache__/ *.pyc .idea/ +【模板】模型及算法与功能对应清单.docx +【模板】GY知识-产品模块架构及功能清单和主要应用场景说明.docx +1.json +2.json +3.json +4.json +6.json +功能清单.md +模型及算法与功能对应清单(完整版).md +维修系统使用说明.md +audio_demo_config.json +create_test_audio.py +demo_universal_dialog.py +Dock按钮样式统一说明.md +Dock面板标题样式线完成说明.md +exam_result_20250926_004454.json +exam_result_20250926_010628.json +exam_result_20250926_011746.json +exam_result_20250926_090330.json +exam_result_20250926_111756.json +exam_test_config.json +Figma布局优化说明.md +Figma设计分析与菜单栏优化文档.md +Figma样式优化完成说明.md +manual_tools_config.json +pm.txt +simple_menubar_test.py +test_audio.py +test_basic_audio.py +test_exam_tool_error.py +test_full_audio_playback.py +test_menu_visibility.py +test_mode_selection.py +test_mp3_audio.py +test_pygame_audio.py +test_score_config.py +test_simple_audio.py +tool_test_config.json +UI设计文档.md +UniversalMessageDialog使用说明.md +.codex/settings/kiroCodex-settings.json +data/projects.json +RenderPipelineFile/config/daytime.yaml +Resources/audio/README.md +Resources/model/a.glb +Resources/model/b.glb +Resources/model/c.glb +Resources/model/JQB_auto_converted.glb +Resources/model/Untitled.glb +Resources/models/Women_1.glb +Subjects/test_maintenance_config.json +Resources/models/Women_1.glb +Resources/models/Women_1.glb +Resources/models/Women_1.glb diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index 6bb60b02..030a6111 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -17,8 +17,8 @@ control_points: scattering: sun_intensity: [[[0.0000000000,0.0000000000],[0.0041666667,0.0000000000],[0.0083333333,0.0000000000],[0.0125000000,0.0000000000],[0.0166666667,0.0000000000],[0.0208333333,0.0000000000],[0.0250000000,0.0000000000],[0.0291666667,0.0000000000],[0.0333333333,0.0000000000],[0.0375000000,0.0000000000],[0.0416666667,0.0000000000],[0.0458333333,0.0000000000],[0.0500000000,0.0000000000],[0.0541666667,0.0000000000],[0.0583333333,0.0000000000],[0.0625000000,0.0000000000],[0.0666666667,0.0000000000],[0.0708333333,0.0000000000],[0.0750000000,0.0000000000],[0.0791666667,0.0000000000],[0.0833333333,0.0000000000],[0.0875000000,0.0000000000],[0.0916666667,0.0000000000],[0.0958333333,0.0000000000],[0.1000000000,0.0000000000],[0.1041666667,0.0000000000],[0.1083333333,0.0000000000],[0.1125000000,0.0000000000],[0.1166666667,0.0000000000],[0.1208333333,0.0000000000],[0.1250000000,0.0000000000],[0.1291666667,0.0000000000],[0.1333333333,0.0000000000],[0.1375000000,0.0000000000],[0.1416666667,0.0000000000],[0.1458333333,0.0000000000],[0.1500000000,0.0000000000],[0.1541666667,0.0000000000],[0.1583333333,0.0000028805],[0.1625000000,0.0003577724],[0.1666666667,0.0013331400],[0.1708333333,0.0029671803],[0.1750000000,0.0052963381],[0.1791666667,0.0083550556],[0.1833333333,0.0121755589],[0.1875000000,0.0167876159],[0.1916666667,0.0222183530],[0.1958333333,0.0284919947],[0.2000000000,0.0356297193],[0.2041666667,0.0436494349],[0.2083333333,0.0525656099],[0.2125000000,0.0623891610],[0.2166666667,0.0731272461],[0.2208333333,0.0847831708],[0.2250000000,0.0973563167],[0.2291666667,0.1108419698],[0.2333333333,0.1252313631],[0.2375000000,0.1405115250],[0.2416666667,0.1566653434],[0.2458333333,0.1736715009],[0.2500000000,0.1915046014],[0.2541666667,0.2101350464],[0.2583333333,0.2295292930],[0.2625000000,0.2496498145],[0.2666666667,0.2704552670],[0.2708333333,0.2919006662],[0.2750000000,0.3139375192],[0.2791666667,0.3365139497],[0.2833333333,0.3595750662],[0.2875000000,0.3830630359],[0.2916666667,0.4069173972],[0.2958333333,0.4310753462],[0.3000000000,0.4554720417],[0.3041666667,0.4800408236],[0.3083333333,0.5047136020],[0.3125000000,0.5294212108],[0.3166666667,0.5540936424],[0.3208333333,0.5786605298],[0.3250000000,0.6030514553],[0.3291666667,0.6271963182],[0.3333333333,0.6510256858],[0.3375000000,0.6744711982],[0.3416666667,0.6974659988],[0.3458333333,0.7199450163],[0.3500000000,0.7418453485],[0.3541666667,0.7631067095],[0.3583333333,0.7836717291],[0.3625000000,0.8034862953],[0.3666666667,0.8224999302],[0.3708333333,0.8406661079],[0.3750000000,0.8579425235],[0.3791666667,0.8742914270],[0.3833333333,0.8896799131],[0.3875000000,0.9040801386],[0.3916666667,0.9174695289],[0.3958333333,0.9298310650],[0.4000000000,0.9411533765],[0.4041666667,0.9514309312],[0.4083333333,0.9606641691],[0.4125000000,0.9688595571],[0.4166666667,0.9760296330],[0.4208333333,0.9821930708],[0.4250000000,0.9873746114],[0.4291666667,0.9916050060],[0.4333333333,0.9949209310],[0.4375000000,0.9973647924],[0.4416666667,0.9989845508],[0.4458333333,0.9998334497],[0.4500000000,0.9999696949],[0.4541666667,0.9994560801],[0.4583333333,0.9983595429],[0.4625000000,0.9967506613],[0.4666666667,0.9947030614],[0.4708333333,0.9922927758],[0.4750000000,0.9895975125],[0.4791666667,0.9866958610],[0.4833333333,0.9836664262],[0.4875000000,0.9805868867],[0.4916666667,0.9775330316],[0.4958333333,0.9745777179],[0.5000000000,0.9717898417],[0.5041666667,0.9692332877],[0.5083333333,0.9669658924],[0.5125000000,0.9650384806],[0.5089595376,0.9690650222],[0.5208333333,0.9623666659],[0.5250000000,0.9616814371],[0.5291666667,0.9614534423],[0.5333333333,0.9616877089],[0.5375000000,0.9623790807],[0.5416666667,0.9635123329],[0.5458333333,0.9650624244],[0.5500000000,0.9669949804],[0.5541666667,0.9692669864],[0.5583333333,0.9718275065],[0.5625000000,0.9746185969],[0.5666666667,0.9775762863],[0.5708333333,0.9806315864],[0.5750000000,0.9837115661],[0.5791666667,0.9867403433],[0.5833333333,0.9896401655],[0.5875000000,0.9923323562],[0.5916666667,0.9947382579],[0.5958333333,0.9967800977],[0.6000000000,0.9983817820],[0.6041666667,0.9994696263],[0.6083333333,0.9999730028],[0.6125000000,0.9998249266],[0.6166666667,0.9989625601],[0.6208333333,0.9973276624],[0.6250000000,0.9948669567],[0.6291666667,0.9915324664],[0.6333333333,0.9872817545],[0.6375000000,0.9820781426],[0.6416666667,0.9758908775],[0.6458333333,0.9686952146],[0.6500000000,0.9604725211],[0.6541666667,0.9512102537],[0.6583333333,0.9409019858],[0.6625000000,0.9295473441],[0.6666666667,0.9171518878],[0.6708333333,0.9037270619],[0.6750000000,0.8892899902],[0.6791666667,0.8738633008],[0.6833333333,0.8574749656],[0.6875000000,0.8401579787],[0.6916666667,0.8219502453],[0.6958333333,0.8028941798],[0.7000000000,0.7830364456],[0.7041666667,0.7624277344],[0.7083333333,0.7411222520],[0.7125000000,0.7191776044],[0.7166666667,0.6966542563],[0.7208333333,0.6736152714],[0.7250000000,0.6501259629],[0.7291666667,0.6262533880],[0.7333333333,0.6020661121],[0.7375000000,0.5776338043],[0.7416666667,0.5530267796],[0.7458333333,0.5283156992],[0.7500000000,0.5035711751],[0.7541666667,0.4788634341],[0.7583333333,0.4542618347],[0.7625000000,0.4298347613],[0.7666666667,0.4056490351],[0.7708333333,0.3817697830],[0.7750000000,0.3582600107],[0.7791666667,0.3351803495],[0.7833333333,0.3125888445],[0.7875000000,0.2905406366],[0.7916666667,0.2690876955],[0.7958333333,0.2482787388],[0.8000000000,0.2281588906],[0.8041666667,0.2087696425],[0.8083333333,0.1901486315],[0.8125000000,0.1723295359],[0.8166666667,0.1553419918],[0.8208333333,0.1392115328],[0.8250000000,0.1239595144],[0.8291666667,0.1096030703],[0.8333333333,0.0961551918],[0.8375000000,0.0836246599],[0.8416666667,0.0720161369],[0.8458333333,0.0613302273],[0.8500000000,0.0515635598],[0.8541666667,0.0427088803],[0.8583333333,0.0347551990],[0.8625000000,0.0276878920],[0.8666666667,0.0214889271],[0.8708333333,0.0161369711],[0.8750000000,0.0116076130],[0.8791666667,0.0078735477],[0.8833333333,0.0049047927],[0.8875000000,0.0026688977],[0.8916666667,0.0011311782],[0.8958333333,0.0002549473],[0.9000000000,0.0000000000],[0.9041666667,0.0000000000],[0.9083333333,0.0000000000],[0.9125000000,0.0000000000],[0.9166666667,0.0000000000],[0.9208333333,0.0000000000],[0.9250000000,0.0000000000],[0.9291666667,0.0000000000],[0.9333333333,0.0000000000],[0.9375000000,0.0000000000],[0.9416666667,0.0000000000],[0.9458333333,0.0000000000],[0.9500000000,0.0000000000],[0.9541666667,0.0000000000],[0.9583333333,0.0000000000],[0.9625000000,0.0000000000],[0.9666666667,0.0000000000],[0.9708333333,0.0000000000],[0.9750000000,0.0000000000],[0.9791666667,0.0000000000],[0.9833333333,0.0000000000],[0.9875000000,0.0000000000],[0.9916666667,0.0000000000],[0.9958333333,0.0000000000]]] sun_color: [[[0.5010435645,0.5818710306],[0.0433100000,0.8999700000],[0.8635787716,0.9130000000],[0.1785000000,0.8973600000],[0.8099800000,0.8651100000],[0.2360800000,0.7712700000],[0.6583432177,0.8485126184],[0.1266806142,0.9648102053],[0.9558541267,0.9090909091],[0.5568400771,0.7353760446]],[[0.5001318426,0.5160300000],[0.0572700000,0.6541600000],[0.2395000000,0.5976800000],[0.8104600000,0.6009000000],[0.6967400000,0.5483900000]],[[0.0862400000,0.4257800000],[0.4955600000,0.4033000000],[0.8234200000,0.4340200000]]] - sun_azimuth: [[[0.5000000000,0.4944444444]]] - sun_altitude: [[[0.5000000000,0.9666666667]]] + sun_azimuth: [[[0.5000000000,0.4972222222]]] + sun_altitude: [[[0.5000000000,0.9555555556]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: fog_ramp_size: [[[0.5510597303,0.7409470752]]] diff --git a/Resources/a b/Resources/a new file mode 100644 index 00000000..e69de29b diff --git a/Resources/icons/heightmap.png b/Resources/icons/heightmap.png new file mode 100644 index 0000000000000000000000000000000000000000..33b6c701179e7505e8ddd75c0996d730ac8800cd GIT binary patch literal 8583 zcmXxq4{Vj!c?R%{&$*!qWz;ie7WI(pS{6bON~lW+aV=_xfI|oYWFZCxLc*V^q(}bdYZrE)AK7{GJpJX&95?6sUbm$7F zW1l!R-W!e>aV)gO^3Wc-(>u?l&RlMeZQ;R?7dm6DS?a@IhJrBO@#*HR3y*}VFd&qL z{4gLB8$x$n5X)n292q(VI~%Tq9uvOh#EphFg$C~yh6|=o0C$F#a2wzYacInjYe4NV z?qawk_#x--bpGP73*={ksW9^W*jK=Uyc^-Eup-y>Spu_JPnDjsti&tPk(w z?b)zNxNYIF_@l5o7KU0gtq3oNN6oc7R7$Wi49NQ+{6+XiCeDn{kYRvFaX5&+UBWej zay%SB^HwLn8rGP$EfxS<6^;mc!TWLY?~N5$>j@`3&JnMUNGAX|?%g|Kw^2`ruGo#2 zg+N@!&SLY;43miRkmt7mS(?d)dnVoUMD`zfqD?mY1aEd)y{UG1HkoFtDVoDgkGtV| zxD*THpjZ-%5-0JEa4TFT$T3{p4J~mM@fvZlA-rgoGVu4Cr#`HUV{xzrM_c)75@9Ox zew+6nc?ZJ7nYbwaTU-(YjOWpLRPG0mRp-?60JMQ|C7eZj-&hn&<8yIktcVk0pKt@c z7viuu(Ab_>BW$A)?U+36{i`4>#OQRN_JlQzdk2)I9Pn6p5(7(pGZYWs&imJltkYwk z;a3D~gZez{T|wqyM^7R$$a}O`B04^to;4*n60Z^*(H=!do6QMQ^mP9{EZWnE@<{Ljbn*WrU+5}^~QgKDG_kXKd9lS+0WKcwzanGqNS8Sa94HRW3!-Wob`6O3lO zA$(uX0j1e5$F<6Fgj|Z^j~stBF~F9+z}2gOZlN!D+#=&4^|8&Ir&Z8@GxBaxp7**1 z^+%mI%llOFkA+8)5N)2_&_9jJjRx=M>}pe7V!M^dAH{%y^7G?TSv@2BiGY;qj`?vG z4ki+6MErz3TRbnxH*v6uA-4e55Von9cj6ckw(-|~3ATjwKHpR$Uk()+UQR;Xq2UHY z&nvHvgr0?NB7a64FVN_49pw|)vWWRcfHM|93*ri%7)i9N^1fn32lLL*y2tZF4;S=R zL1l_C0okYQkBV|YwAJC85`IwP6=88k$h~1dHT#6ys=kvH4GAm9uVA_U0+wrEePWed zX8}@&_zAH}L$8RF<14Y4I1`lZpwL8|Q?W+Qw>=x=x>e2%Xg@CWwy-&##m%cKVuO0v zq$(DMdC88q!+B=9ZoqD*-i5TuGxfrvSZ@0A?2{x|5I+@i6nj>J(}x)Ig}D?*sDW#7 zfbm_7In3X!Krb+Q2~Qo8^KRnSXa5H2?P0qf(oCv)UN}Ui;TA5-R6nGhbE-)afLzfu z@^U{`tJIQHD;k4|Il_-s9sP4ns?1t3JalKHtoth4JH9E1b0pGf2T&qe}U+wRH zGjEjO@o-S0YKzKA0hf?GQh08RK_d^Gq@2lyni0|QpQ8KCq3hL;@x0#+>RwebU3eAj<-qMM1=JoyGYU$nmOqRlh$G8MJvUV;oFw2 z?`52LJmryeOkH32oy>JHK9?=z@&&^FAbyd3g0Vi-$S1N}1%qY@QES{pat*^sez;7Y zqS)qDFU4qDagIrAJ?d}Z3skzw8sQVUb{fnD*7oLgqZH#O{_F4A&!D7)QEt4J}@nG?ydg##|fQHXD5 z*}(v8A^Lg!F`2TCEg#D$oqYZ-mfL%#9;%oQ^2VXKmi{XUumZ7f0x*{&UNChQxIX?q zXT)+arpmp>_#ekN;_&QbM*VSoI~I_((uSs6wRGh;*oEV?=}D{QHrYSRCF{bZyqg(1 zkCIuL>4ySSV)|5|tvTGKjr`Wg$~03xj;Y0a2r>}8+> z-Oq!Vb|7u?z9{rQ{iBX2+JTtFz`eN03u`iQr(xByoTA<+08pB(UHswD;_#2?i#!{&)C$jG?#(P8HInJ-} zn60YLunfa-3wxJzWB93nLrR=G9+y3i4j|Ydv)EOH zW68*7J{@+FdpI|&v1~1mFNJv-*vFW%OpswZerm@>hMW%v&|QoC4RM)FC&;uK$pJUJ z9G`4VA-Jtz7J6SI;5DHyo1>U@`(g96?BAAe9nFvH4SNM{1+Fe!h{buow{qM}+uRJ) zH8PrwiGd#S({itC%*`bD7=#g;(FFCemMAO8F*j~R_85X>m2tjtD>-SnjOQelK)D@G zfLjF8Ud8ynPs8-b(}FjFI8v+bvT?niFOGq*EF+&Wruu{r#;fh~Chcp|^3Y+LGlKp? z$f|IIAcag;&mSY`J~~&-R=ZVR|*@wXy&#RZzK?%1vVxgK8Rp_tvOuL0jAt&QuJ{m7zZIh!) zQ2x^hb54W5W9%@E>8|sJfKaCZ_lJ`hIRW@jHS1Q(TiTN^cD=GMG(2Ij!f|fdSHg-qGQXpE8 zzD7B2P@ZSBr(=#Z*xnwrYqCa6mDwUD94^dg^bO|WWWxc5uOh^o@dG@J!qY6zC4#Tj z6OtE7NjA!nB7|!5y2+5V4x<0Mc?MV+4#Y2V`+`RKpOf`g7VHIO9WPX=h-O}xnIKHX z#C#c!%DFsUA^ZTWaTH(T?@XeU133AyAh!?rs~pZkBAp#4e$aTQg6QV2Azm z_X)Hytr1f&Fjs&g3-f2GRTJR7_-_0tyE6M7eXZ58cVoWYQ@)yMm07x;?1?s@#w;^K zH8zG)zE%?X;R(T-nK7Ra?hxr>Xv^_)kPuzCx#V%hHRc*{8p8}*7Ui}`kO~=(7Jfsn zyq?3r98l6Gy+Mdt@)RKaZmu8n_l;7f?oBdmVC1p6wIhi+A`V6I9t;0nXZKG9jfI1} zk?uZh1iTFkn?NXYWOH~YBj{KTpM{B;=$!0R9}~UCkgGtNX_7&42IBk0<&3)2v8il8&It8d{JEN#Cd2BS;i~gN_rplo&(34vEF=mu`lXg$`f~R7MA|^SyR6ARQ%v!_;Mc|d9{va7{ zal}RM_poA*(mdnyWzb%<4nJ?1KFAHvhZ7k-n4kwt^Y~PO%@H*1{XX})Q^t+T^9A46 z%6VD#;q2czFKtG~f{`|GQ-vMvNPj>^@kiRo%yPl{Ed|$wlT&)b5S*L}o%%;WVY4DUYmItADze4i?2peB}fnkyCE zRcxePM@bG`rO>bHlD%|4PLy7SJB&yZ$kA-ylrC?sVP}VPj@Z0eBWh4LMSxADNLLCe z_f@$);oSJbJ*(pprd+Fc)BuxATj*VgzmEO0_vH*Y%;zdQq8qu+ItpJ?TrJ%UM`GDM z`2cjrLAegm$L6I4gmlGy*7CI9Lf7L4;MTN8q|NJ6HvE~&c-s^|^!MHPlUS4Oll^n~ zA7;tlsg)OGnr;ymWnb~P8k}HMz1&CRQ6PKEv?gM?UX0eXcMSJ<*0VY5%^y9muwv9u)4lxHQ|4U+8?<{QXp z?QyP}vHf1m*d(uku~M-Ee*3l=p9kGwNoLx;U*Uj}xUnoCSEdOBr_jg!9+Z zdwDLWv%1?A%k5Cle#~~SVMk1K$h!)lM(cFf^v8=@%w4jTljB}O-1M5vm~N1s_1z`i zaVIu*X;K%7ywpzYB!hN4mddotzTtaTPs`LwRMx_uR>?&92bQYO8BZv|JCXLM$2nOn zk>%R>6GLwTayo4Axg;FN$!5$HVBnfCZ@FBxc~yd!g}tg$=I~J8Sf)qzCB-4*X2fX< zbZgjCPYYyjai`{Pd(=X;bMN6l%d%!U^0L$6TB6k?Zr>=1F`zKV$_0CUNa z((H%M9pKZ|uneR&Tw2t^34(;|m%uw>^lhx9dvl$dd!rkG>r+L1DClP%jV{*S0OIr5 zW4G`MQ?{y$5sEb398Nd6R++wmqC>)^?1NUkR?y0_Z^T!^$z)C0{sGXzsJ2MSxlcG1 z3%$R?`Q6SOESZz+d6qG+OK7_L3y+5FnQX+v&*D!#exn?J8vmdv9R}>p_XGh1Ezedf%Bz?i z9Y5lv7d3-m-*hCCy(g#sFI^#aXTL*?U+55NZRk=N%_Mn7$xgFe_o8*VV29Ew2}%HC zIRV~`Q?kEj>M8M$EIY;x)6;I_Tof+d_^Y#JU2j2p3pX#@OAbPRJ2AFfJKA{RW_TKe z?8xk&Wd~W7K8?S0@v;dQZwmfiZuy?+b%EZOx?e3|F3RF4?RB<5SL_Cc#}(Nc^j>1V z>+z-RUt5OGsfR~}d)=b61Nj%6>k`px+C!dm?J(FveS;i_LEE08`_HovYf10Yf2L=> z^{^3$rLzAg?ns;435++zz#CX7O9tcgYXFpCDs4`0k#ZW>G^%);ST0C+DDvId+s~L? zn)o3XF$JKdTkIWSCy#6dGu@-NMx4#AlwB>F=gYW?Y8x=J6v_3V%vQAHG%puu+DnV| zVTMX51fj(@m6oe6?Whk?W(s~@iMk00-d=Szh5(Im7;6qe_!;tTCc{GO!l7J$+^J1J z8ortK3oi)04AI4U-&|Rb$u-|)SSVw@Qf^LHNhQ?0qoG_9>S#FYo3vvX&4Bk}pg>dZ zae30CNBjWYH|;1}{N}PviS~N5;ZqrdaMJM|T2qJJ<2N-YcVDZyemI||+uSL5z6zKi z=V|uNEp`R=HQ_Dv?^cj$H*^96ZE=Ml!`01i_`4#P`zchv5^f29O4)6-G_?zP5%pWu z$SHx_TzSk=1ABA3iP_fErtoNlQr9oX){}kB3-OGZOPqVX>bF0qm^=2 z#!@my7De*BjLvp!WQDq|HF(Qzo9;6034i~wy5oK zr7H(_XMKMTm^R}NU4?H<@B5pM>*1Lid+K3>|HjgrMkmnzd_Xc zJ?2G@S>~4CESI%2-TZta2~ibCDx6Uk5Vvs?U93}-mK*`DDZzUYJyJ%=2e;ibS{XK3 zq>l?-L5jnQv9JDL5-vKgNC=;bm;BmVFuRA7{bSpt#jzHGU%O%wS-$ zEC#{cW|})%OuA1rnSSZES0TENrxwi1+fAFKxRMqwLbL+1*#7$^P3dC`R$2k>D!2xA zJ5T#%ytEpKt)Q=We3smw#l$JlssLMR2emjX&%_ehj8p|l!Kc7B-BW zn)A;HdMAD?f0ynW(017!!CM2hkKtsCemIvHYXEqJF|YX%;CN0TccC+J5R$uuPCKQ( zO0W~%EtJ}8@E)3`o7S5JKIZ*?S*@Y;7SBCwdYNI<@n-ek4%lvR>y@jG2vO1xtuNxQ zNw|6}HrO9LFZbzrzYSm4wx0G{sXJm}kkfLvJ7th=RiwM|`X3ed$;EAKQ`f@2%OvT! z!RLBs_2j0%h_%!e-x6w_QO^S~+t^M?ogiFut`#*nntq#`CxsXNieHxZUwY%0^7e)4 z$&US)=&C^J*6%T|Ck;QEYo)v8yYbhbBjADX;6MD!yyoMdpPb_#EHaZGnNt1ncjvwSf5!xJi~s-t literal 0 HcmV?d00001 diff --git a/icons/property_select_image.png b/icons/property_select_image.png new file mode 100644 index 0000000000000000000000000000000000000000..04abe10a7a38065ff8326a0953110cb0738b95bd GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|&H|6fVg?3oVGw3ym^DWND9BhG zO%4LFcI%Ox@Zkw#K(TcHik;K!=ywu1t-opj_^}G(!^RMNn?`Kr} zc<5BvhGhxmYzvxAI~SeE;dRNqlFVSTQRmF0=4;_=8@yuQ@Xz;4FzF&~fTYF#q s;M`pgdfSc|?^ictxF@LbJ9;-G=g-+e{YQ=700WT0)78&qol`;+0Ot&vWdHyG literal 0 HcmV?d00001 diff --git a/icons/success_icon.png b/icons/success_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4dda99f9762fba5f01c070e6c5e25728b677b5ce GIT binary patch literal 546 zcmV+-0^R+IP)( z!axwmcUQt#$OB002v{>2ob`Y~@_Nvk93eXSC3rgyxrs)^vxI)S$OYiBq!(?gL znkU|;7+@SEJ4?!)AD9G~f~}$g&76fMHl;~cGzB}#fmud=KNvI>$Zbc$I5BW|2i5}Y z8>g12Yn>62(~T^(G56&&+6rT;>uXiI4bw`HZ8xon$m>uYwl43|;c_4b0yjp`I9PSXy5dyk9dcjZhKNp30oTrZx-zh4>L%8Ru!nqhzqiy%-Q7-7lOR; z5P8G}nNx_FBs5;Pr4@iliJGaNoWG|~M9-5@oS9KXl<7e04`Ul)M$Zq*6j6w-W0kH) z+L@-IuuKhYQiiWwL3$NlmLi_tC=`273rM0BE~uiL25B3FjWRpWBU`VhqV=|oZPSKb z(=9Yz9E)*;-2?4w!@BdpF%b=qy!huXEp{fO-_wD4V1 literal 0 HcmV?d00001 diff --git a/new/project.json b/new/project.json new file mode 100644 index 00000000..5d10691e --- /dev/null +++ b/new/project.json @@ -0,0 +1,8 @@ +{ + "name": "new", + "path": "D:\\PythonProject\\CH_EG\\EG\\new", + "created_at": "2025-09-09 16:47:27", + "last_modified": "2025-09-11 09:28:24", + "version": "1.0.0", + "engine_version": "1.0.0" +} \ No newline at end of file diff --git a/quick_menu_fix.py b/quick_menu_fix.py new file mode 100644 index 00000000..a0f822d6 --- /dev/null +++ b/quick_menu_fix.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +快速菜单栏修复脚本 +用于诊断和修复菜单栏显示问题 +""" + +import sys +import os +from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget, QPushButton +from PyQt5.QtCore import Qt, QTimer + +# 设置环境变量避免DPI问题 +os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" +os.environ["QT_SCALE_FACTOR"] = "1" + +class QuickMenuFix(QMainWindow): + def __init__(self): + super().__init__() + self.setupUI() + + def setupUI(self): + """设置UI""" + self.setWindowTitle("菜单栏快速修复工具") + self.setGeometry(300, 300, 900, 700) + + # 强制窗口标志 + self.setWindowFlags(Qt.Window) + + # 设置主窗口样式 + self.setStyleSheet(""" + QMainWindow { + background-color: #1E1E1E; + color: #FFFFFF; + } + QPushButton { + background-color: #0078D4; + color: white; + border: none; + padding: 10px 20px; + margin: 5px; + border-radius: 5px; + font-size: 12px; + } + QPushButton:hover { + background-color: #106EBE; + } + QPushButton:pressed { + background-color: #005A9E; + } + """) + + # 创建中央部件 + central_widget = QWidget() + self.setCentralWidget(central_widget) + layout = QVBoxLayout(central_widget) + + # 标题 + title = QLabel("🔧 菜单栏修复工具") + title.setStyleSheet("font-size: 24px; font-weight: bold; color: #0078D4; margin: 20px;") + title.setAlignment(Qt.AlignCenter) + layout.addWidget(title) + + # 状态显示 + self.status_label = QLabel("准备就绪...") + self.status_label.setStyleSheet("font-size: 14px; color: white; margin: 10px; padding: 10px; background-color: #2D2D30; border-radius: 5px;") + self.status_label.setAlignment(Qt.AlignCenter) + layout.addWidget(self.status_label) + + # 按钮区域 + button_layout = QVBoxLayout() + + # 测试按钮 + test_btn = QPushButton("🔍 测试菜单栏显示") + test_btn.clicked.connect(self.testMenuBar) + button_layout.addWidget(test_btn) + + # 修复按钮 + fix_btn = QPushButton("🛠️ 强制修复菜单栏") + fix_btn.clicked.connect(self.forceFixMenuBar) + button_layout.addWidget(fix_btn) + + # 重置按钮 + reset_btn = QPushButton("🔄 重置菜单栏样式") + reset_btn.clicked.connect(self.resetMenuBarStyle) + button_layout.addWidget(reset_btn) + + # 信息按钮 + info_btn = QPushButton("📊 显示系统信息") + info_btn.clicked.connect(self.showSystemInfo) + button_layout.addWidget(info_btn) + + layout.addLayout(button_layout) + + # 创建初始菜单栏 + self.createInitialMenuBar() + + def createInitialMenuBar(self): + """创建初始菜单栏""" + menubar = self.menuBar() + menubar.setNativeMenuBar(False) + + # 添加基本菜单 + file_menu = menubar.addMenu('文件') + file_menu.addAction('新建') + file_menu.addAction('打开') + + edit_menu = menubar.addMenu('编辑') + edit_menu.addAction('复制') + edit_menu.addAction('粘贴') + + help_menu = menubar.addMenu('帮助') + help_menu.addAction('关于') + + print("✅ 初始菜单栏已创建") + + def testMenuBar(self): + """测试菜单栏显示""" + menubar = self.menuBar() + + # 收集信息 + info = [] + info.append(f"可见性: {menubar.isVisible()}") + info.append(f"启用状态: {menubar.isEnabled()}") + info.append(f"高度: {menubar.height()}px") + info.append(f"宽度: {menubar.width()}px") + info.append(f"位置: {menubar.pos()}") + info.append(f"菜单数量: {len(menubar.actions())}") + info.append(f"原生菜单栏: {menubar.isNativeMenuBar()}") + + # 显示结果 + status_text = "📊 菜单栏状态:\n" + "\n".join(info) + self.status_label.setText(status_text) + + # 控制台输出 + print("\n🔍 菜单栏测试结果:") + for item in info: + print(f" - {item}") + + # 判断是否正常 + if menubar.isVisible() and menubar.height() > 0: + self.status_label.setStyleSheet("font-size: 14px; color: white; margin: 10px; padding: 10px; background-color: #0F7B0F; border-radius: 5px;") + print("✅ 菜单栏显示正常") + else: + self.status_label.setStyleSheet("font-size: 14px; color: white; margin: 10px; padding: 10px; background-color: #A80000; border-radius: 5px;") + print("❌ 菜单栏显示异常") + + def forceFixMenuBar(self): + """强制修复菜单栏""" + menubar = self.menuBar() + + print("🛠️ 开始强制修复菜单栏...") + + # 重置基本属性 + menubar.setNativeMenuBar(False) + menubar.setVisible(True) + menubar.setEnabled(True) + + # 设置尺寸 + menubar.setMinimumHeight(50) + menubar.setMaximumHeight(50) + + # 应用强制样式 + menubar.setStyleSheet(""" + QMenuBar { + background-color: #FF4444 !important; + color: #FFFFFF !important; + border: 4px solid #00FF00 !important; + min-height: 50px !important; + max-height: 50px !important; + font-size: 18px !important; + font-weight: bold !important; + padding: 8px !important; + } + QMenuBar::item { + background-color: #FFFF00 !important; + color: #000000 !important; + padding: 12px 20px !important; + margin: 4px !important; + font-size: 18px !important; + font-weight: bold !important; + border: 2px solid #FF0000 !important; + border-radius: 3px !important; + } + QMenuBar::item:selected { + background-color: #0066FF !important; + color: #FFFFFF !important; + } + """) + + # 强制显示和刷新 + menubar.show() + menubar.raise_() + menubar.update() + menubar.repaint() + self.update() + self.repaint() + + self.status_label.setText("🛠️ 强制修复已应用!\n如果看到红色菜单栏说明修复成功") + self.status_label.setStyleSheet("font-size: 14px; color: white; margin: 10px; padding: 10px; background-color: #FF6600; border-radius: 5px;") + + print("🛠️ 强制修复完成") + + # 延迟测试结果 + QTimer.singleShot(1000, self.testMenuBar) + + def resetMenuBarStyle(self): + """重置菜单栏样式""" + menubar = self.menuBar() + + # 清除样式 + menubar.setStyleSheet("") + + # 重新设置基本样式 + menubar.setStyleSheet(""" + QMenuBar { + background-color: #2D2D30; + color: #FFFFFF; + border-bottom: 1px solid #3E3E42; + min-height: 30px; + font-size: 14px; + } + QMenuBar::item { + background-color: transparent; + color: #FFFFFF; + padding: 8px 12px; + margin: 0px 2px; + } + QMenuBar::item:selected { + background-color: #0078D4; + } + """) + + menubar.update() + self.status_label.setText("🔄 菜单栏样式已重置为正常样式") + self.status_label.setStyleSheet("font-size: 14px; color: white; margin: 10px; padding: 10px; background-color: #2D2D30; border-radius: 5px;") + + print("🔄 菜单栏样式已重置") + + def showSystemInfo(self): + """显示系统信息""" + import platform + from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR + + info = [] + info.append(f"操作系统: {platform.system()} {platform.release()}") + info.append(f"Python版本: {sys.version.split()[0]}") + info.append(f"PyQt5版本: {PYQT_VERSION_STR}") + info.append(f"Qt版本: {QT_VERSION_STR}") + info.append(f"屏幕分辨率: {QApplication.desktop().screenGeometry().width()}x{QApplication.desktop().screenGeometry().height()}") + + status_text = "📊 系统信息:\n" + "\n".join(info) + self.status_label.setText(status_text) + self.status_label.setStyleSheet("font-size: 12px; color: white; margin: 10px; padding: 10px; background-color: #0078D4; border-radius: 5px;") + + print("\n📊 系统信息:") + for item in info: + print(f" - {item}") + +def main(): + """主函数""" + app = QApplication(sys.argv) + + # 设置应用样式 + app.setStyle('Fusion') + + print("🚀 启动菜单栏修复工具") + print("📋 这个工具可以帮助诊断和修复菜单栏显示问题") + print("🎯 使用明显的颜色来测试菜单栏是否正常显示") + + window = QuickMenuFix() + window.show() + + return app.exec_() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/scripts/a.py b/scripts/a.py new file mode 100644 index 00000000..34bd5408 --- /dev/null +++ b/scripts/a.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +a - 自定义脚本 +""" + +from core.script_system import ScriptBase + +class A(ScriptBase): + """自定义脚本类""" + + def __init__(self): + super().__init__() + # 在这里初始化您的变量 + + def start(self): + """脚本开始时调用""" + self.log("脚本开始运行!") + + def update(self, dt): + """每帧更新""" + # 在这里编写更新逻辑 + pass + + def on_destroy(self): + """脚本销毁时调用""" + self.log("脚本被销毁") diff --git a/ui/icon_manager.py b/ui/icon_manager.py index 8bfebceb..5d07611d 100644 --- a/ui/icon_manager.py +++ b/ui/icon_manager.py @@ -69,6 +69,9 @@ class IconManager: 'success_icon': 'success_icon.png', 'warning_icon': 'warning_icon.png', 'fail_icon': 'delete_fail_icon.png', + + # 属性面板图标 + 'property_select_image': 'property_select_image.png', } # 初始化默认图标 diff --git a/ui/main_window.py b/ui/main_window.py index 73e8c3bc..4c1b7fdc 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -1504,8 +1504,8 @@ class MainWindow(QMainWindow): padding: 8px 10px; border-bottom: none; text-align: left; - font-family: 'Microsoft YaHei', 'Inter', sans-serif; - font-size: 14px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 12px; font-weight: 500; } QDockWidget::close-button { @@ -1540,6 +1540,7 @@ class MainWindow(QMainWindow): background-color: #19191b; color: #ebebeb; border: none; + padding: 0px 0px 0px 8px; alternate-background-color: #19191b; font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; @@ -1844,16 +1845,16 @@ class MainWindow(QMainWindow): /* 设置内边距,为标题和内容留出空间,左右内边距与主标题对齐 */ padding-top: 20px; /* 为标题留出足够空间 */ - padding-left: 8px; /* 左内边距与主标题分隔线对齐 */ - padding-right: 8px; /* 右内边距与主标题分隔线对齐 */ - + padding-left: 0px; /* Align subtitle with dock title baseline */ + padding-right: 0px; /* 右内边距与主标题分隔线对齐 */ + /* 减少下边距,使相邻组更紧凑 */ background-color: transparent; margin-bottom: 6px; /* 使用边框创建样式线,与主标题分隔线保持相同的8px左右间距 */ border-top: 1px solid rgba(77, 116, 189, 0.4); - margin-left: 8px; + margin-left: 0px; margin-right: 8px; } @@ -1863,7 +1864,7 @@ class MainWindow(QMainWindow): subcontrol-position: top left; /* 标题样式,左边距为0因为已经通过padding-left处理了对齐 */ - padding: 5px 3px 8px 0px; /* 左边距设为0,与内容对齐 */ + padding: 8px 3px 8px 0px; /* 左边距设为0,与内容对齐 */ margin-top: 0px; /* 标题紧贴内边距顶部 */ /* 移除标题上的边框 */ @@ -1875,6 +1876,13 @@ class MainWindow(QMainWindow): font-size: 12px; font-weight: 500; } + QGroupBox[groupRole="first"] { + padding-top: 16px; + margin-top: 0px; + } + QGroupBox[groupRole="first"]::title { + padding: 6px 3px 8px 0px; + } """) # 创建属性面板的主容器和布局 @@ -1883,7 +1891,9 @@ class MainWindow(QMainWindow): try: self.propertyLayout = QVBoxLayout(self.propertyContainer) - self.propertyLayout.setContentsMargins(0, 0, 0, 0) + # Keep subtitles aligned with the dock title left padding + self.propertyLayout.setContentsMargins(10, 0, 10, 12) + self.propertyLayout.setSpacing(12) print(f"✓ 属性布局创建完成: {self.propertyLayout}") # 添加初始提示信息 @@ -2032,7 +2042,7 @@ class MainWindow(QMainWindow): subcontrol-position: top left; /* 标题样式,左边距为0因为已经通过padding-left处理了对齐 */ - padding: 5px 3px 8px 0px; /* 左边距设为0,与内容对齐 */ + padding: 8px 3px 8px 0px; /* 左边距设为0,与内容对齐 */ margin-top: 0px; /* 标题紧贴内边距顶部 */ /* 移除标题上的边框 */ @@ -2044,6 +2054,13 @@ class MainWindow(QMainWindow): font-size: 12px; font-weight: 500; } + QGroupBox[groupRole="first"] { + padding-top: 16px; + margin-top: 0px; + } + QGroupBox[groupRole="first"]::title { + padding: 6px 3px 8px 0px; + } """) # 创建脚本面板的主容器和布局(与属性面板相同结构) @@ -2056,6 +2073,9 @@ class MainWindow(QMainWindow): """) self.scriptContainer.setObjectName("ScriptContainer") self.scriptLayout = QVBoxLayout(self.scriptContainer) + # Match the dock title horizontal padding for consistent alignment + self.scriptLayout.setContentsMargins(10, 0, 10, 12) + self.scriptLayout.setSpacing(12) # 创建滚动区域并设置属性 self.scriptScrollArea = QScrollArea() @@ -2215,6 +2235,14 @@ class MainWindow(QMainWindow): font-size: 12px; font-weight: 500; } + QGroupBox[groupRole="first"] { + border-top: none; + margin-top: 0px; + padding-top: 8px; + } + QGroupBox[groupRole="first"]::title { + padding: 8px 5px 8px 0px; + } """) # 创建包装容器,添加标题下方的分隔线 @@ -2238,7 +2266,14 @@ class MainWindow(QMainWindow): } """) bottomDockLayout.addWidget(bottomSeparator) - bottomDockLayout.addWidget(self.fileView) + + # Wrap the resource view to add consistent left padding + resourceContentWrapper = QWidget() + resourceContentLayout = QVBoxLayout(resourceContentWrapper) + resourceContentLayout.setContentsMargins(8, 0, 0, 0) + resourceContentLayout.setSpacing(0) + resourceContentLayout.addWidget(self.fileView) + bottomDockLayout.addWidget(resourceContentWrapper) self.bottomDock.setWidget(bottomDockContainer) self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock) @@ -2514,6 +2549,8 @@ class MainWindow(QMainWindow): """创建脚本管理面板""" # 脚本状态组 statusGroup = QGroupBox("脚本系统状态") + + statusGroup.setProperty("groupRole", "first") statusLayout = QVBoxLayout() # 脚本系统状态行 diff --git a/ui/property_panel.py b/ui/property_panel.py index b815ee94..0ed44450 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -7,7 +7,7 @@ from typing import Hashable from PyQt5.QtGui import QColor from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton, QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget, - QVBoxLayout, QGroupBox, QGridLayout, QSpinBox, QFileDialog, QMessageBox) + QVBoxLayout, QGroupBox, QGridLayout, QSpinBox, QFileDialog, QMessageBox, QSizePolicy) from PyQt5.QtCore import Qt from deploy_libs.unicodedata import normalize from direct.actor.Actor import Actor @@ -19,7 +19,7 @@ from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib, PartGroup, C from scene import util from direct.gui.DirectGui import DirectLabel, DirectFrame from panda3d.core import TextNode - +from ui.icon_manager import get_icon_manager, has_icon, get_icon class PropertyPanelManager: """属性面板管理器""" @@ -31,6 +31,8 @@ class PropertyPanelManager: self._actor_cache = {} self._spherical_video_controls = {} + self.column_minimum_width = 85 + # 初始化地形编辑参数 if not hasattr(self.world, 'terrain_edit_radius'): self.world.terrain_edit_radius = 3.0 @@ -174,12 +176,12 @@ class PropertyPanelManager: """ self.compact_style += """ QGroupBox { - margin-left: 8px; + margin-left: 0px; padding-left: 0px; } QGroupBox::title { left: 0px; - padding: 0 5px 5px 0; + padding: 8px 5px 5px 0; margin-top: 0px; } """ @@ -423,11 +425,41 @@ class PropertyPanelManager: } """) else: # default - # 使用全局样式,不需要额外设置 + # 使用全局默认样式 pass - return button - + + def _collision_button_style(self): + """返回碰撞检测面板按钮统一样式""" + return """ + QPushButton { + background-color: rgba(89, 98, 118, 0.4); + color: rgba(235, 235, 235, 0.85); + border: 1px solid rgba(89, 98, 118, 0.65); + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 400; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(89, 98, 118, 0.55); + border: 1px solid rgba(89, 98, 118, 0.9); + color: #ffffff; + } + QPushButton:pressed { + background-color: rgba(89, 98, 118, 0.7); + border: 1px solid rgba(89, 98, 118, 0.95); + color: #ffffff; + } + QPushButton:disabled { + background-color: rgba(89, 98, 118, 0.18); + border: 1px solid rgba(89, 98, 118, 0.25); + color: rgba(235, 235, 235, 0.35); + } + """ + def createModernGroupBox(self, title, parent_layout): """创建现代化的分组框 @@ -458,7 +490,7 @@ class PropertyPanelManager: subcontrol-origin: padding; subcontrol-position: top left; left: 0px; - padding: 0 5px 5px 0; + padding: 8px 5px 5px 0; margin-top: 0px; } """) @@ -635,8 +667,7 @@ class PropertyPanelManager: model.setPythonTag("user_visible", True) self.name_group = QGroupBox("物体名称") - name_layout = QHBoxLayout() - name_layout.setContentsMargins(0, 8, 0, 8) + name_layout = QGridLayout() self.active_check = QCheckBox() # 根据模型的实际可见性状态设置复选框 self.active_check.setChecked(user_visible) @@ -646,8 +677,9 @@ class PropertyPanelManager: lambda: self.world.treeWidget.update_item_name(self.name_input.text(), item) ) - name_layout.addWidget(self.active_check) - name_layout.addWidget(self.name_input) + name_layout.addWidget(self.active_check, 0, 0) + name_layout.setColumnMinimumWidth(0, self.column_minimum_width) + name_layout.addWidget(self.name_input, 0, 1, 1, 3) self.name_group.setLayout(name_layout) self._propertyLayout.addWidget(self.name_group) @@ -718,19 +750,20 @@ class PropertyPanelManager: info_group = QGroupBox("地形信息") info_layout = QGridLayout() + info_layout.setColumnMinimumWidth(0, self.column_minimum_width) info_layout.addWidget(QLabel("名称:"), 0, 0) name_label = QLabel(terrain_info.get('name', '未知')) - info_layout.addWidget(name_label, 0, 1) + info_layout.addWidget(name_label, 0, 1, 1, 3) info_layout.addWidget(QLabel("类型:"), 1, 0) type_label = QLabel("高度图地形" if terrain_info.get('heightmap') else "平面地形") - info_layout.addWidget(type_label, 1, 1) + info_layout.addWidget(type_label, 1, 1, 1, 3) if terrain_info.get('heightmap'): info_layout.addWidget(QLabel("高度图:"), 2, 0) heightmap_label = QLabel(os.path.basename(terrain_info['heightmap'])) heightmap_label.setWordWrap(True) - info_layout.addWidget(heightmap_label, 2, 1) + info_layout.addWidget(heightmap_label, 2, 1, 1, 3) info_group.setLayout(info_layout) self._propertyLayout.addWidget(info_group) @@ -768,23 +801,17 @@ class PropertyPanelManager: try: transform_group = QGroupBox("变换 Transform") transform_layout = QGridLayout() + transform_layout.setColumnMinimumWidth(0, self.column_minimum_width) pos = terrain_node.getPos() scale = terrain_node.getScale() transform_layout.addWidget(QLabel("位置"), 0, 0) - x_label = QLabel("X") - y_label = QLabel("Y") - z_label = QLabel("Z") - x_label.setAlignment(Qt.AlignCenter) - y_label.setAlignment(Qt.AlignCenter) - z_label.setAlignment(Qt.AlignCenter) - - transform_layout.addWidget(x_label, 0, 1) - transform_layout.addWidget(y_label, 0, 2) - transform_layout.addWidget(z_label, 0, 3) - + position_row = QHBoxLayout() + position_row.setContentsMargins(0, 0, 0, 0) + position_row.setSpacing(4) + self.pos_x = QDoubleSpinBox() self.pos_y = QDoubleSpinBox() self.pos_z = QDoubleSpinBox() @@ -813,11 +840,20 @@ class PropertyPanelManager: self.pos_z.valueChanged.connect(updateZPosition) - transform_layout.addWidget(self.pos_x, 1, 1) - transform_layout.addWidget(self.pos_y, 1, 2) - transform_layout.addWidget(self.pos_z, 1, 3) + for axis_label, widget in (("X:", self.pos_x), ("Y:", self.pos_y), ("Z:", self.pos_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + position_row.addWidget(axis) + position_row.addWidget(widget) - transform_layout.addWidget(QLabel("旋转"), 2, 0) + transform_layout.addLayout(position_row, 0, 1, 1, 3) + + transform_layout.addWidget(QLabel("旋转"), 1, 0) + rotation_row = QHBoxLayout() + rotation_row.setContentsMargins(0, 0, 0, 0) + rotation_row.setSpacing(4) + self.rot_x = QDoubleSpinBox() self.rot_y = QDoubleSpinBox() self.rot_z = QDoubleSpinBox() @@ -833,21 +869,21 @@ class PropertyPanelManager: self.rot_y.valueChanged.connect(lambda v: terrain_node.setP(v)) self.rot_z.valueChanged.connect(lambda v: terrain_node.setR(v)) - h_label = QLabel("H") - p_label = QLabel("P") - r_label = QLabel("R") - h_label.setAlignment(Qt.AlignCenter) - p_label.setAlignment(Qt.AlignCenter) - r_label.setAlignment(Qt.AlignCenter) + for axis_label, widget in (("H:", self.rot_x), ("P:", self.rot_y), ("R:", self.rot_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + rotation_row.addWidget(axis) + rotation_row.addWidget(widget) - transform_layout.addWidget(h_label, 2, 1) - transform_layout.addWidget(p_label, 2, 2) - transform_layout.addWidget(r_label, 2, 3) - transform_layout.addWidget(self.rot_x, 3, 1) - transform_layout.addWidget(self.rot_y, 3, 2) - transform_layout.addWidget(self.rot_z, 3, 3) + transform_layout.addLayout(rotation_row, 1, 1, 1, 3) - transform_layout.addWidget(QLabel("缩放"), 4, 0) + transform_layout.addWidget(QLabel("缩放"), 2, 0) + + scale_row = QHBoxLayout() + scale_row.setContentsMargins(0, 0, 0, 0) + scale_row.setSpacing(4) + self.scale_x = QDoubleSpinBox() self.scale_y = QDoubleSpinBox() self.scale_z = QDoubleSpinBox() @@ -863,19 +899,14 @@ class PropertyPanelManager: self.scale_y.valueChanged.connect(lambda v: self._updateYScale(terrain_node, v)) self.scale_z.valueChanged.connect(lambda v: self._updateZScale(terrain_node, v)) - x_label2 = QLabel("X") - y_label2 = QLabel("Y") - z_label2 = QLabel("Z") - x_label2.setAlignment(Qt.AlignCenter) - y_label2.setAlignment(Qt.AlignCenter) - z_label2.setAlignment(Qt.AlignCenter) + for axis_label, widget in (("X:", self.scale_x), ("Y:", self.scale_y), ("Z:", self.scale_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + scale_row.addWidget(axis) + scale_row.addWidget(widget) - transform_layout.addWidget(x_label2, 4, 1) - transform_layout.addWidget(y_label2, 4, 2) - transform_layout.addWidget(z_label2, 4, 3) - transform_layout.addWidget(self.scale_x, 5, 1) - transform_layout.addWidget(self.scale_y, 5, 2) - transform_layout.addWidget(self.scale_z, 5, 3) + transform_layout.addLayout(scale_row, 2, 1, 1, 3) transform_group.setLayout(transform_layout) self._propertyLayout.addWidget(transform_group) @@ -886,6 +917,7 @@ class PropertyPanelManager: try: edit_group = QGroupBox("地形编辑") edit_layout = QGridLayout() + edit_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 编辑半径 edit_layout.addWidget(QLabel("编辑半径:"), 0, 0) @@ -894,7 +926,7 @@ class PropertyPanelManager: self.terrain_radius_spin.setSingleStep(0.5) self.terrain_radius_spin.setValue(getattr(self.world, 'terrain_edit_radius', 3.0)) self.terrain_radius_spin.valueChanged.connect(self._onTerrainRadiusChanged) - edit_layout.addWidget(self.terrain_radius_spin, 0, 1) + edit_layout.addWidget(self.terrain_radius_spin, 0, 1, 1, 3) # 编辑强度 edit_layout.addWidget(QLabel("编辑强度:"), 1, 0) @@ -903,7 +935,7 @@ class PropertyPanelManager: self.terrain_strength_spin.setSingleStep(0.1) self.terrain_strength_spin.setValue(getattr(self.world, 'terrain_edit_strength', 0.3)) self.terrain_strength_spin.valueChanged.connect(self._onTerrainStrengthChanged) - edit_layout.addWidget(self.terrain_strength_spin, 1, 1) + edit_layout.addWidget(self.terrain_strength_spin, 1, 1, 1, 3) edit_layout.addWidget(QLabel("操作类型:"), 2, 0) self.terrain_operation_combo = QComboBox() @@ -916,12 +948,12 @@ class PropertyPanelManager: else: self.terrain_operation_combo.setCurrentText("平坦化") self.terrain_operation_combo.currentTextChanged.connect(self._onTerrainOperationChanged) - edit_layout.addWidget(self.terrain_operation_combo, 2, 1) + edit_layout.addWidget(self.terrain_operation_combo, 2, 1, 1, 3) help_label = QLabel("在地形编辑模式下,使用鼠标左键升高地形,右键降低地形") help_label.setWordWrap(True) help_label.setStyleSheet("font-size:10px;color:gray;") - edit_layout.addWidget(help_label, 3, 0, 1, 2) + edit_layout.addWidget(help_label, 3, 0, 1, 4) edit_group.setLayout(edit_layout) self._propertyLayout.addWidget(edit_group) @@ -960,15 +992,17 @@ class PropertyPanelManager: try: material_group = QGroupBox("材质属性") material_layout = QGridLayout() + material_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 颜色设置 material_layout.addWidget(QLabel("颜色:"), 0, 0) color_button = QPushButton("选择颜色") color_button.clicked.connect(lambda: self._selectTerrainColor(terrain_info)) + material_layout.addWidget(color_button, 0, 1, 1, 3) # 纹理设置 material_layout.addWidget(QLabel("纹理:"), 1, 0) texture_button = QPushButton("选择纹理") texture_button.clicked.connect(lambda: self._selectTerrainTexture(terrain_info)) - material_layout.addWidget(texture_button, 1, 1) + material_layout.addWidget(texture_button, 1, 1, 1, 3) material_group.setLayout(material_layout) self._propertyLayout.addWidget(material_group) @@ -1408,22 +1442,33 @@ class PropertyPanelManager: print(f"显示 Cesium tileset 属性: {nodePath.getName()}") # 标题 - title = QLabel("Cesium 3D Tiles") - title.setStyleSheet("font-size: 14px; font-weight: bold; margin: 10px 0;") - self._propertyLayout.addWidget(title) + tileset_group = QGroupBox("Cesium 3D Tiles") + tileset_layout = QGridLayout() + tileset_layout.setColumnMinimumWidth(0, self.column_minimum_width) + # self._propertyLayout.addWidget(title) # URL 信息 if nodePath.hasTag("tileset_url"): url_label = QLabel("URL:") + tileset_layout.addWidget(url_label, 0, 0) url_value = QLabel(nodePath.getTag("tileset_url")) url_value.setWordWrap(True) url_value.setStyleSheet("font-size: 9px; color: #666;") - self._propertyLayout.addWidget(url_label) - self._propertyLayout.addWidget(url_value) + tileset_layout.addWidget(url_value, 0, 1, 1, 3) + # self._propertyLayout.addWidget(url_label) + # self._propertyLayout.addWidget(url_value) + tileset_group.setLayout(tileset_layout) + self._propertyLayout.addWidget(tileset_group) # 位置控制 pos_group = QGroupBox("位置") - pos_layout = QFormLayout() + pos_layout = QGridLayout() + pos_layout.setColumnMinimumWidth(0, self.column_minimum_width) + + pos_row = QHBoxLayout() + pos_row.setContentsMargins(0, 0, 0, 0) + pos_row.setSpacing(4) + pos_layout.addWidget(QLabel("位置"), 0, 0) # X 坐标 x_spin = QDoubleSpinBox() @@ -1433,7 +1478,11 @@ class PropertyPanelManager: pos = nodePath.getPos() x_spin.setValue(pos.getX()) x_spin.valueChanged.connect(lambda v: self._updateTilesetPosition(nodePath, 'x', v)) - pos_layout.addRow("X:", x_spin) + x_label = QLabel("X:") + x_label.setAlignment(Qt.AlignVCenter) + x_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + pos_row.addWidget(x_label) + pos_row.addWidget(x_spin) # Y 坐标 y_spin = QDoubleSpinBox() @@ -1442,7 +1491,11 @@ class PropertyPanelManager: y_spin.setDecimals(2) y_spin.setValue(pos.getY()) y_spin.valueChanged.connect(lambda v: self._updateTilesetPosition(nodePath, 'y', v)) - pos_layout.addRow("Y:", y_spin) + y_label = QLabel("Y:") + y_label.setAlignment(Qt.AlignVCenter) + y_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + pos_row.addWidget(y_label) + pos_row.addWidget(y_spin) # Z 坐标 z_spin = QDoubleSpinBox() @@ -1451,25 +1504,28 @@ class PropertyPanelManager: z_spin.setDecimals(2) z_spin.setValue(pos.getZ()) z_spin.valueChanged.connect(lambda v: self._updateTilesetPosition(nodePath, 'z', v)) - pos_layout.addRow("Z:", z_spin) + z_label = QLabel("Z:") + z_label.setAlignment(Qt.AlignVCenter) + z_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + pos_row.addWidget(z_label) + pos_row.addWidget(z_spin) + + pos_layout.addLayout(pos_row, 0, 1, 1, 3) pos_group.setLayout(pos_layout) - self._propertyLayout.addWidget(pos_group) # 缩放控制 - scale_group = QGroupBox("缩放") - scale_layout = QFormLayout() - + scale_label = QLabel("缩放") + pos_layout.addWidget(scale_label, 1, 0) scale_spin = QDoubleSpinBox() scale_spin.setRange(0.01, 1000) scale_spin.setSingleStep(0.1) scale_spin.setDecimals(2) scale_spin.setValue(nodePath.getScale().getX()) scale_spin.valueChanged.connect(lambda v: self._updateTilesetScale(nodePath, v)) - scale_layout.addRow("缩放:", scale_spin) + pos_layout.addWidget(scale_spin, 1, 1, 1, 3) - scale_group.setLayout(scale_layout) - self._propertyLayout.addWidget(scale_group) + self._propertyLayout.addWidget(pos_group) # # 删除按钮 # delete_btn = QPushButton("删除 Tileset") @@ -1622,234 +1678,168 @@ class PropertyPanelManager: # 获取父节点 parent = model.getParent() - # 变换属性部分 (Transform) - 根据Figma设计优化 + # 变换属性部分 (Transform) self.transform_group = QGroupBox("变换 Transform") - # 应用Figma设计的GroupBox样式 - self.transform_group.setStyleSheet(""" - QGroupBox { - margin-top: 0px; - border: none; - padding-top: 20px; - padding-left: 8px; - padding-right: 8px; - background-color: transparent; - margin-bottom: 6px; - border-top: 1px solid rgba(77, 116, 189, 0.4); - margin-left: 8px; - margin-right: 8px; - } - QGroupBox::title { - subcontrol-origin: padding; - subcontrol-position: top left; - padding: 5px 3px 8px 0px; - margin-top: 0px; - border: none; - color: rgba(255, 255, 255, 0.8); - font-family: 'Inter', 'Microsoft YaHei', sans-serif; - font-size: 12px; - font-weight: 500; - letter-spacing: 0.6px; - } - """) - self._propertyLayout.addWidget(self.transform_group) - transform_layout = QVBoxLayout() - transform_layout.setSpacing(8) # 设置行间距 - transform_layout.setContentsMargins(0, 8, 0, 8) + transform_layout = QGridLayout() + transform_layout.setColumnMinimumWidth(0, self.column_minimum_width) + # transform_layout.setColumnStretch(1, 1) + # transform_layout.setColumnStretch(2, 1) + # transform_layout.setColumnStretch(3, 1) + # 获取当前值 relativePos = model.getPos(parent) if parent else model.getPos() - current_scale = model.getScale() + worldPos = model.getPos(self.world.render) - # 位置行 (Position) - position_container = QWidget() - position_layout = QHBoxLayout(position_container) - position_layout.setContentsMargins(0, 0, 0, 0) - position_layout.setSpacing(6) + position_label = QLabel("位置") + transform_layout.addWidget(position_label, 0, 0) - # 位置标签 - pos_label = QLabel("位置:") - pos_label.setStyleSheet(""" - QLabel { - color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; - font-size: 10px; - font-weight: 300; - letter-spacing: 0.5px; - min-width: 31px; - } - """) - position_layout.addWidget(pos_label) - - # 添加弹性空间 - position_layout.addStretch() - - # X位置 - x_pos_label = QLabel("X:") - x_pos_label.setStyleSheet(""" - QLabel { - color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; - font-size: 10px; - font-weight: 300; - letter-spacing: 0.5px; - } - """) - position_layout.addWidget(x_pos_label) + # 位置 (Position) + position_row = QHBoxLayout() + position_row.setContentsMargins(0, 0, 0, 0) + position_row.setSpacing(4) + # position_row.addWidget(QLabel("位置")) self.pos_x = QDoubleSpinBox() - self.pos_x.setRange(-1000000.0, 1000000.0) - self.pos_x.setValue(relativePos.getX()) - self.pos_x.setStyleSheet(self._getFigmaSpinBoxStyle()) - position_layout.addWidget(self.pos_x) - - # Y位置 - y_pos_label = QLabel("Y:") - y_pos_label.setStyleSheet(""" - QLabel { - color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; - font-size: 10px; - font-weight: 300; - letter-spacing: 0.5px; - } - """) - position_layout.addWidget(y_pos_label) - self.pos_y = QDoubleSpinBox() - self.pos_y.setRange(-1000000.0, 1000000.0) - self.pos_y.setValue(relativePos.getY()) - self.pos_y.setStyleSheet(self._getFigmaSpinBoxStyle()) - position_layout.addWidget(self.pos_y) - - # Z位置 - z_pos_label = QLabel("Z:") - z_pos_label.setStyleSheet(""" - QLabel { - color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; - font-size: 10px; - font-weight: 300; - letter-spacing: 0.5px; - } - """) - position_layout.addWidget(z_pos_label) - self.pos_z = QDoubleSpinBox() - self.pos_z.setRange(-1000000.0, 1000000.0) + + # 设置位置控件属性 + for pos_widget in [self.pos_x, self.pos_y, self.pos_z]: + pos_widget.setRange(-1000000.0, 1000000.0) + + self.pos_x.setValue(relativePos.getX()) + self.pos_y.setValue(relativePos.getY()) self.pos_z.setValue(relativePos.getZ()) - self.pos_z.setStyleSheet(self._getFigmaSpinBoxStyle()) - position_layout.addWidget(self.pos_z) - transform_layout.addWidget(position_container) - - # 缩放行 (Scale) - scale_container = QWidget() - scale_layout = QHBoxLayout(scale_container) - scale_layout.setContentsMargins(0, 0, 0, 0) - scale_layout.setSpacing(6) - - # 缩放标签 - scale_label = QLabel("缩放:") - scale_label.setStyleSheet(""" - QLabel { - color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; - font-size: 10px; - font-weight: 300; - letter-spacing: 0.5px; - min-width: 31px; - } - """) - scale_layout.addWidget(scale_label) - - # 添加弹性空间 - scale_layout.addStretch() - - # X缩放 - x_scale_label = QLabel("X:") - x_scale_label.setStyleSheet(""" - QLabel { - color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; - font-size: 10px; - font-weight: 300; - letter-spacing: 0.5px; - } - """) - scale_layout.addWidget(x_scale_label) - - self.scale_x = QDoubleSpinBox() - self.scale_x.setRange(-1000, 1000) - self.scale_x.setSingleStep(0.1) - self.scale_x.setValue(current_scale.getX()) - self.scale_x.setStyleSheet(self._getFigmaSpinBoxStyle()) - scale_layout.addWidget(self.scale_x) - - # Y缩放 - y_scale_label = QLabel("Y:") - y_scale_label.setStyleSheet(""" - QLabel { - color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; - font-size: 10px; - font-weight: 300; - letter-spacing: 0.5px; - } - """) - scale_layout.addWidget(y_scale_label) - - self.scale_y = QDoubleSpinBox() - self.scale_y.setRange(-1000, 1000) - self.scale_y.setSingleStep(0.1) - self.scale_y.setValue(current_scale.getY()) - self.scale_y.setStyleSheet(self._getFigmaSpinBoxStyle()) - scale_layout.addWidget(self.scale_y) - - # Z缩放 - z_scale_label = QLabel("Z:") - z_scale_label.setStyleSheet(""" - QLabel { - color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; - font-size: 10px; - font-weight: 300; - letter-spacing: 0.5px; - } - """) - scale_layout.addWidget(z_scale_label) - - self.scale_z = QDoubleSpinBox() - self.scale_z.setRange(-1000, 1000) - self.scale_z.setSingleStep(0.1) - self.scale_z.setValue(current_scale.getZ()) - self.scale_z.setStyleSheet(self._getFigmaSpinBoxStyle()) - scale_layout.addWidget(self.scale_z) - - transform_layout.addWidget(scale_container) - - # 连接事件 + # 连接位置变化事件 def updateXPosition(value): model.setX(value) self.refreshModelValues(model) + self.pos_x.valueChanged.connect(updateXPosition) + def updateYPosition(value): model.setY(value) self.refreshModelValues(model) + self.pos_y.valueChanged.connect(updateYPosition) + def updateZPosition(value): model.setZ(value) self.refreshModelValues(model) - self.pos_x.valueChanged.connect(updateXPosition) - self.pos_y.valueChanged.connect(updateYPosition) self.pos_z.valueChanged.connect(updateZPosition) - # 连接缩放变化事件 + # 水平排布 X, Y, Z + for axis_label, widget in (("X:", self.pos_x), ("Y:", self.pos_y), ("Z:", self.pos_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + position_row.addWidget(axis) + position_row.addWidget(widget) + + transform_layout.addLayout(position_row, 0, 1, 1, 3) + + # 世界位置 (只读) + world_position_row = QHBoxLayout() + world_position_row.setContentsMargins(0, 0, 0, 0) + world_position_row.setSpacing(4) + # world_position_row.addWidget(QLabel("世界位置")) + world_position_label = QLabel("世界位置") + transform_layout.addWidget(world_position_label, 1, 0) + + self.world_pos_x = QDoubleSpinBox() + self.world_pos_y = QDoubleSpinBox() + self.world_pos_z = QDoubleSpinBox() + + for world_pos_widget in [self.world_pos_x, self.world_pos_y, self.world_pos_z]: + world_pos_widget.setRange(-1000000.0, 1000000.0) + world_pos_widget.setReadOnly(True) + + self.world_pos_x.setValue(worldPos.getX()) + self.world_pos_y.setValue(worldPos.getY()) + self.world_pos_z.setValue(worldPos.getZ()) + + for axis_label, widget in (("X:", self.world_pos_x), ("Y:", self.world_pos_y), ("Z:", self.world_pos_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + world_position_row.addWidget(axis) + world_position_row.addWidget(widget) + + transform_layout.addLayout(world_position_row, 1, 1, 1, 3) + + # 旋转 (Rotation) + rotation_row = QHBoxLayout() + rotation_row.setContentsMargins(0, 0, 0, 0) + rotation_row.setSpacing(4) + rotation_label = QLabel("旋转") + transform_layout.addWidget(rotation_label, 2, 0) + + self.rot_x = QDoubleSpinBox() + self.rot_y = QDoubleSpinBox() + self.rot_z = QDoubleSpinBox() + + for rot_widget in [self.rot_x, self.rot_y, self.rot_z]: + rot_widget.setRange(-360, 360) + + self.rot_x.setValue(model.getH()) + self.rot_y.setValue(model.getP()) + self.rot_z.setValue(model.getR()) + + self.rot_x.valueChanged.connect(lambda v: model.setH(v)) + self.rot_y.valueChanged.connect(lambda v: model.setP(v)) + self.rot_z.valueChanged.connect(lambda v: model.setR(v)) + + for axis_label, widget in (("H:", self.rot_x), ("P:", self.rot_y), ("R:", self.rot_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + rotation_row.addWidget(axis) + rotation_row.addWidget(widget) + + transform_layout.addLayout(rotation_row, 2, 1, 1, 3) + + # 缩放 (Scale) + scale_row = QHBoxLayout() + scale_row.setContentsMargins(0, 0, 0, 0) + scale_row.setSpacing(4) + scale_label = QLabel("缩放") + transform_layout.addWidget(scale_label, 3, 0) + + self.scale_x = QDoubleSpinBox() + self.scale_y = QDoubleSpinBox() + self.scale_z = QDoubleSpinBox() + + current_scale = model.getScale() + + for scale_widget, scale_value in zip([self.scale_x, self.scale_y, self.scale_z], + [current_scale.getX(), current_scale.getY(), + current_scale.getZ()]): + scale_widget.setRange(-1000, 1000) + scale_widget.setSingleStep(0.1) + scale_widget.setValue(scale_value) + + self.scale_x.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_x, value)) + self.scale_y.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_y, value)) + self.scale_z.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_z, value)) + self.scale_x.valueChanged.connect(lambda value: self._updateXScale(model, value)) self.scale_y.valueChanged.connect(lambda value: self._updateYScale(model, value)) self.scale_z.valueChanged.connect(lambda value: self._updateZScale(model, value)) + for axis_label, widget in (("X:", self.scale_x), ("Y:", self.scale_y), ("Z:", self.scale_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + scale_row.addWidget(axis) + scale_row.addWidget(widget) + + transform_layout.addLayout(scale_row, 3, 1, 1, 3) + self.transform_group.setLayout(transform_layout) + self._propertyLayout.addWidget(self.transform_group) # 碰撞检测面板 self._addCollisionPanel(model) @@ -1934,7 +1924,7 @@ class PropertyPanelManager: gui_element.setPythonTag("user_visible", True) self.name_group = QGroupBox("物体名称") - name_layout = QHBoxLayout() + name_layout = QGridLayout() self.active_check = QCheckBox() # 根据元素的实际可见性状态设置复选框 self.active_check.setChecked(user_visible) @@ -1955,8 +1945,9 @@ class PropertyPanelManager: # 如果失去焦点也更新名称 self.name_input.editingFinished.connect(lambda: updateGUIName(self.name_input.text())) - name_layout.addWidget(self.active_check) - name_layout.addWidget(self.name_input) + name_layout.addWidget(self.active_check, 0, 0) + name_layout.setColumnMinimumWidth(0, self.column_minimum_width) + name_layout.addWidget(self.name_input, 0, 1, 1, 3) self.name_group.setLayout(name_layout) self._propertyLayout.addWidget(self.name_group) @@ -1975,12 +1966,13 @@ class PropertyPanelManager: # GUI基本信息组 gui_info_group = QGroupBox("GUI信息") gui_info_layout = QGridLayout() + gui_info_layout.setColumnMinimumWidth(0, self.column_minimum_width) # GUI类型显示 gui_info_layout.addWidget(QLabel("GUI类型:"), 0, 0) typeValue = QLabel(gui_type) # typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;") - gui_info_layout.addWidget(typeValue, 0, 1) + gui_info_layout.addWidget(typeValue, 0, 1, 1, 3) # 修改 updateGUIPropertyPanel 中的文本属性部分 # 文本属性(如果适用) @@ -2001,7 +1993,7 @@ class PropertyPanelManager: textEdit.returnPressed.connect(updateText) textEdit.editingFinished.connect(updateText) - gui_info_layout.addWidget(textEdit, 1, 1) + gui_info_layout.addWidget(textEdit, 1, 1, 1, 3) gui_info_group.setLayout(gui_info_layout) self._propertyLayout.addWidget(gui_info_group) @@ -2015,6 +2007,7 @@ class PropertyPanelManager: transform_group = QGroupBox("变换 Transform") transform_layout = QGridLayout() + transform_layout.setColumnMinimumWidth(0, self.column_minimum_width) pos = gui_element.getPos() @@ -2024,40 +2017,90 @@ class PropertyPanelManager: logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标 logical_z = pos.getZ() / 0.1 - # 屏幕位置控件 - transform_layout.addWidget(QLabel("屏幕位置"), 0, 0) + position_row = QHBoxLayout() + position_row.setContentsMargins(0, 0, 0, 0) + position_row.setSpacing(4) - # X, Z 标签居中 - x_label = QLabel("X") - z_label = QLabel("Z") - x_label.setAlignment(Qt.AlignCenter) - z_label.setAlignment(Qt.AlignCenter) - - transform_layout.addWidget(x_label, 0, 1) - transform_layout.addWidget(z_label, 0, 2) + screen_title = QLabel("屏幕位置") + transform_layout.addWidget(screen_title, 0, 0) self.pos_x = QDoubleSpinBox() self.pos_x.setRange(-1000, 1000) self.pos_x.setValue(logical_x) - self.pos_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v)) - transform_layout.addWidget(self.pos_x, 1, 1) - self.pos_z = QDoubleSpinBox() self.pos_z.setRange(-1000, 1000) self.pos_z.setValue(logical_z) - self.pos_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v)) - transform_layout.addWidget(self.pos_z, 1, 2) - # 显示实际屏幕坐标(只读) - transform_layout.addWidget(QLabel("实际坐标"), 2, 0) + axis_labels = [] + for col, (axis_label, widget) in enumerate((("X:", self.pos_x), ("Z:", self.pos_z)), start=1): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + axis_labels.append(axis) + position_row.addWidget(axis) + position_row.addWidget(widget) - actualXLabel = QLabel(f"{pos.getX():.3f}") - actualXLabel.setStyleSheet("color: gray; font-size: 10px;") - actualZLabel = QLabel(f"{pos.getZ():.3f}") - actualZLabel.setStyleSheet("color: gray; font-size: 10px;") + transform_layout.addLayout(position_row, 0, 1, 1, 3) - transform_layout.addWidget(actualXLabel, 3, 1) - transform_layout.addWidget(actualZLabel, 3, 2) + # axis_width = max(label.sizeHint().width() for label in axis_labels) + # for label in axis_labels: + # label.setMinimumWidth(axis_width) + + # screen_position_row.setColumnStretch(2, 1) + # screen_position_row.setColumnStretch(4, 1) + + for i in range(4): + transform_layout.setColumnStretch(i, 1) + actual_position_row = QGridLayout() + + actual_row = QHBoxLayout() + actual_row.setContentsMargins(0, 0, 0, 0) + actual_row.setSpacing(4) + + actual_title = QLabel("实际坐标") + # actual_row.addWidget(actual_title) + transform_layout.addWidget(actual_title, 1, 0) + + self.actual_pos_x_label = QLabel(f"{pos.getX():.3f}") + self.actual_pos_x_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + self.actual_pos_x_label.setStyleSheet("color: gray; font-size: 10px;") + self.actual_pos_z_label = QLabel(f"{pos.getZ():.3f}") + self.actual_pos_z_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + self.actual_pos_z_label.setStyleSheet("color: gray; font-size: 10px;") + + for col, (axis_label, widget) in enumerate((("X:", self.actual_pos_x_label), + ("Z:", self.actual_pos_z_label)), start=1): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + # axis.setMinimumWidth(axis_width) + # actual_position_row.addWidget(axis, 0, col * 2 - 1, Qt.AlignLeft | Qt.AlignVCenter) + # actual_position_row.addWidget(widget, 0, col * 2, Qt.AlignLeft | Qt.AlignVCenter) + actual_row.addWidget(axis) + actual_row.addWidget(widget) + + # actual_position_row.setColumnStretch(2, 1) + # actual_position_row.setColumnStretch(4, 1) + + transform_layout.addLayout(actual_row, 1, 1, 1, 3) + + def _update_actual_position_labels(): + current_pos = gui_element.getPos() + self.actual_pos_x_label.setText(f"{current_pos.getX():.3f}") + self.actual_pos_z_label.setText(f"{current_pos.getZ():.3f}") + + _update_actual_position_labels() + + def _on_gui_pos_x_changed(value): + self.world.gui_manager.editGUI2DPosition(gui_element, "x", value) + _update_actual_position_labels() + + def _on_gui_pos_z_changed(value): + self.world.gui_manager.editGUI2DPosition(gui_element, "z", value) + _update_actual_position_labels() + + self.pos_x.valueChanged.connect(_on_gui_pos_x_changed) + self.pos_z.valueChanged.connect(_on_gui_pos_z_changed) if gui_type in ["2d_image", "2d_video_screen"]: scale = gui_element.getScale() @@ -2067,93 +2110,101 @@ class PropertyPanelManager: (tuple, list)) and len( scale) > 1 else scale - transform_layout.addWidget(QLabel("宽度"), 4, 0) + scale_row = QHBoxLayout() + scale_row.setContentsMargins(0, 0, 0, 0) + scale_row.setSpacing(4) + transform_layout.addWidget(QLabel("缩放"), 2, 0) + self.scale_x = QDoubleSpinBox() self.scale_x.setRange(0.1, 100) self.scale_x.setSingleStep(0.01) self.scale_x.setValue(width) self.scale_x.valueChanged.connect(lambda v: self.editGUIScale(gui_element, "x", v)) - transform_layout.addWidget(self.scale_x, 4, 1) - transform_layout.addWidget(QLabel("高度"), 4, 2) self.scale_z = QDoubleSpinBox() self.scale_z.setRange(0.1, 10) self.scale_z.setSingleStep(0.01) self.scale_z.setValue(height) self.scale_z.valueChanged.connect(lambda v: self.editGUIScale(gui_element, "z", v)) - transform_layout.addWidget(self.scale_z, 4, 3) + + for axis_label, widget in (("X:", self.scale_x), ("Z:", self.scale_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + scale_row.addWidget(axis) + scale_row.addWidget(widget) + + transform_layout.addLayout(scale_row, 2, 1, 1, 3) else: - transform_layout.addWidget(QLabel("缩放"), 4, 0) + scale_row = QHBoxLayout() + scale_row.setContentsMargins(0, 0, 0, 0) + scale_row.setSpacing(4) + transform_layout.addWidget(QLabel("缩放"), 2, 0) + scaleSpinBox = QDoubleSpinBox() scaleSpinBox.setRange(0.01, 100) scaleSpinBox.setSingleStep(0.01) scaleSpinBox.setValue(gui_element.getScale().getX() * 2) scaleSpinBox.valueChanged.connect(lambda v: self._update2DImageScale(gui_element, v)) - transform_layout.addWidget(scaleSpinBox, 4, 1) + + axis = QLabel("X:") + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + scale_row.addWidget(axis) + scale_row.addWidget(scaleSpinBox) + transform_layout.addLayout(scale_row, 2, 1, 1, 3) else: # 3D GUI组件使用世界坐标 - transform_layout.addWidget(QLabel("位置"), 0, 0) + position_row = QHBoxLayout() + position_row.setContentsMargins(0, 0, 0, 0) + position_row.setSpacing(4) + # position_row.addWidget(QLabel("位置")) + position_label = QLabel("位置") + transform_layout.addWidget(position_label, 0, 0) - # X, Y, Z 标签居中 - x_label = QLabel("X") - y_label = QLabel("Y") - z_label = QLabel("Z") - x_label.setAlignment(Qt.AlignCenter) - y_label.setAlignment(Qt.AlignCenter) - z_label.setAlignment(Qt.AlignCenter) - - transform_layout.addWidget(x_label, 0, 1) - transform_layout.addWidget(y_label, 0, 2) - transform_layout.addWidget(z_label, 0, 3) - - # 位置数值输入框 self.pos_x = QDoubleSpinBox() self.pos_x.setRange(-100, 100) self.pos_x.setValue(pos.getX()) self.pos_x.setSingleStep(0.01) self.pos_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "x", v)) - transform_layout.addWidget(self.pos_x, 1, 1) self.pos_y = QDoubleSpinBox() self.pos_y.setRange(-100, 100) self.pos_y.setValue(pos.getY()) self.pos_y.setSingleStep(0.01) self.pos_y.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "y", v)) - transform_layout.addWidget(self.pos_y, 1, 2) self.pos_z = QDoubleSpinBox() self.pos_z.setRange(-100, 100) self.pos_z.setValue(pos.getZ()) self.pos_z.setSingleStep(0.01) self.pos_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "z", v)) - transform_layout.addWidget(self.pos_z, 1, 3) - # 缩放属性 + for axis_label, widget in (("X:", self.pos_x), ("Y:", self.pos_y), ("Z:", self.pos_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + position_row.addWidget(axis) + position_row.addWidget(widget) + + transform_layout.addLayout(position_row, 0, 1, 1, 3) + scale = gui_element.getScale() - transform_layout.addWidget(QLabel("缩放"), 2, 0) + scale_row = QHBoxLayout() + scale_row.setContentsMargins(0, 0, 0, 0) + scale_row.setSpacing(4) + # scale_row.addWidget(QLabel("缩放")) + scale_label = QLabel("缩放") + transform_layout.addWidget(scale_label, 1, 0) - # X, Y, Z 缩放标签居中 - sx_label = QLabel("X") - sy_label = QLabel("Y") - sz_label = QLabel("Z") - sx_label.setAlignment(Qt.AlignCenter) - sy_label.setAlignment(Qt.AlignCenter) - sz_label.setAlignment(Qt.AlignCenter) - - transform_layout.addWidget(sx_label, 2, 1) - transform_layout.addWidget(sy_label, 2, 2) - transform_layout.addWidget(sz_label, 2, 3) - - # 缩放数值输入框 self.scale_x = QDoubleSpinBox() self.scale_x.setRange(0.01, 100) self.scale_x.setSingleStep(0.01) self.scale_x.setValue( scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale) self.scale_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v)) - transform_layout.addWidget(self.scale_x, 3, 1) self.scale_y = QDoubleSpinBox() self.scale_y.setRange(0.01, 100) @@ -2162,7 +2213,6 @@ class PropertyPanelManager: scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len( scale) > 1 else scale) self.scale_y.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "y", v)) - transform_layout.addWidget(self.scale_y, 3, 2) self.scale_z = QDoubleSpinBox() self.scale_z.setRange(0.01, 100) @@ -2171,7 +2221,15 @@ class PropertyPanelManager: scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len( scale) > 2 else scale) self.scale_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v)) - transform_layout.addWidget(self.scale_z, 3, 3) + + for axis_label, widget in (("X:", self.scale_x), ("Y:", self.scale_y), ("Z:", self.scale_z)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + scale_row.addWidget(axis) + scale_row.addWidget(widget) + + transform_layout.addLayout(scale_row, 1, 1, 1, 3) transform_group.setLayout(transform_layout) self._propertyLayout.addWidget(transform_group) @@ -2180,6 +2238,7 @@ class PropertyPanelManager: if gui_type in ["2d_image", "2d_video_screen", "info_panel"]: sort_group = QGroupBox("显示顺序") sort_layout = QGridLayout() + sort_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 获取当前sort值,如果没有设置则默认为0 current_sort = int(gui_element.getTag("sort") or "0") @@ -2200,7 +2259,7 @@ class PropertyPanelManager: print(f"✗ 更新{gui_type}渲染顺序失败: {e}") sort_spin.valueChanged.connect(updateSort) - sort_layout.addWidget(sort_spin, 0, 1) + sort_layout.addWidget(sort_spin, 0, 1, 1, 3) sort_group.setLayout(sort_layout) self._propertyLayout.addWidget(sort_group) @@ -2209,12 +2268,13 @@ class PropertyPanelManager: if gui_type in ["button", "label", "3d_text"]: appearance_group = QGroupBox("外观属性") appearance_layout = QGridLayout() + appearance_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 字体颜色选择 appearance_layout.addWidget(QLabel("字体颜色:"), 0, 0) colorButton = QPushButton("选择颜色") colorButton.clicked.connect(lambda checked, elem=gui_element: self._selectGUIColor(elem)) - appearance_layout.addWidget(colorButton, 0, 1) + appearance_layout.addWidget(colorButton, 0, 1, 1, 3) appearance_group.setLayout(appearance_layout) self._propertyLayout.addWidget(appearance_group) @@ -2223,12 +2283,13 @@ class PropertyPanelManager: if gui_type in ["button", "label"]: appearance_group = QGroupBox("外观属性") appearance_layout = QGridLayout() + appearance_layout.setColumnMinimumWidth(0, self.column_minimum_width) appearance_layout.addWidget(QLabel("背景颜色:"), 0, 0) colorButton = QPushButton("选择颜色") colorButton.clicked.connect(lambda: self.world.gui_manager.selectGUIColor(gui_element)) - appearance_layout.addWidget(colorButton, 0, 1) + appearance_layout.addWidget(colorButton, 0, 1, 1, 3) appearance_group.setLayout(appearance_layout) self._propertyLayout.addWidget(appearance_group) @@ -2236,6 +2297,7 @@ class PropertyPanelManager: if gui_type == "3d_image": image_group = QGroupBox("图片设置") image_layout = QGridLayout() + image_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 当前图片路径标签和 current_image_label = QLabel("当前图片:") @@ -2251,11 +2313,14 @@ class PropertyPanelManager: texture_label = QLabel(display_path) texture_label.setWordWrap(True) texture_label.setToolTip(current_texture_path if current_texture_path != "未设置" else "") - image_layout.addWidget(texture_label, 0, 1) + image_layout.addWidget(texture_label, 0, 1, 1, 3) # 选择图片按钮 - select_texture_button = QPushButton("选择图片...") - image_layout.addWidget(select_texture_button, 1, 0, 1, 2) + if has_icon("property_select_image"): + select_texture_button = QPushButton(get_icon("property_select_image"), "选择图片...") + else: + select_texture_button = QPushButton("选择图片...") + image_layout.addWidget(select_texture_button, 1, 0, 1, 4) def onSelectTexture(): from PyQt5.QtWidgets import QFileDialog @@ -2286,6 +2351,7 @@ class PropertyPanelManager: if gui_type in ["2d_image"]: image_group = QGroupBox("2D图片设置") image_layout = QGridLayout() + image_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 当前图片路径标签 current_image_label = QLabel("当前图片:") @@ -2300,11 +2366,15 @@ class PropertyPanelManager: display_path = current_texture_path texture_label = QLabel(display_path) texture_label.setWordWrap(True) - image_layout.addWidget(texture_label, 0, 1) + image_layout.addWidget(texture_label, 0, 1, 1, 3) # 选择图片按钮 - select_texture_button = QPushButton("选择图片...") - image_layout.addWidget(select_texture_button, 1, 0, 1, 2) + if has_icon("property_select_image"): + select_texture_button = QPushButton(get_icon("property_select_image"), "选择图片...") + else: + select_texture_button = QPushButton("选择图片...") + # select_texture_button = QPushButton("选择图片...") + image_layout.addWidget(select_texture_button, 1, 0, 1, 4) def onSelect2DTexture(): from PyQt5.QtWidgets import QFileDialog @@ -2463,6 +2533,7 @@ class PropertyPanelManager: # 背景图片组 image_group = QGroupBox("背景图片设置") image_layout = QGridLayout() + image_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 当前背景图片路径标签 current_image_label = QLabel("当前背景图片:") @@ -2476,16 +2547,16 @@ class PropertyPanelManager: texture_label = QLabel(display_path) texture_label.setWordWrap(True) texture_label.setToolTip(current_bg_image if current_bg_image else "") - image_layout.addWidget(texture_label, 0, 1) + image_layout.addWidget(texture_label, 0, 1, 1, 3) # 选择背景图片按钮 select_texture_button = QPushButton("选择背景图片...") - image_layout.addWidget(select_texture_button, 1, 0, 1, 2) + image_layout.addWidget(select_texture_button, 1, 0, 1, 4) # 清除背景图片按钮 clear_texture_button = QPushButton("清除背景图片") clear_texture_button.setStyleSheet("color: red;") - image_layout.addWidget(clear_texture_button, 2, 0, 1, 2) + image_layout.addWidget(clear_texture_button, 2, 0, 1, 4) def onSelectBackgroundImage(): """选择背景图片""" @@ -2526,6 +2597,7 @@ class PropertyPanelManager: # 显示顺序组 sort_group = QGroupBox("显示顺序") sort_layout = QGridLayout() + sort_layout.setColumnMinimumWidth(0, self.column_minimum_width) sort_layout.addWidget(QLabel("显示优先度:"), 0, 0) sort_spin = QSpinBox() @@ -2543,28 +2615,42 @@ class PropertyPanelManager: print(f"✗ 更新信息面板渲染顺序失败: {e}") sort_spin.valueChanged.connect(updateSort) - sort_layout.addWidget(sort_spin, 0, 1) + sort_layout.addWidget(sort_spin, 0, 1, 1, 3) sort_group.setLayout(sort_layout) self._propertyLayout.addWidget(sort_group) # 面板大小设置组 - size_group = QGroupBox("面板大小设置") + size_group = QGroupBox() + size_group.setTitle("面板大小设置") size_layout = QGridLayout() + size_layout.setColumnMinimumWidth(0, self.column_minimum_width) + + size_row = QHBoxLayout() + size_row.setContentsMargins(0, 0, 0, 0) + size_row.setSpacing(4) + + size_title = QLabel("面板大小设置:") + size_layout.addWidget(size_title, 0, 0) - size_layout.addWidget(QLabel("宽度:"), 0, 0) width_spin = QDoubleSpinBox() width_spin.setRange(0.1, 5.0) width_spin.setSingleStep(0.1) width_spin.setValue(current_size[0]) - size_layout.addWidget(width_spin, 0, 1) - size_layout.addWidget(QLabel("高度:"), 0, 2) height_spin = QDoubleSpinBox() height_spin.setRange(0.1, 5.0) height_spin.setSingleStep(0.1) height_spin.setValue(current_size[1]) - size_layout.addWidget(height_spin, 0, 3) + + for label_text, spinbox in (("宽度:", width_spin), ("高度:", height_spin)): + label = QLabel(label_text) + label.setAlignment(Qt.AlignVCenter) + label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + size_row.addWidget(label) + size_row.addWidget(spinbox) + + size_layout.addLayout(size_row, 0, 1, 1, 3) # 连接大小变化信号 def onSizeChanged(): @@ -2589,36 +2675,40 @@ class PropertyPanelManager: size_group.setLayout(size_layout) self._propertyLayout.addWidget(size_group) - # 位置设置组 (根据面板类型显示不同的位置控件) - position_group = QGroupBox("位置设置") + # 位置设置:X:[box] Y:[box]/Z:[box],右对齐可伸缩 + position_group = QGroupBox() + position_group.setTitle("位置设置") position_layout = QGridLayout() + position_layout.setColumnMinimumWidth(0, self.column_minimum_width) + + position_row = QHBoxLayout() + position_row.setContentsMargins(0, 0, 0, 0) + position_row.setSpacing(4) + + position_title = QLabel("位置设置:") + position_layout.addWidget(position_title, 0, 0) if is_3d_panel: - # 3D面板位置设置 (X, Y, Z) - position_layout.addWidget(QLabel("X:"), 0, 0) + # 3D 面板位置:X、Y、Z pos_x_spin = QDoubleSpinBox() pos_x_spin.setRange(-1000, 1000) pos_x_spin.setSingleStep(0.1) pos_x_spin.setValue(current_position[0] if len(current_position) > 0 else 0) - position_layout.addWidget(pos_x_spin, 0, 1) - position_layout.addWidget(QLabel("Y:"), 0, 2) pos_y_spin = QDoubleSpinBox() pos_y_spin.setRange(-1000, 1000) pos_y_spin.setSingleStep(0.1) pos_y_spin.setValue(current_position[1] if len(current_position) > 1 else 0) - position_layout.addWidget(pos_y_spin, 0, 3) - position_layout.addWidget(QLabel("Z:"), 1, 0) pos_z_spin = QDoubleSpinBox() pos_z_spin.setRange(-1000, 1000) pos_z_spin.setSingleStep(0.1) pos_z_spin.setValue(current_position[2] if len(current_position) > 2 else 0) - position_layout.addWidget(pos_z_spin, 1, 1) - # 连接位置变化信号 + axis_widgets = (("X:", pos_x_spin), ("Y:", pos_y_spin), ("Z:", pos_z_spin)) + def on3DPositionChanged(): - if hasattr(self.world, 'info_panel_manager'): + if hasattr(self.world, "info_panel_manager"): self.world.info_panel_manager.update3DPanelProperties( panel_id, position=(pos_x_spin.value(), pos_y_spin.value(), pos_z_spin.value())) @@ -2626,76 +2716,107 @@ class PropertyPanelManager: pos_y_spin.valueChanged.connect(on3DPositionChanged) pos_z_spin.valueChanged.connect(on3DPositionChanged) else: - # 2D面板位置设置 (X, Z) - position_layout.addWidget(QLabel("X:"), 0, 0) + # 2D 面板位置:X、Z pos_x_spin = QDoubleSpinBox() pos_x_spin.setRange(-1000, 1000) pos_x_spin.setSingleStep(0.1) pos_x_spin.setValue(current_position[0] if len(current_position) > 0 else 0) - position_layout.addWidget(pos_x_spin, 0, 1) - position_layout.addWidget(QLabel("Z:"), 0, 2) pos_z_spin = QDoubleSpinBox() pos_z_spin.setRange(-1000, 1000) pos_z_spin.setSingleStep(0.1) pos_z_spin.setValue(current_position[1] if len(current_position) > 1 else 0) - position_layout.addWidget(pos_z_spin, 0, 3) - # 连接位置变化信号 + axis_widgets = (("X:", pos_x_spin), ("Z:", pos_z_spin)) + def on2DPositionChanged(): - if hasattr(self.world, 'info_panel_manager'): + if hasattr(self.world, "info_panel_manager"): self.world.info_panel_manager.updatePanelProperties( panel_id, position=(pos_x_spin.value(), pos_z_spin.value())) pos_x_spin.valueChanged.connect(on2DPositionChanged) pos_z_spin.valueChanged.connect(on2DPositionChanged) + for axis_label, spinbox in axis_widgets: + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + position_row.addWidget(axis) + position_row.addWidget(spinbox) + + position_layout.addLayout(position_row, 0, 1, 1, 3) position_group.setLayout(position_layout) self._propertyLayout.addWidget(position_group) # 边框属性组 border_group = QGroupBox("边框属性") border_layout = QGridLayout() + border_layout.setColumnMinimumWidth(0, self.column_minimum_width) border_layout.addWidget(QLabel("边框颜色:"), 0, 0) + # border_layout.addWidget(border_color_label, 0, 0) + + border_row_top = QHBoxLayout() + border_row_top.setContentsMargins(0, 0, 0, 0) + border_row_top.setSpacing(4) - # 边框R分量 - border_layout.addWidget(QLabel("R:"), 1, 0) border_r_spin = QDoubleSpinBox() border_r_spin.setRange(0.0, 1.0) border_r_spin.setSingleStep(0.01) border_r_spin.setValue(current_border_color[0]) - border_layout.addWidget(border_r_spin, 1, 1) + color_r_bg = QLabel("R:") + color_r_bg.setAlignment(Qt.AlignVCenter) + color_r_bg.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + border_row_top.addWidget(color_r_bg) + border_row_top.addWidget(border_r_spin) - # 边框G分量 - border_layout.addWidget(QLabel("G:"), 1, 2) border_g_spin = QDoubleSpinBox() border_g_spin.setRange(0.0, 1.0) border_g_spin.setSingleStep(0.01) border_g_spin.setValue(current_border_color[1]) - border_layout.addWidget(border_g_spin, 1, 3) + color_g = QLabel("G:") + color_g.setAlignment(Qt.AlignVCenter) + color_g.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + border_row_top.addWidget(color_g) + border_row_top.addWidget(border_g_spin) + + border_layout.addLayout(border_row_top, 0, 1, 1, 3) + + indent_placeholder = QLabel("") + border_layout.addWidget(indent_placeholder, 1, 0) + # indent_placeholder.setFixedWidth(border_color_label.sizeHint().width()) + # border_layout.addWidget(indent_placeholder, 1, 0) + + border_row_bottom = QHBoxLayout() + border_row_bottom.setContentsMargins(0, 0, 0, 0) + border_row_bottom.setSpacing(4) - # 边框B分量 - border_layout.addWidget(QLabel("B:"), 2, 0) border_b_spin = QDoubleSpinBox() border_b_spin.setRange(0.0, 1.0) border_b_spin.setSingleStep(0.01) border_b_spin.setValue(current_border_color[2]) - border_layout.addWidget(border_b_spin, 2, 1) + color_b = QLabel("B:") + color_b.setAlignment(Qt.AlignVCenter) + color_b.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + border_row_bottom.addWidget(color_b) + border_row_bottom.addWidget(border_b_spin) - # 边框A分量 - border_layout.addWidget(QLabel("A:"), 2, 2) border_a_spin = QDoubleSpinBox() border_a_spin.setRange(0.0, 1.0) border_a_spin.setSingleStep(0.01) border_a_spin.setValue(current_border_color[3]) - border_layout.addWidget(border_a_spin, 2, 3) + color_a = QLabel("A:") + color_a.setAlignment(Qt.AlignVCenter) + color_a.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + border_row_bottom.addWidget(color_a) + border_row_bottom.addWidget(border_a_spin) + + border_layout.addLayout(border_row_bottom, 1, 1, 1, 3) - # 边框颜色选择按钮 border_color_button = QPushButton("选择边框颜色") border_color_button.clicked.connect(lambda: self._selectInfoPanelBorderColor( info_panel, border_r_spin, border_g_spin, border_b_spin, border_a_spin)) - border_layout.addWidget(border_color_button, 3, 0, 1, 4) + border_layout.addWidget(border_color_button, 2, 0, 1, 4) # 连接边框颜色变化信号 def onBorderColorChanged(): @@ -2724,45 +2845,74 @@ class PropertyPanelManager: bg_color_group = QGroupBox("背景颜色设置") bg_color_layout = QGridLayout() + bg_color_layout.setColumnMinimumWidth(0, self.column_minimum_width) - bg_color_layout.addWidget(QLabel("背景颜色:"), 0, 0) + bg_color_label = QLabel("背景颜色:") + bg_color_layout.addWidget(bg_color_label, 0, 0) + bg_color_row_top = QHBoxLayout() + bg_color_row_top.setContentsMargins(0, 0, 0, 0) + bg_color_row_top.setSpacing(4) - bg_color_layout.addWidget(QLabel("R:"), 1, 0) + # R分量 + color_r_bg = QLabel("R:") + color_r_bg.setAlignment(Qt.AlignVCenter) + color_r_bg.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) r_spin = QDoubleSpinBox() r_spin.setRange(0.0, 1.0) r_spin.setSingleStep(0.01) r_spin.setValue(current_bg_color[0]) - bg_color_layout.addWidget(r_spin, 1, 1) + bg_color_row_top.addWidget(color_r_bg) + bg_color_row_top.addWidget(r_spin) # G分量 - bg_color_layout.addWidget(QLabel("G:"), 1, 2) + color_g_bg = QLabel("G:") + color_g_bg.setAlignment(Qt.AlignVCenter) + color_g_bg.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) g_spin = QDoubleSpinBox() g_spin.setRange(0.0, 1.0) g_spin.setSingleStep(0.01) g_spin.setValue(current_bg_color[1]) - bg_color_layout.addWidget(g_spin, 1, 3) + bg_color_row_top.addWidget(color_g_bg) + bg_color_row_top.addWidget(g_spin) + + bg_color_layout.addLayout(bg_color_row_top, 0, 1, 1, 3) + + indent_placeholder = QLabel("") + bg_color_layout.addWidget(indent_placeholder, 1, 0) + + bg_color_row_bottom = QHBoxLayout() + bg_color_row_bottom.setContentsMargins(0, 0, 0, 0) + bg_color_row_bottom.setSpacing(4) # B分量 - bg_color_layout.addWidget(QLabel("B:"), 2, 0) + color_b_bg = QLabel("B:") + color_b_bg.setAlignment(Qt.AlignVCenter) + color_b_bg.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) b_spin = QDoubleSpinBox() b_spin.setRange(0.0, 1.0) b_spin.setSingleStep(0.01) b_spin.setValue(current_bg_color[2]) - bg_color_layout.addWidget(b_spin, 2, 1) + bg_color_row_bottom.addWidget(color_b_bg) + bg_color_row_bottom.addWidget(b_spin) # A分量 - bg_color_layout.addWidget(QLabel("A:"), 2, 2) + color_a_bg = QLabel("A:") + color_a_bg.setAlignment(Qt.AlignVCenter) + color_a_bg.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) a_spin = QDoubleSpinBox() a_spin.setRange(0.0, 1.0) a_spin.setSingleStep(0.01) a_spin.setValue(current_bg_color[3]) - bg_color_layout.addWidget(a_spin, 2, 3) + bg_color_row_bottom.addWidget(color_a_bg) + bg_color_row_bottom.addWidget(a_spin) + + bg_color_layout.addLayout(bg_color_row_bottom, 1, 1, 1, 3) # 颜色选择按钮 color_button = QPushButton("选择颜色") color_button.clicked.connect(lambda: self._selectInfoPanelBackgroundColor( info_panel, r_spin, g_spin, b_spin, a_spin)) - bg_color_layout.addWidget(color_button, 3, 0, 1, 4) + bg_color_layout.addWidget(color_button, 2, 0, 1, 4) # 连接颜色数值变化信号,自动更新背景颜色 def onBackgroundColorChanged(): @@ -2780,6 +2930,7 @@ class PropertyPanelManager: # 标题属性组 title_group = QGroupBox("标题属性") title_layout = QGridLayout() + title_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 标题文本 title_layout.addWidget(QLabel("标题文本:"), 0, 0) @@ -2791,37 +2942,62 @@ class PropertyPanelManager: # 标题颜色 title_layout.addWidget(QLabel("标题颜色:"), 1, 0) + title_color_row = QHBoxLayout() # 占位,保持对齐 + title_color_row.setContentsMargins(0, 0, 0, 0) + title_color_row.setSpacing(4) + # 标题R分量 - title_layout.addWidget(QLabel("R:"), 2, 0) + title_color_label_r = QLabel("R:") + title_color_label_r.setAlignment(Qt.AlignVCenter) + title_color_label_r.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + title_color_row.addWidget(title_color_label_r) title_r_spin = QDoubleSpinBox() title_r_spin.setRange(0.0, 1.0) title_r_spin.setSingleStep(0.01) title_r_spin.setValue(current_title_color[0]) - title_layout.addWidget(title_r_spin, 2, 1) + title_color_row.addWidget(title_r_spin) # 标题G分量 - title_layout.addWidget(QLabel("G:"), 2, 2) + title_color_label_g = QLabel("G:") + title_color_label_g.setAlignment(Qt.AlignVCenter) + title_color_label_g.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + title_color_row.addWidget(title_color_label_g) title_g_spin = QDoubleSpinBox() title_g_spin.setRange(0.0, 1.0) title_g_spin.setSingleStep(0.01) title_g_spin.setValue(current_title_color[1]) - title_layout.addWidget(title_g_spin, 2, 3) + title_color_row.addWidget(title_g_spin) + + title_layout.addLayout(title_color_row, 1, 1, 1, 3) + + title_layout.addWidget(QLabel(""), 2, 0) # 占位 + title_color_row_ = QHBoxLayout() # 占位,保持对齐 + title_color_row_.setContentsMargins(0, 0, 0, 0) + title_color_row_.setSpacing(4) # 标题B分量 - title_layout.addWidget(QLabel("B:"), 3, 0) + title_color_label_b = QLabel("B:") + title_color_label_b.setAlignment(Qt.AlignVCenter) + title_color_label_b.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + title_color_row_.addWidget(title_color_label_b) title_b_spin = QDoubleSpinBox() title_b_spin.setRange(0.0, 1.0) title_b_spin.setSingleStep(0.01) title_b_spin.setValue(current_title_color[2]) - title_layout.addWidget(title_b_spin, 3, 1) + title_color_row_.addWidget(title_b_spin) # 标题A分量 - title_layout.addWidget(QLabel("A:"), 3, 2) + title_color_label_a = QLabel("A:") + title_color_label_a.setAlignment(Qt.AlignVCenter) + title_color_label_a.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + title_color_row_.addWidget(title_color_label_a) title_a_spin = QDoubleSpinBox() title_a_spin.setRange(0.0, 1.0) title_a_spin.setSingleStep(0.01) title_a_spin.setValue(current_title_color[3]) - title_layout.addWidget(title_a_spin, 3, 3) + title_color_row_.addWidget(title_a_spin) + + title_layout.addLayout(title_color_row_, 2, 1, 1, 3) # 标题颜色选择按钮 title_color_button = QPushButton("选择标题颜色") @@ -2839,20 +3015,31 @@ class PropertyPanelManager: # 标题位置控制 (仅对2D面板) if not is_3d_panel: - title_layout.addWidget(QLabel("标题位置:"), 6, 0) - title_layout.addWidget(QLabel("X:"), 7, 0) + title_position_row = QHBoxLayout() + title_position_row.setContentsMargins(0, 0, 0, 0) + title_position_row.setSpacing(4) + + title_position_label = QLabel("标题位置") + title_layout.addWidget(title_position_label, 6, 0) + title_x_spin = QDoubleSpinBox() title_x_spin.setRange(-1.0, 1.0) title_x_spin.setSingleStep(0.01) title_x_spin.setValue(current_title_pos[0]) - title_layout.addWidget(title_x_spin, 7, 1) - title_layout.addWidget(QLabel("Y:"), 7, 2) title_y_spin = QDoubleSpinBox() title_y_spin.setRange(-1.0, 1.0) title_y_spin.setSingleStep(0.01) title_y_spin.setValue(current_title_pos[1]) - title_layout.addWidget(title_y_spin, 7, 3) + + for axis_label, spinbox in (("X:", title_x_spin), ("Y:", title_y_spin)): + label = QLabel(axis_label) + label.setAlignment(Qt.AlignVCenter) + label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + title_position_row.addWidget(label) + title_position_row.addWidget(spinbox) + + title_layout.addLayout(title_position_row, 6, 1, 1, 3) # 连接标题属性变化信号 def onTitleTextChanged(): @@ -2901,6 +3088,7 @@ class PropertyPanelManager: # 内容属性组 content_group = QGroupBox("内容属性") content_layout = QGridLayout() + content_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 内容文本 content_layout.addWidget(QLabel("内容文本:"), 0, 0) @@ -2911,38 +3099,63 @@ class PropertyPanelManager: # 内容颜色 content_layout.addWidget(QLabel("内容颜色:"), 1, 0) + content_color_row = QHBoxLayout() + content_color_row.setContentsMargins(0, 0, 0, 0) + content_color_row.setSpacing(4) + # 内容R分量 - content_layout.addWidget(QLabel("R:"), 2, 0) + content_r = QLabel("R:") + content_r.setAlignment(Qt.AlignVCenter) + content_r.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + content_color_row.addWidget(content_r) content_r_spin = QDoubleSpinBox() content_r_spin.setRange(0.0, 1.0) content_r_spin.setSingleStep(0.01) content_r_spin.setValue(current_content_color[0]) - content_layout.addWidget(content_r_spin, 2, 1) + content_color_row.addWidget(content_r_spin) # 内容G分量 - content_layout.addWidget(QLabel("G:"), 2, 2) + content_g = QLabel("G:") + content_g.setAlignment(Qt.AlignVCenter) + content_g.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + content_color_row.addWidget(content_g) content_g_spin = QDoubleSpinBox() content_g_spin.setRange(0.0, 1.0) content_g_spin.setSingleStep(0.01) content_g_spin.setValue(current_content_color[1]) - content_layout.addWidget(content_g_spin, 2, 3) + content_color_row.addWidget(content_g_spin) + + content_layout.addLayout(content_color_row, 1, 1, 1, 3) + + content_color_row_ = QHBoxLayout() + content_color_row_.setContentsMargins(0, 0, 0, 0) + content_color_row_.setSpacing(4) + content_layout.addWidget(QLabel(":"), 2, 0) # 内容B分量 - content_layout.addWidget(QLabel("B:"), 3, 0) + content_b = QLabel("B:") + content_b.setAlignment(Qt.AlignVCenter) + content_b.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + content_color_row_.addWidget(content_b) content_b_spin = QDoubleSpinBox() content_b_spin.setRange(0.0, 1.0) content_b_spin.setSingleStep(0.01) content_b_spin.setValue(current_content_color[2]) - content_layout.addWidget(content_b_spin, 3, 1) + content_color_row_.addWidget(content_b_spin) # 内容A分量 - content_layout.addWidget(QLabel("A:"), 3, 2) + content_a = QLabel("A:") + content_a.setAlignment(Qt.AlignVCenter) + content_a.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + content_color_row_.addWidget(content_a) content_a_spin = QDoubleSpinBox() content_a_spin.setRange(0.0, 1.0) content_a_spin.setSingleStep(0.01) content_a_spin.setValue(current_content_color[3]) - content_layout.addWidget(content_a_spin, 3, 3) + content_color_row_.addWidget(content_a_spin) + + content_layout.addLayout(content_color_row_, 2, 1, 1, 3) # 内容颜色选择按钮 content_color_button = QPushButton("选择内容颜色") @@ -2960,20 +3173,32 @@ class PropertyPanelManager: # 内容位置控制 (仅对2D面板) if not is_3d_panel: - content_layout.addWidget(QLabel("内容位置:"), 6, 0) - content_layout.addWidget(QLabel("X:"), 7, 0) + content_layout.addLayout(QLabel("内容位置:"), 6, 0) + content_pos_layout = QHBoxLayout() + content_pos_layout.setContentsMargins(0, 0, 0, 0) + content_pos_layout.setSpacing(4) + + content_x = QLabel("X:") + content_x.setAlignment(Qt.AlignVCenter) + content_x.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + content_pos_layout.addWidget(content_x) content_x_spin = QDoubleSpinBox() content_x_spin.setRange(-1.0, 1.0) content_x_spin.setSingleStep(0.01) content_x_spin.setValue(current_content_pos[0]) - content_layout.addWidget(content_x_spin, 7, 1) + content_pos_layout.addWidget(content_x_spin) - content_layout.addWidget(QLabel("Y:"), 7, 2) + content_y = QLabel("Y:") + content_y.setAlignment(Qt.AlignVCenter) + content_y.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + content_pos_layout.addWidget(content_y) content_y_spin = QDoubleSpinBox() content_y_spin.setRange(-1.0, 1.0) content_y_spin.setSingleStep(0.01) content_y_spin.setValue(current_content_pos[1]) - content_layout.addWidget(content_y_spin, 7, 3) + content_pos_layout.addWidget(content_y_spin) + + content_layout.addLayout(content_pos_layout, 6, 1, 1, 3) # 连接内容属性变化信号 def onContentTextChanged(): @@ -3703,7 +3928,7 @@ class PropertyPanelManager: current_radius = float(spherical_video.getTag("original_radius") or "5.0") radius_spin.setValue(current_radius) radius_spin.valueChanged.connect(lambda value: self._updateSphericalVideoRadius(spherical_video, value)) - sphere_layout.addWidget(radius_spin, 0, 1) + sphere_layout.addWidget(radius_spin, 0, 1, 1, 3) sphere_group.setLayout(sphere_layout) self._propertyLayout.addWidget(sphere_group) @@ -3948,6 +4173,7 @@ class PropertyPanelManager: video_info_group = QGroupBox("视频信息") video_info_layout = QGridLayout() + video_info_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 显示当前视频文件路径 - 改进版本 video_path = video_screen.getTag("video_path") if video_screen.hasTag("video_path") else "" @@ -3964,70 +4190,75 @@ class PropertyPanelManager: display_path = video_path path_label.setText(display_path) path_label.setStyleSheet("color: #00AAFF;") - video_info_layout.addWidget(path_label, 0, 1) + video_info_layout.addWidget(path_label, 0, 1, 1, 3) elif os.path.exists(video_path): # 显示本地文件信息 video_info_layout.addWidget(QLabel("视频文件:"), 0, 0) path_label = QLabel(os.path.basename(video_path)) path_label.setWordWrap(True) path_label.setStyleSheet("color: #00AAFF;") - video_info_layout.addWidget(path_label, 0, 1) + video_info_layout.addWidget(path_label, 0, 1, 1, 3) else: # 文件不存在 video_info_layout.addWidget(QLabel("状态:"), 0, 0) status_label = QLabel("文件不存在") status_label.setStyleSheet("color: red;") - video_info_layout.addWidget(status_label, 0, 1) + video_info_layout.addWidget(status_label, 0, 1, 1, 3) else: # 无视频 video_info_layout.addWidget(QLabel("状态:"), 0, 0) status_label = QLabel("无视频文件") status_label.setStyleSheet("color: red;") - video_info_layout.addWidget(status_label, 0, 1) + video_info_layout.addWidget(status_label, 0, 1, 1, 3) video_info_group.setLayout(video_info_layout) self._propertyLayout.addWidget(video_info_group) video_control_group = QGroupBox("视频控制") - video_control_layout = QHBoxLayout() + video_control_layout = QGridLayout() + video_control_layout.setColumnMinimumWidth(0, self.column_minimum_width) + video_control_h_layout = QHBoxLayout() # 播放按钮 play_btn = QPushButton("▶️ 播放") play_btn.clicked.connect(lambda: self.world.gui_manager.playVideo(video_screen)) - video_control_layout.addWidget(play_btn) + video_control_h_layout.addWidget(play_btn) # 暂停按钮 pause_btn = QPushButton("⏸️ 暂停") pause_btn.clicked.connect(lambda: self.world.gui_manager.pauseVideo(video_screen)) - video_control_layout.addWidget(pause_btn) + video_control_h_layout.addWidget(pause_btn) # 停止按钮 stop_btn = QPushButton("⏹️ 停止") stop_btn.clicked.connect(lambda: self.world.gui_manager.stopVideo(video_screen)) - video_control_layout.addWidget(stop_btn) + video_control_h_layout.addWidget(stop_btn) - video_control_group.setLayout(video_control_layout) - self._propertyLayout.addWidget(video_control_group) + video_control_layout.addLayout(video_control_h_layout, 0, 0, 1, 4) # 加载新视频按钮 load_btn = QPushButton("📁 加载新视频...") load_btn.clicked.connect(lambda: self._loadNewVideo(video_screen, item)) - self._propertyLayout.addWidget(load_btn) + video_control_layout.addWidget(load_btn, 1, 0, 1, 4) + + video_control_group.setLayout(video_control_layout) + self._propertyLayout.addWidget(video_control_group) # 添加URL输入区域 url_group = QGroupBox("网络视频") - url_layout = QHBoxLayout() + url_layout = QGridLayout() + url_layout.setColumnMinimumWidth(0, self.column_minimum_width) self.url_input = QLineEdit() self.url_input.setPlaceholderText("输入视频流URL") - url_layout.addWidget(self.url_input) + url_layout.addWidget(self.url_input, 0, 0, 1, 3) load_url_btn = QPushButton("加载URL") self._current_load_url_btn_3d = load_url_btn load_url_btn.clicked.connect( lambda: self._loadVideoFromURLWithOpenCV_3D(video_screen, self.url_input.text().strip())) - url_layout.addWidget(load_url_btn) + url_layout.addWidget(load_url_btn, 0, 3) url_group.setLayout(url_layout) self._propertyLayout.addWidget(url_group) @@ -4045,6 +4276,7 @@ class PropertyPanelManager: # 视频信息组 video_info_group = QGroupBox("2D视频信息") video_info_layout = QGridLayout() + video_info_layout.setColumnMinimumWidth(0, self.column_minimum_width) # 显示当前视频文件路径 # 在显示视频信息时区分本地文件和网络URL @@ -4056,26 +4288,26 @@ class PropertyPanelManager: path_label = QLabel(video_path) path_label.setWordWrap(True) path_label.setStyleSheet("color: #00AAFF;") - video_info_layout.addWidget(path_label, 0, 1) + video_info_layout.addWidget(path_label, 0, 1, 1, 3) elif os.path.exists(video_path): # 显示本地文件信息 video_info_layout.addWidget(QLabel("视频文件:"), 0, 0) path_label = QLabel(os.path.basename(video_path)) path_label.setWordWrap(True) path_label.setStyleSheet("color: #00AAFF;") - video_info_layout.addWidget(path_label, 0, 1) + video_info_layout.addWidget(path_label, 0, 1, 1, 3) else: # 文件不存在 video_info_layout.addWidget(QLabel("状态:"), 0, 0) status_label = QLabel("文件不存在") status_label.setStyleSheet("color: red;") - video_info_layout.addWidget(status_label, 0, 1) + video_info_layout.addWidget(status_label, 0, 1, 1, 3) else: # 无视频 video_info_layout.addWidget(QLabel("状态:"), 0, 0) status_label = QLabel("无视频文件") status_label.setStyleSheet("color: red;") - video_info_layout.addWidget(status_label, 0, 1) + video_info_layout.addWidget(status_label, 0, 1, 1, 3) video_screen.setBin("fixed", 0) @@ -4084,45 +4316,48 @@ class PropertyPanelManager: # 视频控制组 video_control_group = QGroupBox("2D视频控制") - video_control_layout = QHBoxLayout() + video_control_layout = QGridLayout() + video_control_h_layout = QHBoxLayout() # 播放按钮 play_btn = QPushButton("▶️ 播放") play_btn.clicked.connect(lambda: self.play2DVideo(video_screen)) - video_control_layout.addWidget(play_btn) + video_control_h_layout.addWidget(play_btn) # 暂停按钮 pause_btn = QPushButton("⏸️ 暂停") pause_btn.clicked.connect(lambda: self.pause2DVideo(video_screen)) - video_control_layout.addWidget(pause_btn) + video_control_h_layout.addWidget(pause_btn) # 停止按钮 stop_btn = QPushButton("⏹️ 停止") stop_btn.clicked.connect(lambda: self._stop2DVideo(video_screen)) - video_control_layout.addWidget(stop_btn) + video_control_h_layout.addWidget(stop_btn) + + video_control_layout.addLayout(video_control_h_layout, 0, 0, 1, 4) video_control_group.setLayout(video_control_layout) - self._propertyLayout.addWidget(video_control_group) # 加载新视频按钮 load_btn = QPushButton("📁 加载新视频...") load_btn.clicked.connect(lambda: self._loadNew2DVideo(video_screen, item)) - self._propertyLayout.addWidget(load_btn) + video_control_layout.addWidget(load_btn, 1, 0, 1, 4) + self._propertyLayout.addWidget(video_control_group) # 添加URL输入区域 url_group = QGroupBox("网络视频") - url_layout = QHBoxLayout() + url_layout = QGridLayout() self.url_input = QLineEdit() self.url_input.setPlaceholderText("输入视频流URL") - url_layout.addWidget(self.url_input) + url_layout.addWidget(self.url_input, 0, 0, 1, 3) load_url_btn = QPushButton("加载URL") self._current_load_url_btn_2d = load_url_btn load_url_btn.clicked.connect( lambda: self._loadVideoFromURLWithOpenCV(video_screen, self.url_input.text().strip())) - url_layout.addWidget(load_url_btn) + url_layout.addWidget(load_url_btn, 0, 3) url_group.setLayout(url_layout) self._propertyLayout.addWidget(url_group) @@ -5072,123 +5307,137 @@ class PropertyPanelManager: # 变换属性组 transform_group = QGroupBox("变换 Transform") transform_layout = QGridLayout() + transform_layout.setColumnMinimumWidth(0, self.column_minimum_width) + + transform_layout.addWidget(QLabel("位置"), 0, 0) # 位置属性 current_pos = light_object.pos - transform_layout.addWidget(QLabel("位置"), 0, 0) - - # X, Y, Z 标签居中 - x_label = QLabel("X") - y_label = QLabel("Y") - z_label = QLabel("Z") - x_label.setAlignment(Qt.AlignCenter) - y_label.setAlignment(Qt.AlignCenter) - z_label.setAlignment(Qt.AlignCenter) - - transform_layout.addWidget(x_label, 0, 1) - transform_layout.addWidget(y_label, 0, 2) - transform_layout.addWidget(z_label, 0, 3) + position_row = QHBoxLayout() + position_row.setContentsMargins(0, 0, 0, 0) + position_row.setSpacing(4) xPos = QDoubleSpinBox() xPos.setRange(-1000, 1000) xPos.setValue(current_pos.getX()) xPos.valueChanged.connect(lambda v: self._updateLightPosition(light_object, model, 'x', v)) - transform_layout.addWidget(xPos, 1, 1) yPos = QDoubleSpinBox() yPos.setRange(-1000, 1000) yPos.setValue(current_pos.getY()) yPos.valueChanged.connect(lambda v: self._updateLightPosition(light_object, model, 'y', v)) - transform_layout.addWidget(yPos, 1, 2) zPos = QDoubleSpinBox() zPos.setRange(-1000, 1000) zPos.setValue(current_pos.getZ()) zPos.valueChanged.connect(lambda v: self._updateLightPosition(light_object, model, 'z', v)) - transform_layout.addWidget(zPos, 1, 3) - # 世界位置(只读) + for axis_label, widget in (("X:", xPos), ("Y:", yPos), ("Z:", zPos)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + position_row.addWidget(axis) + position_row.addWidget(widget) + + transform_layout.addLayout(position_row, 0, 1, 1, 3) + worldPos = model.getPos(self.world.render) - transform_layout.addWidget(QLabel("世界位置"), 2, 0) + world_position_row = QHBoxLayout() + world_position_row.setContentsMargins(0, 0, 0, 0) + world_position_row.setSpacing(4) + transform_layout.addWidget(QLabel("世界位置"), 1, 0) worldXPos = QDoubleSpinBox() worldXPos.setRange(-1000, 1000) worldXPos.setValue(worldPos.getX()) worldXPos.setReadOnly(True) - transform_layout.addWidget(worldXPos, 3, 1) worldYPos = QDoubleSpinBox() worldYPos.setRange(-1000, 1000) worldYPos.setValue(worldPos.getY()) worldYPos.setReadOnly(True) - transform_layout.addWidget(worldYPos, 3, 2) worldZPos = QDoubleSpinBox() worldZPos.setRange(-1000, 1000) worldZPos.setValue(worldPos.getZ()) worldZPos.setReadOnly(True) - transform_layout.addWidget(worldZPos, 3, 3) + + for axis_label, widget in (("X:", worldXPos), ("Y:", worldYPos), ("Z:", worldZPos)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + world_position_row.addWidget(axis) + world_position_row.addWidget(widget) + + transform_layout.addLayout(world_position_row, 1, 1, 1, 3) # 旋转属性(仅聚光灯) + next_row = 2 if hasattr(light_object, 'direction'): current_hpr = model.getHpr() - transform_layout.addWidget(QLabel("旋转"), 4, 0) - - # H, P, R 标签居中 - h_label = QLabel("H") - p_label = QLabel("P") - r_label = QLabel("R") - h_label.setAlignment(Qt.AlignCenter) - p_label.setAlignment(Qt.AlignCenter) - r_label.setAlignment(Qt.AlignCenter) - - transform_layout.addWidget(h_label, 4, 1) - transform_layout.addWidget(p_label, 4, 2) - transform_layout.addWidget(r_label, 4, 3) + rotation_row = QHBoxLayout() + rotation_row.setContentsMargins(0, 0, 0, 0) + rotation_row.setSpacing(4) + transform_layout.addWidget(QLabel("旋转"), next_row, 0) hRot = QDoubleSpinBox() hRot.setRange(-180, 180) hRot.setValue(current_hpr.getX()) hRot.valueChanged.connect(lambda v: self._updateLightRotation(light_object, model, 'h', v)) - transform_layout.addWidget(hRot, 5, 1) pRot = QDoubleSpinBox() pRot.setRange(-180, 180) pRot.setValue(current_hpr.getY()) pRot.valueChanged.connect(lambda v: self._updateLightRotation(light_object, model, 'p', v)) - transform_layout.addWidget(pRot, 5, 2) rRot = QDoubleSpinBox() rRot.setRange(-180, 180) rRot.setValue(current_hpr.getZ()) rRot.valueChanged.connect(lambda v: self._updateLightRotation(light_object, model, 'r', v)) - transform_layout.addWidget(rRot, 5, 3) + + for axis_label, widget in (("H:", hRot), ("P:", pRot), ("R:", rRot)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + rotation_row.addWidget(axis) + rotation_row.addWidget(widget) + + transform_layout.addLayout(rotation_row, next_row, 1, 1, 3) + next_row += 1 # 缩放属性 current_scale = model.getScale() - scale_row = 6 if hasattr(light_object, 'direction') else 4 - transform_layout.addWidget(QLabel("缩放"), scale_row, 0) + scale_row = QHBoxLayout() + scale_row.setContentsMargins(0, 0, 0, 0) + scale_row.setSpacing(4) + transform_layout.addWidget(QLabel("缩放"), next_row, 0) xScaleSpinBox = QDoubleSpinBox() xScaleSpinBox.setRange(0.01, 100) xScaleSpinBox.setSingleStep(0.1) xScaleSpinBox.setValue(current_scale.getX()) xScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'x', v)) - transform_layout.addWidget(xScaleSpinBox, scale_row + 1, 1) yScaleSpinBox = QDoubleSpinBox() yScaleSpinBox.setRange(0.01, 100) yScaleSpinBox.setSingleStep(0.1) yScaleSpinBox.setValue(current_scale.getY()) yScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'y', v)) - transform_layout.addWidget(yScaleSpinBox, scale_row + 1, 2) zScaleSpinBox = QDoubleSpinBox() zScaleSpinBox.setRange(0.01, 100) zScaleSpinBox.setSingleStep(0.1) zScaleSpinBox.setValue(current_scale.getZ()) zScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'z', v)) - transform_layout.addWidget(zScaleSpinBox, scale_row + 1, 3) + + for axis_label, widget in (("X:", xScaleSpinBox), ("Y:", yScaleSpinBox), ("Z:", zScaleSpinBox)): + axis = QLabel(axis_label) + axis.setAlignment(Qt.AlignVCenter) + axis.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + scale_row.addWidget(axis) + scale_row.addWidget(widget) + + transform_layout.addLayout(scale_row, next_row, 1, 1, 3) transform_group.setLayout(transform_layout) self._propertyLayout.addWidget(transform_group) @@ -5203,7 +5452,7 @@ class PropertyPanelManager: energySpinBox.setRange(0, 10000) energySpinBox.setValue(light_object.energy) energySpinBox.valueChanged.connect(lambda v: self._updateLightEnergy(light_object, v)) - light_layout.addWidget(energySpinBox, 0, 1) + light_layout.addWidget(energySpinBox, 0, 1, 1, 3) # 半径 light_layout.addWidget(QLabel("半径:"), 1, 0) @@ -5211,7 +5460,7 @@ class PropertyPanelManager: radiusSpinBox.setRange(1, 2000) radiusSpinBox.setValue(light_object.radius) radiusSpinBox.valueChanged.connect(lambda v: self._updateLightRadius(light_object, v)) - light_layout.addWidget(radiusSpinBox, 1, 1) + light_layout.addWidget(radiusSpinBox, 1, 1, 1, 3) # 视野角度(仅聚光灯) if hasattr(light_object, 'fov'): @@ -5220,7 +5469,7 @@ class PropertyPanelManager: fovSpinBox.setRange(1, 180) fovSpinBox.setValue(light_object.fov) fovSpinBox.valueChanged.connect(lambda v: self._updateLightFOV(light_object, v)) - light_layout.addWidget(fovSpinBox, 2, 1) + light_layout.addWidget(fovSpinBox, 2, 1, 1, 3) light_group.setLayout(light_layout) self._propertyLayout.addWidget(light_group) @@ -5419,43 +5668,45 @@ class PropertyPanelManager: if base_color is not None: print(f"材质基础颜色: {base_color}") - # 基础颜色标题 + # 基础颜色编辑 color_row = 2 if material_status != "标准PBR材质" else 1 - material_layout.addWidget(QLabel("基础颜色"), color_row, 0) - # R, G, B 标签 - r_label = QLabel("R") - g_label = QLabel("G") - b_label = QLabel("B") - r_label.setAlignment(Qt.AlignCenter) - g_label.setAlignment(Qt.AlignCenter) - b_label.setAlignment(Qt.AlignCenter) - - material_layout.addWidget(r_label, color_row, 1) - material_layout.addWidget(g_label, color_row, 2) - material_layout.addWidget(b_label, color_row, 3) - - # RGB 数值框 r_spinbox = QDoubleSpinBox() r_spinbox.setRange(0.0, 1.0) r_spinbox.setSingleStep(0.01) r_spinbox.setValue(base_color.x) r_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'r', v)) - material_layout.addWidget(r_spinbox, color_row + 1, 1) g_spinbox = QDoubleSpinBox() g_spinbox.setRange(0.0, 1.0) g_spinbox.setSingleStep(0.01) g_spinbox.setValue(base_color.y) g_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'g', v)) - material_layout.addWidget(g_spinbox, color_row + 1, 2) b_spinbox = QDoubleSpinBox() b_spinbox.setRange(0.0, 1.0) b_spinbox.setSingleStep(0.01) b_spinbox.setValue(base_color.z) b_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'b', v)) - material_layout.addWidget(b_spinbox, color_row + 1, 3) + + title_label = QLabel("基础颜色") + material_layout.addWidget(title_label, color_row, 0) + + base_color_layout = QHBoxLayout() + base_color_layout.setContentsMargins(0, 0, 0, 0) + base_color_layout.setSpacing(4) + + for label_text, spinbox in (("R:", r_spinbox), ("G:", g_spinbox), ("B:", b_spinbox)): + channel_label = QLabel(label_text) + channel_label.setAlignment(Qt.AlignVCenter) + channel_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + base_color_layout.addWidget(channel_label) + spinbox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + base_color_layout.addWidget(spinbox) + + # base_color_layout.addStretch(1) + + material_layout.addLayout(base_color_layout, color_row, 1, 1, 4) else: no_base_color_label = QLabel("无法获取材质基础颜色") no_base_color_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") @@ -5463,7 +5714,7 @@ class PropertyPanelManager: material_layout.addWidget(no_base_color_label, 1, 1, 1, 3) # 材质属性行 - current_row = color_row + 2 if base_color is not None else 2 + current_row = color_row + 1 if base_color is not None else 2 # 粗糙度 if hasattr(material, 'roughness') and material.roughness is not None: @@ -5476,18 +5727,18 @@ class PropertyPanelManager: roughness_spinbox.setValue(roughness_value) roughness_spinbox.valueChanged.connect( lambda v, mat=material: self._updateMaterialRoughness(mat, v)) - material_layout.addWidget(roughness_spinbox, current_row, 1) + material_layout.addWidget(roughness_spinbox, current_row, 1, 1, 1) except (TypeError, ValueError) as e: print(f"粗糙度值无效: {material.roughness}, 错误: {e}") material_layout.addWidget(QLabel("粗糙度:"), current_row, 0) no_roughness_label = QLabel("粗糙度值无效") no_roughness_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - material_layout.addWidget(no_roughness_label, current_row, 1) + material_layout.addWidget(no_roughness_label, current_row, 1, 1, 1) else: material_layout.addWidget(QLabel("粗糙度:"), current_row, 0) no_roughness_label = QLabel("不支持") no_roughness_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - material_layout.addWidget(no_roughness_label, current_row, 1) + material_layout.addWidget(no_roughness_label, current_row, 1, 1, 1) current_row += 1 @@ -5501,18 +5752,18 @@ class PropertyPanelManager: metallic_spinbox.setSingleStep(0.01) metallic_spinbox.setValue(metallic_value) metallic_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialMetallic(mat, v)) - material_layout.addWidget(metallic_spinbox, current_row, 1) + material_layout.addWidget(metallic_spinbox, current_row, 1, 1, 1) except (TypeError, ValueError) as e: print(f"金属性值无效: {material.metallic}, 错误: {e}") material_layout.addWidget(QLabel("金属性:"), current_row, 0) no_metallic_label = QLabel("值无效") no_metallic_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - material_layout.addWidget(no_metallic_label, current_row, 1) + material_layout.addWidget(no_metallic_label, current_row, 1, 1, 1) else: material_layout.addWidget(QLabel("金属性:"), current_row, 0) no_metallic_label = QLabel("不支持") no_metallic_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - material_layout.addWidget(no_metallic_label, current_row, 1) + material_layout.addWidget(no_metallic_label, current_row, 1, 1, 1) current_row += 1 @@ -5526,18 +5777,18 @@ class PropertyPanelManager: ior_spinbox.setSingleStep(0.01) ior_spinbox.setValue(ior_value) ior_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialIOR(mat, v)) - material_layout.addWidget(ior_spinbox, current_row, 1) + material_layout.addWidget(ior_spinbox, current_row, 1, 1, 1) except (TypeError, ValueError) as e: print(f"折射率值无效: {material.refractive_index}, 错误: {e}") material_layout.addWidget(QLabel("折射率:"), current_row, 0) no_ior_label = QLabel("折射率值无效") no_ior_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - material_layout.addWidget(no_ior_label, current_row, 1) + material_layout.addWidget(no_ior_label, current_row, 1, 1, 1) else: material_layout.addWidget(QLabel("折射率:"), current_row, 0) no_ior_label = QLabel("此材质不支持折射率编辑") no_ior_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - material_layout.addWidget(no_ior_label, current_row, 1) + material_layout.addWidget(no_ior_label, current_row, 1, 1, 1) current_row += 1 @@ -9353,6 +9604,11 @@ except Exception as e: # 创建碰撞检测组 collision_group = QGroupBox("碰撞检测") collision_layout = QGridLayout() + collision_layout.setColumnMinimumWidth(0, self.column_minimum_width) + # collision_layout.setColumnStretch(0, 0) + # collision_layout.setColumnStretch(1, 1) + # collision_layout.setColumnStretch(2, 1) + # collision_layout.setColumnStretch(3, 1) # 检查模型是否已有碰撞 has_collision = self._hasCollision(model) @@ -9369,16 +9625,24 @@ except Exception as e: header_layout.setContentsMargins(0, 0, 0, 0) header_layout.setSpacing(8) + # 调整列宽,保证控件对齐 + # collision_layout.setColumnStretch(0, 0) + # collision_layout.setColumnStretch(1, 0) + # collision_layout.setColumnStretch(2, 0) + # collision_layout.setColumnStretch(3, 0) + # 碰撞状态行 + status_layout = QHBoxLayout() status_label = QLabel("状态:") - collision_layout.addWidget(status_label, 0, 0) + status_layout.addWidget(status_label) # 状态徽章(使用固定宽度样式) if has_collision: self.collision_status_badge = self.createFixedStatusBadge("已启用", "green") else: self.collision_status_badge = self.createFixedStatusBadge("未启用", "red") - collision_layout.addWidget(self.collision_status_badge, 0, 1) + status_layout.addWidget(self.collision_status_badge) + collision_layout.addLayout(status_layout, 0, 0, 1, 1) # 形状选择标签(始终显示) self.collision_shape_label = QLabel("碰撞形状:") @@ -9393,7 +9657,7 @@ except Exception as e: "平面 (Plane)", "自动选择 (Auto)" ]) - collision_layout.addWidget(self.collision_shape_combo, 1, 1) + collision_layout.addWidget(self.collision_shape_combo, 1, 1, 1, 3) # 保存布局引用,用于动态添加/移除控件 self.collision_layout = collision_layout @@ -9416,15 +9680,19 @@ except Exception as e: # 显示/隐藏切换按钮(使用现代化样式) visibility_text = "隐藏碰撞" if is_collision_visible else "显示碰撞" - self.collision_visibility_button = self.createModernButton(visibility_text, "primary") + self.collision_visibility_button = self.createModernButton(visibility_text) + self.collision_visibility_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.collision_visibility_button.setStyleSheet(self._collision_button_style()) self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model)) - collision_layout.addWidget(self.collision_visibility_button, current_row, 0, 1, 2) + collision_layout.addWidget(self.collision_visibility_button, current_row, 0, 1, 4) current_row += 1 # 移除碰撞按钮(使用现代化样式) - self.collision_button = self.createModernButton("移除碰撞", "danger") + self.collision_button = self.createModernButton("移除碰撞") + self.collision_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.collision_button.setStyleSheet(self._collision_button_style()) self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) - collision_layout.addWidget(self.collision_button, current_row, 0, 1, 2) + collision_layout.addWidget(self.collision_button, current_row, 0, 1, 4) else: # 如果没有碰撞,设置默认选择并允许编辑 self.collision_shape_combo.setCurrentText("球形 (Sphere)") @@ -9438,9 +9706,11 @@ except Exception as e: self.collision_visibility_button.setVisible(False) # 添加碰撞按钮(使用现代化样式) - self.collision_button = self.createModernButton("添加碰撞", "success") + self.collision_button = self.createModernButton("添加碰撞") + self.collision_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.collision_button.setStyleSheet(self._collision_button_style()) self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) - collision_layout.addWidget(self.collision_button, current_row, 0, 1, 2) + collision_layout.addWidget(self.collision_button, current_row, 0, 1, 4) collision_group.setLayout(collision_layout) self._propertyLayout.addWidget(collision_group) @@ -9456,32 +9726,47 @@ except Exception as e: # 位置调整控件(所有类型都有) pos_label = QLabel("位置偏移:") - layout.addWidget(pos_label, current_row, 0) - current_row += 1 + pos_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) + pos_label.setVisible(True) # 确保可见 # X, Y, Z 位置调整 - self.collision_pos_x = self._createCollisionSpinBox(-1000000, 1000000, 2) - self.collision_pos_y = self._createCollisionSpinBox(-1000000, 1000000, 2) - self.collision_pos_z = self._createCollisionSpinBox(-1000000, 1000000, 2) + self.collision_pos_x = self._createCollisionSpinBox(-100, 100, 2) + self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2) + self.collision_pos_z = self._createCollisionSpinBox(-100, 100, 2) - # 只在没有现有碰撞时设置默认值,否则由_loadCurrentCollisionParameters加载实际值 - if not self._hasCollision(model): - # 设置默认位置偏移(无偏移) - self.collision_pos_x.setValue(0.0) - self.collision_pos_y.setValue(0.0) - self.collision_pos_z.setValue(0.0) + for spinbox in (self.collision_pos_x, self.collision_pos_y, self.collision_pos_z): + spinbox.setMinimumWidth(140) + spinbox.setVisible(True) - layout.addWidget(QLabel("X:"), current_row, 0) - layout.addWidget(self.collision_pos_x, current_row, 1) - current_row += 1 + axis_controls = [ + ("X:", self.collision_pos_x), + ("Y:", self.collision_pos_y), + ("Z:", self.collision_pos_z), + ] - layout.addWidget(QLabel("Y:"), current_row, 0) - layout.addWidget(self.collision_pos_y, current_row, 1) - current_row += 1 + for offset, (axis_text, spinbox) in enumerate(axis_controls): + row = current_row + offset + axis_layout = QHBoxLayout() + if offset == 0: + layout.addWidget(pos_label, row, 0) + # axis_label.addWidget(pos_label) + else: + spacer = QLabel("") + spacer.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + spacer.setVisible(True) + layout.addWidget(spacer, row, 0) + # axis_layout.addWidget(spacer) - layout.addWidget(QLabel("Z:"), current_row, 0) - layout.addWidget(self.collision_pos_z, current_row, 1) - current_row += 1 + axis_label = QLabel(axis_text) + axis_label.setAlignment(Qt.AlignVCenter) + axis_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + axis_layout.addWidget(axis_label) + axis_layout.addWidget(spinbox) + # layout.addWidget(axis_label, row, 1) + # layout.addWidget(spinbox, row, 2, 1, 2) + layout.addLayout(axis_layout, row, 1, 1, 3) + + current_row += len(axis_controls) # 连接位置变化信号 self.collision_pos_x.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'x', v)) @@ -9522,6 +9807,7 @@ except Exception as e: # 半径调整 radius_label = QLabel("半径:") layout.addWidget(radius_label, current_row, 0) + # layout.addWidget(QLabel(""), current_row, 1) self.collision_radius = self._createCollisionSpinBox(0.01, 100000, 2) @@ -9541,7 +9827,7 @@ except Exception as e: self.collision_radius.setValue(default_radius) self.collision_radius.valueChanged.connect(lambda v: self._updateSphereRadius(model, v)) - layout.addWidget(self.collision_radius, current_row, 1) + layout.addWidget(self.collision_radius, current_row, 1, 1, 3) current_row += 1 return current_row @@ -9552,7 +9838,8 @@ except Exception as e: size_label = QLabel("尺寸:") layout.addWidget(size_label, current_row, 0) - current_row += 1 + # layout.addWidget(QLabel(""), current_row, 1) + # current_row += 1 # 宽度、长度、高度 self.collision_width = self._createCollisionSpinBox(0.001, 100000, 2) @@ -9578,17 +9865,33 @@ except Exception as e: self.collision_length.setValue(model_size.y) self.collision_height.setValue(model_size.z) - layout.addWidget(QLabel("宽度:"), current_row, 0) - layout.addWidget(self.collision_width, current_row, 1) - current_row += 1 + box_controls = [ + ("宽度:", self.collision_width), + ("长度:", self.collision_length), + ("高度:", self.collision_height), + ] - layout.addWidget(QLabel("长度:"), current_row, 0) - layout.addWidget(self.collision_length, current_row, 1) - current_row += 1 + for offset, (text, spinbox) in enumerate(box_controls): + text_layout = QHBoxLayout() + if offset == 0: + layout.addWidget(size_label, current_row, 0) + else: + spacer = QLabel("") + spacer.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + spacer.setVisible(True) + layout.addWidget(spacer, current_row, 0) + # placeholder = QLabel("") + # layout.addWidget(placeholder, current_row, 0) - layout.addWidget(QLabel("高度:"), current_row, 0) - layout.addWidget(self.collision_height, current_row, 1) - current_row += 1 + axis_label = QLabel(text) + axis_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + text_layout.addWidget(axis_label) + text_layout.addWidget(spinbox) + layout.addLayout(text_layout, current_row, 1, 1, 3) + # layout.addWidget(axis_label, current_row, 1) + # layout.addWidget(spinbox, current_row, 2) + current_row += 1 # 连接信号 self.collision_width.valueChanged.connect(lambda v: self._updateBoxSize(model, 'width', v)) @@ -9604,6 +9907,7 @@ except Exception as e: # 半径和高度 radius_label = QLabel("半径:") layout.addWidget(radius_label, current_row, 0) + # layout.addWidget(QLabel(""), current_row, 1) self.collision_capsule_radius = self._createCollisionSpinBox(0.01, 100000, 2) @@ -9627,11 +9931,12 @@ except Exception as e: self.collision_capsule_radius.setValue(default_radius) self.collision_capsule_radius.valueChanged.connect(lambda v: self._updateCapsuleRadius(model, v)) - layout.addWidget(self.collision_capsule_radius, current_row, 1) + layout.addWidget(self.collision_capsule_radius, current_row, 1, 1, 3) current_row += 1 height_label = QLabel("高度:") layout.addWidget(height_label, current_row, 0) + # layout.addWidget(QLabel(""), current_row, 1) self.collision_capsule_height = self._createCollisionSpinBox(0.01, 10000, 2) @@ -9651,7 +9956,7 @@ except Exception as e: self.collision_capsule_height.setValue(model_size.z) self.collision_capsule_height.valueChanged.connect(lambda v: self._updateCapsuleHeight(model, v)) - layout.addWidget(self.collision_capsule_height, current_row, 1) + layout.addWidget(self.collision_capsule_height, current_row, 1, 1, 3) current_row += 1 return current_row @@ -9663,7 +9968,8 @@ except Exception as e: # 法向量 normal_label = QLabel("法向量:") layout.addWidget(normal_label, current_row, 0) - current_row += 1 + # layout.addWidget(QLabel(""), current_row, 1) + # current_row += 1 self.collision_normal_x = self._createCollisionSpinBox(-1, 1, 2) self.collision_normal_y = self._createCollisionSpinBox(-1, 1, 2) @@ -9676,16 +9982,40 @@ except Exception as e: self.collision_normal_y.setValue(0.0) self.collision_normal_z.setValue(1.0) - layout.addWidget(QLabel("Nx:"), current_row, 0) - layout.addWidget(self.collision_normal_x, current_row, 1) + # placeholder_nx = QLabel("") + # layout.addWidget(placeholder_nx, current_row, 0) + + hbox_nx = QHBoxLayout() + axis_label_nx = QLabel("Nx:") + axis_label_nx.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis_label_nx.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + hbox_nx.addWidget(axis_label_nx) + hbox_nx.addWidget(self.collision_normal_x) + layout.addLayout(hbox_nx, current_row, 1, 1, 3) current_row += 1 - layout.addWidget(QLabel("Ny:"), current_row, 0) - layout.addWidget(self.collision_normal_y, current_row, 1) + placeholder_ny = QLabel("") + layout.addWidget(placeholder_ny, current_row, 0) + + hbox_ny = QHBoxLayout() + axis_label_ny = QLabel("Ny:") + axis_label_ny.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis_label_ny.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + hbox_ny.addWidget(axis_label_ny) + hbox_ny.addWidget(self.collision_normal_y) + layout.addLayout(hbox_ny, current_row, 1, 1, 3) current_row += 1 - layout.addWidget(QLabel("Nz:"), current_row, 0) - layout.addWidget(self.collision_normal_z, current_row, 1) + placeholder_nz = QLabel("") + layout.addWidget(placeholder_nz, current_row, 0) + + hbox_nz = QHBoxLayout() + axis_label_nz = QLabel("Nz:") + axis_label_nz.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis_label_nz.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + hbox_nz.addWidget(axis_label_nz) + hbox_nz.addWidget(self.collision_normal_z) + layout.addLayout(hbox_nz, current_row, 1, 1, 3) current_row += 1 # 连接信号 @@ -9752,28 +10082,8 @@ except Exception as e: button_text = "隐藏碰撞" if is_visible else "显示碰撞" self.collision_visibility_button.setText(button_text) - # 应用主要按钮样式 - self.collision_visibility_button.setStyleSheet(""" - QPushButton { - background-color: rgba(77, 116, 189, 0.8); - color: #ffffff; - border: 1px solid #4d74bd; - border-radius: 4px; - padding: 8px 12px; - font-family: 'Microsoft YaHei', 'Inter', sans-serif; - font-size: 10px; - font-weight: 500; - min-height: 16px; - } - QPushButton:hover { - background-color: rgba(77, 116, 189, 1.0); - border: 1px solid rgba(77, 116, 189, 1.0); - } - QPushButton:pressed { - background-color: rgba(61, 96, 169, 1.0); - border: 1px solid rgba(61, 96, 169, 1.0); - } - """) + # 应用统一按钮样式 + self.collision_visibility_button.setStyleSheet(self._collision_button_style()) print(f"更新可见性按钮:{model.getName()} - {'可见' if is_visible else '隐藏'}") except Exception as e: @@ -9961,10 +10271,11 @@ except Exception as e: return self._updating_collision_panel = True - + print("-------------------------------------") if hasattr(self, 'collision_button') and hasattr(self, 'collision_status_badge') and hasattr(self, 'collision_shape_combo'): has_collision = self._hasCollision(model) + print(f"模型 {model.getName()} 是否有碰撞体: {has_collision}-------------------------------------------------") # 更新状态徽章(使用固定宽度) if has_collision: @@ -9983,7 +10294,7 @@ except Exception as e: # 移除旧徽章并添加新徽章 parent_layout.removeWidget(old_badge) old_badge.deleteLater() - parent_layout.addWidget(new_badge, 0, 1) # 状态徽章在第0行第1列 + parent_layout.addWidget(new_badge, 0, 0, 1, 1) # 状态徽章在第0行第1列 self.collision_status_badge = new_badge break @@ -9992,28 +10303,8 @@ except Exception as e: if hasattr(self, 'collision_button'): # 更新按钮文本和样式(简单方法:直接设置文本和样式) self.collision_button.setText("移除碰撞") - # 应用危险按钮样式 - self.collision_button.setStyleSheet(""" - QPushButton { - background-color: rgba(255, 99, 99, 0.15); - color: #ff6363; - border: 1px solid rgba(255, 99, 99, 0.6); - border-radius: 4px; - padding: 8px 12px; - font-family: 'Microsoft YaHei', 'Inter', sans-serif; - font-size: 10px; - font-weight: 400; - min-height: 16px; - } - QPushButton:hover { - background-color: rgba(255, 99, 99, 0.25); - border: 1px solid #ff6363; - } - QPushButton:pressed { - background-color: rgba(255, 99, 99, 0.4); - color: #ffffff; - } - """) + # 应用统一按钮样式 + self.collision_button.setStyleSheet(self._collision_button_style()) # 重新连接信号 try: @@ -10046,28 +10337,8 @@ except Exception as e: # 无碰撞:显示添加按钮,下拉框变为可编辑 if hasattr(self, 'collision_button'): self.collision_button.setText("添加碰撞") - # 应用成功按钮样式 - self.collision_button.setStyleSheet(""" - QPushButton { - background-color: rgba(45, 255, 196, 0.15); - color: #2dffc4; - border: 1px solid rgba(45, 255, 196, 0.6); - border-radius: 4px; - padding: 8px 12px; - font-family: 'Microsoft YaHei', 'Inter', sans-serif; - font-size: 10px; - font-weight: 400; - min-height: 16px; - } - QPushButton:hover { - background-color: rgba(45, 255, 196, 0.25); - border: 1px solid #2dffc4; - } - QPushButton:pressed { - background-color: rgba(45, 255, 196, 0.4); - color: #ffffff; - } - """) + # 应用统一按钮样式 + self.collision_button.setStyleSheet(self._collision_button_style()) # 先断开所有连接,再重新连接 try: @@ -10538,35 +10809,47 @@ except Exception as e: # 位置调整控件 pos_label = QLabel("位置偏移:") + pos_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) pos_label.setVisible(True) # 确保可见 - layout.addWidget(pos_label, current_row, 0) - current_row += 1 # X, Y, Z 位置调整 self.collision_pos_x = self._createCollisionSpinBox(-100, 100, 2) self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2) self.collision_pos_z = self._createCollisionSpinBox(-100, 100, 2) - x_label = QLabel("X:") - x_label.setVisible(True) - layout.addWidget(x_label, current_row, 0) - self.collision_pos_x.setVisible(True) - layout.addWidget(self.collision_pos_x, current_row, 1) - current_row += 1 + for spinbox in (self.collision_pos_x, self.collision_pos_y, self.collision_pos_z): + spinbox.setMinimumWidth(140) + spinbox.setVisible(True) - y_label = QLabel("Y:") - y_label.setVisible(True) - layout.addWidget(y_label, current_row, 0) - self.collision_pos_y.setVisible(True) - layout.addWidget(self.collision_pos_y, current_row, 1) - current_row += 1 + axis_controls = [ + ("X:", self.collision_pos_x), + ("Y:", self.collision_pos_y), + ("Z:", self.collision_pos_z), + ] - z_label = QLabel("Z:") - z_label.setVisible(True) - layout.addWidget(z_label, current_row, 0) - self.collision_pos_z.setVisible(True) - layout.addWidget(self.collision_pos_z, current_row, 1) - current_row += 1 + for offset, (axis_text, spinbox) in enumerate(axis_controls): + row = current_row + offset + axis_layout = QHBoxLayout() + if offset == 0: + layout.addWidget(pos_label, row, 0) + # axis_label.addWidget(pos_label) + else: + spacer = QLabel("") + spacer.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + spacer.setVisible(True) + layout.addWidget(spacer, row, 0) + # axis_layout.addWidget(spacer) + + axis_label = QLabel(axis_text) + axis_label.setAlignment(Qt.AlignVCenter) + axis_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + axis_layout.addWidget(axis_label) + axis_layout.addWidget(spinbox) + # layout.addWidget(axis_label, row, 1) + # layout.addWidget(spinbox, row, 2, 1, 2) + layout.addLayout(axis_layout, row, 1, 1, 3) + + current_row += len(axis_controls) # 连接位置变化信号 self.collision_pos_x.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'x', v)) @@ -10601,11 +10884,12 @@ except Exception as e: radius_label = QLabel("半径:") radius_label.setVisible(True) layout.addWidget(radius_label, current_row, 0) + layout.addWidget(QLabel(""), current_row, 1) self.collision_radius = self._createCollisionSpinBox(0.01, 10000, 2) self.collision_radius.setVisible(True) self.collision_radius.valueChanged.connect(lambda v: self._updateSphereRadius(model, v)) - layout.addWidget(self.collision_radius, current_row, 1) + layout.addWidget(self.collision_radius, current_row, 1, 1, 3) current_row += 1 return current_row @@ -10617,32 +10901,38 @@ except Exception as e: size_label = QLabel("尺寸:") size_label.setVisible(True) layout.addWidget(size_label, current_row, 0) - current_row += 1 + # layout.addWidget(QLabel(""), current_row, 1) + # current_row += 1 self.collision_width = self._createCollisionSpinBox(0.01, 10000, 2) self.collision_length = self._createCollisionSpinBox(0.01, 10000, 2) self.collision_height = self._createCollisionSpinBox(0.01, 10000, 2) - width_label = QLabel("宽度:") - width_label.setVisible(True) - layout.addWidget(width_label, current_row, 0) - self.collision_width.setVisible(True) - layout.addWidget(self.collision_width, current_row, 1) - current_row += 1 + box_controls = [ + ("宽度:", self.collision_width), + ("长度:", self.collision_length), + ("高度:", self.collision_height), + ] - length_label = QLabel("长度:") - length_label.setVisible(True) - layout.addWidget(length_label, current_row, 0) - self.collision_length.setVisible(True) - layout.addWidget(self.collision_length, current_row, 1) - current_row += 1 + for offset,(text, spinbox) in enumerate(box_controls): + axis_layout = QHBoxLayout() + if offset == 0: + layout.addWidget(size_label, current_row, 0) + else: + spacer = QLabel("") + spacer.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + spacer.setVisible(True) + layout.addWidget(spacer, current_row, 0) - height_label = QLabel("高度:") - height_label.setVisible(True) - layout.addWidget(height_label, current_row, 0) - self.collision_height.setVisible(True) - layout.addWidget(self.collision_height, current_row, 1) - current_row += 1 + axis_label = QLabel(text) + axis_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + axis_label.setVisible(True) + axis_layout.addWidget(axis_label) + spinbox.setVisible(True) + axis_layout.addWidget(spinbox) + layout.addLayout(axis_layout, current_row, 1, 1, 3) + current_row += 1 # 连接信号 self.collision_width.valueChanged.connect(lambda v: self._updateBoxSize(model, 'width', v)) @@ -10660,7 +10950,7 @@ except Exception as e: self.collision_capsule_radius = self._createCollisionSpinBox(0.01, 10000, 2) self.collision_capsule_radius.valueChanged.connect(lambda v: self._updateCapsuleRadius(model, v)) - layout.addWidget(self.collision_capsule_radius, current_row, 1) + layout.addWidget(self.collision_capsule_radius, current_row, 1, 1 , 3) current_row += 1 height_label = QLabel("高度:") @@ -10668,7 +10958,7 @@ except Exception as e: self.collision_capsule_height = self._createCollisionSpinBox(0.01, 10000, 2) self.collision_capsule_height.valueChanged.connect(lambda v: self._updateCapsuleHeight(model, v)) - layout.addWidget(self.collision_capsule_height, current_row, 1) + layout.addWidget(self.collision_capsule_height, current_row, 1, 1, 3) current_row += 1 return current_row @@ -10679,22 +10969,42 @@ except Exception as e: normal_label = QLabel("法向量:") layout.addWidget(normal_label, current_row, 0) - current_row += 1 self.collision_normal_x = self._createCollisionSpinBox(-1, 1, 2) self.collision_normal_y = self._createCollisionSpinBox(-1, 1, 2) self.collision_normal_z = self._createCollisionSpinBox(-1, 1, 2) - layout.addWidget(QLabel("Nx:"), current_row, 0) - layout.addWidget(self.collision_normal_x, current_row, 1) + nx_layout = QHBoxLayout() + axis_label = QLabel("Nx:") + axis_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + nx_layout.addWidget(axis_label) + nx_layout.addWidget(self.collision_normal_x) + layout.addLayout(nx_layout, current_row, 1, 1, 3) current_row += 1 - layout.addWidget(QLabel("Ny:"), current_row, 0) - layout.addWidget(self.collision_normal_y, current_row, 1) + ny_layout = QHBoxLayout() + placeholder = QLabel("") + layout.addWidget(placeholder, current_row, 0) + + axis_label = QLabel("Ny:") + axis_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + ny_layout.addWidget(axis_label) + ny_layout.addWidget(self.collision_normal_y) + layout.addLayout(ny_layout, current_row, 1, 1, 3) current_row += 1 - layout.addWidget(QLabel("Nz:"), current_row, 0) - layout.addWidget(self.collision_normal_z, current_row, 1) + nz_layout = QHBoxLayout() + placeholder = QLabel("") + layout.addWidget(placeholder, current_row, 0) + + axis_label = QLabel("Nz:") + axis_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + axis_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + nz_layout.addWidget(axis_label) + nz_layout.addWidget(self.collision_normal_z) + layout.addLayout(nz_layout, current_row, 1, 1, 3) current_row += 1 # 连接信号 @@ -10715,8 +11025,9 @@ except Exception as e: current_row = layout.rowCount() self.collision_visibility_button = QPushButton("隐藏碰撞" if is_collision_visible else "显示碰撞") + self.collision_visibility_button.setStyleSheet(self._collision_button_style()) self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model)) - layout.addWidget(self.collision_visibility_button, current_row - 1, 0, 1, 2) + layout.addWidget(self.collision_visibility_button, current_row - 1, 0, 1, 4) except Exception as e: print(f"添加可见性按钮失败: {e}") @@ -10729,12 +11040,12 @@ except Exception as e: # 移动可见性按钮 if hasattr(self, 'collision_visibility_button'): - layout.addWidget(self.collision_visibility_button, new_row, 0, 1, 2) + layout.addWidget(self.collision_visibility_button, new_row, 0, 1, 4) new_row += 1 - + # 移动主按钮 if hasattr(self, 'collision_button'): - layout.addWidget(self.collision_button, new_row, 0, 1, 2) + layout.addWidget(self.collision_button, new_row, 0, 1, 4) except Exception as e: print(f"重新定位按钮失败: {e}") @@ -10793,4 +11104,4 @@ except Exception as e: except Exception as e: print(f"隐藏碰撞参数控件失败: {e}") import traceback - traceback.print_exc() + traceback.print_exc() \ No newline at end of file diff --git a/ui/widgets.py b/ui/widgets.py index a4280eb8..b88c8400 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -15,7 +15,7 @@ from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QTreeView, QTreeWidget, QTreeWidgetItem, QWidget, QFileDialog, QMessageBox, QAbstractItemView, QMenu, QDockWidget, QButtonGroup, QToolButton, QFrame, QSizePolicy) from PyQt5.QtCore import Qt, QUrl, QMimeData, QPoint, QSize -from PyQt5.QtGui import QDrag, QPainter, QPixmap, QPen, QBrush +from PyQt5.QtGui import QDrag, QPainter, QPixmap, QPen, QBrush, QFont from PyQt5.sip import wrapinstance from direct.showbase.ShowBaseGlobal import aspect2d from panda3d.core import ModelRoot, NodePath, CollisionNode @@ -592,6 +592,13 @@ class CustomFileView(QTreeView): if parent is None: parent = wrapinstance(0, QWidget) super().__init__(parent) + base_font = self.font() + base_font.setFamily("Inter") + base_font.setPointSize(10) + base_font.setWeight(QFont.Normal) + self.setFont(base_font) + if self.model(): + self.model().rowsInserted.connect(self._handle_rows_inserted) self.world = world self.setupUI() self.setupDragDrop() @@ -2136,7 +2143,7 @@ class UniversalMessageDialog(QDialog): INFO = "info" def __init__(self, parent, title, message, message_type=INFO, show_cancel=True, - confirm_text="确认", cancel_text="取消", icon_size=QSize(48, 48)): + confirm_text="确认", cancel_text="取消", icon_size=QSize(20, 20)): """ 初始化通用消息对话框 @@ -2353,14 +2360,20 @@ class UniversalMessageDialog(QDialog): message_area = QHBoxLayout() message_area.setContentsMargins(0, 0, 0, 0) - message_area.setSpacing(15) + message_area.setSpacing(10) + # 用一个垂直布局包裹icon_label,确保顶部对齐 + icon_vbox = QVBoxLayout() + icon_vbox.setContentsMargins(0, 0, 0, 0) + icon_vbox.setSpacing(0) icon_label = QLabel() if self._message_icon and not self._message_icon.isNull(): icon_label.setPixmap(self._message_icon.pixmap(self.icon_size)) icon_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) icon_label.setFixedSize(self.icon_size) - message_area.addWidget(icon_label) + icon_vbox.addWidget(icon_label, alignment=Qt.AlignTop) + icon_vbox.addStretch() + message_area.addLayout(icon_vbox) self.message_label = QLabel(message) self.message_label.setObjectName('messageLabel') @@ -3517,15 +3530,51 @@ class CustomTreeWidget(QTreeWidget): sceneRoot = QTreeWidgetItem(self, ['场景']) sceneRoot.setData(0, Qt.UserRole, self.world.render) sceneRoot.setData(0, Qt.UserRole + 1, "SCENE_ROOT") + self._apply_font_to_item(sceneRoot) # 添加相机节点 cameraItem = QTreeWidgetItem(sceneRoot, ['相机']) cameraItem.setData(0, Qt.UserRole, self.world.cam) cameraItem.setData(0, Qt.UserRole + 1, "CAMERA_NODE") + self._apply_font_to_item(cameraItem) # 添加地板节点 if hasattr(self.world, 'ground') and self.world.ground: groundItem = QTreeWidgetItem(sceneRoot, ['地板']) groundItem.setData(0, Qt.UserRole, self.world.ground) groundItem.setData(0,Qt.UserRole + 1, "SCENE_NODE") + self._apply_font_to_item(groundItem) + + def _apply_font_to_item(self, item): + """根据节点层级设置字体大小""" + if not item: + return + font = QFont(self.font()) + marker = item.data(0, Qt.UserRole + 1) + if item.parent() is None and (marker == "SCENE_ROOT" or item.text(0) == '场景'): + font.setPointSize(12) + font.setWeight(QFont.Medium) + else: + font.setPointSize(10) + font.setWeight(QFont.Normal) + item.setFont(0, font) + + def _apply_font_recursively(self, item): + """递归应用字体样式""" + if not item: + return + self._apply_font_to_item(item) + for index in range(item.childCount()): + self._apply_font_recursively(item.child(index)) + + def _handle_rows_inserted(self, parent_index, first, last): + """在插入新节点时自动调整字体""" + model = self.model() + if not model: + return + for row in range(first, last + 1): + index = model.index(row, 0, parent_index) + item = self.itemFromIndex(index) + if item: + self._apply_font_recursively(item) def _cleanup_panda_node_resources(self, panda_node): """一个集中的辅助函数,用于清理与Panda3D节点相关的所有资源。""" From 34ce05121acfe180e1a0ade4b98b5c2147c3c928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Mon, 20 Oct 2025 09:30:27 +0800 Subject: [PATCH 7/7] =?UTF-8?q?ui=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/interface_manager.py | 3 ++ ui/main_window.py | 66 ++++++++++++++++++++++------------------- ui/property_panel.py | 7 +++-- ui/widgets.py | 10 ++++--- 4 files changed, 49 insertions(+), 37 deletions(-) diff --git a/ui/interface_manager.py b/ui/interface_manager.py index 959e3201..ab0b9b18 100644 --- a/ui/interface_manager.py +++ b/ui/interface_manager.py @@ -1,4 +1,5 @@ from PIL.ImageChops import lighter +from PyQt5.QtGui import QFont from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu, QStyle from PyQt5.QtCore import Qt from PyQt5.sip import delete @@ -292,6 +293,8 @@ class InterfaceManager: sceneRoot = QTreeWidgetItem(self.treeWidget, ['场景']) sceneRoot.setData(0, Qt.UserRole, self.world.render) sceneRoot.setData(0, Qt.UserRole + 1, "SCENE_ROOT") + font = QFont("Microsoft YaHei", 10, QFont.Light) # 假设您希望字体大小为10 + sceneRoot.setFont(0, font) # 添加相机节点 cameraItem = QTreeWidgetItem(sceneRoot, ['相机']) cameraItem.setData(0, Qt.UserRole, self.world.cam) diff --git a/ui/main_window.py b/ui/main_window.py index 4c1b7fdc..92543480 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -64,7 +64,7 @@ class StyledTerrainDialog(QDialog): } QLabel#titleLabel { color: #FFFFFF; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 14px; font-weight: 500; letter-spacing: 0.7px; @@ -103,14 +103,14 @@ class StyledTerrainDialog(QDialog): } QLabel[role="fieldLabel"] { color: #EBEBEB; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 12px; font-weight: 400; letter-spacing: 0.6px; } QLabel[role="hint"] { color: rgba(235, 235, 235, 0.6); - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 11px; font-weight: 300; letter-spacing: 0.55px; @@ -122,7 +122,7 @@ class StyledTerrainDialog(QDialog): border: 1px solid rgba(76, 92, 110, 0.6); border-radius: 2px; padding: 0px 10px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 11px; font-weight: 300; letter-spacing: 0.55px; @@ -148,7 +148,7 @@ class StyledTerrainDialog(QDialog): border: none; border-radius: 2px; padding: 0px 12px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-weight: 300; font-size: 10px; letter-spacing: 0.5px; @@ -396,6 +396,8 @@ class MainWindow(QMainWindow): background-color: transparent; padding: 6px 12px; color: #D4D4D4; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 12px; } QMenuBar::item:selected { background-color: rgba(48, 103, 192, 0.4); @@ -1504,7 +1506,7 @@ class MainWindow(QMainWindow): padding: 8px 10px; border-bottom: none; text-align: left; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 12px; font-weight: 500; } @@ -1544,7 +1546,7 @@ class MainWindow(QMainWindow): alternate-background-color: #19191b; font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; - font-weight: 300; + font-weight: 400; outline: none; } QTreeView::item { @@ -1879,6 +1881,7 @@ class MainWindow(QMainWindow): QGroupBox[groupRole="first"] { padding-top: 16px; margin-top: 0px; + border-top: none; /* 明确移除顶部边框 */ } QGroupBox[groupRole="first"]::title { padding: 6px 3px 8px 0px; @@ -2057,6 +2060,7 @@ class MainWindow(QMainWindow): QGroupBox[groupRole="first"] { padding-top: 16px; margin-top: 0px; + border-top: none; /* 明确移除顶部边框 */ } QGroupBox[groupRole="first"]::title { padding: 6px 3px 8px 0px; @@ -2196,7 +2200,7 @@ class MainWindow(QMainWindow): alternate-background-color: #19191b; font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; - font-weight: 300; + font-weight: 400; outline: none; } QTreeWidget::item { @@ -2559,7 +2563,7 @@ class MainWindow(QMainWindow): scriptSystemLabel.setStyleSheet(""" QLabel { color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2575,7 +2579,7 @@ class MainWindow(QMainWindow): color: #2dffc4; border-radius: 2px; padding: 2px 8px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2591,7 +2595,7 @@ class MainWindow(QMainWindow): hotReloadLabel.setStyleSheet(""" QLabel { color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2607,7 +2611,7 @@ class MainWindow(QMainWindow): color: #289eff; border-radius: 2px; padding: 2px 8px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2630,7 +2634,7 @@ class MainWindow(QMainWindow): nameLabel.setStyleSheet(""" QLabel { color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2648,7 +2652,7 @@ class MainWindow(QMainWindow): border: 1px solid rgba(76, 92, 110, 0.6); border-radius: 2px; padding: 4px 8px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2670,7 +2674,7 @@ class MainWindow(QMainWindow): templateLabel.setStyleSheet(""" QLabel { color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2688,7 +2692,7 @@ class MainWindow(QMainWindow): border: 1px solid rgba(76, 92, 110, 0.6); border-radius: 2px; padding: 4px 8px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2725,7 +2729,7 @@ class MainWindow(QMainWindow): border: none; padding: 6px 12px; border-radius: 2px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-weight: 300; font-size: 10px; letter-spacing: 0.5px; @@ -2763,7 +2767,7 @@ class MainWindow(QMainWindow): alternate-background-color: rgba(89, 100, 113, 0.15); selection-background-color: rgba(48, 103, 192, 0.4); selection-color: #ffffff; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2802,7 +2806,7 @@ class MainWindow(QMainWindow): border: none; padding: 6px 12px; border-radius: 2px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-weight: 300; font-size: 10px; letter-spacing: 0.5px; @@ -2827,7 +2831,7 @@ class MainWindow(QMainWindow): border: none; padding: 6px 12px; border-radius: 2px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-weight: 300; font-size: 10px; letter-spacing: 0.5px; @@ -2856,7 +2860,7 @@ class MainWindow(QMainWindow): self.selectedObjectLabel.setStyleSheet(""" QLabel { color: #2dffc4; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2877,7 +2881,7 @@ class MainWindow(QMainWindow): border: 1px solid rgba(76, 92, 110, 0.6); border-radius: 2px; padding: 4px 8px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2917,7 +2921,7 @@ class MainWindow(QMainWindow): border: none; padding: 6px 12px; border-radius: 2px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-weight: 300; font-size: 10px; letter-spacing: 0.5px; @@ -2950,7 +2954,7 @@ class MainWindow(QMainWindow): alternate-background-color: rgba(89, 100, 113, 0.15); selection-background-color: rgba(48, 103, 192, 0.4); selection-color: #ffffff; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -2980,7 +2984,7 @@ class MainWindow(QMainWindow): mountedLabel.setStyleSheet(""" QLabel { color: #ebebeb; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -3000,7 +3004,7 @@ class MainWindow(QMainWindow): border: none; padding: 6px 12px; border-radius: 2px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-weight: 300; font-size: 10px; letter-spacing: 0.5px; @@ -4453,7 +4457,7 @@ class MainWindow(QMainWindow): color: #f39d78 ; border-radius: 2px; padding: 2px 8px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -4468,7 +4472,7 @@ class MainWindow(QMainWindow): color: #289eff ; border-radius: 2px; padding: 2px 8px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -4487,7 +4491,7 @@ class MainWindow(QMainWindow): color: #2dffc4; border-radius: 2px; padding: 2px 8px; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -4501,7 +4505,7 @@ class MainWindow(QMainWindow): self.selectedObjectLabel.setStyleSheet(""" QLabel { color: #2dffc4; - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; @@ -4518,7 +4522,7 @@ class MainWindow(QMainWindow): self.selectedObjectLabel.setStyleSheet(""" QLabel { color: rgba(235, 235, 235, 0.5); - font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; font-size: 10px; font-weight: 300; letter-spacing: 0.5px; diff --git a/ui/property_panel.py b/ui/property_panel.py index 0ed44450..2b8a0259 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -53,6 +53,7 @@ class PropertyPanelManager: border-radius: 4px; padding: 4px 6px; font-size: 10px; + } QDoubleSpinBox:focus, QSpinBox:focus { border: 1px solid #4d74bd; @@ -667,6 +668,7 @@ class PropertyPanelManager: model.setPythonTag("user_visible", True) self.name_group = QGroupBox("物体名称") + self.name_group.setProperty("groupRole", "first") name_layout = QGridLayout() self.active_check = QCheckBox() # 根据模型的实际可见性状态设置复选框 @@ -5646,6 +5648,7 @@ class PropertyPanelManager: # material_group = QGroupBox(display_name) material_group = QGroupBox("材质属性") material_layout = QGridLayout() + material_layout.setColumnMinimumWidth(0, self.column_minimum_width) material_layout.addWidget(QLabel("名称:"), 0, 0) limited_length = 25 @@ -5661,7 +5664,7 @@ class PropertyPanelManager: material_layout.addWidget(QLabel("状态:"), 1, 0) status_label = QLabel(material_status) # status_label.setStyleSheet("color:#FFA500;font-style:italic;font-size:10px;") - material_layout.addWidget(status_label, 1, 1, 1, 2) + material_layout.addWidget(status_label, 1, 1, 1, 3) # 基础颜色编辑 base_color = self._getOrCreateMaterialBaseColor(material) @@ -5706,7 +5709,7 @@ class PropertyPanelManager: # base_color_layout.addStretch(1) - material_layout.addLayout(base_color_layout, color_row, 1, 1, 4) + material_layout.addLayout(base_color_layout, color_row, 1, 1, 3) else: no_base_color_label = QLabel("无法获取材质基础颜色") no_base_color_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") diff --git a/ui/widgets.py b/ui/widgets.py index b88c8400..e11d114a 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -1068,6 +1068,8 @@ class CustomAssetsTreeWidget(QTreeWidget): root_item = QTreeWidgetItem([f"📁 {root_name}"]) root_item.setData(0, Qt.UserRole, self.current_path) root_item.setData(0, Qt.UserRole + 1, True) + font = QFont("Microsoft YaHei", 10, QFont.Light) # 假设您希望字体大小为10 + root_item.setFont(0, font) self.addTopLevelItem(root_item) # 加载当前目录内容 @@ -3550,11 +3552,11 @@ class CustomTreeWidget(QTreeWidget): font = QFont(self.font()) marker = item.data(0, Qt.UserRole + 1) if item.parent() is None and (marker == "SCENE_ROOT" or item.text(0) == '场景'): - font.setPointSize(12) - font.setWeight(QFont.Medium) + font = QFont("Microsoft YaHei", 12) + font.setWeight(QFont.Light) else: - font.setPointSize(10) - font.setWeight(QFont.Normal) + font = QFont("Microsoft YaHei", 10) + font.setWeight(QFont.Light) item.setFont(0, font) def _apply_font_recursively(self, item):