#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 创建项目对话框 """ import os from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * from data.project_manager import ProjectManager from ui.icon_manager import IconManager from ui.widget import UniversalMessageDialog class CreateProjectDialog(QDialog): """创建项目对话框""" def __init__(self, project_manager: ProjectManager, project_settings_page=None, parent=None): super().__init__(parent) self.project_manager = project_manager self.project_settings_page = project_settings_page self.selected_template = "empty" self.selected_path = "" # 自定义标题栏相关控件 self.title_bar = None self.title_label = None self.close_btn = None self.drag_offset = None # 设置对话框对象名称以应用样式 self.setObjectName("CreateProjectDialog") self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) self.setAttribute(Qt.WA_StyledBackground, True) self.init_ui() self.connect_signals() # 设置对话框属性 self.setWindowTitle("创建新的项目") if self.title_label is not None: self.title_label.setText(self.windowTitle()) self.setModal(True) self.setMinimumSize(1177, 689) # 根据Figma设计调整尺寸 # self.setMaximumSize(1177, 689) self.resize(1177, 689) # 设置默认尺寸 # 设置默认项目位置 self.set_default_location_from_settings() # 居中显示 self.center_dialog() def init_ui(self): """初始化UI""" layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # 自定义标题栏 self.title_bar = self.create_title_bar() layout.addWidget(self.title_bar) # 主要内容区域 self.create_main_content(layout) def create_title_bar(self): """创建与导入项目对话框一致的自定义标题栏""" title_bar = QWidget() title_bar.setObjectName("customTitleBar") layout = QHBoxLayout(title_bar) layout.setContentsMargins(18, 12, 14, 12) layout.setSpacing(12) self.title_label = QLabel(self.windowTitle()) self.title_label.setObjectName("customTitleLabel") self.title_label.setAttribute(Qt.WA_TransparentForMouseEvents, True) layout.addWidget(self.title_label) layout.addStretch() if IconManager.icon_exists("close_bt_icon"): close_icon = IconManager.get_icon("close_bt_icon", QSize(18, 18)) self.close_btn = QPushButton() self.close_btn.setIcon(close_icon) else: self.close_btn = QPushButton("X") self.close_btn.setObjectName("customCloseBtn") self.close_btn.setFixedSize(18, 18) self.close_btn.setCursor(Qt.PointingHandCursor) self.close_btn.clicked.connect(self.reject) layout.addWidget(self.close_btn) title_bar.installEventFilter(self) title_bar.setCursor(Qt.ArrowCursor) return title_bar def create_main_content(self, layout): """创建主要内容""" content_widget = QWidget() content_widget.setObjectName("createProjectContent") content_layout = QHBoxLayout(content_widget) content_layout.setContentsMargins(10, 0, 10, 15) content_layout.setSpacing(4) # 添加小间距 # 左侧模板选择区域 self.create_template_section(content_layout) # 右侧项目信息区域 self.create_project_info_section(content_layout) layout.addWidget(content_widget) def create_template_section(self, layout): """创建模板选择区域""" template_widget = QWidget() template_widget.setObjectName("templateSection") template_layout = QVBoxLayout(template_widget) template_layout.setContentsMargins(0, 0, 0, 0) template_layout.setSpacing(4) # 标题栏 title_label = QLabel("选择项目模版") # 与Figma设计保持一致 title_label.setObjectName("templateHeaderLabel") title_label.setFixedSize(724, 33) template_layout.addWidget(title_label) template_layout.addSpacing(4) # 模板网格 self.create_template_grid(template_layout) # 设置固定宽度 (根据Figma设计) template_widget.setFixedWidth(724) layout.addWidget(template_widget) def create_template_grid(self, layout): """创建模板网格""" # 滚动区域 scroll_area = QScrollArea() # scroll_area.setFixedSize(724, 595) scroll_area.setObjectName("templateScrollArea") scroll_area.setWidgetResizable(True) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # 模板容器 templates_widget = QWidget() templates_widget.setObjectName("templatesContainer") templates_layout = QGridLayout(templates_widget) templates_layout.setSpacing(15) # 根据Figma设计调整间距 templates_layout.setContentsMargins(15, 15, 15, 15) # 调整容器边距 templates_layout.setAlignment(Qt.AlignTop | Qt.AlignLeft) # 左上对齐 # 模板数据 - 当前只有空白项目模板,后续可以添加更多模板 templates = [ ("empty", "empty_folder_icon", "空白项目模版"), # 移除emoji图标,与Figma设计一致 # 后续可以添加的模板示例: # ("web", "", "Web应用模板"), # ("mobile", "", "移动应用模板"), # ("desktop", "", "桌面应用模板"), # ("api", "", "API服务模板"), # ("data", "", "数据分析模板"), # ("game", "", "游戏开发模板"), ] # 创建模板项 self.template_buttons = [] for i, (template_id, icon, name) in enumerate(templates): row = i // 3 # 每行最多3个模板,为后续扩展做准备 col = i % 3 template_btn = self.create_template_item(template_id, icon, name) templates_layout.addWidget(template_btn, row, col) self.template_buttons.append(template_btn) # 添加弹性空间,确保模板按钮不会被拉伸 templates_layout.setRowStretch(templates_layout.rowCount(), 1) templates_layout.setColumnStretch(templates_layout.columnCount(), 1) scroll_area.setWidget(templates_widget) layout.addWidget(scroll_area) def create_template_item(self, template_id, icon_path, name): """创建模板项""" template_btn = QPushButton() template_btn.setObjectName("templateItem") template_btn.setCheckable(True) # 根据Figma设计调整尺寸 (149x127) template_btn.setFixedSize(149, 127) # 设置默认选中 if template_id == "empty": template_btn.setChecked(True) # 布局 btn_layout = QVBoxLayout(template_btn) btn_layout.setAlignment(Qt.AlignCenter) btn_layout.setSpacing(27) # 减小间距 btn_layout.setContentsMargins(15, 20, 15, 20) # 图标区域 - 根据Figma设计,这里是一个空的框架 if icon_path and IconManager.icon_exists(icon_path): icon = IconManager.get_icon(icon_path, QSize(40, 40)) icon_label = QLabel() # 创建QLabel而不是QIcon icon_label.setObjectName("templateIcon") icon_label.setAlignment(Qt.AlignCenter) icon_label.setPixmap(icon.pixmap(QSize(40, 40))) # 设置图标 btn_layout.addWidget(icon_label) else: # 添加空白空间以保持布局一致 btn_layout.addStretch() # 名称 name_label = QLabel(name) name_label.setObjectName("templateName") name_label.setAlignment(Qt.AlignCenter) name_label.setWordWrap(True) btn_layout.addWidget(name_label) # 点击事件 def on_clicked(): # 取消其他按钮的选中状态 for btn in self.template_buttons: if btn != template_btn: btn.setChecked(False) template_btn.setChecked(True) self.selected_template = template_id self.update_template_description(template_id) template_btn.clicked.connect(on_clicked) return template_btn def create_project_info_section(self, layout): """创建项目信息区域""" info_widget = QWidget() info_widget.setObjectName("projectInfoSection") info_layout = QVBoxLayout(info_widget) info_layout.setContentsMargins(15, 10, 15, 10) # 调整边距 info_layout.setSpacing(15) # 标题和描述容器 header_widget = QWidget() header_widget.setObjectName("projectInfoHeader") header_layout = QVBoxLayout(header_widget) header_layout.setContentsMargins(0, 0, 0, 0) header_layout.setSpacing(10) # 标题 title_label = QLabel("项目信息") title_label.setObjectName("sectionTitle") header_layout.addWidget(title_label) # 模板描述 self.description_label = QLabel("创建一个空白项目,您可以从头开始构建您的应用程序。包含基础的项目结构和配置文件,适用于任何类型的项目开发。") self.description_label.setObjectName("templateDescription") self.description_label.setWordWrap(True) header_layout.addWidget(self.description_label) info_layout.addWidget(header_widget) # 表单 self.create_project_form(info_layout) info_layout.addStretch() # 设置固定宽度 (根据Figma设计) info_widget.setFixedWidth(427) layout.addWidget(info_widget) def create_project_form(self, layout): """创建项目表单""" form_layout = QVBoxLayout() form_layout.setSpacing(15) # 项目名称 name_group = QVBoxLayout() name_label = QLabel("项目名称") name_label.setObjectName("formLabel") name_group.addWidget(name_label) name_group.setSpacing(10) self.name_input = QLineEdit() self.name_input.setObjectName("formInput") self.name_input.setMinimumSize(397, 32) self.name_input.setPlaceholderText("输入项目名称") name_group.addWidget(self.name_input) # 错误提示标签 - 紧贴在输入框下方的小红字 self.name_error_label = QLabel("") self.name_error_label.setObjectName("errorLabel") self.name_error_label.setWordWrap(False) # 不换行,保持一行显示 self.name_error_label.setFixedHeight(18) # 固定高度,一行小字 self.name_error_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.name_error_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.name_error_label.hide() # 初始隐藏 name_group.addWidget(self.name_error_label) form_layout.addLayout(name_group) # 项目描述 desc_group = QVBoxLayout() desc_label = QLabel("项目描述") desc_label.setObjectName("formLabel") desc_group.addWidget(desc_label) desc_group.setSpacing(10) self.desc_input = QTextEdit() self.desc_input.setObjectName("formTextArea") self.desc_input.setPlaceholderText("输入项目描述(可选)") self.desc_input.setMinimumSize(397, 96) desc_group.addWidget(self.desc_input) form_layout.addLayout(desc_group) # 项目位置 location_group = QVBoxLayout() location_label = QLabel("项目位置") location_label.setObjectName("formLabel") location_group.addWidget(location_label) location_group.setSpacing(10) location_layout = QHBoxLayout() self.location_input = QLineEdit() self.location_input.setObjectName("formInput") self.location_input.setFixedSize(280, 32) self.location_input.setPlaceholderText("点击浏览选择位置") self.location_input.setReadOnly(True) location_layout.addWidget(self.location_input) browse_btn = QPushButton("") if IconManager.icon_exists('file_icon'): browse_btn.setIcon(IconManager.get_icon('file_icon', QSize(20, 20))) browse_btn.setObjectName("browseBtn") browse_btn.setFixedSize(103, 32) # 减小尺寸:40x32 -> 32x32 browse_btn.setToolTip("选择项目保存位置") browse_btn.clicked.connect(self.browse_location) location_layout.addWidget(browse_btn) location_group.addLayout(location_layout) form_layout.addLayout(location_group) # form_layout.addStretch() form_layout.addSpacing(15) form_layout.addWidget(self.create_button_area()) layout.addLayout(form_layout) def create_button_area(self): """创建按钮区域""" button_widget = QWidget() button_widget.setObjectName("buttonArea") button_layout = QHBoxLayout(button_widget) button_layout.setContentsMargins(0, 12, 0, 10) button_layout.setSpacing(10) # 设置按钮间距 button_layout.addStretch() # 取消按钮 cancel_btn = QPushButton("取消") cancel_btn.setObjectName("secondaryBtn") cancel_btn.setFixedSize(95, 36) cancel_btn.clicked.connect(self.reject) button_layout.addWidget(cancel_btn) # 创建按钮 self.create_btn = QPushButton("创建") # 与Figma设计保持一致 self.create_btn.setObjectName("primaryBtn") self.create_btn.setFixedSize(95, 36) self.create_btn.clicked.connect(self.create_project) button_layout.addWidget(self.create_btn) return button_widget def update_template_description(self, template_id): """更新模板描述""" descriptions = { "empty": "创建一个空白项目,您可以从头开始构建您的应用程序。包含基础的项目结构和配置文件,适用于任何类型的项目开发。", } description = descriptions.get(template_id, "项目模板描述") self.description_label.setText(description) def browse_location(self): """浏览项目位置""" # 使用项目设置中的默认位置作为起始目录 if self.project_settings_page: start_dir = self.project_settings_page.get_default_project_location() else: start_dir = os.path.expanduser("~/Documents") folder = QFileDialog.getExistingDirectory(self, "选择项目保存位置", start_dir) if folder: self.selected_path = folder self.location_input.setText(folder) # 触发验证 self.validate_input() def create_project(self): """创建项目""" # 验证输入 name = self.name_input.text().strip() if not name: # QMessageBox.warning(self, "输入错误", "请输入项目名称") UniversalMessageDialog.show_warning(self, "输入错误", "请输入项目名称", False, "确定") return if not self.selected_path: UniversalMessageDialog.show_warning(self, "输入错误", "请选择项目保存位置", False, "确定") return # 使用项目管理器的验证方法进行全面检查 is_valid, error_message = self.project_manager.validate_project_creation(name, self.selected_path) if not is_valid: UniversalMessageDialog.show_warning(self, "创建失败", error_message, False, "确定") return # 创建项目 description = self.desc_input.toPlainText().strip() try: project = self.project_manager.add_project( name, description, self.selected_template, self.selected_path ) # 显示详细的成功信息 success_msg = f"""项目创建成功! 项目名称: {name} 项目目录: {project.project_dir} 项目文件结构已创建完成,包含: ? models/ - 模型文件夹 ? textures/ - 贴图文件夹 ? scenes/ - 场景文件夹 ? project.json - 项目配置文件 您可以开始在此基础上开发您的应用程序。""" UniversalMessageDialog.show_info(self, "创建成功", success_msg, False, "确定") self.accept() except Exception as e: UniversalMessageDialog.show_error(self, "创建失败", f"项目创建失败:{str(e)}", False, "确定") def get_template_name(self, template_id): """获取模板名称""" template_names = { "empty": "空白项目模板", } return template_names.get(template_id, "空白项目模板") def connect_signals(self): """连接信号""" # 连接项目名称输入框的文本变化信号 self.name_input.textChanged.connect(self.validate_input) # 连接路径选择的变化 self.location_input.textChanged.connect(self.validate_input) def validate_input(self): """实时验证输入""" name = self.name_input.text().strip() path = self.selected_path # 重置样式属性 self.name_input.setProperty("error", False) self.name_input.setProperty("valid", False) self.create_btn.setEnabled(True) # 如果名称为空,清空错误提示(用户可能还在输入) if not name: self.name_error_label.hide() self.name_input.style().polish(self.name_input) return # 如果路径未选择,不进行完整验证 if not path: self.name_error_label.hide() self.name_input.style().polish(self.name_input) return # 进行验证 is_valid, error_message = self.project_manager.validate_project_creation(name, path) if not is_valid: # 设置错误状态 self.name_input.setProperty("error", True) self.name_input.setProperty("valid", False) # 禁用创建按钮 self.create_btn.setEnabled(False) # 显示错误信息 - 一行小红字 self.name_error_label.setText(error_message) self.name_error_label.show() else: # 设置成功状态 self.name_input.setProperty("error", False) self.name_input.setProperty("valid", True) # 隐藏错误信息 self.name_error_label.hide() # 刷新样式 self.name_input.style().polish(self.name_input) def set_default_location(self, location: str): """设置默认项目位置""" if location and os.path.exists(location): self.selected_path = location self.location_input.setText(location) # 触发验证 self.validate_input() def set_default_location_from_settings(self): """从项目设置中设置默认项目位置""" if self.project_settings_page: default_location = self.project_settings_page.get_default_project_location() if default_location: self.set_default_location(default_location) def eventFilter(self, watched, event): """处理标题栏拖动与按钮交互""" if watched is self.title_bar: if event.type() == QEvent.MouseButtonPress and event.button() == Qt.LeftButton: self.drag_offset = event.globalPos() - self.frameGeometry().topLeft() return True if event.type() == QEvent.MouseMove and event.buttons() & Qt.LeftButton and self.drag_offset is not None: self.move(event.globalPos() - self.drag_offset) return True if event.type() == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton: self.drag_offset = None return True return super().eventFilter(watched, event) def center_dialog(self): """对话框居中""" if self.parent(): parent_rect = self.parent().geometry() x = parent_rect.x() + (parent_rect.width() - self.width()) // 2 y = parent_rect.y() + (parent_rect.height() - self.height()) // 2 self.move(x, y) else: # 如果没有父窗口,则在屏幕中央显示 screen = QApplication.desktop().screenGeometry() x = (screen.width() - self.width()) // 2 y = (screen.height() - self.height()) // 2 self.move(x, y)