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] =?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): """自定义场景树部件"""