diff --git a/.gitignore b/.gitignore index fb0a1be..0318b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -123,3 +123,13 @@ Thumbs.db *.temp *.bak *.backup +BUTTON_CRASH_FIX.md +.gitignore +CORNER_RADIUS_FIX.md +CORNER_RADIUS_UPDATE.md +FIGMA_LAYOUT_IMPROVEMENTS.md +LOGO_AREA_IMPROVEMENTS.md +.codex/settings/kiroCodex-settings.json +MetaCore/LOGO_AREA_REDESIGN.md +MetaCore/LAYOUT_OPTIMIZATION_SUMMARY.md +MetaCore/CROSS_PLATFORM_VENV.md diff --git a/MetaCore/MetaCore/MetaCore/Resources/ProjectPreviews/default_preview_4.png b/MetaCore/MetaCore/MetaCore/Resources/ProjectPreviews/default_preview_4.png new file mode 100644 index 0000000..351f049 Binary files /dev/null and b/MetaCore/MetaCore/MetaCore/Resources/ProjectPreviews/default_preview_4.png differ diff --git a/MetaCore/MetaCore/Resources/ProjectPreviews/preview_1_1758077437250.png b/MetaCore/MetaCore/Resources/ProjectPreviews/preview_1_1758077437250.png deleted file mode 100644 index 4d99b7d..0000000 Binary files a/MetaCore/MetaCore/Resources/ProjectPreviews/preview_1_1758077437250.png and /dev/null differ diff --git a/MetaCore/MetaCore/Resources/ProjectPreviews/preview_1_1760348266174.png b/MetaCore/MetaCore/Resources/ProjectPreviews/preview_1_1760348266174.png new file mode 100644 index 0000000..74870f4 Binary files /dev/null and b/MetaCore/MetaCore/Resources/ProjectPreviews/preview_1_1760348266174.png differ diff --git a/MetaCore/MetaCore/data/projects.json b/MetaCore/MetaCore/data/projects.json new file mode 100644 index 0000000..67146b1 --- /dev/null +++ b/MetaCore/MetaCore/data/projects.json @@ -0,0 +1,46 @@ +[ + { + "id": 4, + "title": "XNWX", + "date": "2025-10-11 12:02:48", + "type": "imported", + "image": "C:\\Users\\29381\\Desktop\\XNWX\\XNWX.png", + "path": "C:\\Users\\29381\\Desktop", + "project_dir": "C:\\Users\\29381\\Desktop\\XNWX", + "description": "从文件夹 XNWX 导入", + "status": "normal" + }, + { + "id": 1, + "title": "示例项目1", + "date": "2024-06-08 15:56:35", + "type": "empty", + "image": "📁", + "path": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\MetaCore", + "project_dir": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\MetaCore", + "description": "这是一个示例空白项目", + "status": "normal" + }, + { + "id": 2, + "title": "示例项目2", + "date": "2023-01-10 12:09:04", + "type": "empty", + "image": "📁", + "path": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\MetaCore", + "project_dir": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\MetaCore", + "description": "这是另一个示例空白项目", + "status": "normal" + }, + { + "id": 3, + "title": "我的第一个项目", + "date": "2024-06-07 06:57:46", + "type": "empty", + "image": "📁", + "path": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\MetaCore", + "project_dir": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\MetaCore", + "description": "从这里开始您的第一个项目", + "status": "normal" + } +] \ No newline at end of file diff --git a/MetaCore/Resources/Icons/close_bt_icon.png b/MetaCore/Resources/Icons/close_bt_icon.png new file mode 100644 index 0000000..b5c923a Binary files /dev/null and b/MetaCore/Resources/Icons/close_bt_icon.png differ diff --git a/MetaCore/Resources/Icons/delete_fail_icon.png b/MetaCore/Resources/Icons/delete_fail_icon.png new file mode 100644 index 0000000..90ba204 Binary files /dev/null and b/MetaCore/Resources/Icons/delete_fail_icon.png differ diff --git a/MetaCore/Resources/Icons/empty_folder_icon.png b/MetaCore/Resources/Icons/empty_folder_icon.png new file mode 100644 index 0000000..3889993 Binary files /dev/null and b/MetaCore/Resources/Icons/empty_folder_icon.png differ diff --git a/MetaCore/Resources/Icons/file_icon.png b/MetaCore/Resources/Icons/file_icon.png new file mode 100644 index 0000000..416df5d Binary files /dev/null and b/MetaCore/Resources/Icons/file_icon.png differ diff --git a/MetaCore/Resources/Icons/import_file_icon.png b/MetaCore/Resources/Icons/import_file_icon.png new file mode 100644 index 0000000..cd77301 Binary files /dev/null and b/MetaCore/Resources/Icons/import_file_icon.png differ diff --git a/MetaCore/Resources/Icons/infomation_check_icon.png b/MetaCore/Resources/Icons/infomation_check_icon.png new file mode 100644 index 0000000..cc279d6 Binary files /dev/null and b/MetaCore/Resources/Icons/infomation_check_icon.png differ diff --git a/MetaCore/Resources/Icons/infomation_hover_icon.png b/MetaCore/Resources/Icons/infomation_hover_icon.png new file mode 100644 index 0000000..670b175 Binary files /dev/null and b/MetaCore/Resources/Icons/infomation_hover_icon.png differ diff --git a/MetaCore/Resources/Icons/infomation_icon.png b/MetaCore/Resources/Icons/infomation_icon.png new file mode 100644 index 0000000..81f9d18 Binary files /dev/null and b/MetaCore/Resources/Icons/infomation_icon.png differ diff --git a/MetaCore/Resources/Icons/open_projectcard_icon.png b/MetaCore/Resources/Icons/open_projectcard_icon.png new file mode 100644 index 0000000..e13d6d7 Binary files /dev/null and b/MetaCore/Resources/Icons/open_projectcard_icon.png differ diff --git a/MetaCore/Resources/Icons/project_empty_icon.png b/MetaCore/Resources/Icons/project_empty_icon.png new file mode 100644 index 0000000..b5f6cc1 Binary files /dev/null and b/MetaCore/Resources/Icons/project_empty_icon.png differ diff --git a/MetaCore/Resources/Icons/refresh_projectcard_icon.png b/MetaCore/Resources/Icons/refresh_projectcard_icon.png new file mode 100644 index 0000000..afe24ae Binary files /dev/null and b/MetaCore/Resources/Icons/refresh_projectcard_icon.png differ diff --git a/MetaCore/Resources/Icons/remove_projectcard_icon.png b/MetaCore/Resources/Icons/remove_projectcard_icon.png new file mode 100644 index 0000000..776275b Binary files /dev/null and b/MetaCore/Resources/Icons/remove_projectcard_icon.png differ diff --git a/MetaCore/Resources/Icons/search_icon.png b/MetaCore/Resources/Icons/search_icon.png new file mode 100644 index 0000000..d10f83c Binary files /dev/null and b/MetaCore/Resources/Icons/search_icon.png differ diff --git a/MetaCore/Resources/Icons/solid_down_arrows.png b/MetaCore/Resources/Icons/solid_down_arrows.png new file mode 100644 index 0000000..6fc740f Binary files /dev/null and b/MetaCore/Resources/Icons/solid_down_arrows.png differ diff --git a/MetaCore/Resources/Icons/solid_right_arrows.png b/MetaCore/Resources/Icons/solid_right_arrows.png new file mode 100644 index 0000000..22b4145 Binary files /dev/null and b/MetaCore/Resources/Icons/solid_right_arrows.png differ diff --git a/MetaCore/Resources/Icons/success_icon copy.png b/MetaCore/Resources/Icons/success_icon copy.png new file mode 100644 index 0000000..4dda99f Binary files /dev/null and b/MetaCore/Resources/Icons/success_icon copy.png differ diff --git a/MetaCore/Resources/Icons/success_icon.png b/MetaCore/Resources/Icons/success_icon.png new file mode 100644 index 0000000..4dda99f Binary files /dev/null and b/MetaCore/Resources/Icons/success_icon.png differ diff --git a/MetaCore/Resources/Icons/warning_icon.png b/MetaCore/Resources/Icons/warning_icon.png new file mode 100644 index 0000000..540f03b Binary files /dev/null and b/MetaCore/Resources/Icons/warning_icon.png differ diff --git a/MetaCore/Resources/ProjectPreviews/default_preview_2.png b/MetaCore/Resources/ProjectPreviews/default_preview_2.png new file mode 100644 index 0000000..c606ffc Binary files /dev/null and b/MetaCore/Resources/ProjectPreviews/default_preview_2.png differ diff --git a/MetaCore/Resources/ProjectPreviews/default_preview_3.png b/MetaCore/Resources/ProjectPreviews/default_preview_3.png new file mode 100644 index 0000000..c606ffc Binary files /dev/null and b/MetaCore/Resources/ProjectPreviews/default_preview_3.png differ diff --git a/MetaCore/Resources/ProjectPreviews/preview_1_1760628516144.png b/MetaCore/Resources/ProjectPreviews/preview_1_1760628516144.png new file mode 100644 index 0000000..80d590f Binary files /dev/null and b/MetaCore/Resources/ProjectPreviews/preview_1_1760628516144.png differ diff --git a/MetaCore/Resources/ProjectPreviews/preview_6_1760436321788.png b/MetaCore/Resources/ProjectPreviews/preview_6_1760436321788.png new file mode 100644 index 0000000..e0211a8 Binary files /dev/null and b/MetaCore/Resources/ProjectPreviews/preview_6_1760436321788.png differ diff --git a/MetaCore/data/project_manager.py b/MetaCore/data/project_manager.py index 3cf190e..f50bc23 100644 --- a/MetaCore/data/project_manager.py +++ b/MetaCore/data/project_manager.py @@ -31,6 +31,8 @@ from pathlib import Path # 现代路径处理 from typing import List, Dict, Optional # 类型提示 import glob import subprocess +from PyQt5.QtWidgets import QDialog +from ui.widget import UniversalMessageDialog # 第三方库导入 from PyQt5.QtCore import QObject, pyqtSignal, QFileSystemWatcher, QTimer # Qt核心对象和信号系统 @@ -751,17 +753,21 @@ if __name__ == "__main__": project_names = [p.title for p in deleted_projects] project_list = "\n".join([f"• {name}" for name in project_names]) - reply = QMessageBox.question( - None, - "多个项目目录已删除", - f"检测到以下 {len(deleted_projects)} 个项目的目录已被删除:\n\n" - f"{project_list}\n\n" - f"是否要从项目列表中移除这些项目?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes - ) + # reply = QMessageBox.question( + # None, + # "多个项目目录已删除", + # f"检测到以下 {len(deleted_projects)} 个项目的目录已被删除:\n\n" + # f"{project_list}\n\n" + # f"是否要从项目列表中移除这些项目?", + # QMessageBox.Yes | QMessageBox.No, + # QMessageBox.Yes + # ) + reply = UniversalMessageDialog.show_info("多个项目目录已删除", f"检测到以下 {len(deleted_projects)} 个项目的目录已被删除:\n\n" + f"{project_list}\n\n" + f"是否要从项目列表中移除这些项目?", + True, "确定", "取消") - if reply == QMessageBox.Yes: + if reply == QDialog.Accepted: for project in deleted_projects: self.remove_project_from_watcher(project) self.remove_project(project.id) @@ -851,16 +857,19 @@ if __name__ == "__main__": if project.status != 'normal': return - reply = QMessageBox.question( - None, - "项目目录已删除", - f"检测到项目 \"{project.title}\" 的目录已被删除:\n{project.project_dir}\n\n" - f"是否要从项目列表中移除此项目?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes - ) + # reply = QMessageBox.question( + # None, + # "项目目录已删除", + # f"检测到项目 \"{project.title}\" 的目录已被删除:\n{project.project_dir}\n\n" + # f"是否要从项目列表中移除此项目?", + # QMessageBox.Yes | QMessageBox.No, + # QMessageBox.Yes + # ) + reply = UniversalMessageDialog.show_info("项目目录已删除", f"检测到项目 \"{project.title}\" 的目录已被删除:\n{project.project_dir}\n\n" + f"是否要从项目列表中移除此项目?", + True, "确定", "取消") - if reply == QMessageBox.Yes: + if reply == QDialog.Accepted: self.remove_project_from_watcher(project) self.remove_project(project.id) print(f"已自动移除被删除的项目: {project.title}") diff --git a/MetaCore/ui/create_project_dialog.py b/MetaCore/ui/create_project_dialog.py index 18f31e4..50c5536 100644 --- a/MetaCore/ui/create_project_dialog.py +++ b/MetaCore/ui/create_project_dialog.py @@ -10,6 +10,8 @@ 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): """创建项目对话框""" @@ -21,14 +23,28 @@ class CreateProjectDialog(QDialog): 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(1000, 725) - self.resize(1000, 725) # 设置默认尺寸 + self.setMinimumSize(1177, 689) # 根据Figma设计调整尺寸 + self.setMaximumSize(1177, 689) + self.resize(1177, 689) # 设置默认尺寸 # 设置默认项目位置 self.set_default_location_from_settings() @@ -41,29 +57,59 @@ class CreateProjectDialog(QDialog): layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) - - # 标题栏 - 使用默认系统标题栏 - # self.create_title_bar(layout) - + + # 自定义标题栏 + self.title_bar = self.create_title_bar() + layout.addWidget(self.title_bar) + # 主要内容区域 self.create_main_content(layout) - - # 底部按钮 - self.create_button_area(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(0, 0, 0, 0) - content_layout.setSpacing(0) + content_layout.setContentsMargins(10, 0, 10, 15) # 根据Figma设计调整边距 + content_layout.setSpacing(4) # 添加小间距 - # 左侧模板选择区域 (2/3) + # 左侧模板选择区域 self.create_template_section(content_layout) - # 右侧项目信息区域 (1/3) + # 右侧项目信息区域 self.create_project_info_section(content_layout) layout.addWidget(content_widget) @@ -73,24 +119,28 @@ class CreateProjectDialog(QDialog): template_widget = QWidget() template_widget.setObjectName("templateSection") template_layout = QVBoxLayout(template_widget) - template_layout.setContentsMargins(30, 30, 20, 30) - - # 标题 - title_label = QLabel("选择项目模板") - title_label.setObjectName("sectionTitle") + 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) - # 设置固定比例 (2/3) - template_widget.setMinimumWidth(600) - layout.addWidget(template_widget, 2) + # 设置固定宽度 (根据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) @@ -100,20 +150,20 @@ class CreateProjectDialog(QDialog): templates_widget = QWidget() templates_widget.setObjectName("templatesContainer") templates_layout = QGridLayout(templates_widget) - templates_layout.setSpacing(20) # 增加间距,适配固定尺寸按钮 - templates_layout.setContentsMargins(10, 10, 10, 10) # 添加容器边距 + templates_layout.setSpacing(15) # 根据Figma设计调整间距 + templates_layout.setContentsMargins(15, 15, 15, 15) # 调整容器边距 templates_layout.setAlignment(Qt.AlignTop | Qt.AlignLeft) # 左上对齐 # 模板数据 - 当前只有空白项目模板,后续可以添加更多模板 templates = [ - ("empty", "📄", "空白项目模板"), + ("empty", "empty_folder_icon", "空白项目模版"), # 移除emoji图标,与Figma设计一致 # 后续可以添加的模板示例: - # ("web", "🌐", "Web应用模板"), - # ("mobile", "📱", "移动应用模板"), - # ("desktop", "🖥️", "桌面应用模板"), - # ("api", "🔌", "API服务模板"), - # ("data", "📊", "数据分析模板"), - # ("game", "🎮", "游戏开发模板"), + # ("web", "", "Web应用模板"), + # ("mobile", "", "移动应用模板"), + # ("desktop", "", "桌面应用模板"), + # ("api", "", "API服务模板"), + # ("data", "", "数据分析模板"), + # ("game", "", "游戏开发模板"), ] # 创建模板项 @@ -133,13 +183,13 @@ class CreateProjectDialog(QDialog): scroll_area.setWidget(templates_widget) layout.addWidget(scroll_area) - def create_template_item(self, template_id, icon, name): + def create_template_item(self, template_id, icon_path, name): """创建模板项""" template_btn = QPushButton() template_btn.setObjectName("templateItem") template_btn.setCheckable(True) - # 设置固定尺寸,确保所有模板按钮大小一致,便于后续添加多个模板 - template_btn.setFixedSize(180, 140) + # 根据Figma设计调整尺寸 (149x127) + template_btn.setFixedSize(149, 127) # 设置默认选中 if template_id == "empty": @@ -148,14 +198,20 @@ class CreateProjectDialog(QDialog): # 布局 btn_layout = QVBoxLayout(template_btn) btn_layout.setAlignment(Qt.AlignCenter) - btn_layout.setSpacing(12) - btn_layout.setContentsMargins(20, 24, 20, 24) + btn_layout.setSpacing(27) # 减小间距 + btn_layout.setContentsMargins(15, 20, 15, 20) - # 图标 - icon_label = QLabel(icon) - icon_label.setObjectName("templateIcon") - icon_label.setAlignment(Qt.AlignCenter) - btn_layout.addWidget(icon_label) + # 图标区域 - 根据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) @@ -183,43 +239,54 @@ class CreateProjectDialog(QDialog): info_widget = QWidget() info_widget.setObjectName("projectInfoSection") info_layout = QVBoxLayout(info_widget) - info_layout.setContentsMargins(20, 30, 30, 30) + 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") - info_layout.addWidget(title_label) + header_layout.addWidget(title_label) # 模板描述 self.description_label = QLabel("创建一个空白项目,您可以从头开始构建您的应用程序。包含基础的项目结构和配置文件,适用于任何类型的项目开发。") self.description_label.setObjectName("templateDescription") self.description_label.setWordWrap(True) - info_layout.addWidget(self.description_label) + header_layout.addWidget(self.description_label) + + info_layout.addWidget(header_widget) # 表单 self.create_project_form(info_layout) info_layout.addStretch() - # 设置固定比例 (1/3),增加最大宽度以容纳更长的错误信息 - info_widget.setMaximumWidth(350) # 从300增加到350 - layout.addWidget(info_widget, 1) + # 设置固定宽度 (根据Figma设计) + info_widget.setFixedWidth(427) + layout.addWidget(info_widget) def create_project_form(self, layout): """创建项目表单""" form_layout = QVBoxLayout() - form_layout.setSpacing(18) + form_layout.setSpacing(15) # 项目名称 name_group = QVBoxLayout() - name_group.setSpacing(4) # 减小间距,让错误提示更紧贴 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) @@ -241,11 +308,12 @@ class CreateProjectDialog(QDialog): 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.setMaximumHeight(70) + self.desc_input.setMinimumSize(397, 96) desc_group.addWidget(self.desc_input) form_layout.addLayout(desc_group) @@ -255,32 +323,38 @@ class CreateProjectDialog(QDialog): 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("📁") + 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(32, 32) # 减小尺寸:40x32 -> 32x32 + 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.addWidget(self.create_button_area()) + layout.addLayout(form_layout) - - def create_button_area(self, layout): + + def create_button_area(self): """创建按钮区域""" button_widget = QWidget() button_widget.setObjectName("buttonArea") button_layout = QHBoxLayout(button_widget) - button_layout.setContentsMargins(30, 20, 30, 30) + button_layout.setContentsMargins(0, 185, 0, 10) # 根据Figma设计调整边距 button_layout.setSpacing(10) # 设置按钮间距 button_layout.addStretch() @@ -288,16 +362,18 @@ class CreateProjectDialog(QDialog): # 取消按钮 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("创建项目") + 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) - layout.addWidget(button_widget) + return button_widget def update_template_description(self, template_id): """更新模板描述""" @@ -328,17 +404,18 @@ class CreateProjectDialog(QDialog): # 验证输入 name = self.name_input.text().strip() if not name: - QMessageBox.warning(self, "输入错误", "请输入项目名称") + # QMessageBox.warning(self, "输入错误", "请输入项目名称") + UniversalMessageDialog.show_warning(self, "输入错误", "请输入项目名称", False, "确定") return if not self.selected_path: - QMessageBox.warning(self, "输入错误", "请选择项目保存位置") + UniversalMessageDialog.show_warning(self, "输入错误", "请选择项目保存位置", False, "确定") return # 使用项目管理器的验证方法进行全面检查 is_valid, error_message = self.project_manager.validate_project_creation(name, self.selected_path) if not is_valid: - QMessageBox.warning(self, "创建失败", error_message) + UniversalMessageDialog.show_warning(self, "创建失败", error_message, False, "确定") return # 创建项目 @@ -355,18 +432,18 @@ class CreateProjectDialog(QDialog): 项目目录: {project.project_dir} 项目文件结构已创建完成,包含: -• models/ - 模型文件夹 -• textures/ - 贴图文件夹 -• scenes/ - 场景文件夹 -• project.json - 项目配置文件 +? models/ - 模型文件夹 +? textures/ - 贴图文件夹 +? scenes/ - 场景文件夹 +? project.json - 项目配置文件 您可以开始在此基础上开发您的应用程序。""" - QMessageBox.information(self, "创建成功", success_msg) + UniversalMessageDialog.show_info(self, "创建成功", success_msg, False, "确定") self.accept() except Exception as e: - QMessageBox.critical(self, "创建失败", f"项目创建失败:{str(e)}") + UniversalMessageDialog.show_error(self, "创建失败", f"项目创建失败:{str(e)}", False, "确定") def get_template_name(self, template_id): """获取模板名称""" @@ -445,6 +522,21 @@ class CreateProjectDialog(QDialog): + 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(): @@ -458,3 +550,18 @@ class CreateProjectDialog(QDialog): x = (screen.width() - self.width()) // 2 y = (screen.height() - self.height()) // 2 self.move(x, y) + + + + + + + + + + + + + + + diff --git a/MetaCore/ui/enhanced_styles.py b/MetaCore/ui/enhanced_styles.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/MetaCore/ui/enhanced_styles.py @@ -0,0 +1 @@ + diff --git a/MetaCore/ui/icon_manager.py b/MetaCore/ui/icon_manager.py index 194a6b2..c3d1180 100644 --- a/MetaCore/ui/icon_manager.py +++ b/MetaCore/ui/icon_manager.py @@ -47,8 +47,29 @@ class IconManager: 'search': 'search_icon.png', 'infomation': 'infomation_icon.png', - + 'infomation_hover': 'infomation_hover_icon.png', + 'infomation_check': 'infomation_check_icon.png', + + # 为获取项目图片 'project_empty_icon': 'project_empty_icon.png', + + # 卡片右键菜单 + 'refresh_projectcard': 'refresh_projectcard_icon.png', + 'open_projectcard': 'open_projectcard_icon.png', + 'remove_projectcard': 'remove_projectcard_icon.png', + + # 导入对话框 + 'import_file': 'import_file_icon.png', + + # 创建项目浏览图标 + 'file_icon': 'file_icon.png', + 'empty_folder_icon': 'empty_folder_icon.png', + 'close_bt_icon': 'close_bt_icon.png', + + # 弹窗图标 + 'success_icon': 'success_icon.png', + 'warning_icon': 'warning_icon.png', + 'fail_icon': 'delete_fail_icon.png', } # 图标缓存 @@ -202,4 +223,4 @@ class IconManager: for icon_name in cls.ICON_FILES: if not cls.icon_exists(icon_name): missing_icons.append(icon_name) - return missing_icons \ No newline at end of file + return missing_icons diff --git a/MetaCore/ui/import_project_dialog.py b/MetaCore/ui/import_project_dialog.py index 9e94aea..97cc10c 100644 --- a/MetaCore/ui/import_project_dialog.py +++ b/MetaCore/ui/import_project_dialog.py @@ -1,391 +1,509 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python3 # -*- coding: utf-8 -*- -""" -导入项目对话框 -""" +"""导入项目对话框.""" import os -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * -from PyQt5.QtGui import * +from typing import List, Optional, Tuple + +from PyQt5.QtCore import QPoint, QSize, QEvent, Qt, pyqtSignal, QObject +from PyQt5.QtGui import QFont, QPainter, QPainterPath, QRegion +from PyQt5.QtWidgets import ( + QApplication, + QCheckBox, + QDialog, + QFileDialog, + QFrame, + QGroupBox, + QHBoxLayout, + QLabel, + QListWidget, + QListWidgetItem, + QPushButton, + QStyle, + QSizePolicy, + QVBoxLayout, + QWidget, +) from data.project_manager import ProjectManager +from ui.icon_manager import IconManager +from ui.widget import UniversalMessageDialog + class ImportProjectDialog(QDialog): - """导入项目对话框""" - - def __init__(self, project_manager: ProjectManager, project_settings_page=None, parent=None): + """导入项目对话框,支持拖拽导入自定义标题栏等交互.""" + + def __init__( + self, + project_manager: ProjectManager, + project_settings_page=None, + parent: Optional[QWidget] = None, + ) -> None: super().__init__(parent) self.project_manager = project_manager self.project_settings_page = project_settings_page - self.selected_files = [] + self.selected_folders: List[str] = [] - self.init_ui() - self.connect_signals() - - # 设置对话框属性 + self.title_bar: Optional[QWidget] = None + self.title_label: Optional[QLabel] = None + self.close_btn: Optional[QPushButton] = None + self.drag_offset: Optional[QPoint] = None + + self.upload_area: Optional[UploadArea] = None + self.file_list: Optional[QListWidget] = None + self.open_after_import_checkbox: Optional[QCheckBox] = None + self.restore_sources_checkbox: Optional[QCheckBox] = None + self.cancel_btn: Optional[QPushButton] = None + self.import_btn: Optional[QPushButton] = None + + self.setObjectName("ImportProjectDialog") self.setWindowTitle("导入文件夹") self.setModal(True) - self.setMinimumSize(1000, 725) - self.resize(1000, 725) # 设置默认尺寸 + self.setMinimumSize(1177, 689) + self.resize(1177, 689) + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_StyledBackground, True) - # 居中显示 + self._build_ui() + self._connect_signals() + self._show_file_list_placeholder("请拖拽或点击下方按钮选择文件夹") self.center_dialog() - - def init_ui(self): - """初始化UI""" - layout = QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - # 标题栏 - # self.create_title_bar(layout) - - # 主要内容区域 - self.create_main_content(layout) - - # 底部按钮 - self.create_button_area(layout) - - def create_title_bar(self, layout): - """创建标题栏""" - title_widget = QWidget() - title_widget.setObjectName("dialogTitle") - title_layout = QHBoxLayout(title_widget) - title_layout.setContentsMargins(30, 20, 30, 20) - - # 标题 - title_label = QLabel("导入项目文件") - title_label.setObjectName("titleLabel") - title_layout.addWidget(title_label) - - title_layout.addStretch() - - # 关闭按钮 - close_btn = QPushButton("✕") - close_btn.setObjectName("closeBtn") - close_btn.setFixedSize(32, 32) - close_btn.clicked.connect(self.reject) - title_layout.addWidget(close_btn) - - layout.addWidget(title_widget) - - def create_main_content(self, layout): - """创建主要内容""" + # 设置圆角遮罩 + self._apply_rounded_corners() + + # --------------------------------------------------------------------- UI -- + def _build_ui(self) -> None: + root_layout = QVBoxLayout(self) + root_layout.setContentsMargins(0, 0, 0, 0) + root_layout.setSpacing(0) + + self.title_bar = self._create_title_bar() + root_layout.addWidget(self.title_bar) + content_widget = QWidget() + content_widget.setObjectName("importContentArea") content_layout = QVBoxLayout(content_widget) - content_layout.setContentsMargins(30, 30, 30, 30) - content_layout.setSpacing(20) - - # 导入区域 - self.create_import_area(content_layout) - - # 导入选项 - self.create_import_options(content_layout) - - layout.addWidget(content_widget) - - def create_import_area(self, layout): - """创建导入区域""" - import_group = QGroupBox("选择文件夹") - import_group.setObjectName("importGroup") - import_layout = QVBoxLayout(import_group) - import_layout.setSpacing(15) - - # 拖拽上传区域 + content_layout.setContentsMargins(10, 0, 10, 0) + content_layout.setSpacing(21) + + self._create_import_area(content_layout) + self._create_options_area(content_layout) + + root_layout.addWidget(content_widget) + root_layout.addWidget(self._create_button_bar()) + + def _create_title_bar(self) -> QWidget: + 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.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("×") + self.close_btn.setObjectName("customCloseBtn") + self.close_btn.setFixedSize(18, 18) + self.close_btn.setCursor(Qt.PointingHandCursor) + layout.addWidget(self.close_btn) + + title_bar.installEventFilter(self) + title_bar.setCursor(Qt.ArrowCursor) + return title_bar + + def _create_import_area(self, parent_layout: QVBoxLayout) -> None: + section_frame = QFrame() + section_frame.setObjectName("importSectionFrame") + section_frame.setFixedSize(1157, 499) + section_layout = QVBoxLayout(section_frame) + section_layout.setContentsMargins(0, 0, 0, 0) + section_layout.setSpacing(4) + + header_frame = QFrame() + header_frame.setObjectName("importSectionHeader") + header_frame.setFixedHeight(33) + + header_layout = QHBoxLayout(header_frame) + header_layout.setContentsMargins(20, 8, 20, 6) + header_layout.setSpacing(0) + + title_label = QLabel("选择文件夹") + title_label.setObjectName("importSectionTitle") + header_layout.addWidget(title_label) + + section_layout.addWidget(header_frame) + + body_frame = QFrame() + body_frame.setObjectName("importSectionBody") + body_frame.setFixedHeight(450) + body_layout = QVBoxLayout(body_frame) + body_layout.setContentsMargins(15, 10, 15, 10) + body_layout.setSpacing(0) # 使用addSpacing()来精确控制间距 + self.upload_area = UploadArea() - self.upload_area.files_dropped.connect(self.on_files_dropped) - self.upload_area.select_files_requested.connect(self.select_files) - import_layout.addWidget(self.upload_area) + body_layout.addWidget(self.upload_area, alignment=Qt.AlignHCenter) - # 或者选择文件 - file_layout = QHBoxLayout() - file_layout.addWidget(QLabel("或者")) - - select_btn = QPushButton("点击选择文件夹") - select_btn.setObjectName("selectBtn") - select_btn.clicked.connect(self.select_files) - file_layout.addWidget(select_btn) - - file_layout.addStretch() - import_layout.addLayout(file_layout) - - # 文件列表 + # 明确设置8px间距 + body_layout.addSpacing(8) + + hint_label = QLabel("或者,点击选择文件夹") + hint_label.setObjectName("uploadHintLabel") + hint_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + hint_label.setWordWrap(True) + hint_label.setAlignment(Qt.AlignLeft) + # 确保label有明确的尺寸,避免布局问题 + hint_label.setMinimumHeight(15) + body_layout.addWidget(hint_label, alignment=Qt.AlignLeft) + + # 文件列表前的间距 + body_layout.addSpacing(10) + self.file_list = QListWidget() self.file_list.setObjectName("fileList") - self.file_list.setMaximumHeight(150) - import_layout.addWidget(self.file_list) - - layout.addWidget(import_group) + self.file_list.setFrameShape(QFrame.NoFrame) + self.file_list.setSpacing(6) + self.file_list.setSelectionMode(QListWidget.NoSelection) + self.file_list.setUniformItemSizes(False) + self.file_list.setVerticalScrollMode(QListWidget.ScrollPerPixel) + self.file_list.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.file_list.setFixedSize(1127, 109) + body_layout.addWidget(self.file_list, alignment=Qt.AlignCenter) - def create_import_options(self, layout): - """创建导入选项""" - options_widget = QWidget() - options_widget.setObjectName("optionsGroup") - options_layout = QHBoxLayout(options_widget) + section_layout.addWidget(body_frame) + parent_layout.addWidget(section_frame) - # 导入选项 - self.extract_checkbox = QCheckBox("导入后打开项目") - self.extract_checkbox.setChecked(True) - options_layout.addWidget(self.extract_checkbox) + def _create_options_area(self, parent_layout: QVBoxLayout) -> None: + options_container = QWidget() + options_container.setObjectName("optionsGroup") + layout = QVBoxLayout(options_container) + layout.setContentsMargins(15, 10, 10, 10) + layout.setSpacing(8) - options_layout.addStretch() + self.open_after_import_checkbox = QCheckBox("导入后打开项目") + self.open_after_import_checkbox.setChecked(False) + layout.addWidget(self.open_after_import_checkbox) - self.overwrite_checkbox = QCheckBox("还原动态数据源") - options_layout.addWidget(self.overwrite_checkbox) + self.restore_sources_checkbox = QCheckBox("还原动态数据源") + self.restore_sources_checkbox.setChecked(False) + layout.addWidget(self.restore_sources_checkbox) - layout.addWidget(options_widget) - - # def create_import_options(self, layout): - # """创建导入选项""" - # options_group = QGroupBox("导入选项") - # options_group.setObjectName("optionsGroup") - # options_layout = QVBoxLayout(options_group) - # options_layout.setSpacing(10) - # - # # 项目名称 - # name_layout = QHBoxLayout() - # name_layout.addWidget(QLabel("项目名称:")) - # - # self.name_input = QLineEdit() - # self.name_input.setObjectName("nameInput") - # self.name_input.setPlaceholderText("自动从文件名提取") - # name_layout.addWidget(self.name_input) - # - # options_layout.addLayout(name_layout) - # - # # 导入位置 - # location_layout = QHBoxLayout() - # location_layout.addWidget(QLabel("导入位置:")) - # - # self.location_input = QLineEdit() - # self.location_input.setObjectName("locationInput") - # self.location_input.setPlaceholderText("选择导入位置") - # self.location_input.setReadOnly(True) - # location_layout.addWidget(self.location_input) - # - # browse_btn = QPushButton("浏览") - # browse_btn.setObjectName("browseBtn") - # browse_btn.clicked.connect(self.browse_location) - # location_layout.addWidget(browse_btn) - # - # options_layout.addLayout(location_layout) - # - # # 导入选项 - # self.extract_checkbox = QCheckBox("解压缩文件") - # self.extract_checkbox.setChecked(True) - # options_layout.addWidget(self.extract_checkbox) - # - # self.overwrite_checkbox = QCheckBox("覆盖已存在的文件") - # options_layout.addWidget(self.overwrite_checkbox) - # - # layout.addWidget(options_group) - - def create_button_area(self, layout): - """创建按钮区域""" + parent_layout.addWidget(options_container) + + def _create_button_bar(self) -> QWidget: button_widget = QWidget() button_widget.setObjectName("buttonArea") - button_layout = QHBoxLayout(button_widget) - button_layout.setContentsMargins(30, 20, 30, 30) - button_layout.setSpacing(10) # 与创建项目对话框保持一致 - button_layout.addStretch() + layout = QHBoxLayout(button_widget) + layout.setContentsMargins(24, 12, 24, 16) + layout.setSpacing(12) + + layout.addStretch() - # 取消按钮 self.cancel_btn = QPushButton("取消") self.cancel_btn.setObjectName("secondaryBtn") - self.cancel_btn.clicked.connect(self.reject) - button_layout.addWidget(self.cancel_btn) + self.cancel_btn.setFixedWidth(95) + self.cancel_btn.setFixedHeight(36) + self.cancel_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + layout.addWidget(self.cancel_btn) - # 导入按钮 self.import_btn = QPushButton("导入项目") self.import_btn.setObjectName("primaryBtn") self.import_btn.setEnabled(False) - self.import_btn.clicked.connect(self.import_project) - button_layout.addWidget(self.import_btn) + self.import_btn.setFixedWidth(125) + self.import_btn.setFixedHeight(36) + self.import_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + layout.addWidget(self.import_btn) - layout.addWidget(button_widget) - - def select_files(self): - """选择文件夹""" - # 使用项目设置中的默认位置作为起始目录 - if self.project_settings_page: - start_dir = self.project_settings_page.get_default_project_location() - else: - start_dir = os.path.expanduser("~/Documents") + return button_widget - folder = QFileDialog.getExistingDirectory( - self, "选择文件夹", - start_dir - ) + # --------------------------------------------------------------- Behaviors -- + def _connect_signals(self) -> None: + if self.close_btn: + self.close_btn.clicked.connect(self.reject) - if folder: - self.selected_files = [folder] - self.update_file_list() - self.update_project_name() - - def browse_location(self): - """浏览导入位置""" - # 当前UI简化版本不需要此功能 - pass - - def on_files_dropped(self, files): - """文件夹拖拽事件""" - # 只接受文件夹 - folders = [f for f in files if os.path.isdir(f)] - if folders: - self.selected_files = folders - self.update_file_list() - self.update_project_name() - else: - QMessageBox.warning(self, "提示", "请拖拽文件夹,不支持单个文件") - - def update_file_list(self): - """更新文件夹列表""" + if self.upload_area: + self.upload_area.files_dropped.connect(self.on_files_dropped) + self.upload_area.select_files_requested.connect(self.select_files) + + if self.cancel_btn: + self.cancel_btn.clicked.connect(self.reject) + + if self.import_btn: + self.import_btn.clicked.connect(self.import_project) + + # ----------------------------------------------------------------- Helpers -- + def _show_file_list_placeholder(self, message: str) -> None: + if not self.file_list: + return self.file_list.clear() - - for folder_path in self.selected_files: - folder_name = os.path.basename(folder_path) - - # 计算文件夹大小和文件数量 - try: - total_size = 0 - file_count = 0 - for root, dirs, files in os.walk(folder_path): - for file in files: - file_path = os.path.join(root, file) - if os.path.exists(file_path): - total_size += os.path.getsize(file_path) - file_count += 1 - - size_str = self.format_file_size(total_size) - item_text = f"📁 {folder_name} ({file_count} 个文件, {size_str})" - except Exception: - item_text = f"📁 {folder_name} (无法计算大小)" - - self.file_list.addItem(item_text) - - # 按钮状态由 update_project_name 方法中的验证逻辑控制 - - def update_project_name(self): - """更新项目名称并验证""" - if not self.selected_files: + placeholder = QListWidgetItem(message) + placeholder.setFlags(Qt.NoItemFlags) + placeholder.setTextAlignment(Qt.AlignCenter) + self.file_list.addItem(placeholder) + if self.import_btn: self.import_btn.setEnabled(False) + + def _populate_file_list(self) -> None: + if not self.file_list: return - # 从第一个文件夹名自动提取项目名称 - folder_path = self.selected_files[0] - folder_name = os.path.basename(folder_path) + self.file_list.clear() + if not self.selected_folders: + self._show_file_list_placeholder("请拖拽或点击下方按钮选择文件夹") + return - # 进行验证 - is_valid, error_message = self.project_manager.validate_project_import(folder_name, folder_path) + for folder_path in self.selected_folders: + folder_name = os.path.basename(folder_path) + file_count, total_size = self._collect_folder_stats(folder_path) + size_str = self._format_file_size(total_size) + + item_widget = self._build_file_list_item( + folder_name, folder_path, file_count, size_str + ) + + # --- 这是修改后的逻辑 --- + # 1. 创建一个空的 QListWidgetItem + item = QListWidgetItem() + + # 2. 将它添加到列表中 + self.file_list.addItem(item) + + # 3. 将自定义小部件设置给它 + # 注意:完全不需要调用 setSizeHint() + self.file_list.setItemWidget(item, item_widget) + # --- 修改结束 --- + + # item = QListWidgetItem() + # self.file_list.addItem(item) + # self.file_list.setItemWidget(item, item_widget) + + # 触发布局以获得正确的 sizeHint + item_widget.layout().activate() + hint = item_widget.sizeHint() + if not hint.isValid() or hint.height() == 0: + hint = item_widget.minimumSizeHint() + if hint.isValid(): + item.setSizeHint(hint) + + # 移除调试输出 + + def _build_file_list_item( + self, + folder_name: str, + folder_path: str, + file_count: int, + size_str: str, + ) -> QWidget: + container = QWidget() + container.setObjectName("fileListItem") + + # --- 新增代码 --- + # 强制为列表项的容器设置一个最小高度。 + # 这是最可靠的方法,可以防止它因尺寸计算问题而变得不可见。 + # 60px 是一个比较合理的值,可以根据您的UI风格调整。 + container.setMinimumHeight(60) + # --- 新增代码结束 --- + + layout = QHBoxLayout(container) + layout.setContentsMargins(16, 10, 16, 10) + layout.setSpacing(12) + + icon_label = QLabel() + icon_label.setObjectName("fileListItemIcon") + icon = QApplication.style().standardIcon(QStyle.SP_DriveHDIcon) + icon_label.setPixmap(icon.pixmap(24, 24)) + layout.addWidget(icon_label, alignment=Qt.AlignCenter) + + text_block = QWidget() + text_layout = QVBoxLayout(text_block) + text_layout.setContentsMargins(0, 0, 0, 0) + text_layout.setSpacing(4) + + title_label = QLabel(folder_name) + title_label.setObjectName("fileListItemTitle") + title_label.setWordWrap(False) + text_layout.addWidget(title_label) + + meta_label = QLabel(f"{folder_path} · {file_count} 个文件 · {size_str}") + meta_label.setObjectName("fileListItemMeta") + meta_label.setWordWrap(False) + meta_label.setToolTip(folder_path) + text_layout.addWidget(meta_label) + + layout.addWidget(text_block, 1) + + return container + + @staticmethod + def _collect_folder_stats(folder_path: str) -> Tuple[int, int]: + file_count = 0 + total_size = 0 + for root, _, files in os.walk(folder_path): + for file_name in files: + file_path = os.path.join(root, file_name) + if os.path.exists(file_path): + try: + total_size += os.path.getsize(file_path) + file_count += 1 + except OSError: + continue + return file_count, total_size + + @staticmethod + def _format_file_size(size: int) -> str: + base = 1024.0 + for unit in ("B", "KB", "MB", "GB"): + if size < base: + return f"{size:.1f} {unit}" + size /= base + return f"{size:.1f} TB" + + # ----------------------------------------------------------------- Actions -- + def select_files(self) -> None: + start_dir = "" + if self.project_settings_page: + try: + start_dir = self.project_settings_page.get_default_project_location() + except AttributeError: + start_dir = "" + + if not start_dir: + start_dir = os.path.expanduser("~/Documents") + + folder = QFileDialog.getExistingDirectory(self, "选择文件夹", start_dir) + if folder: + self.selected_folders = [folder] + self.refresh_selection_state() + + def on_files_dropped(self, files: List[str]) -> None: + folders = [path for path in files if os.path.isdir(path)] + if not folders: + # QMessageBox.warning(self, "提示", "请拖拽文件夹,不支持单个文件。") + UniversalMessageDialog.show_warning(self, "提示", "请拖拽文件夹,不支持单个文件。", False, "确定") + return + + self.selected_folders = folders + self.refresh_selection_state() + + def refresh_selection_state(self) -> None: + print(f"[ImportProjectDialog] 当前选中文件夹: {self.selected_folders}") + self._populate_file_list() + self._update_import_button_state() + + def _update_import_button_state(self) -> None: + if not self.import_btn: + return + + if not self.selected_folders: + self.import_btn.setEnabled(False) + self.import_btn.setToolTip("请选择要导入的文件夹") + return + + folder_path = self.selected_folders[0] + folder_name = os.path.basename(folder_path) + is_valid, error_message = self.project_manager.validate_project_import( + folder_name, folder_path + ) if is_valid: - # 验证通过,启用导入按钮 self.import_btn.setEnabled(True) self.import_btn.setToolTip("") else: - # 验证失败,禁用导入按钮并显示错误信息 self.import_btn.setEnabled(False) - self.import_btn.setToolTip(f"无法导入: {error_message}") - - def format_file_size(self, size): - """格式化文件大小""" - for unit in ['B', 'KB', 'MB', 'GB']: - if size < 1024.0: - return f"{size:.1f} {unit}" - size /= 1024.0 - return f"{size:.1f} TB" - - def import_project(self): - """导入项目""" - # 防止重复点击 - if not self.import_btn.isEnabled(): + self.import_btn.setToolTip(f"无法导入:{error_message}") + + def import_project(self) -> None: + if not self.import_btn or not self.import_btn.isEnabled(): return - # 立即禁用按钮,防止重复点击 self.import_btn.setEnabled(False) + if not self.selected_folders: + UniversalMessageDialog.show_warning(self, "输入错误", "请选择要导入的文件夹。", False, "确定") + self.import_btn.setEnabled(True) + return + + folder_path = self.selected_folders[0] + folder_name = os.path.basename(folder_path) + + is_valid, error_message = self.project_manager.validate_project_import( + folder_name, folder_path + ) + if not is_valid: + UniversalMessageDialog.show_warning(self, "导入失败", error_message, False, "确定") + self.import_btn.setEnabled(True) + return + try: - # 验证输入 - if not self.selected_files: - QMessageBox.warning(self, "输入错误", "请选择要导入的文件夹") - return - - # 从第一个文件夹名自动提取项目名称 - folder_path = self.selected_files[0] - folder_name = os.path.basename(folder_path) - name = folder_name - - # 使用项目管理器的验证方法进行全面检查 - is_valid, error_message = self.project_manager.validate_project_import(name, folder_path) - if not is_valid: - QMessageBox.warning(self, "导入失败", error_message) - return - - # 导入项目 - 直接使用现有文件夹作为项目目录 project = self.project_manager.import_project( - name, # 项目名称 - f"从文件夹 {folder_name} 导入", # 项目描述 - "imported", # 项目类型 - folder_path # 直接使用原文件夹路径作为项目目录 + folder_name, + f"从文件夹 {folder_name} 导入", + "imported", + folder_path, ) - # 处理导入选项 - if self.overwrite_checkbox.isChecked(): + if self.restore_sources_checkbox and self.restore_sources_checkbox.isChecked(): self.restore_dynamic_data_sources(project) - QMessageBox.information(self, "导入成功", f"项目 \"{name}\" 导入成功!\n文件夹位置: {folder_path}") - - # 如果勾选了导入后打开项目,则打开项目 - if self.extract_checkbox.isChecked(): + # QMessageBox.information( + # self, + # "导入成功", + # f"项目“{folder_name}”导入成功!\n源文件夹:{folder_path}", + # ) + UniversalMessageDialog.show_info( + self, + "导入成功", + f"项目“{folder_name}”导入成功!\n源文件夹:{folder_path}", + False, + "确定" + ) + + if ( + self.open_after_import_checkbox + and self.open_after_import_checkbox.isChecked() + ): self.open_imported_project(project) - + self.accept() + except Exception as exc: # noqa: BLE001 + UniversalMessageDialog.show_error(self, "导入失败", f"项目导入失败:{exc}", False, "确定") + self.import_btn.setEnabled(True) - except Exception as e: - QMessageBox.critical(self, "导入失败", f"项目导入失败:{str(e)}") - finally: - # 确保在任何情况下都重新启用按钮(如果对话框没有关闭) - if self.isVisible(): - self.import_btn.setEnabled(True) - - def open_imported_project(self, project): - """打开导入的项目""" + def open_imported_project(self, project) -> None: try: - # 使用项目管理器的验证方法检查项目是否可以打开 - is_valid, error_message = self.project_manager.validate_project_open(project.project_dir) + is_valid, error_message = self.project_manager.validate_project_open( + project.project_dir + ) if not is_valid: - QMessageBox.warning(self, "无法打开项目", f"项目无法打开: {error_message}") + UniversalMessageDialog.show_warning(self, "无法打开项目", f"项目无法打开:{error_message}", False, "确定") return - - # 验证通过,显示成功信息 - QMessageBox.information(self, "打开项目", f"正在打开项目: {project.title}") - - # TODO: 在这里添加实际的项目打开逻辑 - - except Exception as e: - QMessageBox.critical(self, "错误", f"打开项目时发生错误:\n{str(e)}") - def restore_dynamic_data_sources(self, project): - """还原动态数据源""" + # QMessageBox.information(self, "打开项目", f"正在打开项目:{project.title}") + UniversalMessageDialog.show_info(self, "打开项目", f"正在打开项目:{project.title}", False, "确定") + # TODO: 接入实际的项目打开逻辑 + except Exception as exc: # noqa: BLE001 + UniversalMessageDialog.show_error(self, "错误", f"打开项目时发生错误:\n{exc}", False, "确定") + + def restore_dynamic_data_sources(self, project) -> None: try: - # TODO: 在这里添加还原动态数据源的逻辑 - print(f"还原项目 {project.title} 的动态数据源") - - except Exception as e: - QMessageBox.warning(self, "警告", f"还原动态数据源时发生错误:\n{str(e)}") - - def connect_signals(self): - """连接信号""" - # 连接按钮信号 - self.cancel_btn.clicked.connect(self.reject) - self.import_btn.clicked.connect(self.import_project) + # TODO: 根据项目结构实现动态数据源还原逻辑 + print(f"还原项目 {project.title} 的动态数据源") # noqa: T201 + except Exception as exc: # noqa: BLE001 + UniversalMessageDialog.show_warning(self, "警告", f"还原动态数据源时发生错误:\n{exc}", False, "确定") - # 连接文件选择按钮信号 - # self.select_files_btn.clicked.connect(self.select_files) - # self.select_folder_btn.clicked.connect(self.select_folder) - + # ---------------------------------------------------------------- Position -- def center_dialog(self): """对话框居中""" if self.parent(): @@ -393,76 +511,144 @@ class ImportProjectDialog(QDialog): 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) + + def _apply_rounded_corners(self) -> None: + """为无边框对话框应用圆角效果""" + # 创建圆角路径 + path = QPainterPath() + path.addRoundedRect(0, 0, self.width(), self.height(), 5, 5) + + # 将路径转换为区域并设置为窗口遮罩 + region = QRegion(path.toFillPolygon().toPolygon()) + self.setMask(region) + + def resizeEvent(self, event) -> None: # type: ignore[override] + """窗口大小改变时重新应用圆角""" + super().resizeEvent(event) + if hasattr(self, '_apply_rounded_corners'): + self._apply_rounded_corners() + + # ------------------------------------------------------------- Event Filter -- + def eventFilter(self, watched: QObject, event: QEvent) -> bool: # type: ignore[override] + 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) + class UploadArea(QWidget): - """文件上传区域""" + """导入对话框中的拖拽上传区域.""" files_dropped = pyqtSignal(list) select_files_requested = pyqtSignal() - def __init__(self): - super().__init__() + def __init__(self, parent: Optional[QWidget] = None) -> None: + super().__init__(parent) self.setAcceptDrops(True) - self.init_ui() - - def init_ui(self): - """初始化UI""" self.setObjectName("uploadArea") - self.setMinimumHeight(150) + self._build_ui() + + def _build_ui(self) -> None: + # 设置UploadArea的固定尺寸,确保不影响外部布局 + self.setFixedSize(1127, 288) layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) layout.setAlignment(Qt.AlignCenter) - layout.setSpacing(10) - - # 上传图标 - icon_label = QLabel("📁") - icon_label.setStyleSheet("font-size: 32px; color: #666666;") + + content_frame = QFrame() + content_frame.setObjectName("uploadContentFrame") + content_frame.setFixedSize(1127, 288) + + content_layout = QVBoxLayout(content_frame) + content_layout.setSpacing(0) + content_layout.setAlignment(Qt.AlignCenter) + + icon_label = QLabel() + icon_label.setObjectName("uploadIcon") + icon_pixmap = IconManager.get_pixmap("import_file", QSize(50, 50)) + if not icon_pixmap.isNull(): + icon_label.setPixmap( + icon_pixmap.scaled(50, 50, Qt.KeepAspectRatio, Qt.SmoothTransformation) + ) + else: + fallback_icon = QApplication.style().standardIcon(QStyle.SP_DialogOpenButton) + icon_label.setPixmap(fallback_icon.pixmap(50, 50)) + icon_label.setFixedSize(50, 50) icon_label.setAlignment(Qt.AlignCenter) - layout.addWidget(icon_label) - - # 提示文字 - text_label = QLabel("拖拽文件夹到这里,或者") - text_label.setStyleSheet("color: #888888; font-size: 14px;") - text_label.setAlignment(Qt.AlignCenter) - layout.addWidget(text_label) - - # 选择按钮 + content_layout.addWidget(icon_label, alignment=Qt.AlignHCenter) + content_layout.addSpacing(28) + + title_label = QLabel("拖拽文件夹到这里,或者") + title_label.setObjectName("uploadTitle") + title_label.setAlignment(Qt.AlignCenter) + content_layout.addWidget(title_label, alignment=Qt.AlignCenter) + content_layout.addSpacing(10) + select_btn = QPushButton("点击选择") select_btn.setObjectName("uploadBtn") + select_btn.setCursor(Qt.PointingHandCursor) select_btn.clicked.connect(self.select_files_requested.emit) - layout.addWidget(select_btn) - - # 支持格式提示 - format_label = QLabel("支持任意文件夹") - format_label.setStyleSheet("color: #666666; font-size: 12px;") - format_label.setAlignment(Qt.AlignCenter) - layout.addWidget(format_label) - - def dragEnterEvent(self, event): - """拖拽进入事件""" + select_btn.setFixedSize(151, 42) + button_font = select_btn.font() + button_font.setFamily("Inter") + button_font.setPointSize(16) + button_font.setWeight(QFont.Normal) + select_btn.setFont(button_font) + content_layout.addWidget(select_btn, alignment=Qt.AlignCenter) + content_layout.addSpacing(10) + + hint_label = QLabel("支持任意本地文件夹") + hint_label.setObjectName("uploadHint") + hint_label.setAlignment(Qt.AlignCenter) + content_layout.addWidget(hint_label, alignment=Qt.AlignCenter) + + layout.addWidget(content_frame, alignment=Qt.AlignCenter) + + # ----------------------------------------------------------------- Drag & Drop + def dragEnterEvent(self, event) -> None: # type: ignore[override] if event.mimeData().hasUrls(): event.acceptProposedAction() self.setProperty("dragOver", True) self.style().unpolish(self) self.style().polish(self) - - def dragLeaveEvent(self, event): - """拖拽离开事件""" + + def dragLeaveEvent(self, event) -> None: # type: ignore[override] self.setProperty("dragOver", False) self.style().unpolish(self) self.style().polish(self) - - def dropEvent(self, event): - """拖拽放下事件""" + super().dragLeaveEvent(event) + + def dropEvent(self, event) -> None: # type: ignore[override] files = [] for url in event.mimeData().urls(): if url.isLocalFile(): files.append(url.toLocalFile()) - + if files: self.files_dropped.emit(files) - + self.setProperty("dragOver", False) self.style().unpolish(self) self.style().polish(self) + event.acceptProposedAction() + diff --git a/MetaCore/ui/main_window.py b/MetaCore/ui/main_window.py index ae021a9..634c695 100644 --- a/MetaCore/ui/main_window.py +++ b/MetaCore/ui/main_window.py @@ -15,6 +15,7 @@ from ui.import_project_dialog import ImportProjectDialog from ui.project_settings_page import ProjectSettingsPage from data.project_manager import ProjectManager from ui.icon_manager import IconManager +from ui.widget import UniversalMessageDialog class MainWindow(QMainWindow): """主窗口类""" @@ -282,20 +283,31 @@ class MainWindow(QMainWindow): def show_about(self): """显示关于对话框""" - QMessageBox.about(self, "关于 MetaCore", - "MetaCore 项目管理平台\n\n" - "版本: 1.0.0\n" - "基于 PyQt5 开发\n\n" - "© 2024 MetaCore Team") + # QMessageBox.about(self, "关于 MetaCore", + # "MetaCore 项目管理平台\n\n" + # "版本: 1.0.0\n" + # "基于 PyQt5 开发\n\n" + # "© 2024 MetaCore Team") + UniversalMessageDialog.show_info(self, + "关于 MetaCore", + "MetaCore 项目管理平台\n\n" + "版本: 1.0.0\n" + "基于 PyQt5 开发\n\n" + "© 2024 MetaCore Team", + False, "确定") def closeEvent(self, event): """关闭事件""" - reply = QMessageBox.question(self, '确认退出', - '确定要退出 MetaCore 吗?', - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No) + # reply = QMessageBox.question(self, '确认退出', + # '确定要退出 MetaCore 吗?', + # QMessageBox.Yes | QMessageBox.No, + # QMessageBox.No) + reply = UniversalMessageDialog.show_info(self, + '确认退出', + '确定要退出 MetaCore 吗?', + True, "是", "否") - if reply == QMessageBox.Yes: + if reply == QDialog.Accepted: # 保存数据 self.project_manager.save_projects() event.accept() diff --git a/MetaCore/ui/project_area.py b/MetaCore/ui/project_area.py index 7811e1b..4db8fd3 100644 --- a/MetaCore/ui/project_area.py +++ b/MetaCore/ui/project_area.py @@ -63,8 +63,8 @@ class ProjectArea(QWidget): header_widget = QWidget() header_widget.setObjectName("contentHeader") header_layout = QHBoxLayout(header_widget) - header_layout.setContentsMargins(36, 26, 36, 28) - header_layout.setSpacing(16) + header_layout.setContentsMargins(36, 26, 36, 24) + header_layout.setSpacing(0) breadcrumb_container = QWidget() breadcrumb_container.setObjectName("breadcrumbContainer") @@ -115,7 +115,7 @@ class ProjectArea(QWidget): # 网格布局 self.projects_layout = QGridLayout(self.projects_container) - self.projects_layout.setContentsMargins(36, 18, 6, 18) + self.projects_layout.setContentsMargins(36, 24, 6, 18) self.projects_layout.setSpacing(24) self.projects_layout.setAlignment(Qt.AlignTop) # 设置顶部对齐 diff --git a/MetaCore/ui/project_card.py b/MetaCore/ui/project_card.py index f23700d..a292039 100644 --- a/MetaCore/ui/project_card.py +++ b/MetaCore/ui/project_card.py @@ -15,7 +15,10 @@ from PyQt5.QtGui import * from data.project_manager import ProjectManager, Project from ui.icon_manager import IconManager +from ui.styles import StyleSheet from ui.project_settings_page import ProjectSettingsPage +from ui.widget import UniversalMessageDialog + class ImageDisplayWidget(QWidget): """ 一个专门用于显示带圆角图片的控件。 @@ -310,17 +313,45 @@ class ProjectCard(QWidget): self.overlay_btn.setToolTip("确认删除项目") else: self.overlay_btn = QPushButton(self) - if IconManager.icon_exists('infomation'): - self.overlay_btn.setIcon(IconManager.get_icon('infomation', QSize(16, 16))) - self.overlay_btn.setIconSize(QSize(16, 16)) - else: - self.overlay_btn.setText("ⓘ") self.overlay_btn.setObjectName("overlayInfoBtn") + self.overlay_btn.setCursor(Qt.PointingHandCursor) + self.overlay_btn.setAttribute(Qt.WA_Hover, True) + self.overlay_btn.installEventFilter(self) + self._set_overlay_icon("infomation", fallback_text="i") self.overlay_btn.clicked.connect(self.show_context_menu) # self.overlay_btn.setToolTip("项目信息") # 设置按钮位置和大小 self.overlay_btn.setGeometry(248, 8, 20, 20) + + def _set_overlay_icon(self, icon_name: str, fallback_text: str = ""): + """Set overlay button icon with fallback text.""" + if not hasattr(self, "overlay_btn"): + return + + if IconManager.icon_exists(icon_name): + self.overlay_btn.setIcon(IconManager.get_icon(icon_name, QSize(16, 16))) + self.overlay_btn.setIconSize(QSize(16, 16)) + self.overlay_btn.setText("") + else: + self.overlay_btn.setIcon(QIcon()) + self.overlay_btn.setText(fallback_text) + + def eventFilter(self, watched, event): + """Handle hover/click states for the overlay button icon.""" + if hasattr(self, "overlay_btn") and watched is self.overlay_btn: + if event.type() == QEvent.Enter: + self._set_overlay_icon("infomation_hover", fallback_text="i") + elif event.type() == QEvent.Leave: + self._set_overlay_icon("infomation", fallback_text="i") + elif event.type() == QEvent.MouseButtonPress and event.button() == Qt.LeftButton: + self._set_overlay_icon("infomation_check", fallback_text="v") + elif event.type() == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton: + if self.overlay_btn.rect().contains(event.pos()): + self._set_overlay_icon("infomation_hover", fallback_text="i") + else: + self._set_overlay_icon("infomation", fallback_text="i") + return super().eventFilter(watched, event) def create_bottom_overlay(self): """创建底部信息覆盖层""" @@ -557,10 +588,13 @@ class ProjectCard(QWidget): def show_context_menu(self): """显示右键菜单""" menu = QMenu(self) + menu.setWindowFlags(menu.windowFlags() | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint) + menu.setAttribute(Qt.WA_TranslucentBackground, True) + menu.setStyleSheet(StyleSheet.get_context_menu_style()) # 刷新预览图 - if IconManager.icon_exists("Refresh"): - refresh_action = menu.addAction(IconManager.get_icon('Refresh'), "刷新预览图") + if IconManager.icon_exists("refresh_projectcard"): + refresh_action = menu.addAction(IconManager.get_icon('refresh_projectcard'), "刷新预览图") else: refresh_action = menu.addAction("🔄 刷新预览图") refresh_action.triggered.connect(self.refresh_preview_image) @@ -568,8 +602,8 @@ class ProjectCard(QWidget): menu.addSeparator() # 在资源管理器显示 - if IconManager.icon_exists('folder'): - show_in_explorer_action = menu.addAction(IconManager.get_icon('folder'), "在资源管理器显示") + if IconManager.icon_exists('open_projectcard'): + show_in_explorer_action = menu.addAction(IconManager.get_icon('open_projectcard'), "在资源管理器显示") else: show_in_explorer_action = menu.addAction("📁 在资源管理器显示") show_in_explorer_action.triggered.connect(self.show_in_explorer) @@ -577,8 +611,8 @@ class ProjectCard(QWidget): menu.addSeparator() # 删除项目 - if IconManager.icon_exists('delete'): - delete_action = menu.addAction(IconManager.get_icon('delete'), "移除项目") + if IconManager.icon_exists('remove_projectcard'): + delete_action = menu.addAction(IconManager.get_icon('remove_projectcard'), "移除项目") else: delete_action = menu.addAction("🗑️ 移除项目") delete_action.triggered.connect(self.delete_project) @@ -602,18 +636,25 @@ class ProjectCard(QWidget): # 使用项目管理器的验证方法进行全面检查 is_valid, error_message = self.project_manager.validate_project_open(project_path) if not is_valid: - QMessageBox.warning(self, "无法打开项目", f"项目无法打开: {error_message}") + # QMessageBox.warning(self, "无法打开项目", f"项目无法打开: {error_message}") + UniversalMessageDialog.show_warning(self, + "无法打开项目", f"项目无法打开: {error_message}", + False, "确定") return # 验证通过,显示成功信息 # QMessageBox.information(self, "打开项目", f"正在打开项目: {self.project.title}") # 使用question对话框提供明确的确认选项 - reply = QMessageBox.question(self, "打开项目", - f"确定要打开项目: {self.project.title}?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes) + # reply = QMessageBox.question(self, "打开项目", + # f"确定要打开项目: {self.project.title}?", + # QMessageBox.Yes | QMessageBox.No, + # QMessageBox.Yes) + reply = UniversalMessageDialog.show_info(self, + "打开项目", + f"确定要打开项目: {self.project.title}?", + True, "确定", "取消") - if reply == QMessageBox.Yes: + if reply == QDialog.Accepted: # 用户确认打开项目,继续执行打开逻辑 # TODO: 在这里添加实际的项目打开逻辑 print(f"正在打开项目路径: {project_path},正在启动应用程序: {self.project.title}") @@ -633,11 +674,15 @@ class ProjectCard(QWidget): ) if not success: - QMessageBox.warning( - self, - "启动失败", - "无法启动,请检查打开项目路径。" - ) + UniversalMessageDialog.show_warning(self, + "启动失败", + "无法启动,请检查打开项目路径。", + False, "确定") + # QMessageBox.warning( + # self, + # "启动失败", + # "无法启动,请检查打开项目路径。" + # ) # if success: # QMessageBox.information( # self, @@ -678,7 +723,11 @@ class ProjectCard(QWidget): # except Exception as e: # QMessageBox.critical(self, "错误", f"打开项目时发生错误:\n{str(e)}") except Exception as e: - QMessageBox.critical(self, "错误", f"启动PyCharm时发生错误:\n{str(e)}") + # QMessageBox.critical(self, "错误", f"启动PyCharm时发生错误:\n{str(e)}") + UniversalMessageDialog.show_error(self, + "错误", + f"启动PyCharm时发生错误:\n{str(e)}", + False, "确定") def on_pycharm_started(self): """PyCharm启动完成回调""" @@ -686,11 +735,15 @@ class ProjectCard(QWidget): def on_project_method_called(self, target_project_path): """项目方法调用完成回调""" - QMessageBox.information( - self, - "操作完成", - f"已成功在PyCharm中打开项目: {target_project_path}" - ) + # QMessageBox.information( + # self, + # "操作完成", + # f"已成功在PyCharm中打开项目: {target_project_path}" + # ) + UniversalMessageDialog.show_info(self, + "操作完成", + f"已成功在PyCharm中打开项目: {target_project_path}", + False, "确定") def show_in_explorer(self): """在资源管理器中显示项目目录""" @@ -699,7 +752,11 @@ class ProjectCard(QWidget): project_path_str = self.project.project_dir if self.project.project_dir else self.project.path if not project_path_str: - QMessageBox.warning(self, "路径不存在", "项目路径为空,请检查项目配置。") + # QMessageBox.warning(self, "路径不存在", "项目路径为空,请检查项目配置。") + UniversalMessageDialog.show_warning(self, + "路径不存在", + "项目路径为空,请检查项目配置。", + False, "确定") return # 使用pathlib处理路径 @@ -713,8 +770,12 @@ class ProjectCard(QWidget): # 检查项目路径是否存在 if not project_path.exists(): - QMessageBox.warning(self, "路径不存在", - f"项目路径不存在或无效:\n{project_path}\n\n请检查项目是否已被移动或删除。") + # QMessageBox.warning(self, "路径不存在", + # f"项目路径不存在或无效:\n{project_path}\n\n请检查项目是否已被移动或删除。") + UniversalMessageDialog.show_warning(self, + "路径不存在", + f"项目路径不存在或无效:\n{project_path}\n\n请检查项目是否已被移动或删除。", + False, "确定") return # 获取操作系统类型 @@ -725,7 +786,12 @@ class ProjectCard(QWidget): # Windows使用os.startfile,会自动使用系统默认的文件管理器 os.startfile(str(project_path)) except OSError as e: - QMessageBox.critical(self, "打开失败", f"无法打开资源管理器:\n{str(e)}") + # QMessageBox.critical(self, "打开失败", f"无法打开资源管理器:\n{str(e)}") + UniversalMessageDialog.show_error(self, + "打开失败", + f"无法打开资源管理器:\n{str(e)}", + False, "确定") + return elif system == "darwin": # macOS if project_path.is_file(): @@ -752,39 +818,61 @@ class ProjectCard(QWidget): # 最后尝试使用xdg-open subprocess.run(['xdg-open', str(project_path)], check=True) except (FileNotFoundError, subprocess.CalledProcessError): - QMessageBox.warning(self, "无法打开文件管理器", - "系统中没有找到合适的文件管理器。\n" - f"请手动打开路径: {project_path}") + # QMessageBox.warning(self, "无法打开文件管理器", + # "系统中没有找到合适的文件管理器。\n" + # f"请手动打开路径: {project_path}") + UniversalMessageDialog.show_warning(self, + "无法打开文件管理器", + "系统中没有找到合适的文件管理器。\n" + f"请手动打开路径: {project_path}", + False, "确定") print(f"成功在资源管理器中打开: {project_path}") except subprocess.CalledProcessError as e: - QMessageBox.critical(self, "打开失败", - f"无法打开资源管理器:\n{str(e)}") + # QMessageBox.critical(self, "打开失败", + # f"无法打开资源管理器:\n{str(e)}") + UniversalMessageDialog.show_error(self, + "打开失败", + f"无法打开资源管理器:\n{str(e)}", + False, "确定") except Exception as e: - QMessageBox.critical(self, "错误", - f"打开资源管理器时发生错误:\n{str(e)}") + # QMessageBox.critical(self, "错误", + # f"打开资源管理器时发生错误:\n{str(e)}") + UniversalMessageDialog.show_error(self, + "错误", + f"打开资源管理器时发生错误:\n{str(e)}", + False, "确定") def delete_project(self): """删除项目""" - reply = QMessageBox.question(self, "确认移除", - f"确定要移除项目 \"{self.project.title}\" 吗?\n此操作不可撤销。", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No) + # reply = QMessageBox.question(self, "确认移除", + # f"确定要移除项目 \"{self.project.title}\" 吗?\n此操作不可撤销。", + # QMessageBox.Yes | QMessageBox.No, + # QMessageBox.No) + reply = UniversalMessageDialog.show_info(self, + "确认移除", + f"确定要移除项目 \"{self.project.title}\" 吗?\n此操作不可撤销。", + True, "确定", "取消") - if reply == QMessageBox.Yes: + if reply == QDialog.Accepted: self.project_manager.remove_project(self.project.id) def confirm_delete_project(self): """确认删除待删除状态的项目""" - reply = QMessageBox.question(self, "确认删除项目", - f"确定要永久删除项目 \"{self.project.title}\" 吗?\n" - f"此操作不可撤销。\n\n" - f"提示:如果项目目录已恢复,您可以点击项目卡片来恢复项目。", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No) + # reply = QMessageBox.question(self, "确认删除项目", + # f"确定要永久删除项目 \"{self.project.title}\" 吗?\n" + # f"此操作不可撤销。\n\n" + # f"提示:如果项目目录已恢复,您可以点击项目卡片来恢复项目。", + # QMessageBox.Yes | QMessageBox.No, + # QMessageBox.No) + reply = UniversalMessageDialog.show_info(self, + "确认删除项目", + f"确定要永久删除项目 \"{self.project.title}\" 吗?\n" + f"此操作不可撤销。\n\n", + True, "确定", "取消") - if reply == QMessageBox.Yes: + if reply == QDialog.Accepted: self.project_manager.confirm_delete_project(self.project.id) def update_display(self): @@ -852,12 +940,20 @@ class ProjectCard(QWidget): def try_restore_project(self): """尝试恢复待删除状态的项目""" if self.project_manager.restore_project(self.project.id): - QMessageBox.information(self, "项目已恢复", - f"项目 \"{self.project.title}\" 已成功恢复!") + # QMessageBox.information(self, "项目已恢复", + # f"项目 \"{self.project.title}\" 已成功恢复!") + UniversalMessageDialog.show_info(self, + "项目已恢复", + f"项目 \"{self.project.title}\" 已成功恢复!", + False, "确定") else: - QMessageBox.information(self, "项目目录不存在", - f"项目 \"{self.project.title}\" 的目录仍然不存在:\n{self.project.project_dir}\n\n" - f"提示:当您恢复项目目录后,系统会自动检测并恢复项目状态,无需手动操作。") + # QMessageBox.information(self, "项目目录不存在", + # f"项目 \"{self.project.title}\" 的目录仍然不存在:\n{self.project.project_dir}\n\n" + # f"提示:当您恢复项目目录后,系统会自动检测并恢复项目状态,无需手动操作。") + UniversalMessageDialog.show_info(self, + "项目目录不存在", + f"项目 \"{self.project.title}\" 的目录仍然不存在:\n{self.project.project_dir}\n\n" + f"提示:当您恢复项目目录后,系统会自动检测并恢复项目状态,无需手动操作。") def enterEvent(self, event): """鼠标进入事件""" @@ -914,7 +1010,11 @@ class ProjectCard(QWidget): # 获取项目路径 project_path = self.project.project_dir if self.project.project_dir else self.project.path if not project_path or not Path(project_path).exists(): - QMessageBox.warning(self, "路径不存在", "项目路径不存在,无法生成预览图。") + # QMessageBox.warning(self, "路径不存在", "项目路径不存在,无法生成预览图。") + UniversalMessageDialog.show_warning(self, + "路径不存在", + "项目路径不存在,无法生成预览图。", + False, "确定") return # 显示进度提示 @@ -958,11 +1058,18 @@ class ProjectCard(QWidget): # 刷新显示以显示默认图标 self.refresh_image_display() - QMessageBox.warning(self, "生成失败", - "无法生成预览图,已使用默认图标。\n\n可能原因:\n" - "• 项目目录中没有图片文件\n" - "• 图片文件格式不支持\n" - "• 权限不足") + # QMessageBox.warning(self, "生成失败", + # "无法生成预览图,已使用默认图标。\n\n可能原因:\n" + # "• 项目目录中没有图片文件\n" + # "• 图片文件格式不支持\n" + # "• 权限不足") + UniversalMessageDialog.show_warning(self, + "生成失败", + "无法生成预览图,已使用默认图标。\n\n可能原因: \n" + "• 项目目录中没有图片文件\n" + "• 图片文件格式不支持\n" + "• 权限不足", + False, "确定") except Exception as e: # 发生异常时也使用project_empty_icon作为默认图片 @@ -972,7 +1079,11 @@ class ProjectCard(QWidget): # 刷新显示以显示默认图标 self.refresh_image_display() - QMessageBox.critical(self, "错误", f"刷新预览图时发生错误,已使用默认图标:\n{str(e)}") + # QMessageBox.critical(self, "错误", f"刷新预览图时发生错误,已使用默认图标:\n{str(e)}") + UniversalMessageDialog.show_error(self, + "错误", + f"刷新预览图时发生错误,已使用默认图标:\n{str(e)}", + False, "确定") finally: # 恢复按钮状态 if target_btn: diff --git a/MetaCore/ui/project_settings_page.py b/MetaCore/ui/project_settings_page.py index dfffff0..203a565 100644 --- a/MetaCore/ui/project_settings_page.py +++ b/MetaCore/ui/project_settings_page.py @@ -34,131 +34,209 @@ class ProjectSettingsPage(QWidget): def init_ui(self): """初始化用户界面""" - layout = QVBoxLayout(self) - layout.setContentsMargins(30, 30, 30, 30) - layout.setSpacing(20) - - # 页面标题 - self.create_page_header(layout) - - # 默认项目位置设置 - self.create_default_location_section(layout) + self.setObjectName("settingsPage") + self.setAttribute(Qt.WA_StyledBackground, True) - self.create_open_location_section(layout) - - # 项目创建设置 - # self.create_project_creation_section(layout) - - # 按钮区域 - self.create_button_section(layout) - - # 添加弹性空间 - layout.addStretch() - - def create_page_header(self, layout): - """创建页面标题""" + self.main_layout = QVBoxLayout(self) + self.main_layout.setContentsMargins(0, 30, 24, 30) + self.main_layout.setSpacing(0) + + self.content_container = QFrame() + self.content_container.setObjectName("settingsContentArea") + self.content_container.setFrameShape(QFrame.NoFrame) + self.content_container.setAttribute(Qt.WA_StyledBackground, True) + self.main_layout.addWidget(self.content_container) + + self.content_layout = QVBoxLayout(self.content_container) + self.content_layout.setContentsMargins(0, 0, 0, 0) + self.content_layout.setSpacing(0) + + header_widget = self.create_page_header() + self.content_layout.addWidget(header_widget) + + body_widget = self.create_settings_body() + self.content_layout.addWidget(body_widget) + self.content_layout.addStretch() + + def create_page_header(self): + """创建面包屑与标题区域""" header_widget = QWidget() + header_widget.setObjectName("contentHeader") header_layout = QVBoxLayout(header_widget) - header_layout.setContentsMargins(0, 0, 0, 0) - header_layout.setSpacing(8) + header_layout.setContentsMargins(36, 32, 36, 24) + header_layout.setSpacing(0) + + breadcrumb_widget = QWidget() + breadcrumb_widget.setObjectName("breadcrumbContainer") + breadcrumb_layout = QHBoxLayout(breadcrumb_widget) + breadcrumb_layout.setContentsMargins(0, 0, 0, 0) + breadcrumb_layout.setSpacing(8) + + breadcrumb_base = QLabel("设置中心") + breadcrumb_base.setObjectName("breadcrumbBase") + breadcrumb_layout.addWidget(breadcrumb_base) + + breadcrumb_separator = QLabel("/") + breadcrumb_separator.setObjectName("breadcrumbSeparator") + breadcrumb_layout.addWidget(breadcrumb_separator) + + breadcrumb_current = QLabel("项目设置") + breadcrumb_current.setObjectName("breadcrumbCurrent") + breadcrumb_layout.addWidget(breadcrumb_current) + + breadcrumb_layout.addStretch() + header_layout.addWidget(breadcrumb_widget) + + # title_label = QLabel("项目设置") + # title_label.setObjectName("settingsTitle") + # header_layout.addWidget(title_label) + + # subtitle_label = QLabel("配置项目创建和管理的相关设置。") + # subtitle_label.setObjectName("settingsSubtitle") + # subtitle_label.setWordWrap(True) + # header_layout.addWidget(subtitle_label) + + return header_widget + + def create_settings_body(self): + """创建设置内容区域,结构与项目区域组件保持一致""" + scroll_area = QScrollArea() + scroll_area.setObjectName("projectScrollArea") + scroll_area.setFrameShape(QFrame.NoFrame) + scroll_area.setWidgetResizable(True) + scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + + body_container = QWidget() + body_container.setObjectName("settingsBody") + body_layout = QVBoxLayout(body_container) + # body_layout.setContentsMargins(36, 24, 6, 18) + body_layout.setSpacing(24) + + settings_card = self.create_settings_card() + body_layout.addWidget(settings_card) + body_layout.addStretch() + + scroll_area.setWidget(body_container) - # 主标题 - title_label = QLabel("项目设置") - title_label.setObjectName("pageTitle") - header_layout.addWidget(title_label) - - # 副标题 - subtitle_label = QLabel("配置项目创建和管理的相关设置") - subtitle_label.setObjectName("pageSubtitle") - header_layout.addWidget(subtitle_label) - - layout.addWidget(header_widget) - - def create_default_location_section(self, layout): - """创建默认项目位置设置区域""" - # 分组框 - group_box = QGroupBox("默认项目位置") - group_box.setObjectName("settingsGroup") - group_layout = QVBoxLayout(group_box) - group_layout.setSpacing(15) - - # 说明文字 - desc_label = QLabel("设置新项目的默认创建位置。") - desc_label.setObjectName("settingsDescription") + return scroll_area + + def create_settings_card(self): + """创建主体设置卡片内容""" + card = QFrame() + card.setObjectName("settingsCard") + card.setFrameShape(QFrame.NoFrame) + card.setAttribute(Qt.WA_StyledBackground, True) + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(0, 0, 0, 0) + card_layout.setSpacing(0) + + sections = [ + { + "title": "默认项目位置", + "description": "设置新项目的默认创建位置。", + "placeholder": "选择默认项目创建位置...", + "browse": self.browse_default_location, + "reset": self.reset_default_location, + "attr": "path_input", + }, + { + "title": "打开设置", + "description": "设置新项目的默认打开位置。", + "placeholder": "选择默认项目打开位置...", + "browse": self.browse_open_location, + "reset": self.reset_open_location, + "attr": "open_path_input", + }, + ] + + total = len(sections) + for index, section_info in enumerate(sections): + is_first = index == 0 + is_last = index == total - 1 + section_widget = self.create_path_section( + title=section_info["title"], + description=section_info["description"], + placeholder=section_info["placeholder"], + browse_handler=section_info["browse"], + reset_handler=section_info["reset"], + input_attr=section_info["attr"], + is_first=is_first, + is_last=is_last, + ) + card_layout.addWidget(section_widget) + + return card + + def create_path_section( + self, + title, + description, + placeholder, + browse_handler, + reset_handler, + input_attr, + is_first=False, + is_last=False, + ): + """创建路径类型设置行""" + section = QFrame() + section.setObjectName("settingsRow") + section.setFrameShape(QFrame.NoFrame) + section.setAttribute(Qt.WA_StyledBackground, True) + section.setProperty("isFirstRow", "true" if is_first else "false") + section.setProperty("isLastRow", "true" if is_last else "false") + section.style().unpolish(section) + section.style().polish(section) + + section_layout = QVBoxLayout(section) + bottom_margin = 24 if is_last else 0 + section_layout.setContentsMargins(32, 24, 32, bottom_margin) + section_layout.setSpacing(0) + + title_label = QLabel(title) + title_label.setObjectName("settingsSectionTitle") + section_layout.addWidget(title_label) + section_layout.addSpacing(8) + + desc_label = QLabel(description) + desc_label.setObjectName("settingsSectionDesc") desc_label.setWordWrap(True) - group_layout.addWidget(desc_label) - - # 当前路径显示和选择 - path_widget = QWidget() - path_layout = QHBoxLayout(path_widget) - path_layout.setContentsMargins(0, 0, 0, 0) - path_layout.setSpacing(10) - - # 路径输入框 - self.path_input = QLineEdit() - self.path_input.setObjectName("pathInput") - self.path_input.setPlaceholderText("选择默认项目创建位置...") - self.path_input.setReadOnly(True) # 只读,通过按钮选择 - path_layout.addWidget(self.path_input) - - # 浏览按钮 - self.browse_btn = QPushButton("浏览...") - self.browse_btn.setObjectName("browseBtn") - self.browse_btn.clicked.connect(self.browse_default_location) - path_layout.addWidget(self.browse_btn) - - # 重置按钮 - self.reset_btn = QPushButton("重置") - self.reset_btn.setObjectName("resetBtn") - self.reset_btn.clicked.connect(self.reset_default_location) - path_layout.addWidget(self.reset_btn) - - group_layout.addWidget(path_widget) - - layout.addWidget(group_box) + section_layout.addWidget(desc_label) + section_layout.addSpacing(15) - def create_open_location_section(self, layout): - """创建打开位置设置区域""" - # 分组框 - group_box = QGroupBox("打开位置") - group_box.setObjectName("settingsGroup") - group_layout = QVBoxLayout(group_box) - group_layout.setSpacing(15) + field_row = QWidget() + field_row.setObjectName("settingsFieldRow") + field_layout = QHBoxLayout(field_row) + field_layout.setContentsMargins(19, 0, 0, 0) + field_layout.setSpacing(12) - # 说明文字 - desc_label = QLabel("设置项目文件的默认打开位置。") - desc_label.setObjectName("settingsDescription") - desc_label.setWordWrap(True) - group_layout.addWidget(desc_label) + path_input = QLineEdit() + path_input.setObjectName("settingsPathInput") + path_input.setPlaceholderText(placeholder) + path_input.setReadOnly(True) + path_input.setFixedHeight(48) + # path_input.setMinimumWidth(1099) + field_layout.addWidget(path_input, stretch=1) - # 当前路径显示和选择 - path_widget = QWidget() - path_layout = QHBoxLayout(path_widget) - path_layout.setContentsMargins(0, 0, 0, 0) - path_layout.setSpacing(10) + browse_btn = QPushButton("浏览...") + browse_btn.setObjectName("settingsPrimaryBtn") + browse_btn.setCursor(Qt.PointingHandCursor) + browse_btn.setFixedSize(175, 48) + browse_btn.clicked.connect(browse_handler) + field_layout.addWidget(browse_btn) - # 路径输入框 - self.open_path_input = QLineEdit() - self.open_path_input.setObjectName("pathInput") - self.open_path_input.setPlaceholderText("选择默认项目打开位置...") - self.open_path_input.setReadOnly(True) # 只读,通过按钮选择 - path_layout.addWidget(self.open_path_input) + reset_btn = QPushButton("重置") + reset_btn.setObjectName("settingsGhostBtn") + reset_btn.setCursor(Qt.PointingHandCursor) + reset_btn.setFixedSize(175, 48) + reset_btn.clicked.connect(reset_handler) + field_layout.addWidget(reset_btn) - # 浏览按钮 - self.open_browse_btn = QPushButton("浏览...") - self.open_browse_btn.setObjectName("browseBtn") - self.open_browse_btn.clicked.connect(self.browse_open_location) - path_layout.addWidget(self.open_browse_btn) + section_layout.addWidget(field_row) - # 重置按钮 - self.open_reset_btn = QPushButton("重置") - self.open_reset_btn.setObjectName("resetBtn") - self.open_reset_btn.clicked.connect(self.reset_open_location) - path_layout.addWidget(self.open_reset_btn) - - group_layout.addWidget(path_widget) - - layout.addWidget(group_box) + setattr(self, input_attr, path_input) + return section def browse_open_location(self): """浏览选择打开位置""" @@ -179,6 +257,7 @@ class ProjectSettingsPage(QWidget): """重置为默认打开位置""" default_path = self.get_default_projects_path() self.open_path_input.setText(default_path) + self.save_settings_immediately("default_open_location", self.open_path_input.text()) def create_project_creation_section(self, layout): """创建项目创建设置区域""" @@ -238,6 +317,7 @@ class ProjectSettingsPage(QWidget): """重置为默认位置""" default_path = self.get_default_projects_path() self.path_input.setText(default_path) + self.save_settings_immediately("default_project_location", self.path_input.text()) def load_settings(self): """加载保存的设置""" diff --git a/MetaCore/ui/sidebar.py b/MetaCore/ui/sidebar.py index 719dde8..51fc3f3 100644 --- a/MetaCore/ui/sidebar.py +++ b/MetaCore/ui/sidebar.py @@ -140,19 +140,19 @@ class Sidebar(QWidget): # 我的项目分组 self.create_nav_section(nav_layout, "我的项目", [ ("📊", "项目概述", "overview"), - ("📋", "项目管理", "management"), + # ("📋", "项目管理", "management"), ]) # 资源管理分组 - self.create_nav_section(nav_layout, "资源管理", [ - ("🏷", "资源分类", "resource_category"), - ("📦", "资源管理", "resource_management"), - ]) + # self.create_nav_section(nav_layout, "资源管理", [ + # ("🏷", "资源分类", "resource_category"), + # ("📦", "资源管理", "resource_management"), + # ]) # 设置中心分组 self.create_nav_section(nav_layout, "设置中心", [ ("📁", "项目设置", "project_settings"), - ("⚙", "系统设置", "system_settings"), + # ("⚙", "系统设置", "system_settings"), ]) nav_layout.addStretch() @@ -184,14 +184,14 @@ class Sidebar(QWidget): items_widget = QWidget() items_widget.setObjectName("navItems") items_layout = QVBoxLayout(items_widget) - items_layout.setContentsMargins(0, 10, 0, 0) + items_layout.setContentsMargins(50, 10, 0, 0) items_layout.setSpacing(10) # 创建导航项 for icon, text, filter_type in items: nav_item = self.create_nav_item(icon, text, filter_type) - items_layout.addWidget(nav_item) - + items_layout.addWidget(nav_item, alignment=Qt.AlignLeft) + section_layout.addWidget(items_widget) # 展开/收起功能 @@ -229,6 +229,8 @@ class Sidebar(QWidget): item_btn.setObjectName("navItem") item_btn.setCheckable(True) + item_btn.setFixedSize(QSize(186, 30)) + item_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) # 设置默认选中 if filter_type == "overview": diff --git a/MetaCore/ui/styles.py b/MetaCore/ui/styles.py index 6baa996..2807800 100644 --- a/MetaCore/ui/styles.py +++ b/MetaCore/ui/styles.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ MetaCore 应用程序样式表定义 @@ -87,7 +87,7 @@ class StyleSheet: #logoText { background-color: transparent; color: #FFFFFF; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 24px; font-weight: 500; /* Medium weight in Qt */ letter-spacing: 0.4px; @@ -103,7 +103,7 @@ class StyleSheet: border: 1px solid #979fad; border-radius: 6px; color: #ffffff; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-weight: 300; font-size: 16px; letter-spacing: 0.8px; @@ -121,7 +121,7 @@ class StyleSheet: QPushButton#createBtn:pressed, QPushButton#sidebarImportBtn:pressed { - background-color: rgba(84, 89, 98, 0.4); + background-color: #2B56C5; border-color: #b0b6c3; } @@ -142,7 +142,7 @@ class StyleSheet: border: none; background: #000000; color: #ffffff; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 16px; font-weight: 500; letter-spacing: 0.8px; @@ -159,26 +159,29 @@ class StyleSheet: QPushButton#navItem { border: none; border-left: 3px solid transparent; - border-radius: 0px; + border-radius: 4px; background: #000000; color: #ebebeb; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 14px; font-weight: 300; letter-spacing: 0.7px; - padding: 0px 46px 0px 67px; + padding: 0px; + padding-left: 20px; text-align: left; - min-height: 26px; - max-height: 26px; + min-width: 186px; + max-width: 186px; + min-height: 30px; + max-height: 30px; } QPushButton#navItem:hover { - background-color: #000000; + background-color: rgba(84, 89, 98, 0.3); color: #ffffff; } QPushButton#navItem:checked { - background-color: #000000; + background-color: rgba(84, 89, 98, 0.5); color: #ffffff; border-left: 3px solid transparent; font-weight: 300; @@ -196,6 +199,7 @@ class StyleSheet: #contentHeader { background: transparent; border-bottom: 1px solid #1f2532; + margin: 0 36px; /* 上下为0,左右为36px */ } #breadcrumbContainer { @@ -205,7 +209,7 @@ class StyleSheet: #breadcrumbBase { background: transparent; color: rgba(255, 255, 255, 0.7); - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 20px; font-weight: 400; letter-spacing: 1px; @@ -215,7 +219,7 @@ class StyleSheet: background: transparent; color: rgba(255, 255, 255, 0.7); padding: 0 8px; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 20px; font-weight: 400; letter-spacing: 1px; @@ -224,7 +228,7 @@ class StyleSheet: #breadcrumbCurrent { background: transparent; color: #ffffff; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 24px; font-weight: 500; letter-spacing: 1.2px; @@ -235,7 +239,7 @@ class StyleSheet: border: 1px solid rgba(92, 97, 105, 0.8); border-radius: 4px; color: #5c6169; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 18px; font-weight: 300; letter-spacing: 0.9px; @@ -251,7 +255,7 @@ class StyleSheet: QLineEdit#searchInput::placeholder { color: #5c6169; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 18px; font-weight: 300; letter-spacing: 0.9px; @@ -287,7 +291,7 @@ class StyleSheet: #projectTitle { color: #d4d4d4; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 17.3px; font-weight: 500; letter-spacing: 0.865px; @@ -372,7 +376,7 @@ class StyleSheet: #projectDate { color: #d4d4d4; - font-family: "Inter"; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 12.36px; font-weight: 300; letter-spacing: 0.618px; @@ -400,6 +404,114 @@ class StyleSheet: border-radius: 0px; } + /* ---------- Project Settings Page ---------- */ + #settingsPage { + background-color: #000000; + } + + #settingsBody, + #settingsSection, + #settingsFieldRow { + background-color: transparent; + } + + #settingsTitle { + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 28px; + font-weight: 600; + letter-spacing: 1.1px; + } + + #settingsSubtitle { + color: rgba(236, 239, 246, 0.72); + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 15px; + font-weight: 300; + letter-spacing: 0.5px; + } + + #settingsDivider { + background-color: #1f2532; + border: none; + min-height: 1px; + max-height: 1px; + margin: 0 36px; + } + + #settingsCard { + background-color: transparent; + border: none; + } + #settingsRow { + background-color: transparent; + } + + #settingsSectionTitle { + background-color: transparent; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 20px; + font-weight: 500; + letter-spacing: 0.5px; + } + + #settingsSectionDesc { + color: rgba(255, 255, 255, 0.6); + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: 0.3px; + } + + QWidget#settingsFieldRow { + background-color: transparent; + } + + QLineEdit#settingsPathInput { + background-color: rgba(89, 100, 113, 0.2); + border: 1px solid rgba(89, 100, 113, 0.2); + border-radius: 4px; + color: #e6e9f2; + padding: 0 18px; + min-height: 48px; + } + + QLineEdit#settingsPathInput:focus { + border-color: rgba(110, 118, 134, 0.95); + background-color: #1f2430; + } + + QLineEdit#settingsPathInput::placeholder { + color: rgba(255, 255, 255, 0.7); + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 16px; + } + + QPushButton#settingsPrimaryBtn, + QPushButton#settingsGhostBtn { + background-color: rgba(76, 92, 110, 0.6); + border: none; + border-radius: 4px; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 16px; + font-weight: 500; + letter-spacing: 0.4px; + min-height: 48px; + min-width: 175px; + } + + QPushButton#settingsPrimaryBtn:hover, + QPushButton#settingsGhostBtn:hover { + background-color: rgba(76, 92, 110, 0.8); + } + + QPushButton#settingsPrimaryBtn:pressed, + QPushButton#settingsGhostBtn:pressed { + background-color: rgba(45, 94, 221, 0.4); + } + /* ---------- Scrollbars ---------- */ QScrollBar:vertical { background: transparent; @@ -449,6 +561,807 @@ class StyleSheet: border: 1px solid #2a303d; padding: 6px 8px; } + + /* ---------- Create Project Dialog ---------- */ + QDialog#CreateProjectDialog { + background-color: #0c0c0d; + border-radius: 5px; + } + + #createProjectContent{ + background-color: #0c0c0d; + } + + #templateSection { + background-color: #0c0c0d; + } + + QDialog#CreateProjectDialog QLabel#templateHeaderLabel { + background-color: rgba(66, 67, 71, 0.2); + min-width: 724px; + max-width: 724px; + min-height: 33px; + max-height: 33px; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 600; + letter-spacing: 0.6px; + padding: 0 20px; + } + + QDialog#CreateProjectDialog QLabel#sectionTitle{ + color: #ffffff; + border: none; + background-color: transparent; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + } + QDialog#CreateProjectDialog QLabel#templateDescription { + border: none; + background-color: transparent; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 10px; + } + + QScrollArea#templateScrollArea { + background-color: transparent; + border: 0px solid rgba(66, 67, 71, 0.35); + } + + #projectInfoSection { + background-color: #19191B; + } + + #projectInfoHeader{ + background-color: #19191B; + } + + #templatesContainer { + background-color: rgba(66, 67, 71, 0.2); + } + + QPushButton#templateItem { + background-color: rgba(66, 67, 71, 0.4); + border: 1px solid transparent; + border-radius: 5px; + color: #ffffff; + } + + QPushButton#templateItem:hover { + background-color: rgba(66, 67, 71, 0.6); + border-color: rgba(43, 86, 197, 0.3); + } + + QPushButton#templateItem:checked { + background-color: rgba(43, 86, 197, 0.2); /* #2B56C5 20%透明度 */ + border: 1px solid #2b56c5; + } + + #templateIcon { + color: #ffffff; + font-size: 32px; + background: transparent; + } + + #templateName { + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 12px; + font-weight: 400; + letter-spacing: 0.6px; + background: transparent; + } + + #templateDescription { + color: rgba(255, 255, 255, 0.7); + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 10px; + font-weight: 300; + letter-spacing: 0.5px; + line-height: 1.2; + background: transparent; + } + + #formLabel { + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + background: transparent; + margin-bottom: 5px; + } + + QLineEdit#formInput { + background-color: rgba(89, 100, 113, 0.2); + border: none; + border-radius: 4px; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.6px; + padding: 0px 10px; + } + + QLineEdit#formInput:focus { + border-color: rgba(76, 92, 110, 1.0); + background-color: rgba(89, 100, 113, 0.3); + } + + QLineEdit#formInput::placeholder { + color: rgba(235, 235, 235, 0.7); + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.6px; + } + + QLineEdit#formInput[error="true"] { + border-color: #ff6b6b; + background-color: rgba(255, 107, 107, 0.1); + } + + QLineEdit#formInput[valid="true"] { + border-color: #4ade80; + background-color: rgba(74, 222, 128, 0.1); + } + + QTextEdit#formTextArea { + background-color: rgba(89, 100, 113, 0.2); + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 4px; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.6px; + padding: 8px 10px; + } + + QTextEdit#formTextArea:focus { + border-color: rgba(76, 92, 110, 1.0); + background-color: rgba(89, 100, 113, 0.3); + } + + QPushButton#browseBtn { + background-color: rgba(89, 100, 113, 0.2); + border: 1px solid rgba(76, 92, 110, 0.6); + padding: 0px 41px; + border-radius: 6px; + color: #ffffff; + font-size: 16px; + } + + QPushButton#browseBtn:hover { + background-color: rgba(89, 100, 113, 0.4); + border-color: rgba(76, 92, 110, 0.8); + } + + QLabel#errorLabel { + color: #ff6b6b; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 11px; + font-weight: 300; + background: transparent; + } + + /* Custom Bottom Bar Styles */ + #buttonArea { + background-color: transparent; + } + + QPushButton#secondaryBtn { + background-color: rgba(84, 89, 98, 0.4); + border: 1px solid #545962; + border-radius: 2px; + color: #ebebeb; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + min-width: 95px; + min-height: 36px; + max-width: 95px; + max-height: 36px; + } + + QPushButton#secondaryBtn:hover { + background-color: rgba(84, 89, 98, 0.6); + border-color: #6a7080; + } + + QPushButton#secondaryBtn:pressed { + background-color: rgba(84, 89, 98, 0.8); + } + + QPushButton#primaryBtn { + background-color: #2b57c5; + border: none; + border-radius: 2px; + color: #ebebeb; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + min-width: 95px; + min-height: 36px; + max-width: 95px; + max-height: 36px; + } + + QPushButton#primaryBtn:hover { + background-color: #3d67d5; + } + + QPushButton#primaryBtn:pressed { + background-color: #1e47a5; + } + + QPushButton#primaryBtn:disabled { + background-color: rgba(43, 87, 197, 0.4); + color: rgba(235, 235, 235, 0.5); + } + + /* ---------- Import Project Dialog ---------- */ + + #importGroup { + background-color: #19191b; + border: 1px solid rgba(66, 67, 71, 0.3); + border-radius: 4px; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + padding: 10px; + } + + #importGroup::title { + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + background-color: rgba(66, 67, 71, 0.2); + padding: 8px 15px; + margin: 0px; + border-bottom: 1px solid rgba(66, 67, 71, 0.3); + } + + #uploadArea { + background-color: rgba(89, 100, 113, 0.1); + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 4px; + min-height: 288px; + } + + #uploadArea[dragOver="true"] { + background-color: rgba(43, 86, 197, 0.1); + border-color: #2b56c5; + } + + QPushButton#uploadBtn { + background-color: #2b57c5; + border: none; + border-radius: 6px; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 16px; + font-weight: 400; + letter-spacing: 0.8px; + min-width: 151px; + min-height: 42px; + max-width: 151px; + max-height: 42px; + } + + QPushButton#uploadBtn:hover { + background-color: #3d67d5; + } + + QPushButton#uploadBtn:pressed { + background-color: #1e47a5; + } + + QPushButton#selectBtn { + background-color: rgba(84, 89, 98, 0.4); + border: 1px solid rgba(84, 89, 98, 0.6); + border-radius: 4px; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 400; + letter-spacing: 0.7px; + padding: 8px 16px; + } + + QPushButton#selectBtn:hover { + background-color: rgba(84, 89, 98, 0.6); + border-color: rgba(84, 89, 98, 0.8); + } + + #fileList { + background-color: rgba(89, 100, 113, 0.2); + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 4px; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.6px; + padding: 6px; + } + + #fileList::item { + background-color: rgba(84, 89, 98, 0.2); + border-radius: 4px; + color: rgba(255, 255, 255, 0.9); + padding: 6px 8px; + margin: 1px 0px; + min-height: 26px; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.6px; + } + + #fileList::item:hover { + background-color: rgba(84, 89, 98, 0.3); + } + + #fileList::item:selected { + background-color: rgba(43, 86, 197, 0.3); + border: 1px solid #2b56c5; + } + + #fileList::item:selected:hover { + background-color: rgba(43, 86, 197, 0.4); + } + + #optionsGroup { + background-color: transparent; + } + + QDialog#ImportProjectDialog QCheckBox { + background: transparent; + background-color: transparent; + background: transparent; + background-color: transparent; + background: transparent; + background-color: transparent; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.6px; + spacing: 8px; + } + + QDialog#ImportProjectDialog QCheckBox::indicator { + width: 18px; + height: 18px; + border-radius: 2px; + } + + QDialog#ImportProjectDialog QCheckBox::indicator:unchecked { + background-color: rgba(217, 217, 217, 0.1); + border: 1px solid rgba(77, 116, 189, 0.4); + } + + QDialog#ImportProjectDialog QCheckBox::indicator:unchecked:hover { + background-color: rgba(217, 217, 217, 0.15); + border: 1px solid rgba(77, 116, 189, 0.6); + } + + QDialog#ImportProjectDialog QCheckBox::indicator:checked { + background-color: rgba(40, 126, 255, 0.6); + border: 1px solid #289eff; + } + + QDialog#ImportProjectDialog QCheckBox::indicator:checked:hover { + background-color: rgba(40, 126, 255, 0.8); + } + + /* Specific styling for the checked checkbox with checkmark */ + QDialog#ImportProjectDialog QCheckBox::indicator:checked { + background-color: rgba(40, 126, 255, 0.6); + border: 1px solid #289eff; + image: none; + } + + /* Import dialog specific button styles */ + QDialog#ImportProjectDialog QPushButton#primaryBtn:disabled { + background-color: rgba(89, 100, 113, 0.2); + color: rgba(235, 235, 235, 0.5); + border: none; + } + + QDialog#ImportProjectDialog QPushButton#secondaryBtn:hover { + background-color: rgba(84, 89, 98, 0.6); + border-color: #6a7080; + } + + QDialog#ImportProjectDialog QPushButton#secondaryBtn:pressed { + background-color: rgba(84, 89, 98, 0.8); + } + + QDialog#ImportProjectDialog QPushButton#primaryBtn:hover { + background-color: #3d67d5; + } + + QDialog#ImportProjectDialog QPushButton#primaryBtn:pressed { + background-color: #1e47a5; + } + + /* Enhanced upload area feedback */ + #uploadArea { + background-color: rgba(89, 100, 113, 0.1); + border: 1px solid rgba(76, 92, 110, 0.6); + border-radius: 4px; + min-height: 288px; + transition: all 0.2s ease; + } + + #uploadArea[dragOver="true"] { + background-color: rgba(43, 86, 197, 0.15); + border: 2px solid #2b56c5; + border-style: dashed; + } + + + /* ---------- Import Project Dialog Overrides ---------- */ + QDialog#ImportProjectDialog { + background-color: #0c0c0d; + border-radius: 5px; + } + + QDialog#ImportProjectDialog QWidget#importContentArea { + background-color: #0c0c0d; + border-radius: 5px; + } + + + QDialog#CreateProjectDialog #customTitleBar { + background-color: #0c0c0d; + border-bottom: none; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + min-height: 54px; + } + + QDialog#CreateProjectDialog #customTitleLabel { + color: #f4f5f8; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 16px; + font-weight: 600; + letter-spacing: 0.6px; + background: transparent; + } + + QDialog#CreateProjectDialog #customCloseBtn { + background-color: transparent; + border-radius: 4px; + color: #f4f5f8; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + min-width: 24px; + min-height: 24px; + } + + QDialog#CreateProjectDialog #customCloseBtn:hover { + background-color: rgba(235, 235, 235, 0.08); + border-color: rgba(235, 235, 235, 0.7); + } + + QDialog#CreateProjectDialog #customCloseBtn:pressed { + background-color: rgba(235, 235, 235, 0.18); + border-color: #ffffff; + } + + QDialog#ImportProjectDialog #customTitleBar { + background-color: #0c0c0d; + border-bottom: none; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + min-height: 54px; + } + + QDialog#ImportProjectDialog #customTitleIcon { + qproperty-alignment: 'AlignVCenter | AlignLeft'; + } + + QDialog#ImportProjectDialog #customTitleLabel { + color: #f4f5f8; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 16px; + font-weight: 600; + letter-spacing: 0.6px; + background: transparent; + } + + QDialog#ImportProjectDialog #customCloseBtn { + background-color: transparent; + border-radius: 4px; + color: #f4f5f8; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + min-width: 24px; + min-height: 24px; + } + + QDialog#ImportProjectDialog #customCloseBtn:hover { + background-color: rgba(235, 235, 235, 0.08); + border-color: rgba(235, 235, 235, 0.7); + } + + QDialog#ImportProjectDialog #customCloseBtn:pressed { + background-color: rgba(235, 235, 235, 0.18); + border-color: #ffffff; + } + + QDialog#ImportProjectDialog QFrame#importSectionFrame { + background-color: #0c0c0d; + border: none; + border-radius: 5px; + } + + QDialog#ImportProjectDialog QFrame#importSectionHeader { + background-color: #19191B; + border: none; + min-width: 1157px; + max-width: 1157px; + min-height: 33px; + max-height: 33px; + } + + QDialog#ImportProjectDialog QLabel#importSectionTitle { + color: #f4f5f8; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 600; + letter-spacing: 0.6px; + background: transparent; + } + + QDialog#ImportProjectDialog QFrame#importSectionBody { + background-color: #19191B; + min-width: 1157px; + max-width: 1157px; + min-height: 450px; + max-height: 450px; + } + + QDialog#ImportProjectDialog #importGroup { + background-color: #16181d; + border: 1px solid rgba(73, 76, 84, 0.55); + border-radius: 6px; + color: #f4f5f8; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.6px; + padding: 12px; + } + + QDialog#ImportProjectDialog #importGroup::title { + background-color: rgba(48, 51, 58, 0.45); + border-bottom: 1px solid rgba(73, 76, 84, 0.45); + margin: -8px -8px 12px -8px; + padding: 12px 18px; + } + + QDialog#ImportProjectDialog #uploadArea { + background: transparent; + } + + QDialog#ImportProjectDialog #uploadContentFrame { + background-color: rgba(89, 100, 113, 0.1); + border-radius: 4px; + min-width: 1127px; + max-width: 1127px; + min-height: 288px; + max-height: 288px; + border: none; + transition: background-color 0.2s ease, border 0.2s ease; + } + + QDialog#ImportProjectDialog #uploadArea[dragOver="true"] #uploadContentFrame { + background-color: rgba(43, 86, 197, 0.12); + border: 2px dashed #2b56c5; + } + + QDialog#ImportProjectDialog QLabel#uploadIcon { + qproperty-alignment: 'AlignCenter'; + background: transparent; + } + + QDialog#ImportProjectDialog QLabel#uploadTitle { + color: #dfe2ea; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 400; + letter-spacing: 0.4px; + background: transparent; + } + + QDialog#ImportProjectDialog QLabel#uploadHint, + QDialog#ImportProjectDialog QLabel#uploadHintLabel { + color: rgba(255, 255, 255, 0.9); + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 12px; + font-weight: 300; + letter-spacing: 0.3px; + background: transparent; + margin: 0px; + padding: 4px 0px; + } + + QDialog#ImportProjectDialog QPushButton#uploadBtn { + background-color: #2b57c5; + border: none; + border-radius: 6px; + color: #ffffff; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 16px; + font-weight: 400; + letter-spacing: 0.8px; + min-width: 151px; + max-width: 151px; + min-height: 42px; + max-height: 42px; + } + + QDialog#ImportProjectDialog QPushButton#uploadBtn:hover { + background-color: #3d67d5; + } + + QDialog#ImportProjectDialog QPushButton#uploadBtn:pressed { + background-color: #1e47a5; + } + + QDialog#ImportProjectDialog #fileList { + background-color: rgba(89, 100, 113, 0.2); + border: none; + border-radius: 4px; + padding: 8px; + } + + QDialog#ImportProjectDialog #fileList:focus { + border-color: rgba(76, 104, 196, 0.6); + } + + QDialog#ImportProjectDialog #fileList QWidget#fileListItem { + background-color: rgba(62, 68, 84, 0.3); + border: 1px solid rgba(74, 80, 96, 0.45); + border-radius: 6px; + } + + QDialog#ImportProjectDialog #fileList QWidget#fileListItem:hover { + background-color: rgba(76, 104, 164, 0.28); + border-color: rgba(63, 104, 196, 0.6); + } + + QDialog#ImportProjectDialog QLabel#fileListItemTitle { + color: #f3f4f8; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.3px; + } + + QDialog#ImportProjectDialog QLabel#fileListItemMeta { + color: rgba(223, 226, 234, 0.65); + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 12px; + letter-spacing: 0.2px; + } + + QDialog#ImportProjectDialog QWidget#optionsGroup { + background-color: transparent; + border: none; + border-radius: 0; + } + + QDialog#ImportProjectDialog QCheckBox { + background: transparent; + background-color: transparent; + color: rgba(239, 240, 244, 0.88); + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 13px; + font-weight: 400; + letter-spacing: 0.4px; + spacing: 8px; + } + + QDialog#ImportProjectDialog QCheckBox::indicator { + width: 18px; + height: 18px; + border-radius: 3px; + } + + QDialog#ImportProjectDialog QCheckBox::indicator:unchecked { + background-color: rgba(217, 217, 217, 0.08); + border: 1px solid rgba(77, 116, 189, 0.4); + } + + QDialog#ImportProjectDialog QCheckBox::indicator:unchecked:hover { + background-color: rgba(217, 217, 217, 0.14); + border: 1px solid rgba(77, 116, 189, 0.6); + } + + QDialog#ImportProjectDialog QCheckBox::indicator:checked { + background-color: rgba(40, 126, 255, 0.7); + border: 1px solid #289eff; + image: none; + } + + QDialog#ImportProjectDialog QCheckBox::indicator:checked:hover { + background-color: rgba(40, 126, 255, 0.85); + } + + QDialog#ImportProjectDialog #buttonArea { + background-color: #0c0c0d; + border-top: 1px solid rgba(58, 63, 76, 0.7); + margin: 0 10px; /* 上下0px,左右20px */ + min-height: 64px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + } + + QDialog#ImportProjectDialog QPushButton#secondaryBtn { + background-color: rgba(84, 89, 98, 0.35); + border: 1px solid rgba(104, 110, 122, 0.65); + border-radius: 4px; + color: #ebebeb; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + padding: 0px 0px; + min-height: 36px; + max-height: 36px; + max-width: 95px; + min-width: 95px; + } + + QDialog#ImportProjectDialog QPushButton#secondaryBtn:hover { + background-color: rgba(84, 89, 98, 0.55); + border-color: rgba(136, 144, 160, 0.7); + } + + QDialog#ImportProjectDialog QPushButton#secondaryBtn:pressed { + background-color: rgba(84, 89, 98, 0.75); + } + + QDialog#ImportProjectDialog QPushButton#primaryBtn { + background-color: #2b57c5; + border: none; + border-radius: 4px; + color: #ebebeb; + font-family: "Inter" "Microsoft YaHei", "Segoe UI", sans-serif; + font-size: 14px; + padding: 0px 0px; + min-height: 37px; + max-height: 37px; + max-width: 125px; + min-width: 125px; + } + + QDialog#ImportProjectDialog QPushButton#primaryBtn:hover { + background-color: #3d67d5; + } + + QDialog#ImportProjectDialog QPushButton#primaryBtn:pressed { + background-color: #1e47a5; + } + + QDialog#ImportProjectDialog QPushButton#primaryBtn:disabled { + background-color: rgba(89, 100, 113, 0.25); + color: rgba(235, 235, 235, 0.55); + } """ @staticmethod @@ -494,3 +1407,39 @@ class StyleSheet: font-size: 12px; } """ + + @staticmethod + def get_context_menu_style(): + return """ + QMenu { + background-color: #424347; + border: 1px solid #5a5a6a; + border-radius: 6px; + padding: 4px 0px; + } + + QMenu::item { + background-color: transparent; + border-radius: 4px; + padding: 6px 12px; + margin: 1px 6px; + } + + QMenu::item:selected { + background-color: rgba(255, 255, 255, 0.08); + } + + QMenu::icon { + padding-left: 0px; + padding-right: 10px; + } + + QMenu::separator { + height: 1px; + background: rgba(84, 89, 98, 0.8); + margin: 4px 0px; + margin-left: 0px; + margin-right: 0px; + } + """ + diff --git a/MetaCore/ui/widget.py b/MetaCore/ui/widget.py new file mode 100644 index 0000000..2528213 --- /dev/null +++ b/MetaCore/ui/widget.py @@ -0,0 +1,411 @@ + +import os +import re +from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, + QLineEdit, QPushButton, QLabel, + QTreeView, QTreeWidget, QTreeWidgetItem, QWidget, + QFileDialog, QMessageBox, QAbstractItemView, QMenu, QDockWidget, QButtonGroup, QToolButton, QFrame, QSizePolicy) +from PyQt5.QtCore import Qt, QUrl, QMimeData, QPoint, QSize +from PyQt5.QtGui import QDrag, QPainter, QPixmap, QPen, QBrush, QFont +from PyQt5.sip import wrapinstance +from ui.icon_manager import IconManager + +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(20, 20)): + """ + 初始化通用消息对话框 + + 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 = IconManager() + self._title_icon_size = QSize(18, 18) + self._icon_close = self.icon_manager.get_icon('close_bt_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#iconLabel { + background-color: transparent; + } + QLabel#messageLabel { + background-color: transparent; + 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; + } + QWidget#buttonWidget { + background-color: transparent; + } + 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(10) + + # 用一个垂直布局包裹icon_label,确保顶部对齐 + icon_vbox = QVBoxLayout() + icon_vbox.setContentsMargins(0, 0, 0, 0) + icon_vbox.setSpacing(0) + icon_label = QLabel() + icon_label.setObjectName('iconLabel') + 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) + icon_vbox.addWidget(icon_label, alignment=Qt.AlignTop) + icon_vbox.addStretch() + message_area.addLayout(icon_vbox) + + self.message_label = QLabel(message) + self.message_label.setObjectName('messageLabel') + 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_widget.setObjectName('buttonWidget') + 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_() \ No newline at end of file diff --git a/data/projects.json b/data/projects.json index 64910f0..23cb869 100644 --- a/data/projects.json +++ b/data/projects.json @@ -1,4 +1,26 @@ [ + { + "id": 4, + "title": "aasd", + "date": "2025-10-17 16:49:10", + "type": "empty", + "image": "C:\\Users\\29381\\Desktop\\aasd\\aasd.png", + "path": "C:\\Users\\29381\\Desktop", + "project_dir": "C:\\Users\\29381\\Desktop\\aasd", + "description": "asd", + "status": "normal" + }, + { + "id": 3, + "title": "a", + "date": "2025-10-17 16:48:59", + "type": "imported", + "image": "C:\\Users\\29381\\Desktop\\a\\a.png", + "path": "C:\\Users\\29381\\Desktop", + "project_dir": "C:\\Users\\29381\\Desktop\\a", + "description": "从文件夹 a 导入", + "status": "normal" + }, { "id": 2, "title": "ab", @@ -15,7 +37,7 @@ "title": "XNWX", "date": "2025-10-14 18:05:51", "type": "imported", - "image": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\Resources\\ProjectPreviews\\preview_1_1760491477496.png", + "image": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\Resources\\ProjectPreviews\\preview_1_1760628516144.png", "path": "C:\\Users\\29381\\Desktop", "project_dir": "C:\\Users\\29381\\Desktop\\XNWX", "description": "从文件夹 XNWX 导入", diff --git a/scripts/example_script.py b/scripts/example_script.py new file mode 100644 index 0000000..ecba0da --- /dev/null +++ b/scripts/example_script.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +示例脚本 - 演示如何编写脚本 +""" + +from core.script_system import ScriptBase + +class ExampleScript(ScriptBase): + """示例脚本类""" + + def __init__(self): + super().__init__() + self.counter = 0 + self.rotation_speed = 30.0 # 度/秒 + + def start(self): + """脚本开始时调用""" + self.log("示例脚本开始运行!") + self.log(f"挂载到对象: {self.gameObject.getName()}") + + def update(self, dt): + """每帧更新""" + self.counter += 1 + + # 每60帧输出一次信息 + if self.counter % 60 == 0: + self.log(f"运行了 {self.counter} 帧") + + # 让对象旋转 + if self.transform: + current_h = self.transform.getH() + new_h = current_h + self.rotation_speed * dt + self.transform.setH(new_h) + + def on_destroy(self): + """脚本销毁时调用""" + self.log("示例脚本被销毁") + + def on_enable(self): + """脚本启用时调用""" + self.log("示例脚本被启用") + + def on_disable(self): + """脚本禁用时调用""" + self.log("示例脚本被禁用") diff --git a/tests/test_button_fix.py b/tests/test_button_fix.py new file mode 100644 index 0000000..8cce799 --- /dev/null +++ b/tests/test_button_fix.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +测试右上角按钮点击修复 +""" + +import sys +import os +from pathlib import Path + +# 添加项目根目录到Python路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) +sys.path.insert(0, str(project_root / 'MetaCore')) + +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * + +from data.project_manager import ProjectManager, Project +from ui.project_card import ProjectCard +from ui.styles import StyleSheet + +class TestButtonWindow(QWidget): + """测试按钮修复的窗口""" + + def __init__(self): + super().__init__() + self.setWindowTitle("右上角按钮修复测试") + self.setGeometry(100, 100, 800, 600) + self.setStyleSheet(StyleSheet.get_main_stylesheet()) + + # 设置深色背景 + self.setStyleSheet(""" + QWidget { + background-color: #1a1a1a; + color: #ffffff; + } + """ + StyleSheet.get_main_stylesheet()) + + self.init_ui() + + def init_ui(self): + """初始化UI""" + layout = QVBoxLayout(self) + layout.setContentsMargins(40, 40, 40, 40) + layout.setSpacing(20) + + # 标题 + title = QLabel("右上角按钮修复测试") + title.setStyleSheet(""" + QLabel { + font-size: 24px; + font-weight: bold; + color: #ffffff; + margin-bottom: 20px; + } + """) + title.setAlignment(Qt.AlignCenter) + layout.addWidget(title) + + # 说明文字 + description = QLabel(""" + 测试说明: + • 点击右上角的信息按钮应该显示上下文菜单,不应该闪退 + • 菜单应该包含:刷新预览图、在资源管理器显示、移除项目 + • 所有菜单项都应该能正常工作 + """) + description.setStyleSheet(""" + QLabel { + font-size: 14px; + color: #cccccc; + line-height: 1.5; + margin-bottom: 20px; + } + """) + layout.addWidget(description) + + # 创建测试卡片 + self.create_test_cards(layout) + + def create_test_cards(self, layout): + """创建测试卡片""" + # 卡片容器 + cards_widget = QWidget() + cards_layout = QHBoxLayout(cards_widget) + cards_layout.setSpacing(24) + cards_layout.setContentsMargins(20, 20, 20, 20) + + # 创建项目管理器 + project_manager = ProjectManager() + + # 创建测试项目 + test_project = Project( + id='test_1', + title='测试项目', + date='2024-10-14 15:30', + project_type='smart', + status='active', + path="/test/path/1", + project_dir="/test/path/1", + image=None + ) + + # 创建网格视图卡片 + grid_card = ProjectCard(test_project, project_manager, view_mode="grid") + cards_layout.addWidget(grid_card) + + # 创建列表视图卡片 + list_card = ProjectCard(test_project, project_manager, view_mode="list") + cards_layout.addWidget(list_card) + + cards_layout.addStretch() + + layout.addWidget(cards_widget) + layout.addStretch() + +def main(): + """主函数""" + app = QApplication(sys.argv) + + # 设置应用程序属性 + app.setApplicationName("按钮修复测试") + app.setOrganizationName("MetaCore") + + # 创建并显示测试窗口 + window = TestButtonWindow() + window.show() + + sys.exit(app.exec_()) + +if __name__ == '__main__': + main() diff --git a/tests/test_corner_debug.py b/tests/test_corner_debug.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/test_corner_debug.py @@ -0,0 +1 @@ + diff --git a/tests/test_corner_radius.py b/tests/test_corner_radius.py new file mode 100644 index 0000000..2600210 --- /dev/null +++ b/tests/test_corner_radius.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +测试项目卡片5px圆角效果 +""" + +import sys +import os +from pathlib import Path + +# 添加项目根目录到Python路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) +sys.path.insert(0, str(project_root / 'MetaCore')) + +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * + +from data.project_manager import ProjectManager, Project +from ui.project_card import ProjectCard +from ui.styles import StyleSheet + +class CornerRadiusTestWindow(QWidget): + """测试5px圆角效果的窗口""" + + def __init__(self): + super().__init__() + self.setWindowTitle("项目卡片5px圆角测试") + self.setGeometry(100, 100, 1000, 700) + self.setStyleSheet(StyleSheet.get_main_stylesheet()) + + # 设置深色背景 + self.setStyleSheet(""" + QWidget { + background-color: #1a1a1a; + color: #ffffff; + } + """ + StyleSheet.get_main_stylesheet()) + + self.init_ui() + + def init_ui(self): + """初始化UI""" + layout = QVBoxLayout(self) + layout.setContentsMargins(40, 40, 40, 40) + layout.setSpacing(20) + + # 标题 + title = QLabel("项目卡片5px圆角效果测试") + title.setStyleSheet(""" + QLabel { + font-size: 24px; + font-weight: bold; + color: #ffffff; + margin-bottom: 20px; + } + """) + title.setAlignment(Qt.AlignCenter) + layout.addWidget(title) + + # 说明文字 + description = QLabel(""" + 测试说明: + • 所有项目卡片现在都使用5px的圆角 + • 包括卡片外框、背景图片和列表视图图标 + • 圆角效果应该统一且美观 + """) + description.setStyleSheet(""" + QLabel { + font-size: 14px; + color: #cccccc; + line-height: 1.5; + margin-bottom: 20px; + } + """) + layout.addWidget(description) + + # 创建测试卡片网格 + self.create_test_grid(layout) + + def create_test_grid(self, layout): + """创建测试卡片网格""" + # 滚动区域 + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + scroll_area.setStyleSheet(""" + QScrollArea { + border: none; + background: transparent; + } + """) + + # 网格容器 + grid_widget = QWidget() + grid_layout = QGridLayout(grid_widget) + grid_layout.setSpacing(24) + grid_layout.setContentsMargins(20, 20, 20, 20) + + # 创建项目管理器 + project_manager = ProjectManager() + + # 创建测试项目数据 + test_projects = [ + { + 'id': '1', + 'title': '智能家居系统', + 'date': '2024-10-14 15:30', + 'type': 'smart', + 'status': 'active', + 'image': None + }, + { + 'id': '2', + 'title': 'VR体验项目', + 'date': '2024-10-13 09:15', + 'type': 'vr', + 'status': 'active', + 'image': str(project_root / 'MetaCore' / 'Resources' / 'ProjectPreviews' / 'preview_6_1760434704537.png') if (project_root / 'MetaCore' / 'Resources' / 'ProjectPreviews' / 'preview_6_1760434704537.png').exists() else None + }, + { + 'id': '3', + 'title': '工业控制系统', + 'date': '2024-10-12 14:20', + 'type': 'industrial', + 'status': 'active', + 'image': None + }, + { + 'id': '4', + 'title': '游戏开发', + 'date': '2024-10-11 11:45', + 'type': 'game', + 'status': 'active', + 'image': None + } + ] + + # 创建网格视图卡片 + row, col = 0, 0 + for project_data in test_projects: + project = Project( + id=project_data['id'], + title=project_data['title'], + date=project_data['date'], + project_type=project_data['type'], + status=project_data['status'], + path=f"/test/path/{project_data['id']}", + project_dir=f"/test/path/{project_data['id']}", + image=project_data['image'] + ) + + # 创建项目卡片 + card = ProjectCard(project, project_manager, view_mode="grid") + grid_layout.addWidget(card, row, col) + + col += 1 + if col >= 3: # 每行3个卡片 + col = 0 + row += 1 + + # 添加列表视图示例 + list_title = QLabel("列表视图示例(也使用5px圆角):") + list_title.setStyleSheet(""" + QLabel { + font-size: 18px; + font-weight: bold; + color: #ffffff; + margin: 20px 0 10px 0; + } + """) + + # 创建列表视图卡片 + list_widget = QWidget() + list_layout = QVBoxLayout(list_widget) + list_layout.setSpacing(8) + list_layout.setContentsMargins(0, 0, 0, 0) + + for project_data in test_projects[:2]: # 只显示前两个 + project = Project( + id=project_data['id'], + title=project_data['title'], + date=project_data['date'], + project_type=project_data['type'], + status=project_data['status'], + path=f"/test/path/{project_data['id']}", + project_dir=f"/test/path/{project_data['id']}", + image=project_data['image'] + ) + + list_card = ProjectCard(project, project_manager, view_mode="list") + list_layout.addWidget(list_card) + + # 添加到主网格 + grid_layout.addWidget(list_title, row + 1, 0, 1, 3) + grid_layout.addWidget(list_widget, row + 2, 0, 1, 3) + + scroll_area.setWidget(grid_widget) + layout.addWidget(scroll_area) + +def main(): + """主函数""" + app = QApplication(sys.argv) + + # 设置应用程序属性 + app.setApplicationName("圆角测试") + app.setOrganizationName("MetaCore") + + # 创建并显示测试窗口 + window = CornerRadiusTestWindow() + window.show() + + sys.exit(app.exec_()) + +if __name__ == '__main__': + main() diff --git a/tests/test_font.py b/tests/test_font.py new file mode 100644 index 0000000..0421bc4 --- /dev/null +++ b/tests/test_font.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""快速检测Inter字体""" + +def quick_font_check(): + try: + from PyQt5.QtWidgets import QApplication + from PyQt5.QtGui import QFontDatabase, QFont, QFontInfo + import sys + + app = QApplication(sys.argv) + + font_db = QFontDatabase() + families = font_db.families() + + # 检查Inter + inter_families = [f for f in families if 'Inter' in f] + + if inter_families: + print("✅ Inter字体已安装:") + for family in inter_families: + print(f" {family}") + + # 测试实际使用 + test_font = QFont("Inter", 14) + font_info = QFontInfo(test_font) + + print(f"\n实际使用字体: {font_info.family()}") + + if "Inter" in font_info.family(): + print("✅ Inter字体可正常使用") + else: + print("❌ Inter字体回退到其他字体") + else: + print("❌ Inter字体未安装") + print("请从 https://fonts.google.com/specimen/Inter 下载安装") + + app.quit() + + except ImportError: + print("❌ PyQt5未安装,无法检测字体") + +if __name__ == "__main__": + quick_font_check() \ No newline at end of file diff --git a/tests/test_optimized_card.py b/tests/test_optimized_card.py new file mode 100644 index 0000000..2a32c35 --- /dev/null +++ b/tests/test_optimized_card.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +测试优化后的项目卡片组件 +""" + +import sys +import os +from pathlib import Path + +# 添加项目根目录到Python路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) +sys.path.insert(0, str(project_root / 'MetaCore')) + +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * + +from data.project_manager import ProjectManager, Project +from ui.project_card import ProjectCard +from ui.styles import StyleSheet + +class TestWindow(QWidget): + """测试窗口""" + + def __init__(self): + super().__init__() + self.setWindowTitle("优化后的项目卡片测试") + self.setGeometry(100, 100, 1200, 800) + self.setStyleSheet(StyleSheet.get_main_stylesheet()) + + # 设置深色背景 + self.setStyleSheet(""" + QWidget { + background-color: #1a1a1a; + color: #ffffff; + } + """ + StyleSheet.get_main_stylesheet()) + + self.init_ui() + + def init_ui(self): + """初始化UI""" + layout = QVBoxLayout(self) + layout.setContentsMargins(40, 40, 40, 40) + layout.setSpacing(20) + + # 标题 + title = QLabel("项目卡片优化效果展示") + title.setStyleSheet(""" + QLabel { + font-size: 24px; + font-weight: bold; + color: #ffffff; + margin-bottom: 20px; + } + """) + title.setAlignment(Qt.AlignCenter) + layout.addWidget(title) + + # 说明文字 + description = QLabel(""" + 根据Figma设计优化的项目卡片特性: + • 图片作为背景填充整个卡片 (276x191px) + • 底部半透明信息覆盖层显示项目详情 + • 右上角操作按钮悬浮显示 + • 无图片时使用project_empty_icon作为默认背景 + • 完全匹配Figma设计规范的颜色和字体 + """) + description.setStyleSheet(""" + QLabel { + font-size: 14px; + color: #cccccc; + line-height: 1.5; + margin-bottom: 20px; + } + """) + layout.addWidget(description) + + # 创建项目卡片网格 + self.create_project_grid(layout) + + def create_project_grid(self, layout): + """创建项目卡片网格""" + # 滚动区域 + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + scroll_area.setStyleSheet(""" + QScrollArea { + border: none; + background: transparent; + } + """) + + # 网格容器 + grid_widget = QWidget() + grid_layout = QGridLayout(grid_widget) + grid_layout.setSpacing(24) + grid_layout.setContentsMargins(20, 20, 20, 20) + + # 创建项目管理器 + project_manager = ProjectManager() + + # 创建测试项目数据 + test_projects = [ + { + 'id': '1', + 'title': '智能家居控制系统', + 'date': '2024-10-14 15:30', + 'type': 'smart', + 'status': 'active', + 'image': None # 测试默认图标 + }, + { + 'id': '2', + 'title': 'VR虚拟现实项目', + 'date': '2024-10-13 09:15', + 'type': 'vr', + 'status': 'active', + 'image': str(project_root / 'MetaCore' / 'Resources' / 'ProjectPreviews' / 'preview_6_1760434704537.png') + }, + { + 'id': '3', + 'title': '工业自动化系统', + 'date': '2024-10-12 14:20', + 'type': 'industrial', + 'status': 'active', + 'image': None + }, + { + 'id': '4', + 'title': '游戏开发项目', + 'date': '2024-10-11 11:45', + 'type': 'game', + 'status': 'pending_delete', # 测试待删除状态 + 'image': None + }, + { + 'id': '5', + 'title': 'UI设计项目', + 'date': '2024-10-10 16:30', + 'type': 'design', + 'status': 'active', + 'image': None + }, + { + 'id': '6', + 'title': '数据分析平台', + 'date': '2024-10-09 13:15', + 'type': 'smart', + 'status': 'active', + 'image': None + } + ] + + # 创建项目卡片 + row, col = 0, 0 + for project_data in test_projects: + project = Project( + id=project_data['id'], + title=project_data['title'], + date=project_data['date'], + project_type=project_data['type'], + status=project_data['status'], + path=f"/test/path/{project_data['id']}", + project_dir=f"/test/path/{project_data['id']}", + image=project_data['image'] + ) + + # 创建项目卡片 + card = ProjectCard(project, project_manager, view_mode="grid") + grid_layout.addWidget(card, row, col) + + col += 1 + if col >= 4: # 每行4个卡片 + col = 0 + row += 1 + + scroll_area.setWidget(grid_widget) + layout.addWidget(scroll_area) + +def main(): + """主函数""" + app = QApplication(sys.argv) + + # 设置应用程序属性 + app.setApplicationName("项目卡片测试") + app.setOrganizationName("MetaCore") + + # 创建并显示测试窗口 + window = TestWindow() + window.show() + + sys.exit(app.exec_()) + +if __name__ == '__main__': + main() diff --git a/tests/test_simple_corner.py b/tests/test_simple_corner.py new file mode 100644 index 0000000..6656107 --- /dev/null +++ b/tests/test_simple_corner.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +简单的圆角测试 +""" + +import sys +import os +from pathlib import Path + +# 添加项目根目录到Python路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) +sys.path.insert(0, str(project_root / 'MetaCore')) + +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * + +from data.project_manager import ProjectManager, Project +from ui.project_card import ProjectCard + +class SimpleCornerTest(QWidget): + """简单圆角测试""" + + def __init__(self): + super().__init__() + self.setWindowTitle("简单圆角测试") + self.setGeometry(100, 100, 600, 400) + + # 设置深色背景 + self.setStyleSheet(""" + QWidget { + background-color: #2a2a2a; + } + """) + + self.init_ui() + + def init_ui(self): + """初始化UI""" + layout = QVBoxLayout(self) + layout.setContentsMargins(50, 50, 50, 50) + layout.setSpacing(30) + + # 标题 + title = QLabel("项目卡片5px圆角测试") + title.setStyleSheet(""" + QLabel { + color: white; + font-size: 20px; + font-weight: bold; + } + """) + title.setAlignment(Qt.AlignCenter) + layout.addWidget(title) + + # 创建项目管理器和测试项目 + project_manager = ProjectManager() + test_project = Project( + id='test_1', + title='测试项目', + date='2024-10-14', + project_type='smart', + status='active', + path="/test/path", + project_dir="/test/path", + image=None + ) + + # 创建项目卡片 + card = ProjectCard(test_project, project_manager, view_mode="grid") + + # 居中显示卡片 + card_container = QWidget() + card_layout = QHBoxLayout(card_container) + card_layout.addStretch() + card_layout.addWidget(card) + card_layout.addStretch() + + layout.addWidget(card_container) + layout.addStretch() + +def main(): + """主函数""" + app = QApplication(sys.argv) + + window = SimpleCornerTest() + window.show() + + sys.exit(app.exec_()) + +if __name__ == '__main__': + main()