MetaCore-startup/MetaCore/ui/create_project_dialog.py
陈横 fd3eb3e72a feat(ui): 优化项目导入和创建对话框界面
- 移除了创建项目对话框的最大尺寸限制,提升布局灵活性
- 调整了导入项目对话框中文件列表项的样式和结构
- 更新了图标管理器,新增 import_file_list 图标支持
- 修改了文件列表项组件,使用自定义图标并优化布局间距
- 调整了样式表中多个组件的尺寸和颜色,改善视觉效果
- 优化了文件列表项的显示逻辑,修复了可能的布局问题
- 更新了项目预览图片路径,确保正确显示项目封面
2025-10-23 18:56:28 +08:00

565 lines
21 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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)