1
0
forked from Rowland/EG

1.UI更新

This commit is contained in:
陈横 2025-10-16 00:54:47 +08:00
parent 64c7a30bc2
commit b386818f76
8 changed files with 1210 additions and 188 deletions

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 B

View File

@ -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)
window.setWindowTitle(base_title)

View File

@ -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',
}
# 初始化默认图标

View File

@ -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):
"""打开拆装配置界面"""

View File

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

View File

@ -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
# 默认选中场景根节点,通常是第一个顶级节点