From 5a520f2efc8498b2ebacf77c94887df93f05b8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Mon, 16 Mar 2026 20:43:53 +0800 Subject: [PATCH 01/47] =?UTF-8?q?[=E6=96=B0=E5=8A=9F=E8=83=BD]=20=E4=B8=BA?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E9=85=8D=E8=89=B2=E5=AF=B9=E8=AF=9D=E6=A1=86?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=97=A0=E8=BE=B9=E6=A1=86=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 EditPaletteDialog 从 QDialog 迁移到 FramelessDialog - 实现自定义 Fluent Design 风格标题栏,包含 Logo 和标题 - 添加关闭按钮主题适配,支持深色/浅色模式切换 - 添加窗口背景色主题适配 - 添加 PySideSix-Frameless-Window 依赖 - 更新 LICENSE 和 LICENSE.html 添加新依赖版权信息 - 更新 about_dialog.py 添加新依赖说明 - 更新开发经验总结文档,添加无边框窗口改造经验 --- LICENSE | 41 +- README.md | 345 +++++++-------- dialogs/about_dialog.py | 5 + dialogs/edit_palette.py | 153 +++++-- file/LICENSE.html | 41 +- requirements.txt | 1 + ui/color_preview.py | 2 +- utils/theme_colors.py | 15 + ...17\351\252\214\346\200\273\347\273\223.md" | 418 ++++++++++++++++++ 9 files changed, 774 insertions(+), 247 deletions(-) diff --git a/LICENSE b/LICENSE index 1a647a3..2b8b5ef 100644 --- a/LICENSE +++ b/LICENSE @@ -1028,7 +1028,20 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ -6. Open Color +6. PySideSix-Frameless-Window +-------------------------------------------------------------------------------- +版权所有:zhiyiYo +项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window +许可证:GNU General Public License v3.0 + +说明: +PySideSix-Frameless-Window 使用 GPLv3 许可证。由于本项目主许可证也为 GPLv3, +完整的 GPLv3 许可证文本请参考本文档前面的 "GNU GENERAL PUBLIC LICENSE +Version 3" 章节。 + + +================================================================================ +7. Open Color -------------------------------------------------------------------------------- 版权所有:heeyeun (Yeun) 项目地址:https://github.com/yeun/open-color @@ -1060,7 +1073,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -7. Nice Color Palettes +8. Nice Color Palettes -------------------------------------------------------------------------------- 版权所有:Jam3 项目地址:https://github.com/Experience-Monks/nice-color-palettes @@ -1090,7 +1103,7 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -8. Tailwind CSS Colors +9. Tailwind CSS Colors -------------------------------------------------------------------------------- 版权所有:Tailwind Labs, Inc. 项目地址:https://github.com/tailwindlabs/tailwindcss @@ -1122,7 +1135,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -9. Material Design Colors (Apache License 2.0) +10. Material Design Colors (Apache License 2.0) -------------------------------------------------------------------------------- 版权所有:Google LLC 项目地址:https://m3.material.io/styles/color/system/overview @@ -1134,7 +1147,7 @@ Material Design Colors 使用 Apache License 2.0 许可证。完整的许可证 requests 章节中的 "Apache License Version 2.0" 部分。 ================================================================================ -10. ColorBrewer (Apache License 2.0) +11. ColorBrewer (Apache License 2.0) -------------------------------------------------------------------------------- 版权所有:Cynthia Brewer 官网:https://colorbrewer2.org/ @@ -1146,7 +1159,7 @@ ColorBrewer 使用 Apache License 2.0 许可证。完整的许可证文本请参 requests 章节中的 "Apache License Version 2.0" 部分。 ================================================================================ -11. Radix UI Colors +12. Radix UI Colors -------------------------------------------------------------------------------- 版权所有:WorkOS 项目地址:https://github.com/radix-ui/colors @@ -1179,7 +1192,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -12. Nord +13. Nord -------------------------------------------------------------------------------- 版权所有:Sven Greb 项目地址:https://github.com/arcticicestudio/nord @@ -1211,7 +1224,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -13. Dracula +14. Dracula --------------------------------------------------------------------------------- 版权所有:Dracula Theme contributors 官网:https://draculatheme.com/ @@ -1242,7 +1255,7 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -14. Rosé Pine +15. Rosé Pine --------------------------------------------------------------------------------- 版权所有:Rosé Pine 团队 项目地址:https://github.com/rose-pine/rose-pine-theme @@ -1274,7 +1287,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -15. Solarized +16. Solarized -------------------------------------------------------------------------------- 版权所有:Ethan Schoonover 项目地址:https://github.com/altercation/solarized @@ -1304,7 +1317,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -16. Catppuccin +17. Catppuccin -------------------------------------------------------------------------------- 版权所有:Catppuccin 团队 项目地址:https://github.com/catppuccin/catppuccin @@ -1337,7 +1350,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -17. Gruvbox +18. Gruvbox -------------------------------------------------------------------------------- 版权所有:Pavel Pertsev 项目地址:https://github.com/morhetz/gruvbox @@ -1367,7 +1380,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -18. Tokyo Night +19. Tokyo Night -------------------------------------------------------------------------------- 版权所有:enkia 项目地址:https://github.com/enkia/tokyo-night-vscode-theme @@ -1400,7 +1413,7 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -19. 网站使用资源 +20. 网站使用资源 -------------------------------------------------------------------------------- 项目官网 (https://qingshangongzai.github.io/Color_Card/) 使用了以下资源: diff --git a/README.md b/README.md index 00ba755..e0b4289 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,7 @@ 简体中文 | English

- ---- +*** @@ -29,50 +28,50 @@ **开源地址**: -- **主仓库(Gitee)**:https://gitee.com/qingshangongzai/color_card -- **镜像仓库(GitHub)**:https://github.com/qingshangongzai/Color_Card -- **官方网站**:https://qingshangongzai.github.io/Color_Card/ +- **主仓库(Gitee)**: +- **镜像仓库(GitHub)**: +- **官方网站**: ### 开发历程 自 2026-02-05 发布 v1.0.0 以来,项目保持快速稳定的迭代节奏: -| 指标 | 数据 | -| :-- | :-- | -| 发布版本 | 8 个版本(v1.0.0 → v1.5.1) | -| 开发周期 | 38 天 | -| 总更新项 | **102 项** | -| 平均每版本 | 12.75 项 | +| 指标 | 数据 | +| :---- | :--------------------- | +| 发布版本 | 8 个版本(v1.0.0 → v1.5.1) | +| 开发周期 | 38 天 | +| 总更新项 | **102 项** | +| 平均每版本 | 12.75 项 | **详细分类统计**: -| 分类 | 数量 | 说明 | -| :-- | :--: | :-- | -| ✨ 新增功能 | **32** | 包含首次发布的 9 项核心功能 | -| 🔧 问题修复 | **25** | 持续修复 Bug,提升稳定性 | -| 🎨 界面优化 | **25** | 用户体验打磨 | -| ⚡ 性能提升 | **8** | 缓存机制、启动优化等 | -| 📝 内容调整 | **5** | 文本、名称等调整 | -| 🏗️ 代码重构 | **3** | 代码结构优化 | -| 🔮 逻辑优化 | **2** | 算法逻辑改进 | -| 📜 许可证完善 | **1** | 开源合规性 | -| 🚀 功能优化 | **1** | 功能增强 | +| 分类 | 数量 | 说明 | +| :------- | :----: | :-------------- | +| ✨ 新增功能 | **32** | 包含首次发布的 9 项核心功能 | +| 🔧 问题修复 | **25** | 持续修复 Bug,提升稳定性 | +| 🎨 界面优化 | **25** | 用户体验打磨 | +| ⚡ 性能提升 | **8** | 缓存机制、启动优化等 | +| 📝 内容调整 | **5** | 文本、名称等调整 | +| 🏗️ 代码重构 | **3** | 代码结构优化 | +| 🔮 逻辑优化 | **2** | 算法逻辑改进 | +| 📜 许可证完善 | **1** | 开源合规性 | +| 🚀 功能优化 | **1** | 功能增强 | ### 核心功能特色 **一站式色彩解决方案**:从图片分析到配色应用,提供完整的色彩工作流 -| 功能 | 截图预览 | -|------|---------| -| **色彩信息提取**
通过可拖动取色点实时提取图片颜色,支持多色彩空间显示(HSB、LAB、HSL、CMYK、RGB) | ![色彩提取](docs/screenshots/color-extract.png) | -| **明度分析**
将图片按明度分为9个区域(基于Adobe标准),提供直方图可视化,可快速分析图片影调 | ![明度分析](docs/screenshots/luminance-extract.png) | -| **渐变色提取**
通过起始色和结束色生成渐变色序列,支持 RGB/HSB/LAB 三种颜色空间插值 | ![渐变色提取](docs/screenshots/Gradient%20Extract.png) | -| **配色生成**
提供5种专业配色方案(同色系、邻近色、互补色、分离补色、双补色),支持可交互色环选择 | ![配色生成](docs/screenshots/color-generation.png) | -| **配色收藏**
支持收藏、管理配色方案,支持批量导入导出为JSON文件,支持单组色卡导出为 Adobe ASE 格式 | ![配色管理](docs/screenshots/palette-management.png) | -| **内置色彩库**
集成 Open Color、Tailwind CSS、Material Design 等13大开源配色方案,总计661组色卡 | ![内置色彩库](docs/screenshots/preset-colors.png) | -| **配色预览**
支持手机UI、网页、插画、排版、品牌、海报、图案、杂志等8种场景预览,并支持导入自定义SVG | ![配色预览](docs/screenshots/color-preview.png) | -| **多语言支持**
支持简体中文、繁体中文、英语、日语、法语、俄语等6种语言 | ![多语言支持](docs/screenshots/locales.png) | -| **现代化界面**
基于 Fluent Design 设计语言,支持深色/浅色主题切换 | ![深色/浅色模式](./docs/screenshots/Dark%20mode%26light%20mode.png) | +| 功能 | 截图预览 | +| ----------------------------------------------------------------------- | -------------------------------------------------------------------- | +| **色彩信息提取**通过可拖动取色点实时提取图片颜色,支持多色彩空间显示(HSB、LAB、HSL、CMYK、RGB) | !\[色彩提取]\(docs/screenshots/color-extract.png null) | +| **明度分析**将图片按明度分为9个区域(基于Adobe标准),提供直方图可视化,可快速分析图片影调 | !\[明度分析]\(docs/screenshots/luminance-extract.png null) | +| **渐变色提取**通过起始色和结束色生成渐变色序列,支持 RGB/HSB/LAB 三种颜色空间插值 | !\[渐变色提取]\(docs/screenshots/Gradient%20Extract.png null) | +| **配色生成**提供5种专业配色方案(同色系、邻近色、互补色、分离补色、双补色),支持可交互色环选择 | !\[配色生成]\(docs/screenshots/color-generation.png null) | +| **配色收藏**支持收藏、管理配色方案,支持批量导入导出为JSON文件,支持单组色卡导出为 Adobe ASE 格式 | !\[配色管理]\(docs/screenshots/palette-management.png null) | +| **内置色彩库**集成 Open Color、Tailwind CSS、Material Design 等13大开源配色方案,总计661组色卡 | !\[内置色彩库]\(docs/screenshots/preset-colors.png null) | +| **配色预览**支持手机UI、网页、插画、排版、品牌、海报、图案、杂志等8种场景预览,并支持导入自定义SVG | !\[配色预览]\(docs/screenshots/color-preview\.png null) | +| **多语言支持**支持简体中文、繁体中文、英语、日语、法语、俄语等6种语言 | !\[多语言支持]\(docs/screenshots/locales.png null) | +| **现代化界面**基于 Fluent Design 设计语言,支持深色/浅色主题切换 | !\[深色/浅色模式]\(./docs/screenshots/Dark%20mode%26light%20mode.png null) | ### 适用场景 @@ -95,8 +94,7 @@ - **色彩教学**:作为色彩理论和实践的教学工具 - **快速原型**:快速生成配色并预览效果,加速设计迭代 - ---- +*** ## 安装指南 @@ -118,36 +116,31 @@ #### 依赖安装与运行 1. **克隆仓库**: - ```bash # 从 Gitee 克隆(国内推荐) git clone https://gitee.com/qingshangongzai/color_card.git - + # 或从 GitHub 克隆 git clone https://github.com/qingshangongzai/Color_Card.git - + cd color_card ``` 2. **创建虚拟环境(推荐)**: - ```bash python -m venv .venv # 激活虚拟环境 .\.venv\Scripts\activate # Windows ``` 3. **安装项目依赖**: - ```bash pip install -r requirements.txt ``` 4. **启动应用程序**: - ```bash python main.py ``` - ---- +*** ## 使用说明 @@ -163,25 +156,23 @@ ### 功能模块 -|模块 |功能 | -|:---|:---| -|色彩提取 |可拖动取色点、多色彩空间显示、一键复制颜色值 | -|明度分析 |9级明度分区(Zone 0-8)、直方图可视化、区域高亮 | -|渐变色提取 |起始色/结束色设置、RGB/HSB/LAB插值、中间色数量调节 | -|配色生成 |5种配色方案、可交互色环、明度调整 | -|配色管理 |收藏配色、自定义名称、批量导入导出为JSON文件、支持单组配色ASE格式导出(支持Adobe软件) | -|配色预览 |8种内置场景、自定义SVG、智能配色映射 | -|内置色彩库 |13大开源配色方案、661组色卡 | - +| 模块 | 功能 | +| :---- | :------------------------------------------------ | +| 色彩提取 | 可拖动取色点、多色彩空间显示、一键复制颜色值 | +| 明度分析 | 9级明度分区(Zone 0-8)、直方图可视化、区域高亮 | +| 渐变色提取 | 起始色/结束色设置、RGB/HSB/LAB插值、中间色数量调节 | +| 配色生成 | 5种配色方案、可交互色环、明度调整 | +| 配色管理 | 收藏配色、自定义名称、批量导入导出为JSON文件、支持单组配色ASE格式导出(支持Adobe软件) | +| 配色预览 | 8种内置场景、自定义SVG、智能配色映射 | +| 内置色彩库 | 13大开源配色方案、661组色卡 | ---- +*** ## 开发规范 本项目遵循 PEP 8 代码风格规范,采用模块化架构设计。详细的开发规范请参考 [开发规范.md](文档/开发规范.md)。 - ---- +*** ## 贡献指南 @@ -197,7 +188,7 @@ 2. 创建你的特性分支:`git checkout -b feature/你的功能名称` 3. 提交你的更改:`git commit -m '[类型] 添加了某个功能'` 4. 将分支推送到你的 Fork:`git push origin feature/你的功能名称` -5. **在 Gitee 主仓库上对该分支创建一个 Pull Request,并合并至 `Dev` 分支** +5. **在 Gitee 主仓库上对该分支创建一个 Pull Request,并合并至** **`Dev`** **分支** ### 协作规范 @@ -211,8 +202,7 @@ - [开发规范.md](文档/开发规范.md) - 涵盖代码组织、样式、命名等全方位规范 - ---- +*** ## 许可证信息 @@ -225,56 +215,51 @@ Color Card 采用 **GNU General Public License v3.0 (GPL 3.0)** 许可证发布 ### 许可证文件 - **项目完整许可证信息**:[LICENSE](./LICENSE) -- **GPL 3.0 官方文本**:https://www.gnu.org/licenses/gpl-3.0.html +- **GPL 3.0 官方文本**: ### 第三方库许可证 -本项目使用了以下第三方库: - -|库 |许可证 | -|:---|:---:| -|PySide6 |LGPL-3.0 | -|PySide6-Fluent-Widgets |GPL-3.0 | -|Pillow |MIT License | -|requests |Apache-2.0 | -|numpy |BSD-3-Clause | -|Open Color |MIT License | -|Tailwind CSS Colors |MIT License | -|Material Design Colors |Apache-2.0 | -|ColorBrewer |Apache-2.0 | -|Radix Colors |MIT License | -|Nord |MIT License | -|Dracula |MIT License | -|Solarized |MIT License | -|Gruvbox |MIT License | -|Catppuccin |MIT License | -|Rose Pine |MIT License | -|Tokyo Night |MIT License | -|Nice Color Palettes |MIT License | - - ---- +本项目使用了以下第三方库(部分): + +| 库 | 许可证 | +| :--------------------- | :----------: | +| PySide6 | LGPL-3.0 | +| PySide6-Fluent-Widgets | GPL-3.0 | +| Pillow | MIT License | +| requests | Apache-2.0 | +| numpy | BSD-3-Clause | +| Open Color | MIT License | +| Tailwind CSS Colors | MIT License | +| Material Design Colors | Apache-2.0 | +| ColorBrewer | Apache-2.0 | +| Radix Colors | MIT License | +| Nord | MIT License | +| Dracula | MIT License | +| Solarized | MIT License | +| Gruvbox | MIT License | +| Catppuccin | MIT License | +| Rose Pine | MIT License | +| Tokyo Night | MIT License | +| Nice Color Palettes | MIT License | + +*** ## 联系方式 -- **主仓库(Gitee)**:https://gitee.com/qingshangongzai/color_card -- **镜像仓库(GitHub)**:https://github.com/qingshangongzai/Color_Card -- **联系邮箱**:[hxiao_studio@163.com](mailto:hxiao_studio@163.com)、[qingshangongzai@163.com](mailto:qingshangongzai@163.com) +- **主仓库(Gitee)**: +- **镜像仓库(GitHub)**: +- **联系邮箱**: - ---- +*** **免责声明**:Color Card 仅供学习和研究使用。开发者不对因使用本工具导致的任何后果负责,请谨慎使用。 - ---- +*** **取色卡 (Color Card)** - 为摄影师和设计师打造的一站式配色工具和图片色彩分析工具\ Copyright © 2026 浮晓 HXiao Studio - ---- - +*** @@ -292,50 +277,50 @@ Unlike common color tools or websites that only provide a single function, Color **Repository URLs**: -- **Primary Repository (Gitee)**: https://gitee.com/qingshangongzai/color_card -- **Mirror Repository (GitHub)**: https://github.com/qingshangongzai/Color_Card -- **Official Website**: https://qingshangongzai.github.io/Color_Card/ +- **Primary Repository (Gitee)**: +- **Mirror Repository (GitHub)**: +- **Official Website**: ### Development Journey Since the release of v1.0.0 on 2026-02-05, the project has maintained a fast and stable iteration pace: -| Metric | Data | -| :-- | :-- | -| Released Versions | 8 versions (v1.0.0 → v1.5.1) | -| Development Period | 38 days | -| Total Updates | **102 items** | -| Average per Version | 12.75 items | - -**Detailed Category Statistics**: - -| Category | Count | Description | -| :-- | :--: | :-- | -| ✨ New Features | **32** | Including 9 core features from v1.0.0 launch | -| 🔧 Bug Fixes | **25** | Continuous bug fixes for stability | -| 🎨 UI Improvements | **25** | User experience refinements | -| ⚡ Performance | **8** | Cache mechanism, startup optimization | -| 📝 Content Adjustments | **5** | Text, naming adjustments | -| 🏗️ Code Refactoring | **3** | Code structure optimization | -| 🔮 Logic Optimization | **2** | Algorithm improvements | -| 📜 License Compliance | **1** | Open source compliance | -| 🚀 Feature Enhancement | **1** | Feature enhancements | +| Metric | Data | +| :------------------ | :--------------------------- | +| Released Versions | 8 versions (v1.0.0 → v1.5.1) | +| Development Period | 38 days | +| Total Updates | **102 items** | +| Average per Version | 12.75 items | + +**Detailed Category Statistics(portion)**: + +| Category | Count | Description | +| :--------------------- | :----: | :------------------------------------------- | +| ✨ New Features | **32** | Including 9 core features from v1.0.0 launch | +| 🔧 Bug Fixes | **25** | Continuous bug fixes for stability | +| 🎨 UI Improvements | **25** | User experience refinements | +| ⚡ Performance | **8** | Cache mechanism, startup optimization | +| 📝 Content Adjustments | **5** | Text, naming adjustments | +| 🏗️ Code Refactoring | **3** | Code structure optimization | +| 🔮 Logic Optimization | **2** | Algorithm improvements | +| 📜 License Compliance | **1** | Open source compliance | +| 🚀 Feature Enhancement | **1** | Feature enhancements | ### Key Features **One-stop Color Solution**: Complete color workflow from image analysis to color application -| Feature | Screenshot | -|---------|------------| -| **Visual Color Extraction**
Real-time color extraction via draggable color pickers, supporting multiple color spaces (HSB, LAB, HSL, CMYK, RGB) | ![Color Extraction](docs/screenshots/color-extract.png) | -| **Luminance Analysis**
9-zone luminance segmentation (Zone 0-8 based on Adobe standard) with histogram visualization | ![Luminance Analysis](docs/screenshots/luminance-extract.png) | -| **Gradient Extraction**
Generate gradient color sequences from start and end colors, supporting RGB/HSB/LAB color space interpolation | ![Gradient Extraction](docs/screenshots/Gradient%20Extract.png) | -| **Color Scheme Generation**
5 professional color schemes (Monochromatic, Analogous, Complementary, Split-Complementary, Double Complementary) with interactive color wheel | ![Color Generation](docs/screenshots/color-generation.png) | -| **Palette Collection**
Save and manage color schemes, support batch import/export in JSON format, support single palette export to Adobe ASE format | ![Palette Management](docs/screenshots/palette-management.png) | -| **Built-in Color Library**
13 major open-source color schemes including Open Color, Tailwind CSS, Material Design, totaling 661 color palettes | ![Preset Colors](docs/screenshots/preset-colors.png) | -| **Color Preview**
8 built-in scene previews (Mobile UI, Web, Illustration, Typography, Brand, Poster, Pattern, Magazine) with custom SVG support | ![Color Preview](docs/screenshots/color-preview.png) | -| **Multi-language Support**
6 languages including Simplified Chinese, Traditional Chinese, English, Japanese, French, and Russian | ![Multi-language](docs/screenshots/locales.png) | -| **Modern Interface**
Based on Fluent Design, supports dark/light theme switching | ![Dark/Light Mode](./docs/screenshots/Dark%20mode%26light%20mode.png) | +| Feature | Screenshot | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| **Visual Color Extraction**Real-time color extraction via draggable color pickers, supporting multiple color spaces (HSB, LAB, HSL, CMYK, RGB) | !\[Color Extraction]\(docs/screenshots/color-extract.png null) | +| **Luminance Analysis**9-zone luminance segmentation (Zone 0-8 based on Adobe standard) with histogram visualization | !\[Luminance Analysis]\(docs/screenshots/luminance-extract.png null) | +| **Gradient Extraction**Generate gradient color sequences from start and end colors, supporting RGB/HSB/LAB color space interpolation | !\[Gradient Extraction]\(docs/screenshots/Gradient%20Extract.png null) | +| **Color Scheme Generation**5 professional color schemes (Monochromatic, Analogous, Complementary, Split-Complementary, Double Complementary) with interactive color wheel | !\[Color Generation]\(docs/screenshots/color-generation.png null) | +| **Palette Collection**Save and manage color schemes, support batch import/export in JSON format, support single palette export to Adobe ASE format | !\[Palette Management]\(docs/screenshots/palette-management.png null) | +| **Built-in Color Library**13 major open-source color schemes including Open Color, Tailwind CSS, Material Design, totaling 661 color palettes | !\[Preset Colors]\(docs/screenshots/preset-colors.png null) | +| **Color Preview**8 built-in scene previews (Mobile UI, Web, Illustration, Typography, Brand, Poster, Pattern, Magazine) with custom SVG support | !\[Color Preview]\(docs/screenshots/color-preview\.png null) | +| **Multi-language Support**6 languages including Simplified Chinese, Traditional Chinese, English, Japanese, French, and Russian | !\[Multi-language]\(docs/screenshots/locales.png null) | +| **Modern Interface**Based on Fluent Design, supports dark/light theme switching | !\[Dark/Light Mode]\(./docs/screenshots/Dark%20mode%26light%20mode.png null) | ### Use Cases @@ -358,8 +343,7 @@ Since the release of v1.0.0 on 2026-02-05, the project has maintained a fast and - **Color Education**: Serve as a teaching tool for color theory and practice - **Rapid Prototyping**: Quickly generate and preview color schemes, accelerate design iteration - ---- +*** ## Installation @@ -381,36 +365,31 @@ Since the release of v1.0.0 on 2026-02-05, the project has maintained a fast and #### Installation Steps 1. **Clone the repository**: - ```bash # Clone from Gitee (recommended for China) git clone https://gitee.com/qingshangongzai/color_card.git - + # Or clone from GitHub git clone https://github.com/qingshangongzai/Color_Card.git - + cd color_card ``` 2. **Create virtual environment (recommended)**: - ```bash python -m venv .venv # Activate virtual environment .\.venv\Scripts\activate # Windows ``` 3. **Install dependencies**: - ```bash pip install -r requirements.txt ``` 4. **Launch the application**: - ```bash python main.py ``` - ---- +*** ## Usage @@ -426,25 +405,23 @@ Since the release of v1.0.0 on 2026-02-05, the project has maintained a fast and ### Feature Modules -|Module |Features | -|:---|:---| -|Color Extraction |Draggable pickers, multiple color spaces, one-click copy | -|Gradient Extraction |Start/end color selection, RGB/HSB/LAB interpolation, adjustable middle colors | -|Luminance Analysis |9-zone segmentation (Zone 0-8), histogram visualization, zone highlighting | -|Color Generation |5 color schemes, interactive color wheel, luminance adjustment | -|Palette Management |Save palettes, custom names, batch import/export in JSON format, support single palette ASE format export (Adobe software compatible) | -|Color Preview |8 built-in scenes, custom SVG, intelligent color mapping | -|Built-in Library |13 open-source color schemes, 661 color palettes | - +| Module | Features | +| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------ | +| Color Extraction | Draggable pickers, multiple color spaces, one-click copy | +| Gradient Extraction | Start/end color selection, RGB/HSB/LAB interpolation, adjustable middle colors | +| Luminance Analysis | 9-zone segmentation (Zone 0-8), histogram visualization, zone highlighting | +| Color Generation | 5 color schemes, interactive color wheel, luminance adjustment | +| Palette Management | Save palettes, custom names, batch import/export in JSON format, support single palette ASE format export (Adobe software compatible) | +| Color Preview | 8 built-in scenes, custom SVG, intelligent color mapping | +| Built-in Library | 13 open-source color schemes, 661 color palettes | ---- +*** ## Development Standards This project follows PEP 8 code style guidelines and adopts modular architecture design. For detailed development standards, please refer to [开发规范.md](./开发规范.md). - ---- +*** ## Contributing @@ -468,8 +445,7 @@ All contributed code must strictly follow the project's existing development sta - [开发规范.md](./开发规范.md) - Covers code organization, style, naming, and more - ---- +*** ## License Information @@ -482,49 +458,46 @@ Color Card is released under the **GNU General Public License v3.0 (GPL 3.0)** l ### License Files - **Complete Project License Information**: [LICENSE](./LICENSE) -- **GPL 3.0 Official Text**: https://www.gnu.org/licenses/gpl-3.0.html +- **GPL 3.0 Official Text**: ### Third-party Library Licenses This project uses the following third-party libraries: -|Library |License | -|:---|:---:| -|PySide6 |LGPL-3.0 | -|PySide6-Fluent-Widgets |GPL-3.0 | -|Pillow |MIT License | -|requests |Apache-2.0 | -|numpy |BSD-3-Clause | -|Open Color |MIT License | -|Tailwind CSS Colors |MIT License | -|Material Design Colors |Apache-2.0 | -|ColorBrewer |Apache-2.0 | -|Radix Colors |MIT License | -|Nord |MIT License | -|Dracula |MIT License | -|Solarized |MIT License | -|Gruvbox |MIT License | -|Catppuccin |MIT License | -|Rose Pine |MIT License | -|Tokyo Night |MIT License | -|Nice Color Palettes |MIT License | - - ---- +| Library | License | +| :--------------------- | :----------: | +| PySide6 | LGPL-3.0 | +| PySide6-Fluent-Widgets | GPL-3.0 | +| Pillow | MIT License | +| requests | Apache-2.0 | +| numpy | BSD-3-Clause | +| Open Color | MIT License | +| Tailwind CSS Colors | MIT License | +| Material Design Colors | Apache-2.0 | +| ColorBrewer | Apache-2.0 | +| Radix Colors | MIT License | +| Nord | MIT License | +| Dracula | MIT License | +| Solarized | MIT License | +| Gruvbox | MIT License | +| Catppuccin | MIT License | +| Rose Pine | MIT License | +| Tokyo Night | MIT License | +| Nice Color Palettes | MIT License | + +*** ## Contact -- **Primary Repository (Gitee)**: https://gitee.com/qingshangongzai/color_card -- **Mirror Repository (GitHub)**: https://github.com/qingshangongzai/Color_Card -- **Email**: [hxiao_studio@163.com](mailto:hxiao_studio@163.com)、[qingshangongzai@163.com](mailto:qingshangongzai@163.com) +- **Primary Repository (Gitee)**: +- **Mirror Repository (GitHub)**: +- **Email**: - ---- +*** **Disclaimer**: Color Card is for learning and research purposes only. The developers are not responsible for any consequences resulting from the use of this tool. Please use with caution. - ---- +*** **Color Card** - An all-in-one color tool and image color analysis tool for photographers and designers\ -Copyright © 2026 浮晓 HXiao Studio \ No newline at end of file +Copyright © 2026 浮晓 HXiao Studio diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py index 1df6dcd..687f526 100644 --- a/dialogs/about_dialog.py +++ b/dialogs/about_dialog.py @@ -268,6 +268,11 @@ class AboutDialog(QDialog): 项目地址:https://github.com/numpy/numpy 官网:https://numpy.org/ + • 本程序使用 PySideSix-Frameless-Window 实现对话框无边框窗口 + 版权所有:zhiyiYo + 许可证:GPLv3 + 项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window + 【开源配色方案使用说明】 • Open Color 配色方案 版权所有:heeyeun (Yeun) diff --git a/dialogs/edit_palette.py b/dialogs/edit_palette.py index be6239e..3a040aa 100644 --- a/dialogs/edit_palette.py +++ b/dialogs/edit_palette.py @@ -13,6 +13,7 @@ from qfluentwidgets import ( LineEdit, PrimaryPushButton, PushButton, ToolButton, FluentIcon, ScrollArea, isDarkTheme, qconfig ) +from qframelesswindow import FramelessDialog # 项目模块导入 from core import ( @@ -21,7 +22,7 @@ from core import ( rgb_to_cmyk, cmyk_to_rgb, get_color_info ) from core.config import get_config_manager -from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme +from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal from utils.theme_colors import get_dialog_bg_color, get_text_color, get_border_color @@ -242,7 +243,7 @@ class ColorModeSliders(QWidget): # 模式标题 title = QLabel(f"{self._mode}:") - title.setStyleSheet(f"color: {get_text_color().name()}; font-size: 12px;") + title.setStyleSheet(f"color: {get_text_color().name()}; font-size: 12px; background: transparent;") layout.addWidget(title) # 创建3个滑块 @@ -260,7 +261,7 @@ class ColorModeSliders(QWidget): label = QLabel(f"{min_val}{unit}") label.setFixedWidth(45) label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) - label.setStyleSheet(f"color: {get_text_color().name()}; font-size: 11px;") + label.setStyleSheet(f"color: {get_text_color().name()}; font-size: 11px; background: transparent;") self._labels.append((label, unit)) row_layout.addWidget(label) @@ -847,7 +848,7 @@ class ColorInputRow(QWidget): def _update_styles(self): """更新样式以适配主题""" text_color = get_text_color() - self.index_label.setStyleSheet(f"color: {text_color.name()}; font-size: 13px;") + self.index_label.setStyleSheet(f"color: {text_color.name()}; font-size: 13px; background: transparent;") self._update_preview_style(self._color_info) def _update_preview_style(self, color_info): @@ -948,7 +949,7 @@ class ColorInputRow(QWidget): self._update_preview_style(color_info) -class EditPaletteDialog(QDialog): +class EditPaletteDialog(FramelessDialog): """编辑配色对话框""" def __init__(self, default_name="", palette_data=None, parent=None, show_name_input=True): @@ -964,25 +965,21 @@ class EditPaletteDialog(QDialog): self._palette_data = palette_data self._is_edit_mode = palette_data is not None self._show_name_input = show_name_input - self.setWindowTitle("编辑配色" if self._is_edit_mode else "添加配色") - self.setFixedSize(300, 400) self._default_name = default_name self._color_rows = [] + # 设置窗口标题 + self.setWindowTitle("编辑配色" if self._is_edit_mode else "添加配色") + self.setFixedSize(300, 400) + # 设置窗口图标 self.setWindowIcon(load_icon_universal()) - # 设置窗口标志 - self.setWindowFlags( - Qt.WindowType.Window | - Qt.WindowType.WindowTitleHint | - Qt.WindowType.WindowCloseButtonHint | - Qt.WindowType.CustomizeWindowHint - ) + # 设置自定义标题栏 + self._setup_title_bar() - # 设置窗口背景色 - bg_color = get_dialog_bg_color() - self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}") + # 初始化样式(包含窗口背景色和标题颜色) + self._update_styles() self.setup_ui() @@ -990,12 +987,9 @@ class EditPaletteDialog(QDialog): if self._is_edit_mode: self._load_palette_data() - # 修复任务栏图标 - QTimer.singleShot(100, lambda: self._fix_taskbar_icon()) - # 监听主题变化 self._theme_connection = qconfig.themeChangedFinished.connect( - self._update_title_bar_theme + self._update_styles ) def closeEvent(self, event): @@ -1006,17 +1000,119 @@ class EditPaletteDialog(QDialog): pass super().closeEvent(event) + def _setup_title_bar(self): + """设置自定义 Fluent Design 风格标题栏""" + from PySide6.QtWidgets import QLabel + from PySide6.QtCore import Qt + from PySide6.QtGui import QPixmap + from utils.icon import get_icon_path + from utils.theme_colors import get_text_color + + # 获取 FramelessDialog 内置的标题栏 + title_bar = self.titleBar + h_layout = title_bar.layout() + if not h_layout: + return + + # 获取主题颜色 + text_color = get_text_color() + text_color_str = text_color.name() + + # 创建标题栏控件:左边距、Logo、间距、标题 + # 1. 左边距 + left_spacer = QLabel(title_bar) + left_spacer.setFixedWidth(10) + + # 2. Logo + icon_path = get_icon_path() + logo_label = None + if icon_path: + logo_label = QLabel(title_bar) + pixmap = QPixmap(icon_path) + if not pixmap.isNull(): + logo_label.setPixmap(pixmap.scaled(20, 20, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) + + # 3. Logo和标题之间的间距 + spacer_label = QLabel(title_bar) + spacer_label.setFixedWidth(8) + + # 4. 标题 + title_label = QLabel(self.windowTitle(), title_bar) + title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;") + + # 按顺序插入到布局开头(倒序插入) + for widget in [title_label, spacer_label, logo_label, left_spacer]: + if widget: + h_layout.insertWidget(0, widget) + h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter) + + # 保存标题标签引用,以便主题切换时更新 + self._title_label = title_label + + def _update_close_button_color(self, text_color): + """更新关闭按钮颜色以适配主题 + + Args: + text_color: 文本颜色 (QColor) + """ + from utils.theme_colors import ( + get_close_button_hover_bg_color, + get_close_button_hover_color, + get_close_button_pressed_color + ) + + title_bar = self.titleBar + if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn: + close_btn = title_bar.closeBtn + # 设置正常状态颜色 + close_btn.setNormalColor(text_color) + # 设置悬停状态颜色 + close_btn.setHoverColor(get_close_button_hover_color()) + close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color()) + # 设置按下状态颜色 + close_btn.setPressedColor(get_close_button_pressed_color()) + + def _update_styles(self): + """更新样式以适配主题""" + from PySide6.QtGui import QPalette, QColor + from utils.theme_colors import get_text_color, get_dialog_bg_color + + text_color = get_text_color() + text_color_str = text_color.name() + bg_color = get_dialog_bg_color() + + # 设置 QLabel 文字颜色样式表 + self.setStyleSheet(f""" + QLabel {{ + color: {text_color_str}; + }} + """) + + # 使用 QPalette 设置窗口背景色 + palette = self.palette() + palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name())) + self.setPalette(palette) + self.setAutoFillBackground(True) + + # 更新标题标签颜色(如果存在) + if hasattr(self, '_title_label') and self._title_label: + self._title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;") + + # 更新关闭按钮颜色 + self._update_close_button_color(text_color) + def setup_ui(self): """设置界面布局""" layout = QVBoxLayout(self) - layout.setContentsMargins(20, 20, 20, 20) + # 顶部边距设置为40,为无边框窗口的标题栏留出空间 + layout.setContentsMargins(20, 40, 20, 20) layout.setSpacing(15) # 名称输入区域(根据参数决定是否显示) if self._show_name_input: name_layout = QHBoxLayout() name_label = QLabel("配色名称:") - name_label.setStyleSheet(f"color: {get_text_color().name()}; font-size: 13px;") + name_label.setStyleSheet(f"color: {get_text_color().name()}; font-size: 13px; background: transparent;") name_layout.addWidget(name_label) self.name_input = LineEdit() @@ -1035,7 +1131,7 @@ class EditPaletteDialog(QDialog): # 颜色列表标题 colors_title = QLabel(tr('dialogs.edit_palette.colors_title')) - colors_title.setStyleSheet(f"color: {get_text_color().name()}; font-size: 13px;") + colors_title.setStyleSheet(f"color: {get_text_color().name()}; font-size: 13px; background: transparent;") layout.addWidget(colors_title) # 颜色输入区域(可滚动) @@ -1225,10 +1321,6 @@ class EditPaletteDialog(QDialog): """ return getattr(self, '_palette_data', None) - def _update_title_bar_theme(self): - """更新标题栏主题以适配当前主题""" - set_window_title_bar_theme(self, isDarkTheme()) - def _fix_taskbar_icon(self): """修复任务栏图标""" try: @@ -1237,8 +1329,3 @@ class EditPaletteDialog(QDialog): except RuntimeError: # 对象已被销毁 pass - - def showEvent(self, event): - """窗口显示事件 - 在显示前设置标题栏主题避免闪烁""" - self._update_title_bar_theme() - super().showEvent(event) diff --git a/file/LICENSE.html b/file/LICENSE.html index eb89fc4..ca60aba 100644 --- a/file/LICENSE.html +++ b/file/LICENSE.html @@ -689,9 +689,24 @@ + +
+

6. PySideSix-Frameless-Window

+
+

版权所有:zhiyiYo

+

项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window

+

许可证:GNU General Public License v3.0

+
+
+
+

PySideSix-Frameless-Window 使用 GPLv3 许可证。由于本项目主许可证也为 GPLv3,完整的 GPLv3 许可证文本请参考本文档前面的GNU GENERAL PUBLIC LICENSE Version 3章节。

+
+
+
+
-

6. Open Color

+

7. Open Color

版权所有:heeyeun (Yeun)

项目地址:https://github.com/yeun/open-color

@@ -714,7 +729,7 @@
-

7. Nice Color Palettes

+

8. Nice Color Palettes

版权所有:Jam3

项目地址:https://github.com/Experience-Monks/nice-color-palettes

@@ -748,7 +763,7 @@
-

8. Tailwind CSS Colors

+

9. Tailwind CSS Colors

版权所有:Tailwind Labs, Inc.

项目地址:https://github.com/tailwindlabs/tailwindcss

@@ -771,7 +786,7 @@
-

9. Material Design Colors

+

10. Material Design Colors

版权所有:Google LLC

项目地址:https://m3.material.io/styles/color/system/overview

@@ -787,7 +802,7 @@
-

10. ColorBrewer

+

11. ColorBrewer

版权所有:Cynthia Brewer

项目地址:https://colorbrewer2.org/

@@ -804,7 +819,7 @@
-

11. Radix UI Colors

+

12. Radix UI Colors

版权所有:WorkOS

项目地址:https://github.com/radix-ui/colors

@@ -828,7 +843,7 @@
-

12. Nord

+

13. Nord

版权所有:Sven Greb

项目地址:https://github.com/arcticicestudio/nord

@@ -851,7 +866,7 @@
-

13. Dracula

+

14. Dracula

版权所有:Dracula Theme contributors

官网:https://draculatheme.com/

@@ -873,7 +888,7 @@
-

14. Rosé Pine

+

15. Rosé Pine

版权所有:Rosé Pine 团队

项目地址:https://github.com/rose-pine/rose-pine-theme

@@ -896,7 +911,7 @@
-

15. Solarized

+

16. Solarized

版权所有:Ethan Schoonover

项目地址:https://github.com/altercation/solarized

@@ -916,7 +931,7 @@
-

16. Catppuccin

+

17. Catppuccin

版权所有:Catppuccin 团队

项目地址:https://github.com/catppuccin/catppuccin

@@ -939,7 +954,7 @@
-

17. Gruvbox (MIT/X11 License)

+

18. Gruvbox (MIT/X11 License)

版权所有:Pavel Pertsev

项目地址:https://github.com/morhetz/gruvbox

@@ -961,7 +976,7 @@
-

18. Tokyo Night (MIT License)

+

19. Tokyo Night (MIT License)

版权所有:enkia

项目地址:https://github.com/enkia/tokyo-night-vscode-theme

diff --git a/requirements.txt b/requirements.txt index dd0acbc..2863ba3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ PySide6>=6.0.0 PySide6-Fluent-Widgets>=1.0.0 +PySideSix-Frameless-Window>=0.1.0 Pillow>=9.0.0 requests>=2.32.0 numpy>=1.21.0 diff --git a/ui/color_preview.py b/ui/color_preview.py index b0b5b37..860e810 100644 --- a/ui/color_preview.py +++ b/ui/color_preview.py @@ -306,7 +306,7 @@ class ColorDotBar(QWidget): } # 打开编辑对话框(预览配色场景不显示名称输入) - dialog = EditPaletteDialog(palette_data=palette_data, parent=self, show_name_input=False) + dialog = EditPaletteDialog(palette_data=palette_data, parent=self.window(), show_name_input=False) if dialog.exec() == EditPaletteDialog.DialogCode.Accepted: new_palette_data = dialog.get_palette_data() if new_palette_data and 'colors' in new_palette_data: diff --git a/utils/theme_colors.py b/utils/theme_colors.py index 50d7137..cc77ce2 100644 --- a/utils/theme_colors.py +++ b/utils/theme_colors.py @@ -248,6 +248,21 @@ def get_dialog_bg_color(): return QColor(32, 32, 32) if isDarkTheme() else QColor(255, 255, 255) +def get_close_button_hover_bg_color(): + """获取关闭按钮悬停背景颜色""" + return QColor(196, 43, 28) if isDarkTheme() else QColor(232, 17, 35) + + +def get_close_button_hover_color(): + """获取关闭按钮悬停图标颜色""" + return QColor(255, 255, 255) + + +def get_close_button_pressed_color(): + """获取关闭按钮按下图标颜色""" + return QColor(255, 255, 255) + + # ========== Zone框颜色 ========== def get_zone_background_color(): """获取Zone框背景颜色""" diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" index 796a9a4..362593d 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" @@ -697,3 +697,421 @@ def test_queued_connection_safety(qtbot): | terminate() 崩溃 | 使用 cancel() + wait() 优雅退出 | | 线程销毁警告 | 服务类添加 __del__ 析构函数 | | 信号未断开崩溃 | closeEvent 中断开全局信号 | + +--- + +## 10. PySideSix-Frameless-Window 无边框窗口改造经验 + +### 10.1 改造背景与目标 + +将传统 `QDialog` 改造为 `FramelessDialog`,实现 Fluent Design 风格的自定义标题栏,获得更现代、统一的界面效果。 + +### 10.2 核心改造步骤 + +#### 10.2.1 依赖安装 + +```bash +pip install PySideSix-Frameless-Window +``` + +在 `requirements.txt` 中添加: +``` +PySideSix-Frameless-Window>=0.1.0 +``` + +#### 10.2.2 导入变更 + +```python +# 原有导入 +from PySide6.QtWidgets import QDialog + +# 新增导入 +from qframelesswindow import FramelessDialog +``` + +#### 10.2.3 类继承变更 + +```python +# 从 +class EditPaletteDialog(QDialog): + +# 改为 +class EditPaletteDialog(FramelessDialog): +``` + +#### 10.2.4 初始化方法调整 + +**移除的代码:** +- `setWindowFlags()` 调用(FramelessDialog 已处理) +- `set_window_title_bar_theme()` 相关代码(不再使用 Windows DWM API) +- `showEvent` 中的标题栏主题更新逻辑 + +**新增的代码:** +```python +def __init__(self, ...): + super().__init__(parent) + + # 设置窗口标题 + self.setWindowTitle("窗口标题") + + # 设置自定义标题栏 + self._setup_title_bar() + + # 初始化样式(包含窗口背景色和标题颜色) + self._update_styles() + + # 监听主题变化 + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) +``` + +### 10.3 自定义标题栏实现 + +#### 10.3.1 标题栏布局结构 + +``` +[左边距(10px)] [Logo] [间距(8px)] [标题] [Stretch] [最小化] [最大化] [关闭] +``` + +#### 10.3.2 核心实现代码 + +```python +def _setup_title_bar(self): + """设置自定义 Fluent Design 风格标题栏""" + from PySide6.QtWidgets import QLabel + from PySide6.QtCore import Qt + from PySide6.QtGui import QPixmap + from utils.icon import get_icon_path + from utils.theme_colors import get_text_color + + # 获取 FramelessDialog 内置的标题栏 + title_bar = self.titleBar + h_layout = title_bar.layout() + if not h_layout: + return + + # 获取主题颜色 + text_color = get_text_color() + text_color_str = text_color.name() + + # 创建标题栏控件 + # 1. 左边距 + left_spacer = QLabel(title_bar) + left_spacer.setFixedWidth(10) + + # 2. Logo + icon_path = get_icon_path() + logo_label = None + if icon_path: + logo_label = QLabel(title_bar) + pixmap = QPixmap(icon_path) + if not pixmap.isNull(): + logo_label.setPixmap(pixmap.scaled( + 20, 20, + Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation + )) + + # 3. Logo和标题之间的间距 + spacer_label = QLabel(title_bar) + spacer_label.setFixedWidth(8) + + # 4. 标题 + title_label = QLabel(self.windowTitle(), title_bar) + title_label.setStyleSheet( + f"color: {text_color_str}; font-size: 13px; font-weight: 500;" + ) + + # 按顺序插入到布局开头(倒序插入) + for widget in [title_label, spacer_label, logo_label, left_spacer]: + if widget: + h_layout.insertWidget(0, widget) + h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter) + + # 保存标题标签引用,以便主题切换时更新 + self._title_label = title_label +``` + +### 10.4 主题适配关键经验 + +#### 10.4.1 背景色设置方式 + +**❌ 错误方式(样式表不生效):** +```python +self.setStyleSheet(f"FramelessDialog {{ background-color: {bg_color_str}; }}") +``` + +**✅ 正确方式(使用 QPalette):** +```python +from PySide6.QtGui import QPalette, QColor + +palette = self.palette() +palette.setColor(QPalette.ColorRole.Window, QColor(bg_color_str)) +self.setPalette(palette) +self.setAutoFillBackground(True) +``` + +#### 10.4.2 关闭按钮颜色更新 + +```python +def _update_close_button_color(self, text_color): + """更新关闭按钮颜色以适配主题""" + from utils.theme_colors import ( + get_close_button_hover_bg_color, + get_close_button_hover_color, + get_close_button_pressed_color + ) + + title_bar = self.titleBar + if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn: + close_btn = title_bar.closeBtn + # 设置正常状态颜色 + close_btn.setNormalColor(text_color) + # 设置悬停状态颜色 + close_btn.setHoverColor(get_close_button_hover_color()) + close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color()) + # 设置按下状态颜色 + close_btn.setPressedColor(get_close_button_pressed_color()) +``` + +#### 10.4.3 完整样式更新方法 + +```python +def _update_styles(self): + """更新样式以适配主题""" + from PySide6.QtGui import QPalette, QColor + from utils.theme_colors import get_text_color, get_dialog_bg_color + + text_color = get_text_color() + text_color_str = text_color.name() + bg_color = get_dialog_bg_color() + + # 设置 QLabel 文字颜色样式表 + self.setStyleSheet(f""" + QLabel {{ + color: {text_color_str}; + }} + """) + + # 使用 QPalette 设置窗口背景色 + palette = self.palette() + palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name())) + self.setPalette(palette) + self.setAutoFillBackground(True) + + # 更新标题标签颜色 + if hasattr(self, '_title_label') and self._title_label: + self._title_label.setStyleSheet( + f"color: {text_color_str}; font-size: 13px; font-weight: 500;" + ) + + # 更新关闭按钮颜色 + self._update_close_button_color(text_color) +``` + +### 10.5 布局调整经验 + +#### 10.5.1 为标题栏留出空间 + +```python +def setup_ui(self): + """设置界面布局""" + layout = QVBoxLayout(self) + # 顶部边距设置为40,为无边框窗口的标题栏留出空间 + layout.setContentsMargins(20, 40, 20, 20) + layout.setSpacing(15) +``` + +#### 10.5.2 标题栏控件插入技巧 + +**关键原则:倒序插入,保证正序显示** + +```python +# 期望的最终顺序:左边距 → Logo → 间距 → 标题 +# 倒序插入:标题 → 间距 → Logo → 左边距 +for widget in [title_label, spacer_label, logo_label, left_spacer]: + if widget: + h_layout.insertWidget(0, widget) +``` + +### 10.6 常见问题与解决方案 + +| 问题 | 原因 | 解决方案 | +|------|------|----------| +| 标题栏和内容重叠 | 未为标题栏留出空间 | 设置 `layout.setContentsMargins(20, 40, 20, 20)` | +| 背景色不生效 | FramelessDialog 不使用样式表设置背景 | 使用 `QPalette` + `setAutoFillBackground(True)` | +| 标题颜色不随主题切换 | 样式表未应用到标题栏控件 | 直接为标题标签设置样式表,并在 `_update_styles` 中更新 | +| 关闭按钮颜色不随主题切换 | 未设置关闭按钮的颜色属性 | 使用 `closeBtn.setNormalColor()` 等方法设置 | +| Logo/标题/按钮挤在一起 | 清除了原有布局的所有项 | 保留原有布局,只在开头插入新控件 | +| 标题紧贴左边框 | 未设置左边距 | 添加固定宽度的占位符 QLabel | +| 从子控件打开对话框时背景色异常 | 父窗口不是顶层窗口 | 使用 `parent=self.window()` 而不是 `parent=self` | + +### 10.7 主题颜色函数(theme_colors.py) + +在 `utils/theme_colors.py` 中添加关闭按钮颜色函数: + +```python +def get_close_button_hover_bg_color(): + """获取关闭按钮悬停背景颜色""" + return QColor(196, 43, 28) if isDarkTheme() else QColor(232, 17, 35) + + +def get_close_button_hover_color(): + """获取关闭按钮悬停图标颜色""" + return QColor(255, 255, 255) + + +def get_close_button_pressed_color(): + """获取关闭按钮按下图标颜色""" + return QColor(255, 255, 255) +``` + +### 10.8 改造检查清单 + +- [ ] 安装 PySideSix-Frameless-Window 依赖 +- [ ] 修改类继承为 `FramelessDialog` +- [ ] 移除 `setWindowFlags` 调用 +- [ ] 移除 `set_window_title_bar_theme` 相关代码 +- [ ] 移除 `_fix_taskbar_icon` 调用(FramelessDialog 自动处理) +- [ ] 实现 `_setup_title_bar()` 方法 +- [ ] 实现 `_update_styles()` 方法 +- [ ] 实现 `_update_close_button_color()` 方法 +- [ ] 在 `theme_colors.py` 中添加关闭按钮颜色函数 +- [ ] 调整布局边距为标题栏留出空间 +- [ ] 监听主题变化信号 +- [ ] 在 `closeEvent` 中断开信号连接 +- [ ] 确保对话框父窗口是顶层窗口(`parent=self.window()`) +- [ ] 测试深色/浅色主题切换 +- [ ] 测试窗口拖动和调整大小功能 + +### 10.9 后续对话框改造模板 + +```python +from qframelesswindow import FramelessDialog +from PySide6.QtGui import QPalette, QColor +from utils.theme_colors import get_text_color, get_dialog_bg_color + +class CustomDialog(FramelessDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("对话框标题") + self.setFixedSize(400, 300) + + # 设置自定义标题栏 + self._setup_title_bar() + + # 初始化样式 + self._update_styles() + + # 设置界面 + self.setup_ui() + + # 监听主题变化 + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) + + def _setup_title_bar(self): + """参考 10.3.2 实现""" + from PySide6.QtWidgets import QLabel + from PySide6.QtCore import Qt + from PySide6.QtGui import QPixmap + from utils.icon import get_icon_path + from utils.theme_colors import get_text_color + + title_bar = self.titleBar + h_layout = title_bar.layout() + if not h_layout: + return + + text_color = get_text_color() + text_color_str = text_color.name() + + # 创建标题栏控件 + left_spacer = QLabel(title_bar) + left_spacer.setFixedWidth(10) + + icon_path = get_icon_path() + logo_label = None + if icon_path: + logo_label = QLabel(title_bar) + pixmap = QPixmap(icon_path) + if not pixmap.isNull(): + logo_label.setPixmap(pixmap.scaled( + 20, 20, + Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation + )) + + spacer_label = QLabel(title_bar) + spacer_label.setFixedWidth(8) + + title_label = QLabel(self.windowTitle(), title_bar) + title_label.setStyleSheet( + f"color: {text_color_str}; font-size: 13px; font-weight: 500;" + ) + + for widget in [title_label, spacer_label, logo_label, left_spacer]: + if widget: + h_layout.insertWidget(0, widget) + h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter) + + self._title_label = title_label + + def _update_close_button_color(self, text_color): + """参考 10.4.2 实现""" + from utils.theme_colors import ( + get_close_button_hover_bg_color, + get_close_button_hover_color, + get_close_button_pressed_color + ) + + title_bar = self.titleBar + if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn: + close_btn = title_bar.closeBtn + close_btn.setNormalColor(text_color) + close_btn.setHoverColor(get_close_button_hover_color()) + close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color()) + close_btn.setPressedColor(get_close_button_pressed_color()) + + def _update_styles(self): + """参考 10.4.3 实现""" + text_color = get_text_color() + text_color_str = text_color.name() + bg_color = get_dialog_bg_color() + + self.setStyleSheet(f""" + QLabel {{ + color: {text_color_str}; + }} + """) + + palette = self.palette() + palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name())) + self.setPalette(palette) + self.setAutoFillBackground(True) + + if hasattr(self, '_title_label') and self._title_label: + self._title_label.setStyleSheet( + f"color: {text_color_str}; font-size: 13px; font-weight: 500;" + ) + + self._update_close_button_color(text_color) + + def setup_ui(self): + """设置界面""" + layout = QVBoxLayout(self) + layout.setContentsMargins(20, 40, 20, 20) # 注意顶部边距 + # ... 其他控件 + + def closeEvent(self, event): + """关闭事件:断开信号连接""" + if hasattr(self, '_theme_connection'): + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + delattr(self, '_theme_connection') + super().closeEvent(event) +``` -- Gitee From 48a06d1ee6f602ecc15137f418eacc4faf0cc146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Mon, 16 Mar 2026 21:16:15 +0800 Subject: [PATCH 02/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E6=8F=90=E5=8F=96?= =?UTF-8?q?=20BaseFramelessDialog=20=E5=9F=BA=E7=B1=BB=E5=B9=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=9B=B8=E5=85=B3=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 dialogs/base_frameless_dialog.py,封装无边框对话框通用功能 - 修改 dialogs/edit_palette.py,ColorPickerDialog 和 EditPaletteDialog 继承 BaseFramelessDialog - 修改 dialogs/__init__.py,导出 BaseFramelessDialog - 修改 ui/gradient_extract.py,修复 ColorPickerDialog 父窗口为顶层窗口 - 更新开发规范,新增 5.1.1 节无边框对话框基类使用规范 - 更新开发规范项目结构(1.3节),添加 base_frameless_dialog.py - 更新开发规范 22.2 更新记录,添加版本 3.42 - 更新开发经验总结,新增 10.1.1 节推荐使用 BaseFramelessDialog 基类 - 更新开发经验总结 10.8 改造检查清单,区分新对话框和旧对话框迁移 - 更新开发经验总结 10.9 改造模板,提供简化版基类使用模板 --- dialogs/__init__.py | 2 + dialogs/base_frameless_dialog.py | 141 +++++++++++++ dialogs/edit_palette.py | 154 ++------------ ui/gradient_extract.py | 3 +- ...17\351\252\214\346\200\273\347\273\223.md" | 195 +++++++++--------- ...00\345\217\221\350\247\204\350\214\203.md" | 99 +++++++++ 6 files changed, 356 insertions(+), 238 deletions(-) create mode 100644 dialogs/base_frameless_dialog.py diff --git a/dialogs/__init__.py b/dialogs/__init__.py index a0bf7e5..5ab8219 100644 --- a/dialogs/__init__.py +++ b/dialogs/__init__.py @@ -1,6 +1,7 @@ """对话框模块""" from .about_dialog import AboutDialog +from .base_frameless_dialog import BaseFramelessDialog from .colorblind_dialog import ColorblindPreviewDialog from .contrast_dialog import ContrastCheckDialog from .edit_palette import EditPaletteDialog, ColorPickerDialog @@ -9,6 +10,7 @@ from .update_dialog import UpdateAvailableDialog __all__ = [ 'AboutDialog', + 'BaseFramelessDialog', 'ColorblindPreviewDialog', 'ColorPickerDialog', 'ContrastCheckDialog', diff --git a/dialogs/base_frameless_dialog.py b/dialogs/base_frameless_dialog.py new file mode 100644 index 0000000..ad35f10 --- /dev/null +++ b/dialogs/base_frameless_dialog.py @@ -0,0 +1,141 @@ +# 第三方库导入 +from PySide6.QtWidgets import QLabel +from PySide6.QtCore import Qt +from PySide6.QtGui import QPixmap, QPalette, QColor +from qframelesswindow import FramelessDialog + +# 项目模块导入 +from utils.icon import get_icon_path +from utils.theme_colors import ( + get_text_color, get_dialog_bg_color, + get_close_button_hover_bg_color, + get_close_button_hover_color, + get_close_button_pressed_color +) + + +class BaseFramelessDialog(FramelessDialog): + """无边框对话框基类 + + 提供统一的 Fluent Design 风格标题栏和主题适配功能。 + 子类只需调用 _setup_title_bar() 和 _update_styles() 即可。 + + 使用示例: + class MyDialog(BaseFramelessDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("我的对话框") + self.setFixedSize(400, 300) + + # 设置自定义标题栏 + self._setup_title_bar() + + # 初始化样式 + self._update_styles() + + # 设置界面 + self.setup_ui() + + # 监听主题变化 + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) + + def closeEvent(self, event): + # 断开信号连接 + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + super().closeEvent(event) + """ + + def _setup_title_bar(self): + """设置自定义 Fluent Design 风格标题栏""" + # 获取 FramelessDialog 内置的标题栏 + title_bar = self.titleBar + h_layout = title_bar.layout() + if not h_layout: + return + + # 获取主题颜色 + text_color = get_text_color() + text_color_str = text_color.name() + + # 创建标题栏控件:左边距、Logo、间距、标题 + # 1. 左边距 + left_spacer = QLabel(title_bar) + left_spacer.setFixedWidth(10) + + # 2. Logo + icon_path = get_icon_path() + logo_label = None + if icon_path: + logo_label = QLabel(title_bar) + pixmap = QPixmap(icon_path) + if not pixmap.isNull(): + logo_label.setPixmap(pixmap.scaled( + 20, 20, + Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation + )) + + # 3. Logo和标题之间的间距 + spacer_label = QLabel(title_bar) + spacer_label.setFixedWidth(8) + + # 4. 标题 + title_label = QLabel(self.windowTitle(), title_bar) + title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;") + + # 按顺序插入到布局开头(倒序插入) + for widget in [title_label, spacer_label, logo_label, left_spacer]: + if widget: + h_layout.insertWidget(0, widget) + h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter) + + # 保存标题标签引用,以便主题切换时更新 + self._title_label = title_label + + def _update_close_button_color(self, text_color): + """更新关闭按钮颜色以适配主题 + + Args: + text_color: 文本颜色 (QColor) + """ + title_bar = self.titleBar + if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn: + close_btn = title_bar.closeBtn + # 设置正常状态颜色 + close_btn.setNormalColor(text_color) + # 设置悬停状态颜色 + close_btn.setHoverColor(get_close_button_hover_color()) + close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color()) + # 设置按下状态颜色 + close_btn.setPressedColor(get_close_button_pressed_color()) + + def _update_styles(self): + """更新样式以适配主题""" + text_color = get_text_color() + text_color_str = text_color.name() + bg_color = get_dialog_bg_color() + + # 设置 QLabel 文字颜色样式表 + self.setStyleSheet(f""" + QLabel {{ + color: {text_color_str}; + }} + """) + + # 使用 QPalette 设置窗口背景色 + palette = self.palette() + palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name())) + self.setPalette(palette) + self.setAutoFillBackground(True) + + # 更新标题标签颜色(如果存在) + if hasattr(self, '_title_label') and self._title_label: + self._title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;") + + # 更新关闭按钮颜色 + self._update_close_button_color(text_color) diff --git a/dialogs/edit_palette.py b/dialogs/edit_palette.py index 3a040aa..68cf950 100644 --- a/dialogs/edit_palette.py +++ b/dialogs/edit_palette.py @@ -11,10 +11,8 @@ from PySide6.QtWidgets import ( from PySide6.QtGui import QColor, QPainter, QLinearGradient, QBrush, QPen, QMouseEvent from qfluentwidgets import ( LineEdit, PrimaryPushButton, PushButton, ToolButton, FluentIcon, - ScrollArea, isDarkTheme, qconfig + ScrollArea, qconfig ) -from qframelesswindow import FramelessDialog - # 项目模块导入 from core import ( hex_to_rgb, rgb_to_hex, rgb_to_hsb, hsb_to_rgb, @@ -22,9 +20,12 @@ from core import ( rgb_to_cmyk, cmyk_to_rgb, get_color_info ) from core.config import get_config_manager -from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal +from utils import tr, load_icon_universal from utils.theme_colors import get_dialog_bg_color, get_text_color, get_border_color +# 对话框模块导入 +from .base_frameless_dialog import BaseFramelessDialog + # ==================== 颜色选择器对话框组件 ==================== @@ -389,7 +390,7 @@ class ColorModeSliders(QWidget): self._sliders[3].set_gradient(gradient_k) -class ColorPickerDialog(QDialog): +class ColorPickerDialog(BaseFramelessDialog): """颜色选择器对话框""" def __init__(self, initial_color: Optional[Tuple[int, int, int]] = None, parent=None): @@ -402,27 +403,18 @@ class ColorPickerDialog(QDialog): self.setWindowTitle(tr('dialogs.color_picker.title')) self.setFixedSize(520, 420) - # 设置窗口标志 - self.setWindowFlags( - Qt.WindowType.Window | - Qt.WindowType.WindowTitleHint | - Qt.WindowType.WindowCloseButtonHint | - Qt.WindowType.CustomizeWindowHint - ) + # 设置自定义标题栏 + self._setup_title_bar() - # 设置背景色 - bg_color = get_dialog_bg_color() - self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}") + # 初始化样式(包含窗口背景色和标题颜色) + self._update_styles() self.setup_ui() self._update_from_rgb() - # 修复任务栏图标 - QTimer.singleShot(100, lambda: self._fix_taskbar_icon()) - # 监听主题变化 self._theme_connection = qconfig.themeChangedFinished.connect( - self._update_title_bar_theme + self._update_styles ) def closeEvent(self, event): @@ -436,7 +428,8 @@ class ColorPickerDialog(QDialog): def setup_ui(self): """设置界面布局""" main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(20, 20, 20, 20) + # 顶部边距设置为40,为无边框窗口的标题栏留出空间 + main_layout.setContentsMargins(20, 40, 20, 20) main_layout.setSpacing(15) # 内容区域(左右分割) @@ -736,22 +729,7 @@ class ColorPickerDialog(QDialog): """获取选择的颜色信息""" return self._color_info - def _update_title_bar_theme(self): - """更新标题栏主题""" - set_window_title_bar_theme(self, isDarkTheme()) - def _fix_taskbar_icon(self): - """修复任务栏图标""" - try: - if self and self.isVisible(): - fix_windows_taskbar_icon_for_window(self) - except RuntimeError: - pass - - def showEvent(self, event): - """窗口显示事件""" - self._update_title_bar_theme() - super().showEvent(event) class ColorInputRow(QWidget): @@ -839,7 +817,8 @@ class ColorInputRow(QWidget): else: initial = (128, 128, 128) # 默认灰色 - dialog = ColorPickerDialog(initial, self) + # 使用顶层窗口作为父窗口,避免背景色异常和两套窗口控制器 + dialog = ColorPickerDialog(initial, self.window()) if dialog.exec() == QDialog.DialogCode.Accepted: color_info = dialog.get_color_info() if color_info: @@ -949,7 +928,7 @@ class ColorInputRow(QWidget): self._update_preview_style(color_info) -class EditPaletteDialog(FramelessDialog): +class EditPaletteDialog(BaseFramelessDialog): """编辑配色对话框""" def __init__(self, default_name="", palette_data=None, parent=None, show_name_input=True): @@ -1000,107 +979,6 @@ class EditPaletteDialog(FramelessDialog): pass super().closeEvent(event) - def _setup_title_bar(self): - """设置自定义 Fluent Design 风格标题栏""" - from PySide6.QtWidgets import QLabel - from PySide6.QtCore import Qt - from PySide6.QtGui import QPixmap - from utils.icon import get_icon_path - from utils.theme_colors import get_text_color - - # 获取 FramelessDialog 内置的标题栏 - title_bar = self.titleBar - h_layout = title_bar.layout() - if not h_layout: - return - - # 获取主题颜色 - text_color = get_text_color() - text_color_str = text_color.name() - - # 创建标题栏控件:左边距、Logo、间距、标题 - # 1. 左边距 - left_spacer = QLabel(title_bar) - left_spacer.setFixedWidth(10) - - # 2. Logo - icon_path = get_icon_path() - logo_label = None - if icon_path: - logo_label = QLabel(title_bar) - pixmap = QPixmap(icon_path) - if not pixmap.isNull(): - logo_label.setPixmap(pixmap.scaled(20, 20, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) - - # 3. Logo和标题之间的间距 - spacer_label = QLabel(title_bar) - spacer_label.setFixedWidth(8) - - # 4. 标题 - title_label = QLabel(self.windowTitle(), title_bar) - title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;") - - # 按顺序插入到布局开头(倒序插入) - for widget in [title_label, spacer_label, logo_label, left_spacer]: - if widget: - h_layout.insertWidget(0, widget) - h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter) - - # 保存标题标签引用,以便主题切换时更新 - self._title_label = title_label - - def _update_close_button_color(self, text_color): - """更新关闭按钮颜色以适配主题 - - Args: - text_color: 文本颜色 (QColor) - """ - from utils.theme_colors import ( - get_close_button_hover_bg_color, - get_close_button_hover_color, - get_close_button_pressed_color - ) - - title_bar = self.titleBar - if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn: - close_btn = title_bar.closeBtn - # 设置正常状态颜色 - close_btn.setNormalColor(text_color) - # 设置悬停状态颜色 - close_btn.setHoverColor(get_close_button_hover_color()) - close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color()) - # 设置按下状态颜色 - close_btn.setPressedColor(get_close_button_pressed_color()) - - def _update_styles(self): - """更新样式以适配主题""" - from PySide6.QtGui import QPalette, QColor - from utils.theme_colors import get_text_color, get_dialog_bg_color - - text_color = get_text_color() - text_color_str = text_color.name() - bg_color = get_dialog_bg_color() - - # 设置 QLabel 文字颜色样式表 - self.setStyleSheet(f""" - QLabel {{ - color: {text_color_str}; - }} - """) - - # 使用 QPalette 设置窗口背景色 - palette = self.palette() - palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name())) - self.setPalette(palette) - self.setAutoFillBackground(True) - - # 更新标题标签颜色(如果存在) - if hasattr(self, '_title_label') and self._title_label: - self._title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;") - - # 更新关闭按钮颜色 - self._update_close_button_color(text_color) - def setup_ui(self): """设置界面布局""" layout = QVBoxLayout(self) diff --git a/ui/gradient_extract.py b/ui/gradient_extract.py index 271da41..070b2bf 100644 --- a/ui/gradient_extract.py +++ b/ui/gradient_extract.py @@ -584,7 +584,8 @@ class GradientExtractInterface(QWidget): # 将HEX转换为RGB r, g, b = self._hex_to_rgb(current_color) - dialog = ColorPickerDialog((r, g, b), self) + # 使用顶层窗口作为父窗口,避免背景色异常和两套窗口控制器 + dialog = ColorPickerDialog((r, g, b), self.window()) if dialog.exec() == QDialog.DialogCode.Accepted: color_info = dialog.get_color_info() if color_info: diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" index 362593d..954f936 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" @@ -706,7 +706,61 @@ def test_queued_connection_safety(qtbot): 将传统 `QDialog` 改造为 `FramelessDialog`,实现 Fluent Design 风格的自定义标题栏,获得更现代、统一的界面效果。 -### 10.2 核心改造步骤 +### 10.1.1 推荐使用 BaseFramelessDialog 基类 + +**项目已提供统一的 `BaseFramelessDialog` 基类**,封装了完整的标题栏和主题适配功能。新对话框应直接继承此基类,无需重复实现。 + +**文件位置:** `dialogs/base_frameless_dialog.py` + +**使用方法:** + +```python +from dialogs import BaseFramelessDialog +from qfluentwidgets import qconfig + +class MyDialog(BaseFramelessDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("我的对话框") + self.setFixedSize(400, 300) + + # 设置标题栏和样式(基类提供) + self._setup_title_bar() + self._update_styles() + + # 设置界面 + self.setup_ui() + + # 监听主题变化 + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) + + def closeEvent(self, event): + # 断开信号连接 + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + super().closeEvent(event) + + def setup_ui(self): + """设置界面""" + from PySide6.QtWidgets import QVBoxLayout + layout = QVBoxLayout(self) + # 顶部边距40px为标题栏留出空间 + layout.setContentsMargins(20, 40, 20, 20) + # ... 其他控件 +``` + +**关键要点:** +- 继承 `BaseFramelessDialog` 而非直接使用 `FramelessDialog` +- 调用 `self._setup_title_bar()` 设置标题栏 +- 调用 `self._update_styles()` 初始化样式 +- 布局顶部边距设置为 40px +- 父窗口使用 `self.window()` + +### 10.2 核心改造步骤(旧对话框迁移参考) #### 10.2.1 依赖安装 @@ -946,6 +1000,8 @@ for widget in [title_label, spacer_label, logo_label, left_spacer]: | Logo/标题/按钮挤在一起 | 清除了原有布局的所有项 | 保留原有布局,只在开头插入新控件 | | 标题紧贴左边框 | 未设置左边距 | 添加固定宽度的占位符 QLabel | | 从子控件打开对话框时背景色异常 | 父窗口不是顶层窗口 | 使用 `parent=self.window()` 而不是 `parent=self` | +| 出现两套窗口控制器(最小化/最大化/关闭按钮) | 父控件不是窗口,导致 Windows 显示系统按钮 | 确保 `parent` 参数传入顶层窗口(`self.window()`) | +| 深色模式下背景仍为白色/灰色 | `QPalette` 设置时机不对或父窗口问题 | 1. 确保使用 `QPalette` + `setAutoFillBackground(True)` 设置背景
2. 确保父窗口是顶层窗口
3. 在 `_update_styles` 中设置,而非 `__init__` 中 | ### 10.7 主题颜色函数(theme_colors.py) @@ -969,8 +1025,18 @@ def get_close_button_pressed_color(): ### 10.8 改造检查清单 +#### 新对话框(推荐) +- [ ] 继承 `BaseFramelessDialog` 基类 +- [ ] 调用 `_setup_title_bar()` 设置标题栏 +- [ ] 调用 `_update_styles()` 初始化样式 +- [ ] 调整布局边距为标题栏留出空间(顶部40px) +- [ ] 监听主题变化信号 +- [ ] 在 `closeEvent` 中断开信号连接 +- [ ] **关键**:确保对话框父窗口是顶层窗口(`parent=self.window()`) + +#### 旧对话框迁移(参考) - [ ] 安装 PySideSix-Frameless-Window 依赖 -- [ ] 修改类继承为 `FramelessDialog` +- [ ] 修改类继承为 `FramelessDialog`(或迁移到 `BaseFramelessDialog`) - [ ] 移除 `setWindowFlags` 调用 - [ ] 移除 `set_window_title_bar_theme` 相关代码 - [ ] 移除 `_fix_taskbar_icon` 调用(FramelessDialog 自动处理) @@ -981,27 +1047,29 @@ def get_close_button_pressed_color(): - [ ] 调整布局边距为标题栏留出空间 - [ ] 监听主题变化信号 - [ ] 在 `closeEvent` 中断开信号连接 -- [ ] 确保对话框父窗口是顶层窗口(`parent=self.window()`) -- [ ] 测试深色/浅色主题切换 +- [ ] **关键**:确保对话框父窗口是顶层窗口(`parent=self.window()`) +- [ ] **关键**:测试从子控件打开对话框时背景色是否正常 +- [ ] **关键**:测试深色/浅色主题切换时关闭按钮颜色是否正常 - [ ] 测试窗口拖动和调整大小功能 ### 10.9 后续对话框改造模板 +#### 推荐模板(使用 BaseFramelessDialog 基类) + ```python -from qframelesswindow import FramelessDialog -from PySide6.QtGui import QPalette, QColor -from utils.theme_colors import get_text_color, get_dialog_bg_color +from dialogs import BaseFramelessDialog +from qfluentwidgets import qconfig + +class CustomDialog(BaseFramelessDialog): + """自定义对话框""" -class CustomDialog(FramelessDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("对话框标题") self.setFixedSize(400, 300) - # 设置自定义标题栏 + # 设置标题栏和样式(基类提供) self._setup_title_bar() - - # 初始化样式 self._update_styles() # 设置界面 @@ -1012,97 +1080,12 @@ class CustomDialog(FramelessDialog): self._update_styles ) - def _setup_title_bar(self): - """参考 10.3.2 实现""" - from PySide6.QtWidgets import QLabel - from PySide6.QtCore import Qt - from PySide6.QtGui import QPixmap - from utils.icon import get_icon_path - from utils.theme_colors import get_text_color - - title_bar = self.titleBar - h_layout = title_bar.layout() - if not h_layout: - return - - text_color = get_text_color() - text_color_str = text_color.name() - - # 创建标题栏控件 - left_spacer = QLabel(title_bar) - left_spacer.setFixedWidth(10) - - icon_path = get_icon_path() - logo_label = None - if icon_path: - logo_label = QLabel(title_bar) - pixmap = QPixmap(icon_path) - if not pixmap.isNull(): - logo_label.setPixmap(pixmap.scaled( - 20, 20, - Qt.AspectRatioMode.KeepAspectRatio, - Qt.TransformationMode.SmoothTransformation - )) - - spacer_label = QLabel(title_bar) - spacer_label.setFixedWidth(8) - - title_label = QLabel(self.windowTitle(), title_bar) - title_label.setStyleSheet( - f"color: {text_color_str}; font-size: 13px; font-weight: 500;" - ) - - for widget in [title_label, spacer_label, logo_label, left_spacer]: - if widget: - h_layout.insertWidget(0, widget) - h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter) - - self._title_label = title_label - - def _update_close_button_color(self, text_color): - """参考 10.4.2 实现""" - from utils.theme_colors import ( - get_close_button_hover_bg_color, - get_close_button_hover_color, - get_close_button_pressed_color - ) - - title_bar = self.titleBar - if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn: - close_btn = title_bar.closeBtn - close_btn.setNormalColor(text_color) - close_btn.setHoverColor(get_close_button_hover_color()) - close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color()) - close_btn.setPressedColor(get_close_button_pressed_color()) - - def _update_styles(self): - """参考 10.4.3 实现""" - text_color = get_text_color() - text_color_str = text_color.name() - bg_color = get_dialog_bg_color() - - self.setStyleSheet(f""" - QLabel {{ - color: {text_color_str}; - }} - """) - - palette = self.palette() - palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name())) - self.setPalette(palette) - self.setAutoFillBackground(True) - - if hasattr(self, '_title_label') and self._title_label: - self._title_label.setStyleSheet( - f"color: {text_color_str}; font-size: 13px; font-weight: 500;" - ) - - self._update_close_button_color(text_color) - def setup_ui(self): """设置界面""" + from PySide6.QtWidgets import QVBoxLayout layout = QVBoxLayout(self) - layout.setContentsMargins(20, 40, 20, 20) # 注意顶部边距 + # 顶部边距40px为标题栏留出空间 + layout.setContentsMargins(20, 40, 20, 20) # ... 其他控件 def closeEvent(self, event): @@ -1114,4 +1097,18 @@ class CustomDialog(FramelessDialog): pass delattr(self, '_theme_connection') super().closeEvent(event) + + +# ==================== 使用示例 ==================== + +# ✅ 正确:从子控件打开对话框时,使用 self.window() 作为父窗口 +dialog = CustomDialog(parent=self.window()) +dialog.exec() + +# ❌ 错误:直接使用 self 作为父窗口(self 是子控件而非窗口) +# dialog = CustomDialog(parent=self) # 会导致背景色异常和两套窗口控制器 ``` + +#### 旧模板(直接使用 FramelessDialog,供参考) + +如需自定义标题栏样式或实现特殊需求,可直接继承 `FramelessDialog`,参考 10.3-10.4 节实现相关方法。 diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" index 107b29e..cbc82e8 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" @@ -79,6 +79,7 @@ color_card/ ├── dialogs/ # 对话框模块目录 │ ├── __init__.py │ ├── about_dialog.py # 关于对话框 +│ ├── base_frameless_dialog.py # 无边框对话框基类(BaseFramelessDialog,提供统一标题栏和主题适配) │ ├── colorblind_dialog.py # 色盲模拟预览对话框 │ ├── contrast_dialog.py # 对比度检查对话框 │ ├── edit_palette.py # 配色编辑对话框(EditPaletteDialog、ColorPickerDialog、PresetGrid、ColorModeSliders、GradientSlider、ColorPreview) @@ -492,6 +493,103 @@ class MainWindow(FluentWindow): self.addSubInterface(self.interface, FluentIcon.PALETTE, "界面名称") ``` +### 5.1.1 无边框对话框基类使用规范 + +**使用 `BaseFramelessDialog` 作为无边框对话框基类:** + +项目提供统一的 `BaseFramelessDialog` 基类,封装了 Fluent Design 风格的自定义标题栏和主题适配功能。 + +**文件位置:** `dialogs/base_frameless_dialog.py` + +**使用步骤:** + +1. **继承基类** +```python +from dialogs import BaseFramelessDialog + +class MyDialog(BaseFramelessDialog): + """我的对话框""" + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("对话框标题") + self.setFixedSize(400, 300) +``` + +2. **设置标题栏和样式** +```python +# 设置自定义标题栏(显示Logo和标题) +self._setup_title_bar() + +# 初始化样式(背景色、文字颜色等) +self._update_styles() +``` + +3. **监听主题变化** +```python +from qfluentwidgets import qconfig + +# 在 __init__ 中连接信号 +self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles +) +``` + +4. **断开信号连接** +```python +def closeEvent(self, event): + """关闭事件 - 断开信号连接""" + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + super().closeEvent(event) +``` + +**完整示例:** +```python +from dialogs import BaseFramelessDialog +from qfluentwidgets import qconfig + +class CustomDialog(BaseFramelessDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("自定义对话框") + self.setFixedSize(400, 300) + + # 设置标题栏和样式 + self._setup_title_bar() + self._update_styles() + + # 设置界面 + self.setup_ui() + + # 监听主题变化 + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) + + def closeEvent(self, event): + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + super().closeEvent(event) + + def setup_ui(self): + """设置界面布局""" + from PySide6.QtWidgets import QVBoxLayout + layout = QVBoxLayout(self) + # 顶部边距40px为标题栏留出空间 + layout.setContentsMargins(20, 40, 20, 20) + # ... 其他控件 +``` + +**关键要点:** +- 必须调用 `_setup_title_bar()` 设置自定义标题栏 +- 必须调用 `_update_styles()` 初始化样式 +- 布局边距顶部设置为 40px,为标题栏留出空间 +- 父窗口必须是顶层窗口:`parent=self.window()` + ### 5.2 界面组织规范 - 每个功能模块创建独立的 `QWidget` 子类 @@ -1959,6 +2057,7 @@ def _get_about_text(self): | 版本 | 日期 | 变更内容 | | :--: | :--------: | :------------------------------------------------------------------------: | +| 3.42 | 2026-03-16 | 新增无边框对话框基类规范(5.1.1节):提取 BaseFramelessDialog 到独立文件,统一标题栏和主题适配;更新项目结构(1.3节) | | 3.41 | 2026-03-13 | 精简开发规范中的代码示例,删除冗余注释和重复内容,使文档更加简洁易读 | | 3.40 | 2026-03-07 | 更新项目结构(1.3节):新增 export\_settings\_dialog.py 到 dialogs/ 目录 | | 3.39 | 2026-03-04 | 新增资源路径规范(2.4节):说明 PyInstaller 打包后的资源路径获取方式,避免打包后资源加载失败 | -- Gitee From 9749db5824d12574d73ffa0c266e56eed2ab3253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Mon, 16 Mar 2026 21:29:09 +0800 Subject: [PATCH 03/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E5=85=B3=E4=BA=8E?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=94=B9=E9=80=A0=E4=B8=BA=E6=97=A0?= =?UTF-8?q?=E8=BE=B9=E6=A1=86=E7=AA=97=E5=8F=A3=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 AboutDialog 从 QDialog 改造为 BaseFramelessDialog,实现 Fluent Design 风格标题栏 - 添加自定义标题栏设置和主题适配功能 - 设置文本编辑器背景和边框透明,文字颜色根据主题自动切换 - 删除冗余的 _get_base_path 函数,复用 utils 模块中的版本 - 合并 _open_license_file 和 _open_agreement_file 为通用的 _open_file_or_url 方法 - 调整布局边距为顶部 40px,为无边框标题栏留出空间 - 添加 closeEvent 断开主题变化信号连接,避免内存泄漏 --- dialogs/about_dialog.py | 148 ++++++++++++++++------------------------ utils/__init__.py | 3 +- 2 files changed, 59 insertions(+), 92 deletions(-) diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py index 687f526..641de53 100644 --- a/dialogs/about_dialog.py +++ b/dialogs/about_dialog.py @@ -1,40 +1,19 @@ # 标准库导入 -import os -import sys from pathlib import Path # 第三方库导入 -from PySide6.QtCore import Qt, QTimer, QUrl +from PySide6.QtCore import Qt, QUrl from PySide6.QtGui import QDesktopServices -from PySide6.QtWidgets import ( - QDialog, QFrame, QHBoxLayout, QVBoxLayout, QWidget -) +from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget from qfluentwidgets import CaptionLabel, PlainTextEdit, PushButton, isDarkTheme, qconfig # 项目模块导入 -from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme +from utils import tr, load_icon_universal, get_base_path from version import version_manager -from utils.theme_colors import get_dialog_bg_color, get_text_color +from dialogs.base_frameless_dialog import BaseFramelessDialog -def _get_base_path() -> str: - """获取应用程序基础路径 - - 支持开发环境和 PyInstaller 打包后的环境 - - Returns: - str: 应用程序基础路径 - """ - if getattr(sys, 'frozen', False): - # PyInstaller 打包后的环境 - if hasattr(sys, '_MEIPASS'): - return sys._MEIPASS - return os.path.dirname(sys.executable) - # 开发环境 - 返回项目根目录 - return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -class AboutDialog(QDialog): +class AboutDialog(BaseFramelessDialog): """关于对话框 显示应用程序信息、版本信息、开发团队信息、 @@ -49,30 +28,24 @@ class AboutDialog(QDialog): # 设置窗口图标 self.setWindowIcon(load_icon_universal()) - # 设置窗口标志:只保留关闭按钮(必须在设置窗口标题之后) - self.setWindowFlags( - Qt.WindowType.Window | - Qt.WindowType.WindowTitleHint | - Qt.WindowType.WindowCloseButtonHint | - Qt.WindowType.CustomizeWindowHint - ) + # 设置自定义标题栏 + self._setup_title_bar() - # 设置窗口背景色(与 FluentWindow 一致) - bg_color = get_dialog_bg_color() - self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}") + # 初始化样式 + self._update_styles() + # 设置界面 self.setup_ui() - # 修复任务栏图标(在窗口显示后调用) - QTimer.singleShot(100, lambda: fix_windows_taskbar_icon_for_window(self)) - # 监听主题变化 - qconfig.themeChangedFinished.connect(self._update_title_bar_theme) + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) def setup_ui(self): """设置界面布局""" layout = QVBoxLayout(self) - layout.setContentsMargins(20, 20, 20, 20) + layout.setContentsMargins(20, 40, 20, 20) layout.setSpacing(15) # 内容区域 @@ -86,7 +59,7 @@ class AboutDialog(QDialog): def _create_content_area(self, parent_layout): """创建内容显示区域 - + Args: parent_layout: 父布局对象 """ @@ -94,24 +67,24 @@ class AboutDialog(QDialog): self.text_edit.setReadOnly(True) self.text_edit.setPlainText(self._get_about_text()) self.text_edit.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) - # 禁用焦点,去除底部蓝色条 self.text_edit.setFocusPolicy(Qt.FocusPolicy.NoFocus) - - # 设置主题感知的样式 - bg_color = get_dialog_bg_color() - text_color = get_text_color() - self.text_edit.setStyleSheet( - f"PlainTextEdit {{ background-color: {bg_color.name()}; " - f"color: {text_color.name()}; border: none; }}\n" - f"PlainTextEdit:focus {{ border: none; outline: none; }}\n" - f"PlainTextEdit::focus {{ border: none; }}\n" - f"QPlainTextEdit {{ border: none; }}\n" - f"QPlainTextEdit:focus {{ border: none; outline: none; }}" - ) - - parent_layout.addWidget(self.text_edit, stretch=1) + # 设置背景和边框透明,文字颜色根据主题变化 + text_color = "#ffffff" if isDarkTheme() else "#333333" + self.text_edit.setStyleSheet(f""" + PlainTextEdit {{ + background-color: transparent; + border: none; + color: {text_color}; + }} + QPlainTextEdit {{ + background-color: transparent; + border: none; + color: {text_color}; + }} + """) + parent_layout.addWidget(self.text_edit, stretch=1) def _create_buttons_area(self, parent_layout): """创建按钮区域 @@ -196,33 +169,27 @@ class AboutDialog(QDialog): """ QDesktopServices.openUrl(QUrl(url)) - def _open_license_file(self): - """打开开源许可文件""" - # 获取许可证文件路径(相对于项目根目录的 file/LICENSE.html) - base_path = _get_base_path() - license_path = Path(base_path) / "file" / "LICENSE.html" - - if license_path.exists(): - # 转换为文件URL并打开 - file_url = QUrl.fromLocalFile(str(license_path.absolute())) - QDesktopServices.openUrl(file_url) + def _open_file_or_url(self, filename: str, fallback_url: str) -> None: + """打开本地文件,不存在则打开URL + + Args: + filename: 文件名(位于 file/ 目录下) + fallback_url: 文件不存在时的备用URL + """ + file_path = Path(get_base_path()) / "file" / filename + + if file_path.exists(): + QDesktopServices.openUrl(QUrl.fromLocalFile(str(file_path.absolute()))) else: - # 如果文件不存在,打开项目地址 - self._open_url("https://gitee.com/qingshangongzai/color_card") + self._open_url(fallback_url) + + def _open_license_file(self) -> None: + """打开开源许可文件""" + self._open_file_or_url("LICENSE.html", "https://gitee.com/qingshangongzai/color_card") - def _open_agreement_file(self): + def _open_agreement_file(self) -> None: """打开用户协议文件""" - # 获取用户协议文件路径(相对于项目根目录的 file/UserAgreement.html) - base_path = _get_base_path() - agreement_path = Path(base_path) / "file" / "UserAgreement.html" - - if agreement_path.exists(): - # 转换为文件URL并打开 - file_url = QUrl.fromLocalFile(str(agreement_path.absolute())) - QDesktopServices.openUrl(file_url) - else: - # 如果文件不存在,打开项目地址 - self._open_url("https://gitee.com/qingshangongzai/color-card") + self._open_file_or_url("UserAgreement.html", "https://gitee.com/qingshangongzai/color-card") def _get_about_text(self): """获取关于页面的文本内容""" @@ -380,16 +347,15 @@ class AboutDialog(QDialog): • 感谢Adobe Color、色采、palettemakel等优秀产品为我们提供的灵感和参考 """ - def _update_title_bar_theme(self): - """更新标题栏主题以适配当前主题""" - set_window_title_bar_theme(self, isDarkTheme()) - - def showEvent(self, event): - """窗口显示事件 - 在显示前设置标题栏主题避免闪烁""" - # 先设置标题栏主题(在父类 showEvent 之前) - self._update_title_bar_theme() - # 调用父类的 showEvent - super().showEvent(event) + def closeEvent(self, event): + """关闭事件 - 断开信号连接""" + if hasattr(self, '_theme_connection'): + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + delattr(self, '_theme_connection') + super().closeEvent(event) def contextMenuEvent(self, event): """屏蔽原生右键菜单""" diff --git a/utils/__init__.py b/utils/__init__.py index 622ca68..1fb9c18 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,6 +1,6 @@ """工具函数模块""" -from .icon import load_icon_universal, get_icon_path, create_fallback_icon +from .icon import load_icon_universal, get_icon_path, create_fallback_icon, get_base_path from .platform import set_app_user_model_id, fix_windows_taskbar_icon_for_window, set_window_title_bar_theme from .layout import calculate_grid_columns from .locale import ( @@ -16,6 +16,7 @@ __all__ = [ 'load_icon_universal', 'get_icon_path', 'create_fallback_icon', + 'get_base_path', 'set_app_user_model_id', 'fix_windows_taskbar_icon_for_window', 'set_window_title_bar_theme', -- Gitee From 4400e50ec72f33bb92842dc923b39ed1a65f376f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Mon, 16 Mar 2026 21:57:03 +0800 Subject: [PATCH 04/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E5=B0=86=E8=89=B2?= =?UTF-8?q?=E7=9B=B2=E6=A8=A1=E6=8B=9F=E5=92=8C=E5=AF=B9=E6=AF=94=E5=BA=A6?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=94=B9=E9=80=A0?= =?UTF-8?q?=E4=B8=BA=E6=97=A0=E8=BE=B9=E6=A1=86=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 BaseFramelessDialog 基类,封装 Fluent Design 风格标题栏和主题适配 - 改造 ColorblindPreviewDialog 继承 BaseFramelessDialog,移除冗余代码 - 改造 ContrastCheckDialog 继承 BaseFramelessDialog,移除冗余代码 - 优化主题切换处理,简化 _update_styles 方法 - 清理未使用的导入和重复的颜色转换 - 移动局部导入到文件顶部,符合代码规范 - 更新开发经验总结文档,完善无边框窗口改造指南 --- dialogs/base_frameless_dialog.py | 20 +++-- dialogs/colorblind_dialog.py | 81 +++++++++--------- dialogs/contrast_dialog.py | 83 +++++++++---------- ...17\351\252\214\346\200\273\347\273\223.md" | 54 ++++++------ 4 files changed, 114 insertions(+), 124 deletions(-) diff --git a/dialogs/base_frameless_dialog.py b/dialogs/base_frameless_dialog.py index ad35f10..977eb58 100644 --- a/dialogs/base_frameless_dialog.py +++ b/dialogs/base_frameless_dialog.py @@ -119,23 +119,27 @@ class BaseFramelessDialog(FramelessDialog): text_color = get_text_color() text_color_str = text_color.name() bg_color = get_dialog_bg_color() + bg_color_str = bg_color.name() - # 设置 QLabel 文字颜色样式表 + # 使用 QPalette 设置窗口背景色 + palette = self.palette() + palette.setColor(QPalette.ColorRole.Window, bg_color) + self.setPalette(palette) + self.setAutoFillBackground(True) + + # 设置样式表 - QLabel 文字颜色 self.setStyleSheet(f""" QLabel {{ color: {text_color_str}; + background-color: transparent; }} """) - # 使用 QPalette 设置窗口背景色 - palette = self.palette() - palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name())) - self.setPalette(palette) - self.setAutoFillBackground(True) - # 更新标题标签颜色(如果存在) if hasattr(self, '_title_label') and self._title_label: - self._title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;") + self._title_label.setStyleSheet( + f"color: {text_color_str}; font-size: 13px; font-weight: 500;" + ) # 更新关闭按钮颜色 self._update_close_button_color(text_color) diff --git a/dialogs/colorblind_dialog.py b/dialogs/colorblind_dialog.py index 44955fb..e980269 100644 --- a/dialogs/colorblind_dialog.py +++ b/dialogs/colorblind_dialog.py @@ -7,23 +7,24 @@ from typing import List, Dict, Tuple # 第三方库导入 -from PySide6.QtCore import Qt, QTimer +from PySide6.QtCore import Qt from PySide6.QtWidgets import ( - QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget, + QHBoxLayout, QLabel, QVBoxLayout, QWidget, QFrame ) from PySide6.QtGui import QColor from qfluentwidgets import ( - ComboBox, isDarkTheme, qconfig, ScrollArea + ComboBox, qconfig, ScrollArea ) # 项目模块导入 -from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme +from utils import tr, load_icon_universal +from dialogs import BaseFramelessDialog from core.colorblind import ( simulate_colorblind, get_colorblind_info, get_all_colorblind_types ) from utils.theme_colors import ( - get_dialog_bg_color, get_text_color, get_border_color, + get_text_color, get_border_color, get_secondary_text_color, get_title_color ) @@ -62,13 +63,11 @@ class ColorBlock(QWidget): class ColorComparisonRow(QWidget): """颜色对比行组件 - 显示原颜色和模拟颜色""" - + def __init__(self, parent=None): super().__init__(parent) self.setup_ui() self._update_styles() - # 监听主题变化 - qconfig.themeChangedFinished.connect(self._update_styles) def setup_ui(self): """设置界面""" @@ -140,15 +139,15 @@ class ColorComparisonRow(QWidget): self.simulated_hex.setText(f"{tr('dialogs.colorblind.simulated')}: {simulated_hex}") -class ColorblindPreviewDialog(QDialog): +class ColorblindPreviewDialog(BaseFramelessDialog): """色盲模拟预览对话框 - + 显示配色方案在不同色盲类型下的视觉效果。 """ - + def __init__(self, scheme_name: str, colors: List[Dict], parent=None): """初始化色盲预览对话框 - + Args: scheme_name: 配色方案名称 colors: 颜色列表,每个颜色是一个字典,包含 'rgb' 键 @@ -158,38 +157,32 @@ class ColorblindPreviewDialog(QDialog): self._scheme_name = scheme_name self._colors = colors self._current_type = 'normal' - + self.setWindowTitle(tr('dialogs.colorblind.window_title', name=scheme_name)) self.setFixedSize(320, 420) - + # 设置窗口图标 self.setWindowIcon(load_icon_universal()) - - # 设置窗口标志 - self.setWindowFlags( - Qt.WindowType.Window | - Qt.WindowType.WindowTitleHint | - Qt.WindowType.WindowCloseButtonHint | - Qt.WindowType.CustomizeWindowHint - ) - - # 设置窗口背景色 - bg_color = get_dialog_bg_color() - self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}") - + + # 设置界面 self.setup_ui() + + # 设置标题栏和样式 + self._setup_title_bar() + self._update_styles() + + # 更新预览 self.update_preview() - - # 修复任务栏图标 - QTimer.singleShot(100, lambda: fix_windows_taskbar_icon_for_window(self)) - + # 监听主题变化 - qconfig.themeChangedFinished.connect(self._update_title_bar_theme) + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) def setup_ui(self): """设置界面布局""" main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(20, 20, 20, 20) + main_layout.setContentsMargins(20, 40, 20, 20) main_layout.setSpacing(15) # 获取主题颜色 @@ -281,9 +274,8 @@ class ColorblindPreviewDialog(QDialog): } """) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) - + self.comparison_container = QWidget() - self.comparison_container.setStyleSheet("background: transparent;") self.comparison_layout = QVBoxLayout(self.comparison_container) self.comparison_layout.setContentsMargins(0, 0, 0, 0) self.comparison_layout.setSpacing(5) @@ -328,12 +320,13 @@ class ColorblindPreviewDialog(QDialog): # 更新说明文字 info = get_colorblind_info(self._current_type) self.description_label.setText(tr('dialogs.colorblind.description', text=info['description'])) - - def _update_title_bar_theme(self): - """更新标题栏主题以适配当前主题""" - set_window_title_bar_theme(self, isDarkTheme()) - - def showEvent(self, event): - """窗口显示事件""" - self._update_title_bar_theme() - super().showEvent(event) + + def closeEvent(self, event): + """关闭事件:断开信号连接""" + if hasattr(self, '_theme_connection'): + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + delattr(self, '_theme_connection') + super().closeEvent(event) diff --git a/dialogs/contrast_dialog.py b/dialogs/contrast_dialog.py index 5341804..bae3a6b 100644 --- a/dialogs/contrast_dialog.py +++ b/dialogs/contrast_dialog.py @@ -8,25 +8,26 @@ from typing import List, Dict, Tuple # 第三方库导入 -from PySide6.QtCore import Qt, QTimer, Signal +from PySide6.QtCore import Qt, Signal, QPointF from PySide6.QtWidgets import ( - QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget, - QFrame, QGridLayout, QScrollArea + QHBoxLayout, QLabel, QVBoxLayout, QWidget, + QFrame, QScrollArea ) -from PySide6.QtGui import QColor, QPainter, QBrush, QPen, QFont +from PySide6.QtGui import QColor, QPainter, QBrush, QPen, QFont, QPolygonF from qfluentwidgets import ( ComboBox, PushButton, ToolButton, FluentIcon, isDarkTheme, qconfig, CardWidget ) # 项目模块导入 -from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme +from utils import tr, load_icon_universal +from dialogs import BaseFramelessDialog from core.contrast import ( calculate_contrast_ratio, get_contrast_info, rgb_to_hex, get_contrast_status_color ) from utils.theme_colors import ( - get_dialog_bg_color, get_text_color, get_border_color, + get_text_color, get_border_color, get_secondary_text_color, get_title_color, get_card_background_color ) @@ -272,8 +273,6 @@ class GraphicWidget(QWidget): painter.drawEllipse(50, 20, 20, 20) # 绘制三角形 - from PySide6.QtGui import QPolygonF - from PySide6.QtCore import QPointF triangle = QPolygonF([ QPointF(90, 20), QPointF(80, 40), @@ -350,16 +349,16 @@ class GraphicPreviewCard(QWidget): painter.drawRoundedRect(0, 0, self.width() - 1, self.height() - 1, 8, 8) -class ContrastCheckDialog(QDialog): +class ContrastCheckDialog(BaseFramelessDialog): """对比度检查对话框 - + 允许用户选择配色方案中的两种颜色进行对比度检查, 并实时预览文字可读性效果。 """ - + def __init__(self, scheme_name: str, colors: List[Dict], parent=None): """初始化对比度检查对话框 - + Args: scheme_name: 配色方案名称 colors: 颜色列表,每个颜色是一个字典,包含 'rgb' 键 @@ -368,43 +367,36 @@ class ContrastCheckDialog(QDialog): super().__init__(parent) self._scheme_name = scheme_name self._colors = colors - + self.setWindowTitle(tr('dialogs.contrast.window_title', name=scheme_name)) - + # 设置窗口图标 self.setWindowIcon(load_icon_universal()) - - # 设置窗口标志 - 使用 MSWindowsFixedSizeDialogHint 防止 Windows 自动调整大小 - self.setWindowFlags( - Qt.WindowType.Window | - Qt.WindowType.WindowTitleHint | - Qt.WindowType.WindowCloseButtonHint | - Qt.WindowType.CustomizeWindowHint | - Qt.WindowType.MSWindowsFixedSizeDialogHint - ) - - # 设置固定大小(使用最小/最大尺寸确保不被压缩) + + # 设置固定大小 self.setMinimumSize(480, 420) self.setMaximumSize(480, 420) self.resize(480, 420) - - # 设置窗口背景色 - bg_color = get_dialog_bg_color() - self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}") - + + # 设置界面 self.setup_ui() + + # 设置标题栏和样式 + self._setup_title_bar() + self._update_styles() + + # 更新对比度 self._update_contrast() - - # 修复任务栏图标 - QTimer.singleShot(100, lambda: fix_windows_taskbar_icon_for_window(self)) - + # 监听主题变化 - qconfig.themeChangedFinished.connect(self._update_title_bar_theme) + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) def setup_ui(self): """设置界面布局""" main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(20, 20, 20, 20) + main_layout.setContentsMargins(20, 40, 20, 20) main_layout.setSpacing(15) # 获取主题颜色 @@ -591,12 +583,13 @@ class ContrastCheckDialog(QDialog): self.normal_preview.set_colors(bg_rgb, text_rgb) self.large_preview.set_colors(bg_rgb, text_rgb) self.graphic_preview.set_colors(bg_rgb, text_rgb) - - def _update_title_bar_theme(self): - """更新标题栏主题以适配当前主题""" - set_window_title_bar_theme(self, isDarkTheme()) - - def showEvent(self, event): - """窗口显示事件""" - self._update_title_bar_theme() - super().showEvent(event) + + def closeEvent(self, event): + """关闭事件:断开信号连接""" + if hasattr(self, '_theme_connection'): + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + delattr(self, '_theme_connection') + super().closeEvent(event) diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" index 954f936..fbecff2 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" @@ -717,6 +717,7 @@ def test_queued_connection_safety(qtbot): ```python from dialogs import BaseFramelessDialog from qfluentwidgets import qconfig +from PySide6.QtWidgets import QVBoxLayout class MyDialog(BaseFramelessDialog): def __init__(self, parent=None): @@ -724,29 +725,30 @@ class MyDialog(BaseFramelessDialog): self.setWindowTitle("我的对话框") self.setFixedSize(400, 300) + # 设置界面 + self.setup_ui() + # 设置标题栏和样式(基类提供) self._setup_title_bar() self._update_styles() - # 设置界面 - self.setup_ui() - # 监听主题变化 self._theme_connection = qconfig.themeChangedFinished.connect( self._update_styles ) def closeEvent(self, event): - # 断开信号连接 - try: - qconfig.themeChangedFinished.disconnect(self._theme_connection) - except (TypeError, RuntimeError): - pass + """关闭事件:断开信号连接""" + if hasattr(self, '_theme_connection'): + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + delattr(self, '_theme_connection') super().closeEvent(event) def setup_ui(self): """设置界面""" - from PySide6.QtWidgets import QVBoxLayout layout = QVBoxLayout(self) # 顶部边距40px为标题栏留出空间 layout.setContentsMargins(20, 40, 20, 20) @@ -755,10 +757,10 @@ class MyDialog(BaseFramelessDialog): **关键要点:** - 继承 `BaseFramelessDialog` 而非直接使用 `FramelessDialog` -- 调用 `self._setup_title_bar()` 设置标题栏 -- 调用 `self._update_styles()` 初始化样式 -- 布局顶部边距设置为 40px -- 父窗口使用 `self.window()` +- 先调用 `setup_ui()` 设置界面,再调用 `_setup_title_bar()` 和 `_update_styles()` +- 布局顶部边距设置为 40px,为标题栏留出空间 +- 父窗口使用 `self.window()` 传递顶层窗口 +- 在 `closeEvent` 中断开主题变化信号连接 ### 10.2 核心改造步骤(旧对话框迁移参考) @@ -934,26 +936,24 @@ def _update_close_button_color(self, text_color): ```python def _update_styles(self): """更新样式以适配主题""" - from PySide6.QtGui import QPalette, QColor - from utils.theme_colors import get_text_color, get_dialog_bg_color - text_color = get_text_color() text_color_str = text_color.name() bg_color = get_dialog_bg_color() - # 设置 QLabel 文字颜色样式表 + # 使用 QPalette 设置窗口背景色 + palette = self.palette() + palette.setColor(QPalette.ColorRole.Window, bg_color) + self.setPalette(palette) + self.setAutoFillBackground(True) + + # 设置样式表 - QLabel 文字颜色 self.setStyleSheet(f""" QLabel {{ color: {text_color_str}; + background-color: transparent; }} """) - # 使用 QPalette 设置窗口背景色 - palette = self.palette() - palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name())) - self.setPalette(palette) - self.setAutoFillBackground(True) - # 更新标题标签颜色 if hasattr(self, '_title_label') and self._title_label: self._title_label.setStyleSheet( @@ -1059,6 +1059,7 @@ def get_close_button_pressed_color(): ```python from dialogs import BaseFramelessDialog from qfluentwidgets import qconfig +from PySide6.QtWidgets import QVBoxLayout class CustomDialog(BaseFramelessDialog): """自定义对话框""" @@ -1068,13 +1069,13 @@ class CustomDialog(BaseFramelessDialog): self.setWindowTitle("对话框标题") self.setFixedSize(400, 300) + # 设置界面 + self.setup_ui() + # 设置标题栏和样式(基类提供) self._setup_title_bar() self._update_styles() - # 设置界面 - self.setup_ui() - # 监听主题变化 self._theme_connection = qconfig.themeChangedFinished.connect( self._update_styles @@ -1082,7 +1083,6 @@ class CustomDialog(BaseFramelessDialog): def setup_ui(self): """设置界面""" - from PySide6.QtWidgets import QVBoxLayout layout = QVBoxLayout(self) # 顶部边距40px为标题栏留出空间 layout.setContentsMargins(20, 40, 20, 20) -- Gitee From 2d057675c79057cac518586c366ceb2c623f90b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Mon, 16 Mar 2026 22:06:08 +0800 Subject: [PATCH 05/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E5=B0=86=20Export?= =?UTF-8?q?SettingsDialog=20=E6=94=B9=E9=80=A0=E4=B8=BA=E6=97=A0=E8=BE=B9?= =?UTF-8?q?=E6=A1=86=E7=AA=97=E5=8F=A3=E5=B9=B6=E6=B8=85=E7=90=86=E5=86=97?= =?UTF-8?q?=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 ExportSettingsDialog 从 QDialog 迁移到 BaseFramelessDialog 基类 - 移除 setWindowFlags、set_window_title_bar_theme 等冗余代码 - 删除未使用的导入(QPixmap、isDarkTheme、get_text_color) - 移除冗余的 _update_styles 方法,完全依赖基类实现 - 调整布局边距为标题栏留出空间(顶部40px) - 新增 closeEvent 方法断开主题变化信号连接 - 更新开发经验总结文档,添加代码清理与避免冗余章节 --- dialogs/export_settings_dialog.py | 66 +++++++------------ ...17\351\252\214\346\200\273\347\273\223.md" | 60 ++++++++++++++++- 2 files changed, 82 insertions(+), 44 deletions(-) diff --git a/dialogs/export_settings_dialog.py b/dialogs/export_settings_dialog.py index dd2f3c6..ea2c9f4 100644 --- a/dialogs/export_settings_dialog.py +++ b/dialogs/export_settings_dialog.py @@ -4,21 +4,20 @@ from typing import List, Optional # 第三方库导入 from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import ( - QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget, - QFrame, QFileDialog, QMessageBox + QHBoxLayout, QLabel, QVBoxLayout, QWidget, + QFileDialog, QMessageBox ) -from PySide6.QtGui import QPixmap from qfluentwidgets import ( - PushButton, LineEdit, RadioButton, isDarkTheme, qconfig, + PushButton, LineEdit, RadioButton, qconfig, PrimaryPushButton, CheckBox, ScrollArea ) # 项目模块导入 -from utils import tr, set_window_title_bar_theme -from utils.theme_colors import get_text_color, get_card_background_color, get_dialog_bg_color +from dialogs import BaseFramelessDialog +from utils import tr -class ExportSettingsDialog(QDialog): +class ExportSettingsDialog(BaseFramelessDialog): """导出设置对话框 用于配置配色预览导出选项,包括选择图片、设置文件名前缀、选择导出格式等。 @@ -39,32 +38,25 @@ class ExportSettingsDialog(QDialog): self._png_radio: Optional[RadioButton] = None self.setWindowTitle(tr('dialogs.export_settings.title')) + self.setFixedSize(400, 450) - # 设置窗口标志 - 只保留关闭按钮 - self.setWindowFlags( - Qt.WindowType.Window | - Qt.WindowType.WindowTitleHint | - Qt.WindowType.WindowCloseButtonHint | - Qt.WindowType.CustomizeWindowHint - ) - - # 设置背景色 - bg_color = get_dialog_bg_color() - self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}") - + # 设置界面 self.setup_ui() + + # 设置标题栏和样式(基类提供) + self._setup_title_bar() self._update_styles() # 监听主题变化 - qconfig.themeChangedFinished.connect(self._update_styles) - - # 设置固定大小(必须在 setup_ui 之后) - self.setFixedSize(400, 450) + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) def setup_ui(self): """设置界面""" layout = QVBoxLayout(self) - layout.setContentsMargins(20, 20, 20, 20) + # 顶部边距40px为标题栏留出空间 + layout.setContentsMargins(20, 40, 20, 20) layout.setSpacing(16) # 选择图片区域 @@ -227,20 +219,12 @@ class ExportSettingsDialog(QDialog): return "png" return "svg" - def showEvent(self, event): - """窗口显示前设置标题栏主题,避免闪烁""" - self._update_title_bar_theme() - super().showEvent(event) - - def _update_title_bar_theme(self): - """更新标题栏主题""" - set_window_title_bar_theme(self, isDarkTheme()) - - def _update_styles(self): - """更新样式以适配主题""" - text_color = get_text_color() - text_color_hex = text_color.name() - - # 更新所有标签的文字颜色 - for widget in self.findChildren(QLabel): - widget.setStyleSheet(f"color: {text_color_hex};") + def closeEvent(self, event): + """关闭事件:断开信号连接""" + if hasattr(self, '_theme_connection'): + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + delattr(self, '_theme_connection') + super().closeEvent(event) diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" index fbecff2..357c4cf 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" @@ -1003,7 +1003,57 @@ for widget in [title_label, spacer_label, logo_label, left_spacer]: | 出现两套窗口控制器(最小化/最大化/关闭按钮) | 父控件不是窗口,导致 Windows 显示系统按钮 | 确保 `parent` 参数传入顶层窗口(`self.window()`) | | 深色模式下背景仍为白色/灰色 | `QPalette` 设置时机不对或父窗口问题 | 1. 确保使用 `QPalette` + `setAutoFillBackground(True)` 设置背景
2. 确保父窗口是顶层窗口
3. 在 `_update_styles` 中设置,而非 `__init__` 中 | -### 10.7 主题颜色函数(theme_colors.py) +### 10.7 代码清理与避免冗余 + +#### 10.7.1 不要重复实现基类已有的功能 + +**❌ 错误方式(子类重复实现样式更新):** +```python +class MyDialog(BaseFramelessDialog): + def _update_styles(self): + """更新样式以适配主题""" + # 调用父类方法 + super()._update_styles() + + # 冗余:基类已通过 setStyleSheet 统一设置 QLabel 颜色 + text_color = get_text_color() + text_color_hex = text_color.name() + for widget in self.findChildren(QLabel): + widget.setStyleSheet(f"color: {text_color_hex};") +``` + +**✅ 正确方式(完全依赖基类):** +```python +class MyDialog(BaseFramelessDialog): + # 不需要重写 _update_styles,完全使用基类实现 + pass +``` + +**关键原则:** +- `BaseFramelessDialog._update_styles()` 已通过 `self.setStyleSheet()` 为所有 QLabel 统一设置颜色 +- 子类无需重复设置 QLabel 颜色,除非需要特殊的样式覆盖 +- 保持代码简洁,遵循 DRY 原则 + +#### 10.7.2 及时清理未使用的导入 + +改造完成后应检查并删除未使用的导入: + +```python +# 改造前可能需要 +from PySide6.QtGui import QPixmap +from qfluentwidgets import isDarkTheme +from utils.theme_colors import get_text_color + +# 改造后若未使用,应删除上述导入 +``` + +**常见可删除的导入(使用 BaseFramelessDialog 后):** +- `QPixmap` - 若对话框不涉及图片显示 +- `isDarkTheme` - 基类已处理主题检测 +- `get_text_color` / `get_dialog_bg_color` - 基类已处理颜色获取 +- `set_window_title_bar_theme` - 无边框窗口不再需要 + +### 10.8 主题颜色函数(theme_colors.py) 在 `utils/theme_colors.py` 中添加关闭按钮颜色函数: @@ -1023,7 +1073,7 @@ def get_close_button_pressed_color(): return QColor(255, 255, 255) ``` -### 10.8 改造检查清单 +### 10.9 改造检查清单 #### 新对话框(推荐) - [ ] 继承 `BaseFramelessDialog` 基类 @@ -1033,6 +1083,8 @@ def get_close_button_pressed_color(): - [ ] 监听主题变化信号 - [ ] 在 `closeEvent` 中断开信号连接 - [ ] **关键**:确保对话框父窗口是顶层窗口(`parent=self.window()`) +- [ ] **清理**:删除未使用的导入(`QPixmap`、`isDarkTheme`、`get_text_color` 等) +- [ ] **清理**:避免重写 `_update_styles`,除非需要特殊样式覆盖 #### 旧对话框迁移(参考) - [ ] 安装 PySideSix-Frameless-Window 依赖 @@ -1051,8 +1103,10 @@ def get_close_button_pressed_color(): - [ ] **关键**:测试从子控件打开对话框时背景色是否正常 - [ ] **关键**:测试深色/浅色主题切换时关闭按钮颜色是否正常 - [ ] 测试窗口拖动和调整大小功能 +- [ ] **清理**:删除未使用的导入(`QPixmap`、`isDarkTheme`、`set_window_title_bar_theme` 等) +- [ ] **清理**:检查并删除冗余的 `_update_styles` 重写 -### 10.9 后续对话框改造模板 +### 10.10 后续对话框改造模板 #### 推荐模板(使用 BaseFramelessDialog 基类) -- Gitee From 8b06a9ded79ed95b7de3b7dc2defd79b8f12a5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Mon, 16 Mar 2026 22:30:31 +0800 Subject: [PATCH 06/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E5=B0=86=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E6=9B=B4=E6=96=B0=E6=A1=86=E6=94=B9=E9=80=A0=E4=B8=BA?= =?UTF-8?q?=E6=97=A0=E8=BE=B9=E6=A1=86=E7=AA=97=E5=8F=A3=EF=BC=8C=E7=B2=BE?= =?UTF-8?q?=E7=AE=80=E4=BA=86=E4=BB=A3=E7=A0=81=EF=BC=8C=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20BaseFramelessDialog=20=E5=9F=BA=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 改造 update_dialog.py 为无边框窗口,继承 BaseFramelessDialog - 增强 BaseFramelessDialog 基类,添加 closeEvent 实现自动断开信号连接 - 清理 edit_palette.py 冗余导入和未使用方法(QDialog、get_dialog_bg_color、_fix_taskbar_icon) - 简化所有对话框的 closeEvent 方法,改为调用 super().closeEvent(event) - 删除开发规范中 Windows DWM API 相关内容(3.8.5节) - 更新无边框对话框基类使用规范,说明基类自动处理信号断开 - 更新核心原则.md 第10节为无边框对话框使用指南 - 更新开发规范版本号至 3.43 --- dialogs/about_dialog.py | 10 +-- dialogs/base_frameless_dialog.py | 15 +++++ dialogs/colorblind_dialog.py | 10 +-- dialogs/contrast_dialog.py | 10 +-- dialogs/edit_palette.py | 31 ++------- dialogs/export_settings_dialog.py | 10 +-- dialogs/update_dialog.py | 63 +++++++----------- ...00\345\217\221\350\247\204\350\214\203.md" | 64 ++----------------- ...70\345\277\203\345\216\237\345\210\231.md" | 52 ++++++++++----- 9 files changed, 96 insertions(+), 169 deletions(-) diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py index 641de53..f2db650 100644 --- a/dialogs/about_dialog.py +++ b/dialogs/about_dialog.py @@ -348,14 +348,8 @@ class AboutDialog(BaseFramelessDialog): """ def closeEvent(self, event): - """关闭事件 - 断开信号连接""" - if hasattr(self, '_theme_connection'): - try: - qconfig.themeChangedFinished.disconnect(self._theme_connection) - except (TypeError, RuntimeError): - pass - delattr(self, '_theme_connection') - super().closeEvent(event) + """关闭事件""" + super().closeEvent(event) # 基类处理信号断开 def contextMenuEvent(self, event): """屏蔽原生右键菜单""" diff --git a/dialogs/base_frameless_dialog.py b/dialogs/base_frameless_dialog.py index 977eb58..7f17544 100644 --- a/dialogs/base_frameless_dialog.py +++ b/dialogs/base_frameless_dialog.py @@ -3,6 +3,7 @@ from PySide6.QtWidgets import QLabel from PySide6.QtCore import Qt from PySide6.QtGui import QPixmap, QPalette, QColor from qframelesswindow import FramelessDialog +from qfluentwidgets import qconfig # 项目模块导入 from utils.icon import get_icon_path @@ -143,3 +144,17 @@ class BaseFramelessDialog(FramelessDialog): # 更新关闭按钮颜色 self._update_close_button_color(text_color) + + def closeEvent(self, event): + """关闭事件:断开主题变化信号连接 + + 子类可以重写此方法,但应调用 super().closeEvent(event) + 以确保信号正确断开。 + """ + if hasattr(self, '_theme_connection'): + try: + qconfig.themeChangedFinished.disconnect(self._theme_connection) + except (TypeError, RuntimeError): + pass + delattr(self, '_theme_connection') + super().closeEvent(event) diff --git a/dialogs/colorblind_dialog.py b/dialogs/colorblind_dialog.py index e980269..0aa7c35 100644 --- a/dialogs/colorblind_dialog.py +++ b/dialogs/colorblind_dialog.py @@ -322,11 +322,5 @@ class ColorblindPreviewDialog(BaseFramelessDialog): self.description_label.setText(tr('dialogs.colorblind.description', text=info['description'])) def closeEvent(self, event): - """关闭事件:断开信号连接""" - if hasattr(self, '_theme_connection'): - try: - qconfig.themeChangedFinished.disconnect(self._theme_connection) - except (TypeError, RuntimeError): - pass - delattr(self, '_theme_connection') - super().closeEvent(event) + """关闭事件""" + super().closeEvent(event) # 基类处理信号断开 diff --git a/dialogs/contrast_dialog.py b/dialogs/contrast_dialog.py index bae3a6b..cf8309a 100644 --- a/dialogs/contrast_dialog.py +++ b/dialogs/contrast_dialog.py @@ -585,11 +585,5 @@ class ContrastCheckDialog(BaseFramelessDialog): self.graphic_preview.set_colors(bg_rgb, text_rgb) def closeEvent(self, event): - """关闭事件:断开信号连接""" - if hasattr(self, '_theme_connection'): - try: - qconfig.themeChangedFinished.disconnect(self._theme_connection) - except (TypeError, RuntimeError): - pass - delattr(self, '_theme_connection') - super().closeEvent(event) + """关闭事件""" + super().closeEvent(event) # 基类处理信号断开 diff --git a/dialogs/edit_palette.py b/dialogs/edit_palette.py index 68cf950..8fb45bb 100644 --- a/dialogs/edit_palette.py +++ b/dialogs/edit_palette.py @@ -6,7 +6,7 @@ from typing import Dict, Any, List, Tuple, Optional # 第三方库导入 from PySide6.QtCore import Qt, QTimer, Signal, QPoint, QRect from PySide6.QtWidgets import ( - QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget, QGridLayout, QApplication + QHBoxLayout, QLabel, QVBoxLayout, QWidget, QGridLayout, QApplication ) from PySide6.QtGui import QColor, QPainter, QLinearGradient, QBrush, QPen, QMouseEvent from qfluentwidgets import ( @@ -21,7 +21,7 @@ from core import ( ) from core.config import get_config_manager from utils import tr, load_icon_universal -from utils.theme_colors import get_dialog_bg_color, get_text_color, get_border_color +from utils.theme_colors import get_text_color, get_border_color # 对话框模块导入 from .base_frameless_dialog import BaseFramelessDialog @@ -754,15 +754,7 @@ class ColorInputRow(QWidget): self._debounce_timer.setSingleShot(True) self._debounce_timer.timeout.connect(self._process_hex_input) - def closeEvent(self, event): - """关闭事件 - 断开信号连接""" - try: - if hasattr(self, '_theme_connection'): - qconfig.themeChangedFinished.disconnect(self._theme_connection) - delattr(self, '_theme_connection') - except (TypeError, RuntimeError): - pass - super().closeEvent(event) + def setup_ui(self): """设置界面""" @@ -972,12 +964,8 @@ class EditPaletteDialog(BaseFramelessDialog): ) def closeEvent(self, event): - """关闭事件 - 断开信号连接""" - try: - qconfig.themeChangedFinished.disconnect(self._theme_connection) - except (TypeError, RuntimeError): - pass - super().closeEvent(event) + """关闭事件""" + super().closeEvent(event) # 基类处理信号断开 def setup_ui(self): """设置界面布局""" @@ -1199,11 +1187,4 @@ class EditPaletteDialog(BaseFramelessDialog): """ return getattr(self, '_palette_data', None) - def _fix_taskbar_icon(self): - """修复任务栏图标""" - try: - if self and self.isVisible(): - fix_windows_taskbar_icon_for_window(self) - except RuntimeError: - # 对象已被销毁 - pass + diff --git a/dialogs/export_settings_dialog.py b/dialogs/export_settings_dialog.py index ea2c9f4..49c2a36 100644 --- a/dialogs/export_settings_dialog.py +++ b/dialogs/export_settings_dialog.py @@ -220,11 +220,5 @@ class ExportSettingsDialog(BaseFramelessDialog): return "svg" def closeEvent(self, event): - """关闭事件:断开信号连接""" - if hasattr(self, '_theme_connection'): - try: - qconfig.themeChangedFinished.disconnect(self._theme_connection) - except (TypeError, RuntimeError): - pass - delattr(self, '_theme_connection') - super().closeEvent(event) + """关闭事件""" + super().closeEvent(event) # 基类处理信号断开 diff --git a/dialogs/update_dialog.py b/dialogs/update_dialog.py index 76e676b..5039b68 100644 --- a/dialogs/update_dialog.py +++ b/dialogs/update_dialog.py @@ -3,10 +3,10 @@ import re from typing import List, Tuple # 第三方库导入 -from PySide6.QtCore import Qt, QThread, QTimer, QUrl, Signal +from PySide6.QtCore import Qt, QThread, Signal from PySide6.QtGui import QDesktopServices -from PySide6.QtWidgets import QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget -from qfluentwidgets import InfoBar, InfoBarPosition, PrimaryPushButton, PushButton, isDarkTheme, qconfig +from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget +from qfluentwidgets import InfoBar, InfoBarPosition, PrimaryPushButton, PushButton, qconfig try: import requests @@ -14,8 +14,8 @@ except ImportError: requests = None # 项目模块导入 -from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme -from utils.theme_colors import get_dialog_bg_color, get_text_color +from utils import tr, load_icon_universal +from dialogs import BaseFramelessDialog class UpdateCheckThread(QThread): @@ -134,7 +134,7 @@ def compare_versions(current: str, latest: str) -> int: return 0 -class UpdateAvailableDialog(QDialog): +class UpdateAvailableDialog(BaseFramelessDialog): """新版本可用提示对话框 当检测到有新版本时弹出,提供跳转到发行页面的功能。 @@ -159,45 +159,33 @@ class UpdateAvailableDialog(QDialog): # 设置窗口图标 self.setWindowIcon(load_icon_universal()) - # 设置窗口标志 - self.setWindowFlags( - Qt.WindowType.Window - | Qt.WindowType.WindowTitleHint - | Qt.WindowType.WindowCloseButtonHint - | Qt.WindowType.CustomizeWindowHint - ) - - # 设置窗口背景色 - bg_color = get_dialog_bg_color() - self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}") - + # 设置界面 self.setup_ui() - # 修复任务栏图标 - QTimer.singleShot(100, lambda: fix_windows_taskbar_icon_for_window(self)) + # 设置标题栏和样式(基类提供) + self._setup_title_bar() + self._update_styles() # 监听主题变化 - qconfig.themeChangedFinished.connect(self._update_title_bar_theme) + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) def setup_ui(self): """设置界面布局""" layout = QVBoxLayout(self) - layout.setContentsMargins(20, 20, 20, 20) + # 顶部边距40px为标题栏留出空间 + layout.setContentsMargins(20, 40, 20, 20) layout.setSpacing(15) - # 提示文本 - text_color = get_text_color() + # 提示文本(基类统一处理文字颜色) info_label = QLabel(tr('dialogs.update.new_version')) info_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - info_label.setStyleSheet( - f"QLabel {{ color: {text_color.name()}; font-size: 16px; font-weight: bold; }}" - ) layout.addWidget(info_label) - # 版本信息 + # 版本信息(基类统一处理文字颜色) version_label = QLabel(tr('dialogs.update.version_info', current=self.current_version, latest=self.latest_version)) version_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - version_label.setStyleSheet(f"QLabel {{ color: {text_color.name()}; font-size: 12px; }}") layout.addWidget(version_label) layout.addStretch() @@ -232,16 +220,9 @@ class UpdateAvailableDialog(QDialog): QDesktopServices.openUrl(QUrl(url)) self.accept() - def _update_title_bar_theme(self): - """更新标题栏主题以适配当前主题""" - set_window_title_bar_theme(self, isDarkTheme()) - - def showEvent(self, event): - """窗口显示事件 - 在显示前设置标题栏主题避免闪烁""" - # 先设置标题栏主题(在父类 showEvent 之前) - self._update_title_bar_theme() - # 调用父类的 showEvent - super().showEvent(event) + def closeEvent(self, event): + """关闭事件""" + super().closeEvent(event) # 基类处理信号断开 @staticmethod def check_update(parent, current_version): @@ -278,7 +259,9 @@ class UpdateAvailableDialog(QDialog): ) else: # 有新版本可用,显示对话框 - dialog = UpdateAvailableDialog(parent, current_version, latest_version) + # 使用 window() 获取顶层窗口,确保无边框对话框正常显示 + top_parent = parent.window() if parent else None + dialog = UpdateAvailableDialog(top_parent, current_version, latest_version) dialog.exec() else: InfoBar.warning( diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" index cbc82e8..dd09dc7 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" @@ -368,51 +368,6 @@ class MyWidget(QWidget): **避免使用** `!important`:优先使用组件特定的选择器;如必须使用,确保在主题切换后重新应用。 -#### 3.8.5 Windows 原生标题栏深色模式 - -对于继承自 `QDialog` 的对话框,使用 Windows DWM API 设置原生标题栏的沉浸式深色模式。 - -**工具函数**(放在 `utils/platform.py`): - -```python -import ctypes -import sys - -DWMWA_USE_IMMERSIVE_DARK_MODE = 20 - -def set_window_title_bar_theme(window, is_dark=False): - """为窗口设置标题栏主题(Windows 10+)""" - if sys.platform != "win32": - return False - try: - hwnd = int(window.windowHandle().winId()) - value = ctypes.c_int(1 if is_dark else 0) - return ctypes.windll.dwmapi.DwmSetWindowAttribute( - hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, - ctypes.byref(value), ctypes.sizeof(value) - ) == 0 - except Exception: - return False -``` - -**在对话框中应用**(使用 `showEvent` 避免闪烁): - -```python -class MyDialog(QDialog): - def __init__(self, parent=None): - super().__init__(parent) - qconfig.themeChangedFinished.connect(self._update_title_bar_theme) - - def showEvent(self, event): - self._update_title_bar_theme() - super().showEvent(event) - - def _update_title_bar_theme(self): - set_window_title_bar_theme(self, isDarkTheme()) -``` - -**关键要点:** 使用 `showEvent` 在窗口显示前设置标题栏主题;连接 `qconfig.themeChangedFinished` 信号支持已打开对话框的主题切换;仅支持 Windows 10 版本 2004 及以上。 - *** ## 4. 基类设计规范 @@ -534,15 +489,11 @@ self._theme_connection = qconfig.themeChangedFinished.connect( ) ``` -4. **断开信号连接** +4. **关闭事件处理(基类已自动处理信号断开)** ```python def closeEvent(self, event): - """关闭事件 - 断开信号连接""" - try: - qconfig.themeChangedFinished.disconnect(self._theme_connection) - except (TypeError, RuntimeError): - pass - super().closeEvent(event) + """关闭事件""" + super().closeEvent(event) # 基类自动断开信号连接 ``` **完整示例:** @@ -569,11 +520,8 @@ class CustomDialog(BaseFramelessDialog): ) def closeEvent(self, event): - try: - qconfig.themeChangedFinished.disconnect(self._theme_connection) - except (TypeError, RuntimeError): - pass - super().closeEvent(event) + """关闭事件""" + super().closeEvent(event) # 基类自动断开信号连接 def setup_ui(self): """设置界面布局""" @@ -589,6 +537,7 @@ class CustomDialog(BaseFramelessDialog): - 必须调用 `_update_styles()` 初始化样式 - 布局边距顶部设置为 40px,为标题栏留出空间 - 父窗口必须是顶层窗口:`parent=self.window()` +- `closeEvent` 中调用 `super().closeEvent(event)`,基类会自动断开主题变化信号 ### 5.2 界面组织规范 @@ -2057,6 +2006,7 @@ def _get_about_text(self): | 版本 | 日期 | 变更内容 | | :--: | :--------: | :------------------------------------------------------------------------: | +| 3.43 | 2026-03-16 | 更新无边框对话框基类规范(5.1.1节):`closeEvent` 信号断开逻辑移至基类;删除 Windows DWM API 相关内容(3.8.5节) | | 3.42 | 2026-03-16 | 新增无边框对话框基类规范(5.1.1节):提取 BaseFramelessDialog 到独立文件,统一标题栏和主题适配;更新项目结构(1.3节) | | 3.41 | 2026-03-13 | 精简开发规范中的代码示例,删除冗余注释和重复内容,使文档更加简洁易读 | | 3.40 | 2026-03-07 | 更新项目结构(1.3节):新增 export\_settings\_dialog.py 到 dialogs/ 目录 | diff --git "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" index 3696230..d9b25ec 100644 --- "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" +++ "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" @@ -268,25 +268,47 @@ class MyInterface(QWidget): --- -## 10. Windows 原生标题栏深色模式 +## 10. 无边框对话框 -对于继承自 `QDialog` 的对话框,使用 Windows DWM API 设置原生标题栏主题: +使用 `BaseFramelessDialog` 作为对话框基类,提供 Fluent Design 风格的自定义标题栏: ```python -from qfluentwidgets import isDarkTheme, qconfig -from utils import set_window_title_bar_theme +from dialogs import BaseFramelessDialog +from qfluentwidgets import qconfig -class MyDialog(QDialog): +class MyDialog(BaseFramelessDialog): def __init__(self, parent=None): super().__init__(parent) - qconfig.themeChangedFinished.connect(self._update_title_bar_theme) - - def showEvent(self, event): - """窗口显示前设置标题栏主题,避免闪烁""" - self._update_title_bar_theme() - super().showEvent(event) - - def _update_title_bar_theme(self): - """更新标题栏主题""" - set_window_title_bar_theme(self, isDarkTheme()) + self.setWindowTitle("对话框标题") + self.setFixedSize(400, 300) + + # 设置标题栏和样式 + self._setup_title_bar() + self._update_styles() + + # 设置界面 + self.setup_ui() + + # 监听主题变化 + self._theme_connection = qconfig.themeChangedFinished.connect( + self._update_styles + ) + + def closeEvent(self, event): + """关闭事件""" + super().closeEvent(event) # 基类自动断开信号连接 + + def setup_ui(self): + """设置界面布局""" + from PySide6.QtWidgets import QVBoxLayout + layout = QVBoxLayout(self) + # 顶部边距40px为标题栏留出空间 + layout.setContentsMargins(20, 40, 20, 20) ``` + +**关键要点:** +- 继承 `BaseFramelessDialog` 而非 `QDialog` +- 调用 `_setup_title_bar()` 设置自定义标题栏 +- 调用 `_update_styles()` 初始化样式 +- 布局边距顶部设置为 40px,为标题栏留出空间 +- `closeEvent` 中调用 `super().closeEvent(event)`,基类会自动断开主题变化信号 -- Gitee From df59fd3487791f3c64c4c9a44c1f8f7470d58081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Mon, 16 Mar 2026 22:46:12 +0800 Subject: [PATCH 07/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=AF=B9=E6=AF=94=E5=BA=A6=E6=A3=80=E6=9F=A5=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E6=A1=86=E7=A7=BB=E5=8A=A8=E5=90=8E=E9=AB=98=E5=BA=A6=E7=BC=A9?= =?UTF-8?q?=E5=B0=8F=E5=92=8C=E5=86=85=E5=AE=B9=E9=87=8D=E5=8F=A0=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复窗口大小设置:将 setMinimumSize/setMaximumSize/resize 合并为 setFixedSize - 调整窗口高度:从 420px 增加到 580px,确保所有控件正常显示不重叠 - 优化窗口尺寸计算:根据各控件实际高度需求重新计算总高度 --- dialogs/contrast_dialog.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dialogs/contrast_dialog.py b/dialogs/contrast_dialog.py index cf8309a..2032590 100644 --- a/dialogs/contrast_dialog.py +++ b/dialogs/contrast_dialog.py @@ -374,9 +374,7 @@ class ContrastCheckDialog(BaseFramelessDialog): self.setWindowIcon(load_icon_universal()) # 设置固定大小 - self.setMinimumSize(480, 420) - self.setMaximumSize(480, 420) - self.resize(480, 420) + self.setFixedSize(480, 580) # 设置界面 self.setup_ui() -- Gitee From 7caaf17a5cb10c14b7447d645b5f480936507739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Mon, 16 Mar 2026 23:13:43 +0800 Subject: [PATCH 08/47] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E5=9C=A8=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E5=8E=9F=E5=88=99=E4=B8=AD=E6=B7=BB=E5=8A=A0=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E7=B3=BB=E7=BB=9F=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在文档/核心原则.md 中新增第10章"日志系统说明" - 说明日志存储位置为用户主目录,非项目目录 - 添加AI提示,避免沙箱环境问题导致过度修改 --- ...40\270\345\277\203\345\216\237\345\210\231.md" | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" index d9b25ec..84478bd 100644 --- "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" +++ "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" @@ -268,7 +268,20 @@ class MyInterface(QWidget): --- -## 10. 无边框对话框 +## 10. 日志系统说明 + +日志存储在用户主目录 `~/.color_card/logs/`,**不是项目目录**。 + +```python +# 日志目录自动创建,已存在不会报错 +self._log_dir.mkdir(parents=True, exist_ok=True) +``` + +> **AI 提示**:沙箱环境无法访问日志是环境限制,不是代码问题。不要修改日志系统,不用手动创建目录。 + +--- + +## 12. 无边框对话框 使用 `BaseFramelessDialog` 作为对话框基类,提供 Fluent Design 风格的自定义标题栏: -- Gitee From 27c2b1cc8684b17071eba70c11118bc8d686c938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Mon, 16 Mar 2026 23:24:56 +0800 Subject: [PATCH 09/47] =?UTF-8?q?[=E5=86=85=E5=AE=B9=E8=B0=83=E6=95=B4]=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- version.py | 4 ++-- version.txt | 6 +++--- version_info.txt | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/version.py b/version.py index a22ae18..416913f 100644 --- a/version.py +++ b/version.py @@ -9,9 +9,9 @@ class VersionManager: # 版本号组件 self.major: int = 1 self.minor: int = 5 - self.patch: int = 1 + self.patch: int = 2 self.build: int = 0 - self.prerelease: str = "" + self.prerelease: str = "Beta" # 核心版本信息 self.version: str = f"{self.major}.{self.minor}.{self.patch}{self.prerelease}" diff --git a/version.txt b/version.txt index ab7dd43..3f3a13a 100644 --- a/version.txt +++ b/version.txt @@ -1,6 +1,6 @@ -1.5.1 -2026.3.15.1 -1.5.1.0 +1.5.2 +2026.3.16.1 +1.5.2.0 浮晓 HXiao Studio © 2026 浮晓 HXiao Studio 取色卡 - Color Card \ No newline at end of file diff --git a/version_info.txt b/version_info.txt index 691c4f1..1e6a277 100644 --- a/version_info.txt +++ b/version_info.txt @@ -1,7 +1,7 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(2026,3,15,1), - prodvers=(1,5,1,0), + filevers=(2026,3,16,1), + prodvers=(1,5,2,0), mask=0x3f, flags=0x0, OS=0x4, @@ -17,12 +17,12 @@ VSVersionInfo( [ StringStruct(u'CompanyName', u'浮晓 HXiao Studio'), StringStruct(u'FileDescription', u'取色卡 - Color Card'), - StringStruct(u'FileVersion', u'1.5.1'), + StringStruct(u'FileVersion', u'1.5.2'), StringStruct(u'InternalName', u'Color_Card'), StringStruct(u'LegalCopyright', u'© 2026 浮晓 HXiao Studio'), StringStruct(u'OriginalFilename', u'Color_Card.exe'), StringStruct(u'ProductName', u'取色卡'), - StringStruct(u'ProductVersion', u'1.5.1'), + StringStruct(u'ProductVersion', u'1.5.2'), StringStruct(u'Comments', u'一站式的图片的图片分析和配色工具') ] ) -- Gitee From 98f07303bfda8d324ce4e3ebf8f2d632a0fba929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Tue, 17 Mar 2026 01:35:56 +0800 Subject: [PATCH 10/47] =?UTF-8?q?[=E5=86=85=E5=AE=B9=E8=B0=83=E6=95=B4]=20?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20PySideSix-Frameless-Window=20=E8=AE=B8?= =?UTF-8?q?=E5=8F=AF=E8=AF=81=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 about_dialog.py 中 PySideSix-Frameless-Window 的许可证为 LGPLv3 - 修正 LICENSE 文件中 PySideSix-Frameless-Window 的许可证为 LGPLv3 - 修正 LICENSE.html 中 PySideSix-Frameless-Window 的许可证为 LGPLv3 --- LICENSE | 8 ++++---- dialogs/about_dialog.py | 2 +- file/LICENSE.html | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LICENSE b/LICENSE index 2b8b5ef..9a4d027 100644 --- a/LICENSE +++ b/LICENSE @@ -1032,12 +1032,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- 版权所有:zhiyiYo 项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window -许可证:GNU General Public License v3.0 +许可证:GNU Lesser General Public License v3.0 说明: -PySideSix-Frameless-Window 使用 GPLv3 许可证。由于本项目主许可证也为 GPLv3, -完整的 GPLv3 许可证文本请参考本文档前面的 "GNU GENERAL PUBLIC LICENSE -Version 3" 章节。 +PySideSix-Frameless-Window 使用 LGPLv3 许可证。由于本项目主许可证为 GPLv3, +而 LGPLv3 是 GPLv3 的补充版本,完整的 LGPLv3 许可证文本请参考本文档 +"PySide6" 章节中的 "GNU LESSER GENERAL PUBLIC LICENSE Version 3" 部分。 ================================================================================ diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py index f2db650..e859148 100644 --- a/dialogs/about_dialog.py +++ b/dialogs/about_dialog.py @@ -237,7 +237,7 @@ class AboutDialog(BaseFramelessDialog): • 本程序使用 PySideSix-Frameless-Window 实现对话框无边框窗口 版权所有:zhiyiYo - 许可证:GPLv3 + 许可证:LGPLv3 项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window 【开源配色方案使用说明】 diff --git a/file/LICENSE.html b/file/LICENSE.html index ca60aba..82bd779 100644 --- a/file/LICENSE.html +++ b/file/LICENSE.html @@ -695,11 +695,11 @@

版权所有:zhiyiYo

项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window

-

许可证:GNU General Public License v3.0

+

许可证:GNU Lesser General Public License v3.0

-

PySideSix-Frameless-Window 使用 GPLv3 许可证。由于本项目主许可证也为 GPLv3,完整的 GPLv3 许可证文本请参考本文档前面的GNU GENERAL PUBLIC LICENSE Version 3章节。

+

PySideSix-Frameless-Window 使用 LGPLv3 许可证。由于本项目主许可证为 GPLv3,而 LGPLv3 是 GPLv3 的补充版本,完整的 LGPLv3 许可证文本请参考本文档 PySide6 章节中的 "GNU LESSER GENERAL PUBLIC LICENSE Version 3" 部分。

-- Gitee From 6b12abc21c041d72008ffbe508386d4eb614bb2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Tue, 17 Mar 2026 01:41:46 +0800 Subject: [PATCH 11/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=BB=E7=AA=97=E5=8F=A3=E5=90=AF=E5=8A=A8=E6=97=B6=E6=9C=AA?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=BC=B9=E5=87=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复窗口状态冲突导致主窗口被最小化到任务栏的问题 - 当窗口已最大化或全屏时,避免重复调用 show() 方法 - 添加 raise_() 和 activateWindow() 确保窗口在前台显示 --- main.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index d3e3650..a237bd5 100644 --- a/main.py +++ b/main.py @@ -218,7 +218,19 @@ def main(): logger.info("创建主窗口...") window = MainWindow() - window.show() + + # 根据保存的窗口状态决定如何显示 + # 如果窗口已经在 __init__ 中被最大化或全屏,则不需要再调用 show() + if not window.isMaximized() and not window.isFullScreen(): + window.show() + logger.info("主窗口以普通模式显示") + else: + logger.info(f"主窗口以 {'全屏' if window.isFullScreen() else '最大化'} 模式显示") + + # 确保窗口在前台显示 + window.raise_() + window.activateWindow() + logger.info("主窗口显示完成") # 关闭启动画面并修复任务栏图标(在窗口显示后调用) -- Gitee From ec6e7494329e4d0bb824af92addfe2a22a556199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Tue, 17 Mar 2026 18:26:56 +0800 Subject: [PATCH 12/47] =?UTF-8?q?[=E6=A0=B7=E5=BC=8F]=20=E7=A6=81=E7=94=A8?= =?UTF-8?q?=E5=85=B3=E4=BA=8E=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E9=AB=98=E4=BA=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 PlainTextEdit 上设置 NoTextInteraction 标志 - 禁止用户选中文本,消除蓝色高亮标记 --- dialogs/about_dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py index e859148..4a57549 100644 --- a/dialogs/about_dialog.py +++ b/dialogs/about_dialog.py @@ -68,6 +68,7 @@ class AboutDialog(BaseFramelessDialog): self.text_edit.setPlainText(self._get_about_text()) self.text_edit.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) self.text_edit.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.text_edit.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction) # 设置背景和边框透明,文字颜色根据主题变化 text_color = "#ffffff" if isDarkTheme() else "#333333" -- Gitee From 1cc403e2636a6935aa8c7e07fef04e173064b53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Tue, 17 Mar 2026 19:55:46 +0800 Subject: [PATCH 13/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E8=89=B2=E8=BD=AE=E9=87=87=E6=A0=B7=E7=82=B9=E5=9C=A8RGB?= =?UTF-8?q?=E5=92=8CRYB=E6=A8=A1=E5=BC=8F=E4=B8=8B=E7=9A=84=E8=A1=8C?= =?UTF-8?q?=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除RYB模式下基准色相变化时重新生成配色的特殊处理 - RYB模式下色相偏移时通过RYB色轮转换保持角度关系 - 两种模式现在都采用相同的色相偏移逻辑 - 采样点保持相对角度关系,不随基准点重新定位 --- ui/color_generation.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/ui/color_generation.py b/ui/color_generation.py index 2f0ecf3..86256ca 100644 --- a/ui/color_generation.py +++ b/ui/color_generation.py @@ -22,7 +22,10 @@ from qfluentwidgets import ( ) # 项目模块导入 -from core import get_color_info, get_config_manager, hsb_to_rgb, rgb_to_hsb, adjust_brightness +from core import ( + get_color_info, get_config_manager, hsb_to_rgb, rgb_to_hsb, adjust_brightness, + rgb_hue_to_ryb_hue, ryb_hue_to_rgb_hue +) from utils import tr, get_locale_manager from dialogs import EditPaletteDialog from .cards import BaseCard, BaseCardPanel, ColorModeContainer, get_text_color, get_placeholder_color, get_border_color @@ -550,7 +553,7 @@ class ColorGenerationInterface(QWidget): def on_base_color_changed(self, h, s, b): """基准颜色改变回调 - 色相变化时,所有采样点跟随旋转; + 色相变化时,所有采样点跟随旋转,保持相对角度关系; 饱和度变化时,仅基准点变化,其他采样点保持原位。 """ # 计算色相变化量 @@ -562,17 +565,23 @@ class ColorGenerationInterface(QWidget): self._base_hue = h self._base_saturation = s - # RYB模式下,重新生成配色以保持RYB色轮上的相对角度关系 - if self._color_wheel_mode == 'RYB' and delta_h != 0: - self._generate_scheme_colors() - return - - # 色相变化:所有采样点跟着旋转(仅RGB模式) + # 色相变化:所有采样点跟着旋转 if delta_h != 0 and self._scheme_colors: - for i in range(len(self._scheme_colors)): - old_h, old_s, old_b = self._scheme_colors[i] - new_h = (old_h + delta_h) % 360 - self._scheme_colors[i] = (new_h, old_s, old_b) + if self._color_wheel_mode == 'RYB': + # RYB模式下需要在RYB色轮上进行偏移,保持RYB角度关系 + for i in range(len(self._scheme_colors)): + old_h, old_s, old_b = self._scheme_colors[i] + # RGB -> RYB -> 偏移 -> RGB + ryb_h = rgb_hue_to_ryb_hue(old_h) + new_ryb_h = (ryb_h + delta_h) % 360 + new_h = ryb_hue_to_rgb_hue(new_ryb_h) + self._scheme_colors[i] = (new_h, old_s, old_b) + else: + # RGB模式下直接在RGB色轮上偏移 + for i in range(len(self._scheme_colors)): + old_h, old_s, old_b = self._scheme_colors[i] + new_h = (old_h + delta_h) % 360 + self._scheme_colors[i] = (new_h, old_s, old_b) # 饱和度变化:更新 _scheme_colors[0](基准点) if delta_s != 0 and self._scheme_colors: -- Gitee From c2f000a845374e1677cf2b282771192e978812f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Tue, 17 Mar 2026 20:22:32 +0800 Subject: [PATCH 14/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E6=8F=90=E5=8F=96?= =?UTF-8?q?RGB/RYB=E9=85=8D=E8=89=B2=E6=96=B9=E6=A1=88=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=9A=84=E5=85=AC=E5=85=B1=E5=86=85=E9=83=A8?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增6个私有内部函数:_build_monochromatic_colors、_calculate_analogous_hues、_build_analogous_colors、_build_complementary_colors、_build_split_complementary_colors、_build_double_complementary_colors - 简化5个RGB配色方案公共API函数,改为调用内部实现 - 简化5个RYB配色方案公共API函数,改为调用内部实现 - 消除约180行重复代码,代码量减少约50% - 保持所有公共API的函数签名和行为不变 - 按规范添加中文文档字符串和类型注解 --- core/color.py | 435 ++++++++++++++++++++++++++------------------------ 1 file changed, 226 insertions(+), 209 deletions(-) diff --git a/core/color.py b/core/color.py index 7c1f399..d0fe2db 100644 --- a/core/color.py +++ b/core/color.py @@ -679,15 +679,15 @@ def cmyk_to_rgb(c: float, m: float, y: float, k: float) -> Tuple[int, int, int]: return round(r * 255), round(g * 255), round(b_out * 255) -def generate_monochromatic(hue: float, count: int = 4, base_saturation: float = 100) -> List[Tuple[float, float, float]]: - """生成同色系配色方案 +# ========== 配色方案内部实现(私有函数)========== - 基于同一色相,通过调整饱和度和亮度生成和谐的颜色组合 +def _build_monochromatic_colors(rgb_hue: float, count: int, base_saturation: float) -> List[Tuple[float, float, float]]: + """构建同色系颜色(与色彩空间无关的内部实现) Args: - hue: 基准色相 (0-360) - count: 生成颜色数量 (默认4) - base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度序列 + rgb_hue: RGB色相值 (0-360) + count: 生成颜色数量 + base_saturation: 基准饱和度 (0-100) Returns: list: HSB颜色列表 [(h, s, b), ...] @@ -699,73 +699,82 @@ def generate_monochromatic(hue: float, count: int = 4, base_saturation: float = for i in range(count): s = max(MIN_SATURATION, min(100, saturations[i] if i < len(saturations) else 50)) b = max(40, min(100, brightnesses[i] if i < len(brightnesses) else 70)) - colors.append((hue % 360, s, b)) + colors.append((rgb_hue % 360, s, b)) return colors -def generate_analogous(hue: float, angle: float = 30, count: int = 4, base_saturation: float = 100) -> List[Tuple[float, float, float]]: - """生成邻近色配色方案 - - 在色相环上选择与基准色相邻的颜色,创造和谐统一的视觉效果 +def _calculate_analogous_hues(base_hue: float, angle: float, count: int) -> List[float]: + """计算邻近色的色相值列表 Args: - hue: 基准色相 (0-360) - angle: 邻近角度范围 (默认30度) - count: 生成颜色数量 (默认4) - base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度 + base_hue: 基准色相 + angle: 邻近角度范围 + count: 颜色数量 Returns: - list: HSB颜色列表 [(h, s, b), ...] + list: 色相值列表 """ - colors = [] if count == 4: - # 4个颜色:基准色两侧各1个,加上基准色和另一个过渡色 - hues = [ - (hue - angle) % 360, - (hue - angle / 2) % 360, - hue % 360, - (hue + angle / 2) % 360 + return [ + (base_hue - angle) % 360, + (base_hue - angle / 2) % 360, + base_hue % 360, + (base_hue + angle / 2) % 360 ] else: step = (2 * angle) / max(count - 1, 1) - hues = [(hue - angle + i * step) % 360 for i in range(count)] + return [(base_hue - angle + i * step) % 360 for i in range(count)] - # 基于基准饱和度生成变化的饱和度 - for i, h in enumerate(hues): - # 距离基准色越远,饱和度略有变化 - distance_from_center = abs(i - len(hues) / 2) / (len(hues) / 2) - saturation_variation = 1 - distance_from_center * 0.3 # 变化范围30% - s = max(60, min(100, base_saturation * saturation_variation)) - colors.append((h, s, 90)) - return colors +def _build_analogous_colors( + rgb_hues: List[float], + base_saturation: float +) -> List[Tuple[float, float, float]]: + """构建邻近色颜色(与色彩空间无关的内部实现) + Args: + rgb_hues: RGB色相值列表 (已计算好的邻近色相) + base_saturation: 基准饱和度 (0-100) -def generate_complementary(hue: float, count: int = 5, base_saturation: float = 100) -> List[Tuple[float, float, float]]: - """生成互补色配色方案 + Returns: + list: HSB颜色列表 [(h, s, b), ...] + """ + colors = [] + for i, h in enumerate(rgb_hues): + distance_from_center = abs(i - len(rgb_hues) / 2) / (len(rgb_hues) / 2) + saturation_variation = 1 - distance_from_center * 0.3 + s = max(60, min(100, base_saturation * saturation_variation)) + colors.append((h % 360, s, 90)) + return colors - 选择色相环上相对位置的颜色(相差180度),创造强烈对比 - 所有采样点集中在两个区域:基准色区域和互补色区域 + +def _build_complementary_colors( + base_hue: float, + comp_hue: float, + count: int, + base_saturation: float +) -> List[Tuple[float, float, float]]: + """构建互补色颜色(与色彩空间无关的内部实现) Args: - hue: 基准色相 (0-360) - count: 生成颜色数量 (默认5) - base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度 + base_hue: 基准RGB色相 + comp_hue: 互补RGB色相 + count: 生成颜色数量 + base_saturation: 基准饱和度 Returns: list: HSB颜色列表 [(h, s, b), ...] """ colors = [] - comp_hue = (hue + 180) % 360 if count == 5: base_saturations = _generate_saturation_steps(base_saturation, 3) comp_saturations = _generate_saturation_steps(base_saturation, 2) colors = [ - (hue, base_saturations[0], 100), - (hue, max(30, base_saturations[1]), 90), - (hue, max(30, base_saturations[2]), 80), + (base_hue, base_saturations[0], 100), + (base_hue, max(30, base_saturations[1]), 90), + (base_hue, max(30, base_saturations[2]), 80), (comp_hue, comp_saturations[0], 100), (comp_hue, max(30, comp_saturations[1]), 90), ] @@ -779,7 +788,7 @@ def generate_complementary(hue: float, count: int = 5, base_saturation: float = for i in range(base_count): s = max(30, base_saturations[i]) b = 100 - i * (20 / max(base_count, 1)) - colors.append((hue, s, max(80, b))) + colors.append((base_hue, s, max(80, b))) for i in range(comp_count): s = max(30, comp_saturations[i]) @@ -789,90 +798,185 @@ def generate_complementary(hue: float, count: int = 5, base_saturation: float = return colors -def generate_split_complementary(hue: float, angle: float = 30, count: int = 3, base_saturation: float = 100) -> List[Tuple[float, float, float]]: - """生成分离补色配色方案 - - 选择基准色和互补色两侧的颜色,既有对比又更柔和 +def _build_split_complementary_colors( + base_hue: float, + left_hue: float, + right_hue: float, + count: int, + base_saturation: float +) -> List[Tuple[float, float, float]]: + """构建分离补色颜色(与色彩空间无关的内部实现) Args: - hue: 基准色相 (0-360) - angle: 分离角度 (默认30度) - count: 生成颜色数量 (默认3) - base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度 + base_hue: 基准RGB色相 + left_hue: 左侧分离补色RGB色相 + right_hue: 右侧分离补色RGB色相 + count: 生成颜色数量 + base_saturation: 基准饱和度 Returns: list: HSB颜色列表 [(h, s, b), ...] """ colors = [] - comp_hue = (hue + 180) % 360 - left_comp = (comp_hue - angle) % 360 - right_comp = (comp_hue + angle) % 360 if count == 3: - # 3个颜色:基准色 + 两个分离补色 colors = [ - (hue, base_saturation, 100), - (left_comp, max(50, base_saturation * 0.9), 100), - (right_comp, max(50, base_saturation * 0.9), 100) + (base_hue, base_saturation, 100), + (left_hue, max(50, base_saturation * 0.9), 100), + (right_hue, max(50, base_saturation * 0.9), 100) ] else: - colors.append((hue, base_saturation, 100)) - colors.append((left_comp, max(50, base_saturation * 0.9), 100)) - colors.append((right_comp, max(50, base_saturation * 0.9), 100)) + colors.extend([ + (base_hue, base_saturation, 100), + (left_hue, max(50, base_saturation * 0.9), 100), + (right_hue, max(50, base_saturation * 0.9), 100) + ]) remaining = count - 3 for i in range(remaining): - blend_hue = (hue + (i + 1) * 60) % 360 + blend_hue = (base_hue + (i + 1) * 60) % 360 s = max(50, base_saturation * (0.7 - i * 0.1)) colors.append((blend_hue, s, 85)) return colors -def generate_double_complementary(hue: float, angle: float = 30, count: int = 4, base_saturation: float = 100) -> List[Tuple[float, float, float]]: - """生成双补色配色方案 - - 选择两组互补色,创造丰富而平衡的配色方案 +def _build_double_complementary_colors( + hues: List[float], + saturations: List[float], + count: int +) -> List[Tuple[float, float, float]]: + """构建双补色颜色(与色彩空间无关的内部实现) Args: - hue: 基准色相 (0-360) - angle: 分离角度 (默认30度) - count: 生成颜色数量 (默认4) - base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度 + hues: RGB色相值列表 [基准色, 互补色, 第二色, 第二互补色] + saturations: 饱和度列表 + count: 生成颜色数量 Returns: list: HSB颜色列表 [(h, s, b), ...] """ colors = [] - comp_hue = (hue + 180) % 360 - second_hue = (hue + angle) % 360 - second_comp = (second_hue + 180) % 360 if count == 4: - # 4个颜色:两组互补色,使用基准饱和度 colors = [ - (hue, base_saturation, 100), - (comp_hue, max(50, base_saturation * 0.9), 100), - (second_hue, max(50, base_saturation * 0.9), 100), - (second_comp, max(50, base_saturation * 0.8), 100) + (hues[0], saturations[0], 100), + (hues[1], max(50, saturations[1] * 0.9), 100), + (hues[2], max(50, saturations[2] * 0.9), 100), + (hues[3], max(50, saturations[3] * 0.8), 100) ] else: - hues = [hue, comp_hue, second_hue, second_comp] - saturations = [ - base_saturation, - max(50, base_saturation * 0.9), - max(50, base_saturation * 0.9), - max(50, base_saturation * 0.8) - ] for i in range(min(count, 4)): colors.append((hues[i], saturations[i], 95)) for i in range(4, count): - blend_hue = (hue + i * 45) % 360 - s = max(50, base_saturation * (0.7 - (i - 4) * 0.1)) + blend_hue = (hues[0] + i * 45) % 360 + s = max(50, saturations[0] * (0.7 - (i - 4) * 0.1)) colors.append((blend_hue, s, 85)) return colors +# ========== RGB配色方案公共API ========== + +def generate_monochromatic(hue: float, count: int = 4, base_saturation: float = 100) -> List[Tuple[float, float, float]]: + """生成同色系配色方案 + + 基于同一色相,通过调整饱和度和亮度生成和谐的颜色组合 + + Args: + hue: 基准色相 (0-360) + count: 生成颜色数量 (默认4) + base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度序列 + + Returns: + list: HSB颜色列表 [(h, s, b), ...] + """ + return _build_monochromatic_colors(hue, count, base_saturation) + + +def generate_analogous(hue: float, angle: float = 30, count: int = 4, base_saturation: float = 100) -> List[Tuple[float, float, float]]: + """生成邻近色配色方案 + + 在色相环上选择与基准色相邻的颜色,创造和谐统一的视觉效果 + + Args: + hue: 基准色相 (0-360) + angle: 邻近角度范围 (默认30度) + count: 生成颜色数量 (默认4) + base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度 + + Returns: + list: HSB颜色列表 [(h, s, b), ...] + """ + hues = _calculate_analogous_hues(hue, angle, count) + return _build_analogous_colors(hues, base_saturation) + + +def generate_complementary(hue: float, count: int = 5, base_saturation: float = 100) -> List[Tuple[float, float, float]]: + """生成互补色配色方案 + + 选择色相环上相对位置的颜色(相差180度),创造强烈对比 + 所有采样点集中在两个区域:基准色区域和互补色区域 + + Args: + hue: 基准色相 (0-360) + count: 生成颜色数量 (默认5) + base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度 + + Returns: + list: HSB颜色列表 [(h, s, b), ...] + """ + comp_hue = (hue + 180) % 360 + return _build_complementary_colors(hue, comp_hue, count, base_saturation) + + +def generate_split_complementary(hue: float, angle: float = 30, count: int = 3, base_saturation: float = 100) -> List[Tuple[float, float, float]]: + """生成分离补色配色方案 + + 选择基准色和互补色两侧的颜色,既有对比又更柔和 + + Args: + hue: 基准色相 (0-360) + angle: 分离角度 (默认30度) + count: 生成颜色数量 (默认3) + base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度 + + Returns: + list: HSB颜色列表 [(h, s, b), ...] + """ + comp_hue = (hue + 180) % 360 + left_comp = (comp_hue - angle) % 360 + right_comp = (comp_hue + angle) % 360 + return _build_split_complementary_colors(hue, left_comp, right_comp, count, base_saturation) + + +def generate_double_complementary(hue: float, angle: float = 30, count: int = 4, base_saturation: float = 100) -> List[Tuple[float, float, float]]: + """生成双补色配色方案 + + 选择两组互补色,创造丰富而平衡的配色方案 + + Args: + hue: 基准色相 (0-360) + angle: 分离角度 (默认30度) + count: 生成颜色数量 (默认4) + base_saturation: 基准饱和度 (0-100),用于生成变化的饱和度 + + Returns: + list: HSB颜色列表 [(h, s, b), ...] + """ + comp_hue = (hue + 180) % 360 + second_hue = (hue + angle) % 360 + second_comp = (second_hue + 180) % 360 + + hues = [hue, comp_hue, second_hue, second_comp] + saturations = [ + base_saturation, + max(50, base_saturation * 0.9), + max(50, base_saturation * 0.9), + max(50, base_saturation * 0.8) + ] + return _build_double_complementary_colors(hues, saturations, count) + + def adjust_brightness(hsb_colors: List[Tuple[float, float, float]], brightness_delta: float) -> List[Tuple[float, float, float]]: """调整配色方案的明度 @@ -1540,6 +1644,8 @@ def ryb_hue_to_rgb_hue(ryb_hue: float) -> float: return hue +# ========== RYB配色方案公共API ========== + def generate_ryb_monochromatic(ryb_hue: float, count: int = 4, base_saturation: float = 100) -> List[Tuple[float, float, float]]: """生成 RYB 同色系配色方案 @@ -1551,18 +1657,8 @@ def generate_ryb_monochromatic(ryb_hue: float, count: int = 4, base_saturation: Returns: list: HSB颜色列表 [(h, s, b), ...] (RGB色相) """ - colors = [] - saturations = _generate_saturation_steps(base_saturation, count) - brightnesses = _generate_brightness_steps(count) - rgb_hue = ryb_hue_to_rgb_hue(ryb_hue) - - for i in range(count): - s = max(MIN_SATURATION, min(100, saturations[i] if i < len(saturations) else 50)) - b = max(40, min(100, brightnesses[i] if i < len(brightnesses) else 70)) - colors.append((rgb_hue % 360, s, b)) - - return colors + return _build_monochromatic_colors(rgb_hue, count, base_saturation) def generate_ryb_analogous(ryb_hue: float, angle: float = 30, count: int = 4, base_saturation: float = 100) -> List[Tuple[float, float, float]]: @@ -1577,30 +1673,11 @@ def generate_ryb_analogous(ryb_hue: float, angle: float = 30, count: int = 4, ba Returns: list: HSB颜色列表 [(h, s, b), ...] (RGB色相) """ - colors = [] - if count == 4: - # 4个颜色:基准色两侧各1个,加上基准色和另一个过渡色 - ryb_hues = [ - (ryb_hue - angle) % 360, - (ryb_hue - angle / 2) % 360, - ryb_hue % 360, - (ryb_hue + angle / 2) % 360 - ] - else: - step = (2 * angle) / max(count - 1, 1) - ryb_hues = [(ryb_hue - angle + i * step) % 360 for i in range(count)] - - # 基于基准饱和度生成变化的饱和度 - for i, h in enumerate(ryb_hues): - # 距离基准色越远,饱和度略有变化 - distance_from_center = abs(i - len(ryb_hues) / 2) / (len(ryb_hues) / 2) - saturation_variation = 1 - distance_from_center * 0.3 # 变化范围30% - s = max(60, min(100, base_saturation * saturation_variation)) - # 转换 RYB 色相到 RGB 色相 - rgb_hue = ryb_hue_to_rgb_hue(h) - colors.append((rgb_hue, s, 90)) - - return colors + # 在RYB空间计算邻近色相 + ryb_hues = _calculate_analogous_hues(ryb_hue, angle, count) + # 转换为RGB色相 + rgb_hues = [ryb_hue_to_rgb_hue(h) for h in ryb_hues] + return _build_analogous_colors(rgb_hues, base_saturation) def generate_ryb_complementary(ryb_hue: float, count: int = 5, base_saturation: float = 100) -> List[Tuple[float, float, float]]: @@ -1616,40 +1693,12 @@ def generate_ryb_complementary(ryb_hue: float, count: int = 5, base_saturation: Returns: list: HSB颜色列表 [(h, s, b), ...] (RGB色相) """ - colors = [] + # 在RYB空间计算互补色 ryb_comp_hue = (ryb_hue + 180) % 360 - + # 转换为RGB色相 rgb_hue = ryb_hue_to_rgb_hue(ryb_hue) rgb_comp_hue = ryb_hue_to_rgb_hue(ryb_comp_hue) - - if count == 5: - base_saturations = _generate_saturation_steps(base_saturation, 3) - comp_saturations = _generate_saturation_steps(base_saturation, 2) - colors = [ - (rgb_hue, base_saturations[0], 100), - (rgb_hue, max(30, base_saturations[1]), 90), - (rgb_hue, max(30, base_saturations[2]), 80), - (rgb_comp_hue, comp_saturations[0], 100), - (rgb_comp_hue, max(30, comp_saturations[1]), 90), - ] - else: - base_count = (count + 1) // 2 - comp_count = count - base_count - - base_saturations = _generate_saturation_steps(base_saturation, base_count) - comp_saturations = _generate_saturation_steps(base_saturation, comp_count) - - for i in range(base_count): - s = max(30, base_saturations[i]) - b = 100 - i * (20 / max(base_count, 1)) - colors.append((rgb_hue, s, max(80, b))) - - for i in range(comp_count): - s = max(30, comp_saturations[i]) - b = 100 - i * (20 / max(comp_count, 1)) - colors.append((rgb_comp_hue, s, max(80, b))) - - return colors + return _build_complementary_colors(rgb_hue, rgb_comp_hue, count, base_saturation) def generate_ryb_split_complementary(ryb_hue: float, angle: float = 30, count: int = 3, base_saturation: float = 100) -> List[Tuple[float, float, float]]: @@ -1664,34 +1713,17 @@ def generate_ryb_split_complementary(ryb_hue: float, angle: float = 30, count: i Returns: list: HSB颜色列表 [(h, s, b), ...] (RGB色相) """ - colors = [] + # 在RYB空间计算 ryb_comp_hue = (ryb_hue + 180) % 360 - ryb_left_comp = (ryb_comp_hue - angle) % 360 - ryb_right_comp = (ryb_comp_hue + angle) % 360 + ryb_left = (ryb_comp_hue - angle) % 360 + ryb_right = (ryb_comp_hue + angle) % 360 - # 转换到 RGB 色相 + # 转换为RGB色相 rgb_hue = ryb_hue_to_rgb_hue(ryb_hue) - rgb_left = ryb_hue_to_rgb_hue(ryb_left_comp) - rgb_right = ryb_hue_to_rgb_hue(ryb_right_comp) - - if count == 3: - colors = [ - (rgb_hue, base_saturation, 100), - (rgb_left, max(50, base_saturation * 0.9), 100), - (rgb_right, max(50, base_saturation * 0.9), 100) - ] - else: - colors.append((rgb_hue, base_saturation, 100)) - colors.append((rgb_left, max(50, base_saturation * 0.9), 100)) - colors.append((rgb_right, max(50, base_saturation * 0.9), 100)) - remaining = count - 3 - for i in range(remaining): - blend_hue = (ryb_hue + (i + 1) * 60) % 360 - rgb_blend = ryb_hue_to_rgb_hue(blend_hue) - s = max(50, base_saturation * (0.7 - i * 0.1)) - colors.append((rgb_blend, s, 85)) + rgb_left = ryb_hue_to_rgb_hue(ryb_left) + rgb_right = ryb_hue_to_rgb_hue(ryb_right) - return colors + return _build_split_complementary_colors(rgb_hue, rgb_left, rgb_right, count, base_saturation) def generate_ryb_double_complementary(ryb_hue: float, angle: float = 30, count: int = 4, base_saturation: float = 100) -> List[Tuple[float, float, float]]: @@ -1706,41 +1738,26 @@ def generate_ryb_double_complementary(ryb_hue: float, angle: float = 30, count: Returns: list: HSB颜色列表 [(h, s, b), ...] (RGB色相) """ - colors = [] + # 在RYB空间计算 ryb_comp_hue = (ryb_hue + 180) % 360 ryb_second_hue = (ryb_hue + angle) % 360 ryb_second_comp = (ryb_second_hue + 180) % 360 - # 转换到 RGB 色相 - rgb_hue = ryb_hue_to_rgb_hue(ryb_hue) - rgb_comp = ryb_hue_to_rgb_hue(ryb_comp_hue) - rgb_second = ryb_hue_to_rgb_hue(ryb_second_hue) - rgb_second_comp = ryb_hue_to_rgb_hue(ryb_second_comp) - - if count == 4: - colors = [ - (rgb_hue, base_saturation, 100), - (rgb_comp, max(50, base_saturation * 0.9), 100), - (rgb_second, max(50, base_saturation * 0.9), 100), - (rgb_second_comp, max(50, base_saturation * 0.8), 100) - ] - else: - hues = [rgb_hue, rgb_comp, rgb_second, rgb_second_comp] - saturations = [ - base_saturation, - max(50, base_saturation * 0.9), - max(50, base_saturation * 0.9), - max(50, base_saturation * 0.8) - ] - for i in range(min(count, 4)): - colors.append((hues[i], saturations[i], 95)) - for i in range(4, count): - blend_ryb = (ryb_hue + i * 45) % 360 - rgb_blend = ryb_hue_to_rgb_hue(blend_ryb) - s = max(50, base_saturation * (0.7 - (i - 4) * 0.1)) - colors.append((rgb_blend, s, 85)) - - return colors + # 转换为RGB色相 + rgb_hues = [ + ryb_hue_to_rgb_hue(ryb_hue), + ryb_hue_to_rgb_hue(ryb_comp_hue), + ryb_hue_to_rgb_hue(ryb_second_hue), + ryb_hue_to_rgb_hue(ryb_second_comp) + ] + saturations = [ + base_saturation, + max(50, base_saturation * 0.9), + max(50, base_saturation * 0.9), + max(50, base_saturation * 0.8) + ] + + return _build_double_complementary_colors(rgb_hues, saturations, count) def get_scheme_preview_colors_ryb( -- Gitee From bd9027d88c571813ada862ff97de01a355ba9675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Tue, 17 Mar 2026 20:27:51 +0800 Subject: [PATCH 15/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=85=A8=E5=B1=8F=E6=A8=A1=E5=BC=8F=E4=B8=8B=E4=B8=BB=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E6=9C=AA=E8=87=AA=E5=8A=A8=E5=BC=B9=E5=87=BA=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 fix_windows_taskbar_icon_for_window 函数中全屏窗口状态判断错误 - 全屏窗口 isVisible() 可能返回 False,增加 isFullScreen() 判断避免错误调用 show() - 防止全屏窗口被重置为普通窗口导致最小化到任务栏 --- utils/platform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/platform.py b/utils/platform.py index c95695f..046bb51 100644 --- a/utils/platform.py +++ b/utils/platform.py @@ -145,7 +145,8 @@ def fix_windows_taskbar_icon_for_window(window) -> bool: try: # 确保窗口已经显示 - if not window.isVisible(): + # 注意:全屏窗口的 isVisible 可能返回 False,需要特殊处理 + if not window.isVisible() and not window.isFullScreen(): window.show() window.raise_() window.activateWindow() -- Gitee From 2d2067184b0bb9a4080a03020df040fc6892181c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Tue, 17 Mar 2026 21:09:06 +0800 Subject: [PATCH 16/47] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E7=B2=BE=E7=AE=80?= =?UTF-8?q?=E6=A0=B8=E5=BF=83=E5=8E=9F=E5=88=99=E6=96=87=E6=A1=A3=E5=B9=B6?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 精简核心原则文档(274行→187行),删除冗余内容 - 新增AI编程约束:延迟抽象、适度防御、禁止模糊变量名 - 强化导入原则:禁止预导入未使用的模块 - 运行ruff自动修复65个代码问题(未使用导入、f-string等) --- core/config.py | 1 - core/image_service.py | 1 - core/svg_color_mapper.py | 2 +- dialogs/about_dialog.py | 2 +- dialogs/base_frameless_dialog.py | 2 +- dialogs/contrast_dialog.py | 10 +- dialogs/edit_palette.py | 2 +- dialogs/export_settings_dialog.py | 4 +- main.py | 2 +- tests/test_logger.py | 1 - tests/test_threading.py | 7 +- ui/canvases.py | 2 +- ui/color_extract.py | 2 +- ui/color_generation.py | 6 +- ui/gradient_extract.py | 9 +- ui/histograms.py | 2 +- ui/main_window.py | 10 +- ui/palette_management.py | 4 +- ui/preset_color.py | 4 +- ui/settings.py | 7 +- ui/zoom_viewer.py | 2 +- utils/platform.py | 2 +- ...70\345\277\203\345\216\237\345\210\231.md" | 287 +++++------------- 23 files changed, 109 insertions(+), 262 deletions(-) diff --git a/core/config.py b/core/config.py index b27d433..86a7b7c 100644 --- a/core/config.py +++ b/core/config.py @@ -3,7 +3,6 @@ import json import os import shutil import sys -import uuid from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Optional, Tuple diff --git a/core/image_service.py b/core/image_service.py index 30430c2..b3993d2 100644 --- a/core/image_service.py +++ b/core/image_service.py @@ -12,7 +12,6 @@ from typing import Optional # 第三方库导入 from PIL import Image -from PIL.ExifTags import TAGS from PySide6.QtCore import QObject, QThread, Signal from PySide6.QtGui import QImage, QPixmap diff --git a/core/svg_color_mapper.py b/core/svg_color_mapper.py index 4691c40..07db65d 100644 --- a/core/svg_color_mapper.py +++ b/core/svg_color_mapper.py @@ -1074,7 +1074,7 @@ class SVGColorMapper: if modified_css != css_text: style_elem.text = modified_css - print(f"已更新 CSS 样式中的颜色映射") + print("已更新 CSS 样式中的颜色映射") def set_element_color(self, element_id: str, color: str, color_type: str = 'fill') -> bool: """设置单个元素的颜色 diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py index 4a57549..2298064 100644 --- a/dialogs/about_dialog.py +++ b/dialogs/about_dialog.py @@ -197,7 +197,7 @@ class AboutDialog(BaseFramelessDialog): app_info = version_manager.get_app_info() version = version_manager.get_version() - return f"""  取色卡(Color Card)是一款专为摄影师和设计师开发的图片分析及配色工具,旨在帮助摄影爱好者和专业人士快速分析图像的色彩分布、亮度信息等关键数据,并提供一站式的本地配色解决方案。 + return """  取色卡(Color Card)是一款专为摄影师和设计师开发的图片分析及配色工具,旨在帮助摄影爱好者和专业人士快速分析图像的色彩分布、亮度信息等关键数据,并提供一站式的本地配色解决方案。 项目功能设计借鉴参考了Adobe Color、色采、palettemakel等优秀的在线配色工具。 diff --git a/dialogs/base_frameless_dialog.py b/dialogs/base_frameless_dialog.py index 7f17544..f589099 100644 --- a/dialogs/base_frameless_dialog.py +++ b/dialogs/base_frameless_dialog.py @@ -1,7 +1,7 @@ # 第三方库导入 from PySide6.QtWidgets import QLabel from PySide6.QtCore import Qt -from PySide6.QtGui import QPixmap, QPalette, QColor +from PySide6.QtGui import QPixmap, QPalette from qframelesswindow import FramelessDialog from qfluentwidgets import qconfig diff --git a/dialogs/contrast_dialog.py b/dialogs/contrast_dialog.py index 2032590..153648b 100644 --- a/dialogs/contrast_dialog.py +++ b/dialogs/contrast_dialog.py @@ -11,9 +11,9 @@ from typing import List, Dict, Tuple from PySide6.QtCore import Qt, Signal, QPointF from PySide6.QtWidgets import ( QHBoxLayout, QLabel, QVBoxLayout, QWidget, - QFrame, QScrollArea + QFrame ) -from PySide6.QtGui import QColor, QPainter, QBrush, QPen, QFont, QPolygonF +from PySide6.QtGui import QColor, QPainter, QBrush, QPen, QPolygonF from qfluentwidgets import ( ComboBox, PushButton, ToolButton, FluentIcon, isDarkTheme, qconfig, CardWidget @@ -23,12 +23,12 @@ from qfluentwidgets import ( from utils import tr, load_icon_universal from dialogs import BaseFramelessDialog from core.contrast import ( - calculate_contrast_ratio, get_contrast_info, + get_contrast_info, rgb_to_hex, get_contrast_status_color ) from utils.theme_colors import ( get_text_color, get_border_color, - get_secondary_text_color, get_title_color, get_card_background_color + get_secondary_text_color, get_title_color ) @@ -476,7 +476,7 @@ class ContrastCheckDialog(BaseFramelessDialog): # 等级标签 self.level_label = QLabel("--") - self.level_label.setStyleSheet(f"font-size: 16px; font-weight: bold;") + self.level_label.setStyleSheet("font-size: 16px; font-weight: bold;") result_layout.addWidget(self.level_label) result_layout.addStretch() diff --git a/dialogs/edit_palette.py b/dialogs/edit_palette.py index 8fb45bb..8deebdf 100644 --- a/dialogs/edit_palette.py +++ b/dialogs/edit_palette.py @@ -1,7 +1,7 @@ # 标准库导入 import re from datetime import datetime -from typing import Dict, Any, List, Tuple, Optional +from typing import Dict, Any, Tuple, Optional # 第三方库导入 from PySide6.QtCore import Qt, QTimer, Signal, QPoint, QRect diff --git a/dialogs/export_settings_dialog.py b/dialogs/export_settings_dialog.py index 49c2a36..ed98d45 100644 --- a/dialogs/export_settings_dialog.py +++ b/dialogs/export_settings_dialog.py @@ -2,10 +2,10 @@ from typing import List, Optional # 第三方库导入 -from PySide6.QtCore import Qt, Signal +from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QHBoxLayout, QLabel, QVBoxLayout, QWidget, - QFileDialog, QMessageBox + QMessageBox ) from qfluentwidgets import ( PushButton, LineEdit, RadioButton, qconfig, diff --git a/main.py b/main.py index a237bd5..b9880dc 100644 --- a/main.py +++ b/main.py @@ -180,7 +180,7 @@ def main(): from core import get_config_manager logger.info("core 模块导入完成") - from utils import fix_windows_taskbar_icon_for_window, load_icon_universal, tr, get_locale_manager + from utils import fix_windows_taskbar_icon_for_window, load_icon_universal, get_locale_manager logger.info("utils 模块导入完成") from ui import MainWindow diff --git a/tests/test_logger.py b/tests/test_logger.py index 8b36066..a0da9c7 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -7,7 +7,6 @@ import logging import time from pathlib import Path -from typing import Any, Dict from unittest.mock import patch # 第三方库导入 diff --git a/tests/test_threading.py b/tests/test_threading.py index 8a23c55..1acf107 100644 --- a/tests/test_threading.py +++ b/tests/test_threading.py @@ -5,19 +5,18 @@ # 标准库导入 import time -from typing import List, Tuple # 第三方库导入 import pytest -from PySide6.QtCore import Qt, QCoreApplication, QThread +from PySide6.QtCore import Qt from PySide6.QtGui import QImage # 项目模块导入 from core.histogram_service import HistogramCalculator, HistogramService from core.luminance_service import LuminanceCalculator, LuminanceService from core.color_service import DominantColorExtractor, ColorService -from core.image_service import ProgressiveImageLoader, ImageService -from core.palette_service import PaletteImporter, PaletteExporter, PaletteService +from core.image_service import ProgressiveImageLoader +from core.palette_service import PaletteImporter, PaletteExporter class TestHistogramCalculator: diff --git a/ui/canvases.py b/ui/canvases.py index 0152296..b525daa 100644 --- a/ui/canvases.py +++ b/ui/canvases.py @@ -10,7 +10,7 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel from qfluentwidgets import Action, FluentIcon, RoundMenu # 项目模块导入 -from core import get_luminance, get_zone, ServiceFactory, ZONE_WIDTH, log_user_action +from core import get_luminance, get_zone, ServiceFactory, log_user_action from utils import tr from .color_picker import ColorPicker from .zoom_viewer import ZoomViewer diff --git a/ui/color_extract.py b/ui/color_extract.py index ce8f031..e624506 100644 --- a/ui/color_extract.py +++ b/ui/color_extract.py @@ -9,7 +9,7 @@ import uuid from datetime import datetime # 第三方库导入 -from PySide6.QtCore import Qt, Signal +from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QFileDialog, QHBoxLayout, QSplitter, QStackedWidget, QSizePolicy, QVBoxLayout, QWidget diff --git a/ui/color_generation.py b/ui/color_generation.py index 86256ca..975532e 100644 --- a/ui/color_generation.py +++ b/ui/color_generation.py @@ -15,16 +15,14 @@ from PySide6.QtWidgets import ( QSplitter ) from PySide6.QtCore import Qt, Signal, QTimer -from PySide6.QtGui import QColor from qfluentwidgets import ( - CardWidget, PushButton, ToolButton, FluentIcon, InfoBar, InfoBarPosition, + PushButton, ToolButton, FluentIcon, InfoBar, InfoBarPosition, qconfig, isDarkTheme, ComboBox, PrimaryPushButton, Slider ) # 项目模块导入 from core import ( - get_color_info, get_config_manager, hsb_to_rgb, rgb_to_hsb, adjust_brightness, - rgb_hue_to_ryb_hue, ryb_hue_to_rgb_hue + get_color_info, get_config_manager, hsb_to_rgb, rgb_to_hsb, rgb_hue_to_ryb_hue, ryb_hue_to_rgb_hue ) from utils import tr, get_locale_manager from dialogs import EditPaletteDialog diff --git a/ui/gradient_extract.py b/ui/gradient_extract.py index 070b2bf..c5f4a34 100644 --- a/ui/gradient_extract.py +++ b/ui/gradient_extract.py @@ -7,20 +7,19 @@ from typing import List, Tuple from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QColor, QPainter from PySide6.QtWidgets import ( - QApplication, QDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, - QSizePolicy, QSplitter, QVBoxLayout, QWidget + QDialog, QHBoxLayout, QLabel, QLineEdit, QSizePolicy, QSplitter, QVBoxLayout, QWidget ) from qfluentwidgets import ( - FluentIcon, InfoBar, InfoBarPosition, PushButton, Slider, ToolButton, qconfig, isDarkTheme, ScrollArea + FluentIcon, InfoBar, InfoBarPosition, PushButton, Slider, qconfig, ScrollArea ) # 项目模块导入 -from core import generate_gradient, generate_random_gradient, get_color_info, rgb_to_hex +from core import generate_gradient, generate_random_gradient, get_color_info from core import get_config_manager from core.logger import get_logger, log_user_action from ui.cards import ColorCard from utils import tr, get_locale_manager, calculate_grid_columns -from utils.theme_colors import get_border_color, get_card_background_color, get_text_color +from utils.theme_colors import get_border_color, get_text_color logger = get_logger("gradient_extract") diff --git a/ui/histograms.py b/ui/histograms.py index d703329..62507bc 100644 --- a/ui/histograms.py +++ b/ui/histograms.py @@ -1,6 +1,6 @@ # 第三方库导入 import math -from typing import List, Optional +from typing import List from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QColor, QFont, QLinearGradient, QPainter, QPen, QMouseEvent from PySide6.QtWidgets import QWidget diff --git a/ui/main_window.py b/ui/main_window.py index 6a55395..1f520dd 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -3,15 +3,14 @@ import sys from typing import List, Dict, Any # 第三方库导入 -from PySide6.QtCore import Qt, QTimer -from PySide6.QtGui import QIcon, QKeySequence, QScreen, QShortcut +from PySide6.QtCore import Qt +from PySide6.QtGui import QIcon, QKeySequence, QShortcut from PySide6.QtWidgets import ( - QApplication, QFileDialog, QHBoxLayout, QLabel, QSplitter, QVBoxLayout, QWidget + QApplication, QLabel ) from qfluentwidgets import FluentIcon, FluentWindow, NavigationItemPosition, qrouter, FluentTitleBar, ToolButton, setTheme, Theme, isDarkTheme # 项目模块导入 -from core import get_color_info from core import get_config_manager, ImageMediator from utils import tr, get_locale_manager from version import version_manager @@ -23,10 +22,7 @@ from .palette_management import PaletteManagementInterface from .preset_color import PresetColorInterface from .settings import SettingsInterface from .color_preview import ColorPreviewInterface -from .cards import ColorCardPanel -from .histograms import LuminanceHistogramWidget, RGBHistogramWidget from .color_wheel import HSBColorWheel, InteractiveColorWheel -from .canvases import ImageCanvas, LuminanceCanvas class CustomTitleBar(FluentTitleBar): diff --git a/ui/palette_management.py b/ui/palette_management.py index 8565a1e..25133e4 100644 --- a/ui/palette_management.py +++ b/ui/palette_management.py @@ -11,7 +11,7 @@ from PySide6.QtWidgets import ( ) from qfluentwidgets import ( CardWidget, ScrollArea, ToolButton, FluentIcon, ComboBox, - InfoBar, InfoBarPosition, isDarkTheme, qconfig, + InfoBar, InfoBarPosition, qconfig, PushButton, SubtitleLabel, MessageBox ) @@ -22,7 +22,7 @@ from core.async_loader import BaseBatchLoader from core.grouping import generate_groups from core.logger import get_logger, log_user_action, log_performance from .cards import ColorModeContainer, get_text_color, get_border_color, get_placeholder_color -from utils.theme_colors import get_card_background_color, get_title_color, get_interface_background_color +from utils.theme_colors import get_title_color from dialogs import ColorblindPreviewDialog, ContrastCheckDialog, EditPaletteDialog logger = get_logger("palette_management") diff --git a/ui/preset_color.py b/ui/preset_color.py index ba0c493..735ac25 100644 --- a/ui/preset_color.py +++ b/ui/preset_color.py @@ -2,7 +2,7 @@ import math import uuid from datetime import datetime -from typing import List, Dict, Any +from typing import Dict, Any # 第三方库导入 from PySide6.QtCore import Qt, Signal @@ -23,7 +23,7 @@ from core.color_data import ( get_color_source, get_all_color_sources, get_random_palettes, ColorSource ) from .cards import ColorModeContainer, get_text_color, get_border_color, get_placeholder_color -from utils.theme_colors import get_card_background_color, get_title_color, get_interface_background_color, get_secondary_text_color +from utils.theme_colors import get_title_color, get_secondary_text_color # ============================================================================= diff --git a/ui/settings.py b/ui/settings.py index 49ee4c4..fec7e14 100644 --- a/ui/settings.py +++ b/ui/settings.py @@ -1,11 +1,10 @@ # 标准库导入 from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import ( - QFileDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget + QHBoxLayout, QLabel, QVBoxLayout, QWidget ) from qfluentwidgets import ( - ComboBox, FluentIcon, InfoBar, InfoBarPosition, - PushButton, PushSettingCard, ScrollArea, SettingCardGroup, SubtitleLabel, SwitchButton, qconfig, isDarkTheme + ComboBox, FluentIcon, PushSettingCard, ScrollArea, SettingCardGroup, SubtitleLabel, SwitchButton, qconfig ) # 项目模块导入 @@ -17,7 +16,7 @@ from dialogs import AboutDialog, UpdateAvailableDialog logger = get_logger("settings") from version import version_manager -from utils.theme_colors import get_title_color, get_text_color, get_interface_background_color, get_card_background_color, get_border_color +from utils.theme_colors import get_title_color AVAILABLE_COLOR_MODES = ['HSB', 'LAB', 'HSL', 'CMYK', 'RGB'] diff --git a/ui/zoom_viewer.py b/ui/zoom_viewer.py index 9705747..59a63eb 100644 --- a/ui/zoom_viewer.py +++ b/ui/zoom_viewer.py @@ -1,6 +1,6 @@ # 第三方库导入 from PySide6.QtCore import QPoint, Qt -from PySide6.QtGui import QColor, QImage, QPainter, QPainterPath, QPen +from PySide6.QtGui import QPainter, QPainterPath, QPen from PySide6.QtWidgets import QWidget # 项目模块导入 diff --git a/utils/platform.py b/utils/platform.py index 046bb51..17d6364 100644 --- a/utils/platform.py +++ b/utils/platform.py @@ -5,7 +5,7 @@ import sys from typing import Dict, Optional # 第三方库导入 -from PySide6.QtCore import QObject, Qt, QTimer, Signal +from PySide6.QtCore import QObject, QTimer, Signal # 项目模块导入 from .icon import get_icon_path diff --git "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" index 84478bd..7219419 100644 --- "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" +++ "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" @@ -1,14 +1,12 @@ # Color Card 核心原则 -> **详细规范请参阅**:`文档/开发规范.md`(包含完整的代码编写规范、布局规范、配色规范等) +> **详细规范请参阅**:`文档/开发规范.md` ## 1. 技术栈 -| 项目 | 说明 | -|:---:|:---| -| Python | 3.11+ | -| GUI 框架 | PySide6 + PySide6-Fluent-Widgets | -| 注释语言 | 中文 | +- Python 3.11+ +- GUI: PySide6 + PySide6-Fluent-Widgets +- 注释语言: 中文 --- @@ -16,312 +14,173 @@ ``` color_card/ -├── main.py # 程序入口 -├── core/ # 核心功能模块 -│ ├── color.py # 颜色处理 -│ ├── config.py # 配置管理 -│ └── ... -├── ui/ # UI 模块(扁平化) -│ ├── main_window.py # 主窗口 -│ ├── canvases.py # 画布组件 -│ ├── cards.py # 卡片组件 -│ ├── theme_colors.py # 主题颜色管理 -│ └── ... -├── dialogs/ # 对话框模块 -├── utils/ # 工具函数 -│ ├── locale.py # 多语言国际化 -│ └── ... -├── locales/ # 语言包目录 -│ -└── color_data/ # 颜色数据(JSON) +├── main.py # 程序入口 +├── core/ # 核心功能(业务层) +├── ui/ # UI模块(展示层) +├── dialogs/ # 对话框 +├── utils/ # 工具函数 +├── locales/ # 语言包 +└── color_data/ # 颜色数据 ``` -**核心理念:UI层和业务层分层,公共模块统一管理,业务模块按需调用;UI层只负责UI,功能层面全部下到业务层** +**核心理念:UI层和业务层分离,业务逻辑全部下沉到 core 模块** --- ## 3. 命名规范 | 类型 | 规范 | 示例 | -|:---:|:---:|:---| -| 类名 | 驼峰命名法 | `ColorPicker`, `ImageCanvas` | -| 函数/方法 | 小写+下划线 | `extract_color()` | -| 变量 | 小写+下划线 | `picker_positions` | -| 常量 | 大写+下划线 | `PICKER_RADIUS = 12` | +|:---:|:---|:---| +| 类名 | 驼峰 | `ColorPicker` | +| 函数/变量 | 小写+下划线 | `extract_color()` | +| 常量 | 大写+下划线 | `MAX_SIZE = 1000` | | 私有属性 | 单下划线前缀 | `_dragging` | -| 信号 | 小写+下划线 | `color_picked = Signal(int, tuple)` | + +**禁止**:`l`(易与1混淆)、`O`(易与0混淆) --- -## 4. 导入顺序 +## 4. 导入原则 ```python -# 标准库导入 +# 标准库 → 第三方 → 项目模块 import sys from pathlib import Path -# 第三方库导入 from PySide6.QtWidgets import QWidget -from qfluentwidgets import FluentWindow, setTheme, Theme +from qfluentwidgets import FluentWindow -# 项目模块导入 from core import get_color_info -from ui.theme_colors import get_text_color ``` -**导入清理原则:** -- 按模块类型分组导入,添加清晰的分组注释 -- 定期清理未使用的导入,合并同一模块的多次导入 -- 优先使用绝对导入 - ---- - -## 5. 代码风格 - -| 规则 | 说明 | -|:---:|:---| -| 代码风格 | PEP 8 | -| 缩进 | 4 空格 | -| 行长 | ≤ 100 字符 | +- **只导入实际使用的模块** +- 禁止预导入"可能用到"的模块 +- 按分组添加注释 --- -## 6. 关键约束 - -### 6.1 颜色管理 +## 5. 关键约束 +### 颜色管理 ```python -# 禁止硬编码颜色 -painter.setPen(QColor(255, 255, 255)) # ❌ 错误 +# ❌ 禁止硬编码 +painter.setPen(QColor(255, 255, 255)) -# 必须使用主题颜色模块 +# ✓ 必须使用主题颜色 from ui.theme_colors import get_text_color -painter.setPen(get_text_color()) # ✓ 正确 +painter.setPen(get_text_color()) ``` -### 6.2 信号同步防循环 - +### 信号防循环 ```python -# 双向同步时使用 emit_sync 参数 def set_image_data(self, pixmap, image, emit_sync=True): self._pixmap = pixmap - if emit_sync: # 只在独立操作时发射信号 + if emit_sync: self.image_loaded.emit() ``` -### 6.3 主题切换 - +### 异常处理 ```python -from qfluentwidgets import qconfig, isDarkTheme - -# 监听主题变化 -qconfig.themeChangedFinished.connect(self._update_styles) - -# 更新样式 -def _update_styles(self): - if isDarkTheme(): - # 深色主题样式 - pass -``` - -### 6.4 异常处理 - -```python -# 禁止裸 except -except Exception: # ❌ 错误 - pass - -# 必须指定具体类型 -except (OSError, ValueError) as e: # ✓ 正确 +# ❌ 禁止裸 except +# ✓ 必须指定具体类型 +except (OSError, ValueError) as e: print(f"错误: {e}") ``` -### 6.5 QSplitter 样式 - +### QSplitter ```python -# 所有 QSplitter 必须隐藏分隔条 -splitter = QSplitter(Qt.Orientation.Vertical) -splitter.setHandleWidth(0) # 必须设置 +splitter.setHandleWidth(0) # 必须隐藏分隔条 ``` -### 6.6 代码清理同步 - -修改代码时同步清理相关重复代码,避免遗留冗余逻辑。 +### 代码清理 +- 删除未使用的变量、导入 +- 禁止复制粘贴后仅做微调 +- 重复代码提取公共方法或基类 -- 删除未使用的变量、函数、导入和注释 -- 合并重复的逻辑和相似的功能 -- 检查并清除相关的重复冗余代码 -- 保持代码整洁,提高代码复用性 +### 设计原则 +- **延迟抽象**:只有一种实现时不用工厂/策略模式 +- **适度防御**:边界检查要有实际意义 +- **单一职责**:一个类只负责一类功能 -### 6.7 基类设计 - -- 单一职责:一个基类只负责一类功能 -- 接口清晰:抽象方法定义明确 -- 可扩展:便于添加新子类 - -### 6.8 多线程使用 +### 多线程使用 耗时操作(复杂计算等场景)应使用多线程,避免阻塞UI主线程。 --- -## 7. 文档规范 - -### 7.1 编写原则 - -禁止使用浮夸、空洞的词汇,保持务实、准确的表述风格。 +## 6. 文档字符串 -如:最佳实践这类词应禁止使用,而应使用更具体的描述。 - -### 7.2 文档字符串(重要) - -**所有公共类和方法必须添加文档字符串,使用中文。** +使用中文,避免过度详细: ```python -# 类文档字符串(简洁) class ImageCanvas(QWidget): """图片显示画布,支持取色点拖动""" pass -# 方法文档字符串(包含参数说明) -def set_image(self, image_path): - """加载并显示图片 - - Args: - image_path: 图片文件的完整路径 - """ - pass - -# 带返回值的文档字符串(完整格式) def get_color_info(self, r, g, b): """获取颜色信息 Args: - r: 红色通道值 (0-255) - g: 绿色通道值 (0-255) - b: 蓝色通道值 (0-255) - + r, g, b: RGB通道值 (0-255) Returns: - dict: 包含RGB、HSB、LAB、HEX颜色信息的字典 + dict: 颜色信息 """ pass ``` --- -## 8. 常用模块导入 +## 7. 常用导入 ```python # 主题颜色 -from ui.theme_colors import ( - get_text_color, - get_card_background_color, - get_border_color -) +from ui.theme_colors import get_text_color, get_card_background_color # 配置管理 -from core import get_config_manager, get_scene_config_manager +from core import get_config_manager -# 多语言国际化 -from utils import tr, set_language, get_locale_manager +# 国际化 +from utils import tr, set_language -# Fluent Widgets -from qfluentwidgets import ( - FluentWindow, setTheme, Theme, FluentIcon, - RoundMenu, Action, MessageBox, qconfig, isDarkTheme -) +# Fluent +from qfluentwidgets import FluentWindow, qconfig, isDarkTheme ``` --- -## 9. 多语言国际化 - -### 9.1 基本用法 +## 8. 主题切换 ```python -from utils import tr, set_language, get_locale_manager - -# 获取翻译文本 -text = tr('navigation.color_extract') - -# 参数化翻译 -text = tr('messages.copy_success.content', value='#FF5733') +qconfig.themeChangedFinished.connect(self._update_styles) -# 切换语言 -set_language('en_US') +def _update_styles(self): + if isDarkTheme(): + # 深色主题样式 + pass ``` -### 9.2 界面支持语言切换 +--- + +## 9. 多语言 ```python +text = tr('navigation.color_extract') + class MyInterface(QWidget): def __init__(self, parent=None): super().__init__(parent) - get_locale_manager().language_changed.connect(self._on_language_changed) - - def _on_language_changed(self, language_code): - self.update_texts() - - def update_texts(self): - self.title_label.setText(tr('my_interface.title')) + get_locale_manager().language_changed.connect(self.update_texts) ``` --- -## 10. 日志系统说明 +## 10. 日志 -日志存储在用户主目录 `~/.color_card/logs/`,**不是项目目录**。 +日志存储在 `~/.color_card/logs/`(用户主目录,不是项目目录) ```python # 日志目录自动创建,已存在不会报错 self._log_dir.mkdir(parents=True, exist_ok=True) -``` - -> **AI 提示**:沙箱环境无法访问日志是环境限制,不是代码问题。不要修改日志系统,不用手动创建目录。 - ---- -## 12. 无边框对话框 - -使用 `BaseFramelessDialog` 作为对话框基类,提供 Fluent Design 风格的自定义标题栏: - -```python -from dialogs import BaseFramelessDialog -from qfluentwidgets import qconfig - -class MyDialog(BaseFramelessDialog): - def __init__(self, parent=None): - super().__init__(parent) - self.setWindowTitle("对话框标题") - self.setFixedSize(400, 300) - - # 设置标题栏和样式 - self._setup_title_bar() - self._update_styles() - - # 设置界面 - self.setup_ui() - - # 监听主题变化 - self._theme_connection = qconfig.themeChangedFinished.connect( - self._update_styles - ) - - def closeEvent(self, event): - """关闭事件""" - super().closeEvent(event) # 基类自动断开信号连接 - - def setup_ui(self): - """设置界面布局""" - from PySide6.QtWidgets import QVBoxLayout - layout = QVBoxLayout(self) - # 顶部边距40px为标题栏留出空间 - layout.setContentsMargins(20, 40, 20, 20) +# 沙箱环境无法访问日志是环境限制,不是代码问题。不要修改日志系统。 ``` - -**关键要点:** -- 继承 `BaseFramelessDialog` 而非 `QDialog` -- 调用 `_setup_title_bar()` 设置自定义标题栏 -- 调用 `_update_styles()` 初始化样式 -- 布局边距顶部设置为 40px,为标题栏留出空间 -- `closeEvent` 中调用 `super().closeEvent(event)`,基类会自动断开主题变化信号 -- Gitee From 7f7551b6992fb32ab87bf5f381143e4ea2bc4d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Tue, 17 Mar 2026 21:27:54 +0800 Subject: [PATCH 17/47] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A3=80=E6=9F=A5=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...273\243\347\240\201\346\243\200\346\237\245.txt" | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 "\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" diff --git "a/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" "b/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" new file mode 100644 index 0000000..55f01ca --- /dev/null +++ "b/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" @@ -0,0 +1,13 @@ +# 定期代码检查 + +# 安装依赖 +pip install ruff mypy radon bandit vulture + +# 日常检查 +ruff check . --fix +mypy . +radon cc . -a -s + +# 发布前检查 +bandit -r . +vulture . -- Gitee From bc5d1eab31a49e393ec0c73500923d2379cd1019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Wed, 18 Mar 2026 16:06:32 +0800 Subject: [PATCH 18/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20Hex=20=E5=80=BC=E5=8C=BA=E5=9F=9F=E5=AD=97=E9=87=8D=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 ColorCard 类主题切换时未更新 HEX 按钮样式的问题 - 修复 GenerationColorInfoCard 类主题切换时未更新 HEX 按钮样式的问题 - 为两个类添加 _update_styles() 方法统一更新样式 - 确保主题切换时 HEX 按钮字重保持 bold 不变 --- ui/cards.py | 7 ++++++- ui/color_generation.py | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/cards.py b/ui/cards.py index 8597508..184f962 100644 --- a/ui/cards.py +++ b/ui/cards.py @@ -285,9 +285,14 @@ class ColorCard(BaseCard): super().__init__(index, parent) # 监听主题变化 self._theme_connection = qconfig.themeChangedFinished.connect( - self._update_color_block_style + self._update_styles ) + def _update_styles(self): + """更新样式以适配主题""" + self._update_hex_button_style() + self._update_color_block_style() + def closeEvent(self, event): """关闭事件 - 断开信号连接""" try: diff --git a/ui/color_generation.py b/ui/color_generation.py index 975532e..ea3b7fd 100644 --- a/ui/color_generation.py +++ b/ui/color_generation.py @@ -48,7 +48,12 @@ class GenerationColorInfoCard(BaseCard): self._hex_visible = True super().__init__(index, parent) # 监听主题变化 - qconfig.themeChangedFinished.connect(self._update_color_block_style) + qconfig.themeChangedFinished.connect(self._update_styles) + + def _update_styles(self): + """更新样式以适配主题""" + self._update_hex_button_style() + self._update_color_block_style() def setup_ui(self): """设置界面""" -- Gitee From 89f60a71defcf4fbebe9bec83a3c9955b2bb9825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Wed, 18 Mar 2026 19:08:03 +0800 Subject: [PATCH 19/47] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A3=80=E6=9F=A5=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...41\346\237\245\350\246\201\351\242\206.md" | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 "\346\226\207\346\241\243/\344\273\243\347\240\201\345\256\241\346\237\245\350\246\201\351\242\206.md" diff --git "a/\346\226\207\346\241\243/\344\273\243\347\240\201\345\256\241\346\237\245\350\246\201\351\242\206.md" "b/\346\226\207\346\241\243/\344\273\243\347\240\201\345\256\241\346\237\245\350\246\201\351\242\206.md" new file mode 100644 index 0000000..a0f647f --- /dev/null +++ "b/\346\226\207\346\241\243/\344\273\243\347\240\201\345\256\241\346\237\245\350\246\201\351\242\206.md" @@ -0,0 +1,177 @@ +# 代码审查要领(非程序员版) + +> 无需编程基础,只需关注代码"整洁度" + +--- + +## 一、审查时机 + +- **AI 生成新功能后** +- **每次修改代码后** +- **每月定期审查**(运行工具检查) + +--- + +## 二、三个核心检查点 + +### 1. 导入检查(最简单) + +**问题**:AI 经常导入一堆没用的模块 + +**检查方法**: +```python +# ❌ 坏例子 - 导入了很多没用的 +from PySide6.QtWidgets import ( + QWidget, QPushButton, QLabel, QDialog, + QFileDialog, QScrollArea, QSplitter +) + +# 实际只用了 QWidget +class MyWidget(QWidget): + pass +``` + +**你的动作**:问 AI "这些导入都用到了吗?" + +**工具辅助**: +```bash +ruff check . # 会自动标记未使用的导入 +``` + +--- + +### 2. 重复检查(肉眼可见) + +**问题**:AI 喜欢复制粘贴代码 + +**检查方法**: +- 两段代码看起来**几乎一样**? +- 只是改了**几个数字或变量名**? + +**示例**: +```python +# ❌ 坏例子 - 重复代码 +self.themeButton.setStyleSheet(""" + ToolButton { background: transparent; } +""") + +self.fullscreenButton.setStyleSheet(""" + ToolButton { background: transparent; } +""") # 完全一样! +``` + +**你的动作**:问 AI "这段代码和前面那段能合并吗?" + +--- + +### 3. 长度检查(最直观) + +**问题**:AI 经常写出超长函数 + +**检查标准**: +| 指标 | 警戒线 | 危险线 | +|:---|:---:|:---:| +| 单个函数 | 50 行 | 100 行 | +| 单个文件 | 500 行 | 1000 行 | +| 类的方法数 | 10 个 | 20 个 | + +**你的动作**: +- 超过警戒线 → 问 AI "能拆短一点吗?" +- 超过危险线 → 必须拆分 + +**工具辅助**: +```bash +radon cc . -a -s # 显示复杂度高的函数 +``` + +--- + +## 三、审查问题清单 + +每次 AI 生成代码后,问这 3 个问题: + +1. [ ] **导入问题**:"这段代码有未使用的导入吗?" +2. [ ] **重复问题**:"这段代码和项目里其他代码有重复吗?" +3. [ ] **长度问题**:"这个函数能再短一点吗?" + +--- + +## 四、工具自动化检查 + +### 每月运行一次 + +```bash +# 1. 检查代码规范问题 +ruff check . + +# 2. 检查复杂度 +radon cc . -a -s + +# 3. 自动修复能修复的问题 +ruff check . --fix +``` + +### 看不懂报错怎么办? + +直接复制报错信息给 AI: +> "ruff 报了这个错误,是什么意思?怎么修?" + +--- + +## 五、常见 AI 代码问题 + +| 问题类型 | 表现 | 严重程度 | +|:---|:---|:---:| +| 未使用导入 | 导入了很多模块但没用 | 🟢 低 | +| 重复代码 | 复制粘贴后微调 | 🟡 中 | +| 函数过长 | 一个函数做太多事 | 🟡 中 | +| 过度设计 | 只有一种实现却用工厂模式 | 🟡 中 | +| 模糊变量名 | 用 `l` 做变量名 | 🟢 低 | +| 未定义名称 | 用了没导入的类 | 🔴 高 | + +--- + +## 六、审查原则 + +### 记住这三句话 + +1. **少就是多** - 导入少、代码少、功能聚焦 +2. **重复是坏味道** - 看到相似代码就问能否合并 +3. **工具是帮手** - 让 ruff、radon 帮你发现问题 + +### 不需要懂的东西 + +- ❌ 英语(AI 会翻译) +- ❌ 数学(除非做算法) +- ❌ 数据结构(AI 会处理) +- ❌ 设计模式(记住"延迟抽象"即可) + +--- + +## 七、快速决策流程 + +``` +AI 生成代码 + ↓ +看导入 → 太多?→ 问 AI "都用到了吗?" + ↓ +看重复 → 相似代码?→ 问 AI "能合并吗?" + ↓ +看长度 → 超过50行?→ 问 AI "能拆分吗?" + ↓ +运行 ruff → 有问题?→ 让 AI 解释并修复 + ↓ +完成审查 +``` + +--- + +## 八、参考文档 + +- [核心原则](核心原则.md) - 约束 AI 生成代码的规范 +- [代码审查报告](代码审查报告-26.03.md) - 当前项目已知问题 +- [开发规范](开发规范.md) - 详细的代码编写规范 + +--- + +> **核心思想**:不需要懂代码细节,只需要关注"整洁度"。让工具发现问题,让 AI 解释和修复问题。 -- Gitee From d8a19ac4180b5247123fb8e97761f0a43617a4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Wed, 18 Mar 2026 19:31:02 +0800 Subject: [PATCH 20/47] =?UTF-8?q?[=E6=96=B0=E5=8A=9F=E8=83=BD]=20=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E8=AE=BE=E7=BD=AE=E5=A2=9E=E5=8A=A0=E8=B7=9F=E9=9A=8F?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增"跟随系统"语言选项,默认使用系统语言 - 添加系统语言映射表,支持6种语言的自动识别 - 如果系统语言不在支持列表中,自动回退到简体中文 - 语言切换后"跟随系统"选项文本实时更新 - 更新README.md和官网文档,说明支持跟随系统语言 - 清理冗余代码,删除未使用的便捷函数 --- README.md | 4 +-- core/config.py | 2 +- docs/index.html | 2 +- locales/FR_FR.json | 1 + locales/JA_JP.json | 1 + locales/RU_RU.json | 1 + locales/ZW_FT.json | 1 + locales/ZW_JT.json | 1 + locales/en_US.json | 1 + ui/settings.py | 9 +++++- utils/locale.py | 69 ++++++++++++++++++++++++++++++++++++++++------ website/index.html | 2 +- 12 files changed, 80 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e0b4289..cd6b9b5 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ | **配色收藏**支持收藏、管理配色方案,支持批量导入导出为JSON文件,支持单组色卡导出为 Adobe ASE 格式 | !\[配色管理]\(docs/screenshots/palette-management.png null) | | **内置色彩库**集成 Open Color、Tailwind CSS、Material Design 等13大开源配色方案,总计661组色卡 | !\[内置色彩库]\(docs/screenshots/preset-colors.png null) | | **配色预览**支持手机UI、网页、插画、排版、品牌、海报、图案、杂志等8种场景预览,并支持导入自定义SVG | !\[配色预览]\(docs/screenshots/color-preview\.png null) | -| **多语言支持**支持简体中文、繁体中文、英语、日语、法语、俄语等6种语言 | !\[多语言支持]\(docs/screenshots/locales.png null) | +| **多语言支持**支持简体中文、繁体中文、英语、日语、法语、俄语等6种语言,支持跟随系统语言自动切换 | !\[多语言支持]\(docs/screenshots/locales.png null) | | **现代化界面**基于 Fluent Design 设计语言,支持深色/浅色主题切换 | !\[深色/浅色模式]\(./docs/screenshots/Dark%20mode%26light%20mode.png null) | ### 适用场景 @@ -319,7 +319,7 @@ Since the release of v1.0.0 on 2026-02-05, the project has maintained a fast and | **Palette Collection**Save and manage color schemes, support batch import/export in JSON format, support single palette export to Adobe ASE format | !\[Palette Management]\(docs/screenshots/palette-management.png null) | | **Built-in Color Library**13 major open-source color schemes including Open Color, Tailwind CSS, Material Design, totaling 661 color palettes | !\[Preset Colors]\(docs/screenshots/preset-colors.png null) | | **Color Preview**8 built-in scene previews (Mobile UI, Web, Illustration, Typography, Brand, Poster, Pattern, Magazine) with custom SVG support | !\[Color Preview]\(docs/screenshots/color-preview\.png null) | -| **Multi-language Support**6 languages including Simplified Chinese, Traditional Chinese, English, Japanese, French, and Russian | !\[Multi-language]\(docs/screenshots/locales.png null) | +| **Multi-language Support**6 languages including Simplified Chinese, Traditional Chinese, English, Japanese, French, and Russian, with system language auto-detection | !\[Multi-language]\(docs/screenshots/locales.png null) | | **Modern Interface**Based on Fluent Design, supports dark/light theme switching | !\[Dark/Light Mode]\(./docs/screenshots/Dark%20mode%26light%20mode.png null) | ### Use Cases diff --git a/core/config.py b/core/config.py index 86a7b7c..da1f35d 100644 --- a/core/config.py +++ b/core/config.py @@ -79,7 +79,7 @@ class ConfigManager: "color_wheel_mode": "RGB", "theme": "auto", "color_wheel_labels_visible": True, - "language": "ZW_JT" + "language": "auto" }, "scheme": { "default_scheme": "monochromatic", diff --git a/docs/index.html b/docs/index.html index e5ad493..2b0b91a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1262,7 +1262,7 @@ { icon: 'eye', title: '配色预览', desc: '支持手机UI、网页、插画、排版、品牌、海报、图案、杂志等8种场景预览,支持自定义SVG' }, { icon: 'barchart', title: '明度分析', desc: '将图片按明度分为9个区域,提供直方图可视化,辅助调色决策' }, { icon: 'gradient', title: '渐变生成', desc: '选择起始和结束颜色,生成渐变色序列,支持RGB/HSB/LAB三种颜色空间插值,可调节1-10个中间色' }, - { icon: 'globe', title: '多语言支持', desc: '支持简体中文、繁体中文、英语、日语、法语、俄语六种语言界面切换' }, + { icon: 'globe', title: '多语言支持', desc: '支持简体中文、繁体中文、英语、日语、法语、俄语六种语言界面切换,支持跟随系统语言自动切换' }, { icon: 'moon', title: '明暗主题', desc: '支持深色模式和浅色模式切换,保护视力,适应不同使用环境' }, ]; diff --git a/locales/FR_FR.json b/locales/FR_FR.json index 9d0ac79..df106d3 100644 --- a/locales/FR_FR.json +++ b/locales/FR_FR.json @@ -229,6 +229,7 @@ "language": "Paramètres de langue", "language_title": "Langue de l'interface", "language_desc": "Sélectionner la langue d'affichage de l'application", + "language_auto": "Suivre le système", "help": "Aide", "check_update": "Vérifier", "version_update": "Mise à jour", diff --git a/locales/JA_JP.json b/locales/JA_JP.json index b20d7b6..621bfe5 100644 --- a/locales/JA_JP.json +++ b/locales/JA_JP.json @@ -229,6 +229,7 @@ "language": "言語設定", "language_title": "インターフェース言語", "language_desc": "アプリケーションの表示言語を選択", + "language_auto": "システムに従う", "help": "ヘルプ", "check_update": "更新を確認", "version_update": "バージョン更新", diff --git a/locales/RU_RU.json b/locales/RU_RU.json index 58af60f..10e9633 100644 --- a/locales/RU_RU.json +++ b/locales/RU_RU.json @@ -229,6 +229,7 @@ "language": "Настройки языка", "language_title": "Язык интерфейса", "language_desc": "Выберите язык отображения приложения", + "language_auto": "Следовать системе", "help": "Справка", "check_update": "Проверить", "version_update": "Обновление версии", diff --git a/locales/ZW_FT.json b/locales/ZW_FT.json index 53d0cff..a8a86c3 100644 --- a/locales/ZW_FT.json +++ b/locales/ZW_FT.json @@ -229,6 +229,7 @@ "language": "語言設置", "language_title": "頁面語言", "language_desc": "選擇應用程序的顯示語言", + "language_auto": "跟隨系統", "help": "幫助", "check_update": "檢查更新", "version_update": "版本更新", diff --git a/locales/ZW_JT.json b/locales/ZW_JT.json index 16672d7..fa123ea 100644 --- a/locales/ZW_JT.json +++ b/locales/ZW_JT.json @@ -229,6 +229,7 @@ "language": "语言设置", "language_title": "页面语言", "language_desc": "选择应用程序的显示语言", + "language_auto": "跟随系统", "help": "帮助", "check_update": "检查更新", "version_update": "版本更新", diff --git a/locales/en_US.json b/locales/en_US.json index 1ecb3cb..572ddf3 100644 --- a/locales/en_US.json +++ b/locales/en_US.json @@ -229,6 +229,7 @@ "language": "Language Settings", "language_title": "Interface Language", "language_desc": "Select the display language for the application", + "language_auto": "Follow System", "help": "Help", "check_update": "Check for Updates", "version_update": "Version Update", diff --git a/ui/settings.py b/ui/settings.py index fec7e14..7519ab7 100644 --- a/ui/settings.py +++ b/ui/settings.py @@ -239,12 +239,17 @@ class SettingsInterface(QWidget): card.button.setVisible(False) combo_box = ComboBox(self.content_widget) + + combo_box.addItem(tr('settings.language_auto')) + combo_box.setItemData(0, 'auto') + supported_languages = get_supported_languages() for code, name in supported_languages.items(): + if code == 'auto': + continue combo_box.addItem(name) combo_box.setItemData(combo_box.count() - 1, code) - # 设置当前语言 for i in range(combo_box.count()): if combo_box.itemData(i) == self._language: combo_box.setCurrentIndex(i) @@ -296,6 +301,8 @@ class SettingsInterface(QWidget): # 更新语言卡片 self.language_card.titleLabel.setText(tr('settings.language_title')) self.language_card.contentLabel.setText(tr('settings.language_desc')) + # 更新"跟随系统"选项文本 + self.language_card.combo_box.setItemText(0, tr('settings.language_auto')) # 更新16进制显示卡片 self.hex_display_card.titleLabel.setText(tr('settings.hex_display')) diff --git a/utils/locale.py b/utils/locale.py index 2961b54..af963ef 100644 --- a/utils/locale.py +++ b/utils/locale.py @@ -9,7 +9,26 @@ import sys from pathlib import Path from typing import Any, Dict, Optional -from PySide6.QtCore import QObject, Signal +from PySide6.QtCore import QLocale, QObject, Signal + + +SYSTEM_LANGUAGE_MAPPING: Dict[str, str] = { + 'zh_CN': 'ZW_JT', + 'zh_Hans': 'ZW_JT', + 'zh': 'ZW_JT', + 'zh_TW': 'ZW_FT', + 'zh_HK': 'ZW_FT', + 'zh_Hant': 'ZW_FT', + 'en': 'EN_US', + 'en_US': 'EN_US', + 'en_GB': 'EN_US', + 'ja': 'JA_JP', + 'ja_JP': 'JA_JP', + 'fr': 'FR_FR', + 'fr_FR': 'FR_FR', + 'ru': 'RU_RU', + 'ru_RU': 'RU_RU', +} def _get_base_path() -> str: @@ -38,6 +57,7 @@ class LocaleManager(QObject): language_changed = Signal(str) SUPPORTED_LANGUAGES = { + 'auto': '跟随系统', 'ZW_JT': '简体中文', 'ZW_FT': '繁體中文', 'EN_US': 'English', @@ -46,7 +66,8 @@ class LocaleManager(QObject): 'RU_RU': 'Русский' } - DEFAULT_LANGUAGE = 'ZW_JT' + DEFAULT_LANGUAGE = 'auto' + FALLBACK_LANGUAGE = 'ZW_JT' def __init__(self): """初始化多语言管理器""" @@ -68,19 +89,21 @@ class LocaleManager(QObject): """加载指定语言的翻译数据 Args: - language_code: 语言代码(如 'ZW_JT', 'EN_US') + language_code: 语言代码(如 'ZW_JT', 'EN_US', 'auto') Returns: bool: 是否加载成功 """ - if language_code not in self.SUPPORTED_LANGUAGES: - language_code = self.DEFAULT_LANGUAGE + resolved_code = self.resolve_language(language_code) + + if resolved_code not in self.SUPPORTED_LANGUAGES: + resolved_code = self.FALLBACK_LANGUAGE - locale_file = self._locales_dir / f'{language_code}.json' + locale_file = self._locales_dir / f'{resolved_code}.json' if not locale_file.exists(): - if language_code != self.DEFAULT_LANGUAGE: - return self.load_language(self.DEFAULT_LANGUAGE) + if resolved_code != self.FALLBACK_LANGUAGE: + return self.load_language(self.FALLBACK_LANGUAGE) return False try: @@ -91,6 +114,36 @@ class LocaleManager(QObject): except (json.JSONDecodeError, IOError, OSError): return False + def resolve_language(self, language_code: str) -> str: + """解析语言代码,如果是 'auto' 则返回系统语言 + + Args: + language_code: 语言代码(如 'auto', 'ZW_JT', 'EN_US') + + Returns: + str: 实际的语言代码 + """ + if language_code == 'auto': + return self.get_system_language() + return language_code + + def get_system_language(self) -> str: + """获取系统语言并映射到项目支持的语言代码 + + Returns: + str: 映射后的语言代码,未匹配则返回默认语言 + """ + try: + system_locale = QLocale.system().name() + if system_locale in SYSTEM_LANGUAGE_MAPPING: + return SYSTEM_LANGUAGE_MAPPING[system_locale] + base_locale = system_locale.split('_')[0] + if base_locale in SYSTEM_LANGUAGE_MAPPING: + return SYSTEM_LANGUAGE_MAPPING[base_locale] + except (AttributeError, ValueError): + pass + return self.FALLBACK_LANGUAGE + def set_language(self, language_code: str) -> bool: """设置当前语言 diff --git a/website/index.html b/website/index.html index e5ad493..2b0b91a 100644 --- a/website/index.html +++ b/website/index.html @@ -1262,7 +1262,7 @@ { icon: 'eye', title: '配色预览', desc: '支持手机UI、网页、插画、排版、品牌、海报、图案、杂志等8种场景预览,支持自定义SVG' }, { icon: 'barchart', title: '明度分析', desc: '将图片按明度分为9个区域,提供直方图可视化,辅助调色决策' }, { icon: 'gradient', title: '渐变生成', desc: '选择起始和结束颜色,生成渐变色序列,支持RGB/HSB/LAB三种颜色空间插值,可调节1-10个中间色' }, - { icon: 'globe', title: '多语言支持', desc: '支持简体中文、繁体中文、英语、日语、法语、俄语六种语言界面切换' }, + { icon: 'globe', title: '多语言支持', desc: '支持简体中文、繁体中文、英语、日语、法语、俄语六种语言界面切换,支持跟随系统语言自动切换' }, { icon: 'moon', title: '明暗主题', desc: '支持深色模式和浅色模式切换,保护视力,适应不同使用环境' }, ]; -- Gitee From ebf774a9cc784e5aa1b2bebaf4d81d96bc5576f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Wed, 18 Mar 2026 19:39:10 +0800 Subject: [PATCH 21/47] =?UTF-8?q?[=E5=86=85=E5=AE=B9=E8=B0=83=E6=95=B4]=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- version.py | 4 ++-- version.txt | 6 +++--- version_info.txt | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/version.py b/version.py index 416913f..f9edd26 100644 --- a/version.py +++ b/version.py @@ -8,8 +8,8 @@ class VersionManager: """初始化版本管理器""" # 版本号组件 self.major: int = 1 - self.minor: int = 5 - self.patch: int = 2 + self.minor: int = 6 + self.patch: int = 0 self.build: int = 0 self.prerelease: str = "Beta" diff --git a/version.txt b/version.txt index 3f3a13a..9b107ef 100644 --- a/version.txt +++ b/version.txt @@ -1,6 +1,6 @@ -1.5.2 -2026.3.16.1 -1.5.2.0 +1.6.0 +2026.3.18.1 +1.6.0.0 浮晓 HXiao Studio © 2026 浮晓 HXiao Studio 取色卡 - Color Card \ No newline at end of file diff --git a/version_info.txt b/version_info.txt index 1e6a277..ac78628 100644 --- a/version_info.txt +++ b/version_info.txt @@ -1,7 +1,7 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(2026,3,16,1), - prodvers=(1,5,2,0), + filevers=(2026,3,18,1), + prodvers=(1,6,0,0), mask=0x3f, flags=0x0, OS=0x4, @@ -17,12 +17,12 @@ VSVersionInfo( [ StringStruct(u'CompanyName', u'浮晓 HXiao Studio'), StringStruct(u'FileDescription', u'取色卡 - Color Card'), - StringStruct(u'FileVersion', u'1.5.2'), + StringStruct(u'FileVersion', u'1.6.0'), StringStruct(u'InternalName', u'Color_Card'), StringStruct(u'LegalCopyright', u'© 2026 浮晓 HXiao Studio'), StringStruct(u'OriginalFilename', u'Color_Card.exe'), StringStruct(u'ProductName', u'取色卡'), - StringStruct(u'ProductVersion', u'1.5.2'), + StringStruct(u'ProductVersion', u'1.6.0'), StringStruct(u'Comments', u'一站式的图片的图片分析和配色工具') ] ) -- Gitee From 96aa296b02bf6f070855d214b0239aeec7cc2d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Wed, 18 Mar 2026 19:40:04 +0800 Subject: [PATCH 22/47] =?UTF-8?q?Revert=20"[=E5=86=85=E5=AE=B9=E8=B0=83?= =?UTF-8?q?=E6=95=B4]=20=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC=E4=BF=A1?= =?UTF-8?q?=E6=81=AF"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ebf774a9cc784e5aa1b2bebaf4d81d96bc5576f0. --- version.py | 4 ++-- version.txt | 6 +++--- version_info.txt | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/version.py b/version.py index f9edd26..416913f 100644 --- a/version.py +++ b/version.py @@ -8,8 +8,8 @@ class VersionManager: """初始化版本管理器""" # 版本号组件 self.major: int = 1 - self.minor: int = 6 - self.patch: int = 0 + self.minor: int = 5 + self.patch: int = 2 self.build: int = 0 self.prerelease: str = "Beta" diff --git a/version.txt b/version.txt index 9b107ef..3f3a13a 100644 --- a/version.txt +++ b/version.txt @@ -1,6 +1,6 @@ -1.6.0 -2026.3.18.1 -1.6.0.0 +1.5.2 +2026.3.16.1 +1.5.2.0 浮晓 HXiao Studio © 2026 浮晓 HXiao Studio 取色卡 - Color Card \ No newline at end of file diff --git a/version_info.txt b/version_info.txt index ac78628..1e6a277 100644 --- a/version_info.txt +++ b/version_info.txt @@ -1,7 +1,7 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(2026,3,18,1), - prodvers=(1,6,0,0), + filevers=(2026,3,16,1), + prodvers=(1,5,2,0), mask=0x3f, flags=0x0, OS=0x4, @@ -17,12 +17,12 @@ VSVersionInfo( [ StringStruct(u'CompanyName', u'浮晓 HXiao Studio'), StringStruct(u'FileDescription', u'取色卡 - Color Card'), - StringStruct(u'FileVersion', u'1.6.0'), + StringStruct(u'FileVersion', u'1.5.2'), StringStruct(u'InternalName', u'Color_Card'), StringStruct(u'LegalCopyright', u'© 2026 浮晓 HXiao Studio'), StringStruct(u'OriginalFilename', u'Color_Card.exe'), StringStruct(u'ProductName', u'取色卡'), - StringStruct(u'ProductVersion', u'1.6.0'), + StringStruct(u'ProductVersion', u'1.5.2'), StringStruct(u'Comments', u'一站式的图片的图片分析和配色工具') ] ) -- Gitee From a9d62b3f35a56c2908d97db11dde7721eadb6ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Wed, 18 Mar 2026 21:54:29 +0800 Subject: [PATCH 23/47] =?UTF-8?q?Reapply=20"[=E5=86=85=E5=AE=B9=E8=B0=83?= =?UTF-8?q?=E6=95=B4]=20=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC=E4=BF=A1?= =?UTF-8?q?=E6=81=AF"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 96aa296b02bf6f070855d214b0239aeec7cc2d43. --- version.py | 4 ++-- version.txt | 6 +++--- version_info.txt | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/version.py b/version.py index 416913f..f9edd26 100644 --- a/version.py +++ b/version.py @@ -8,8 +8,8 @@ class VersionManager: """初始化版本管理器""" # 版本号组件 self.major: int = 1 - self.minor: int = 5 - self.patch: int = 2 + self.minor: int = 6 + self.patch: int = 0 self.build: int = 0 self.prerelease: str = "Beta" diff --git a/version.txt b/version.txt index 3f3a13a..9b107ef 100644 --- a/version.txt +++ b/version.txt @@ -1,6 +1,6 @@ -1.5.2 -2026.3.16.1 -1.5.2.0 +1.6.0 +2026.3.18.1 +1.6.0.0 浮晓 HXiao Studio © 2026 浮晓 HXiao Studio 取色卡 - Color Card \ No newline at end of file diff --git a/version_info.txt b/version_info.txt index 1e6a277..ac78628 100644 --- a/version_info.txt +++ b/version_info.txt @@ -1,7 +1,7 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(2026,3,16,1), - prodvers=(1,5,2,0), + filevers=(2026,3,18,1), + prodvers=(1,6,0,0), mask=0x3f, flags=0x0, OS=0x4, @@ -17,12 +17,12 @@ VSVersionInfo( [ StringStruct(u'CompanyName', u'浮晓 HXiao Studio'), StringStruct(u'FileDescription', u'取色卡 - Color Card'), - StringStruct(u'FileVersion', u'1.5.2'), + StringStruct(u'FileVersion', u'1.6.0'), StringStruct(u'InternalName', u'Color_Card'), StringStruct(u'LegalCopyright', u'© 2026 浮晓 HXiao Studio'), StringStruct(u'OriginalFilename', u'Color_Card.exe'), StringStruct(u'ProductName', u'取色卡'), - StringStruct(u'ProductVersion', u'1.5.2'), + StringStruct(u'ProductVersion', u'1.6.0'), StringStruct(u'Comments', u'一站式的图片的图片分析和配色工具') ] ) -- Gitee From 71cd2bcb11e611dfbb875ec8a9fbae7f5270822c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Wed, 18 Mar 2026 21:55:42 +0800 Subject: [PATCH 24/47] =?UTF-8?q?[=E6=A0=B7=E5=BC=8F]=20HSB=E8=89=B2?= =?UTF-8?q?=E8=BD=AE=E6=94=B9=E4=B8=BA=E9=A1=BA=E6=97=B6=E9=92=88=E6=8E=92?= =?UTF-8?q?=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 HSBColorWheel._generate_wheel_cache 方法,添加色相取反实现水平翻转 - 修改 HSBColorWheel._hsb_to_position 方法,调整角度计算使采样点顺时针排列 - 修改 HSBColorWheel._draw_hue_labels 方法,调整标签角度使标签顺时针排列 - 修改 InteractiveColorWheel._generate_wheel_cache 方法,添加色相取反实现水平翻转 - 修改 InteractiveColorWheel._hsb_to_position 方法,调整角度计算使选择器顺时针排列 - 修改 InteractiveColorWheel._position_to_hsb 方法,添加色相取反实现鼠标位置正确转换 - 修改 InteractiveColorWheel._draw_hue_labels 方法,调整标签角度使标签顺时针排列 - 保持RYB模式兼容性,配色计算逻辑不受影响 --- ui/color_wheel.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ui/color_wheel.py b/ui/color_wheel.py index 26fc121..766b0f1 100644 --- a/ui/color_wheel.py +++ b/ui/color_wheel.py @@ -127,9 +127,10 @@ class HSBColorWheel(QWidget): """ import math - # 色相转换为角度(0°在上方12点钟方向,逆时针增加) + # 色相转换为角度(0°在上方12点钟方向,顺时针增加) # 加上90度将0°从右侧(3点钟)旋转到上方(12点钟) - angle_rad = ((h + 90) * math.pi / 180.0) + # 360-h实现水平翻转,使色相顺时针排列 + angle_rad = ((360 - h + 90) * math.pi / 180.0) # 饱和度转换为半径(0%在中心,100%在边缘) # 使用完整的色轮半径,让采样点可以到达圆周 @@ -175,6 +176,7 @@ class HSBColorWheel(QWidget): # 减90度偏移,使0°色相(红色)位于12点钟方向 angle = math.atan2(-dy, dx) - math.pi / 2 hue = (angle / (2 * math.pi)) % 1.0 + hue = (1.0 - hue) % 1.0 # 水平翻转:色相取反,实现顺时针排列 # 计算饱和度(距离中心的远近) saturation = min(distance / self._wheel_radius, 1.0) @@ -306,7 +308,8 @@ class HSBColorWheel(QWidget): for angle, label in hue_labels: # 计算标签位置(注意Y轴翻转) # 加上90度将0°从右侧(3点钟)旋转到上方(12点钟) - adjusted_angle = angle + 90 + # 360-angle实现水平翻转,使标签顺时针排列 + adjusted_angle = (360 - angle) + 90 rad = math.radians(adjusted_angle) x = self._center_x + label_radius * math.cos(rad) y = self._center_y - label_radius * math.sin(rad) @@ -457,7 +460,8 @@ class InteractiveColorWheel(QWidget): (x, y) 坐标 """ # 加上90度将0°从右侧(3点钟)旋转到上方(12点钟) - angle_rad = ((h + 90) * math.pi / 180.0) + # 360-h实现水平翻转,使色相顺时针排列 + angle_rad = ((360 - h + 90) * math.pi / 180.0) # 使用完整的色轮半径,让采样点可以到达圆周 max_radius = self._wheel_radius @@ -491,6 +495,7 @@ class InteractiveColorWheel(QWidget): angle = math.atan2(-dy, dx) # 减90度偏移,使0°色相(红色)位于12点钟方向 hue = ((angle - math.pi / 2) / (2 * math.pi)) % 1.0 * 360 + hue = (360 - hue) % 360 # 水平翻转:色相取反,实现顺时针排列 return hue, saturation @@ -596,6 +601,7 @@ class InteractiveColorWheel(QWidget): angle = math.atan2(-dy, dx) # 减90度偏移,使0°色相(红色)位于12点钟方向 hue = ((angle - math.pi / 2) / (2 * math.pi)) % 1.0 + hue = (1.0 - hue) % 1.0 # 水平翻转:色相取反,实现顺时针排列 saturation = min(distance / self._wheel_radius, 1.0) # 使用全局明度值 value = brightness_value @@ -675,7 +681,8 @@ class InteractiveColorWheel(QWidget): for angle, label in hue_labels: # 计算标签位置(注意Y轴翻转) # 加上90度将0°从右侧(3点钟)旋转到上方(12点钟) - adjusted_angle = angle + 90 + # 360-angle实现水平翻转,使标签顺时针排列 + adjusted_angle = (360 - angle) + 90 rad = math.radians(adjusted_angle) x = self._center_x + label_radius * math.cos(rad) y = self._center_y - label_radius * math.sin(rad) -- Gitee From 74c76f1114359d76a530ea4e579cb481e619253d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Thu, 19 Mar 2026 02:17:54 +0800 Subject: [PATCH 25/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E6=A0=B7=E5=BC=8F=E5=B8=B8=E9=87=8F=EF=BC=8C?= =?UTF-8?q?=E6=B6=88=E9=99=A4=E9=87=8D=E5=A4=8D=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 main_window.py 中添加模块级常量 _TOOLBUTTON_STYLE - 将 themeButton 和 fullscreenButton 的重复样式提取为常量 - 消除约24行重复代码,提高代码可维护性 - 符合开发规范第3.2节代码清理原则和第3.3节命名规范 --- ui/main_window.py | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/ui/main_window.py b/ui/main_window.py index 1f520dd..a312b15 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -24,6 +24,20 @@ from .settings import SettingsInterface from .color_preview import ColorPreviewInterface from .color_wheel import HSBColorWheel, InteractiveColorWheel +# 工具按钮统一样式 +_TOOLBUTTON_STYLE = """ + ToolButton { + background-color: transparent !important; + border: none !important; + } + ToolButton:hover { + background-color: rgba(128, 128, 128, 30) !important; + } + ToolButton:pressed { + background-color: rgba(128, 128, 128, 50) !important; + } +""" + class CustomTitleBar(FluentTitleBar): """自定义标题栏,添加深色模式切换按钮和全屏切换按钮""" @@ -35,18 +49,7 @@ class CustomTitleBar(FluentTitleBar): self.themeButton = ToolButton(self) self.themeButton.setFixedSize(40, 32) self.themeButton.setToolTip(tr('title_bar.toggle_theme')) - self.themeButton.setStyleSheet(""" - ToolButton { - background-color: transparent !important; - border: none !important; - } - ToolButton:hover { - background-color: rgba(128, 128, 128, 30) !important; - } - ToolButton:pressed { - background-color: rgba(128, 128, 128, 50) !important; - } - """) + self.themeButton.setStyleSheet(_TOOLBUTTON_STYLE) self._update_theme_icon() # 连接点击事件 @@ -56,18 +59,7 @@ class CustomTitleBar(FluentTitleBar): self.fullscreenButton = ToolButton(self) self.fullscreenButton.setFixedSize(40, 32) self.fullscreenButton.setToolTip(tr('title_bar.toggle_fullscreen')) - self.fullscreenButton.setStyleSheet(""" - ToolButton { - background-color: transparent !important; - border: none !important; - } - ToolButton:hover { - background-color: rgba(128, 128, 128, 30) !important; - } - ToolButton:pressed { - background-color: rgba(128, 128, 128, 50) !important; - } - """) + self.fullscreenButton.setStyleSheet(_TOOLBUTTON_STYLE) self._update_fullscreen_icon() # 连接点击事件 -- Gitee From b0b5d10c7c9229fa7fa771c406df9b14817df0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Thu, 19 Mar 2026 13:35:47 +0800 Subject: [PATCH 26/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E7=A9=BA=E9=97=B4=E5=8F=98=E9=87=8F=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E8=A7=84=E8=8C=83=E5=B9=B6=E6=B8=85=E7=90=86=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 LAB/HSL/LMS 颜色空间变量统一为大写简写(L, A, B, H, S, M等) - 删除 11 处未使用的变量(F841) - 更新开发规范,添加颜色空间命名规范(3.3.2节) - 涉及文件:core/color.py, core/colorblind.py, core/gradient.py, dialogs/edit_palette.py, dialogs/about_dialog.py, dialogs/base_frameless_dialog.py, dialogs/contrast_dialog.py, dialogs/update_dialog.py, ui/cards.py, ui/color_generation.py, ui/gradient_extract.py, core/svg_color_mapper.py --- core/color.py | 60 +++++----- core/colorblind.py | 108 +++++++++--------- core/gradient.py | 28 ++--- core/svg_color_mapper.py | 2 - dialogs/about_dialog.py | 3 - dialogs/base_frameless_dialog.py | 1 - dialogs/contrast_dialog.py | 4 +- dialogs/edit_palette.py | 12 +- dialogs/update_dialog.py | 6 +- ui/cards.py | 4 +- ui/color_generation.py | 4 +- ui/gradient_extract.py | 1 - ...00\345\217\221\350\247\204\350\214\203.md" | 20 ++++ 13 files changed, 130 insertions(+), 123 deletions(-) diff --git a/core/color.py b/core/color.py index d0fe2db..6cd5ae0 100644 --- a/core/color.py +++ b/core/color.py @@ -130,11 +130,11 @@ def rgb_to_lab(r: int, g: int, b: int) -> Tuple[float, float, float]: # L = 116*f(Y) - 16 (亮度分量) # A = 500*(f(X) - f(Y)) (红绿对立分量) # B = 200*(f(Y) - f(Z)) (黄蓝对立分量) - l = 116 * f(y) - 16 - a_val = 500 * (f(x) - f(y)) - b_val = 200 * (f(y) - f(z)) + L = 116 * f(y) - 16 + A = 500 * (f(x) - f(y)) + B = 200 * (f(y) - f(z)) - return l, a_val, b_val + return L, A, B def rgb_to_hex(r: int, g: int, b: int) -> str: @@ -194,8 +194,8 @@ def rgb_to_hsl(r: int, g: int, b: int) -> Tuple[float, float, float]: tuple: (色相 0-360, 饱和度 0-100, 亮度 0-100) """ r_norm, g_norm, b_norm = r / 255.0, g / 255.0, b / 255.0 - h, l, s = colorsys.rgb_to_hls(r_norm, g_norm, b_norm) - return h * 360, s * 100, l * 100 + H, L, S = colorsys.rgb_to_hls(r_norm, g_norm, b_norm) + return H * 360, S * 100, L * 100 def rgb_to_cmyk(r: int, g: int, b: int) -> Tuple[float, float, float, float]: @@ -233,17 +233,17 @@ def get_color_info(r: int, g: int, b: int) -> Dict[str, Any]: Returns: dict: 包含RGB、HSB、LAB、HEX、HSL、CMYK颜色信息的字典 """ - h, s, b_val = rgb_to_hsb(r, g, b) - l, a, b_lab = rgb_to_lab(r, g, b) - h_hsl, s_hsl, l_hsl = rgb_to_hsl(r, g, b) - c, m, y, k = rgb_to_cmyk(r, g, b) + H, S, B = rgb_to_hsb(r, g, b) + L, A, B_lab = rgb_to_lab(r, g, b) + H2, S2, L2 = rgb_to_hsl(r, g, b) + C, M, Y, K = rgb_to_cmyk(r, g, b) return { 'rgb': (r, g, b), - 'hsb': (round(h), round(s), round(b_val)), - 'lab': (round(l), round(a), round(b_lab)), - 'hsl': (round(h_hsl), round(s_hsl), round(l_hsl)), - 'cmyk': (round(c), round(m), round(y), round(k)), + 'hsb': (round(H), round(S), round(B)), + 'lab': (round(L), round(A), round(B_lab)), + 'hsl': (round(H2), round(S2), round(L2)), + 'cmyk': (round(C), round(M), round(Y), round(K)), 'rgb_display': (r, g, b), 'hex': rgb_to_hex(r, g, b) } @@ -575,7 +575,7 @@ def hsb_to_rgb(h: float, s: float, b: float) -> Tuple[int, int, int]: return round(r * 255), round(g * 255), round(b_out * 255) -def lab_to_rgb(l: float, a: float, b: float) -> Tuple[int, int, int]: +def lab_to_rgb(L: float, A: float, B: float) -> Tuple[int, int, int]: """将LAB转换为RGB 转换步骤: @@ -586,9 +586,9 @@ def lab_to_rgb(l: float, a: float, b: float) -> Tuple[int, int, int]: 5. 转换到0-255范围 Args: - l: 亮度 (0-100) - a: 红绿对立通道 (-128-127) - b: 黄蓝对立通道 (-128-127) + L: 亮度 (0-100) + A: 红绿对立通道 (-128-127) + B: 黄蓝对立通道 (-128-127) Returns: tuple: (R 0-255, G 0-255, B 0-255) @@ -602,9 +602,9 @@ def lab_to_rgb(l: float, a: float, b: float) -> Tuple[int, int, int]: else: return 3 * (delta ** 2) * (t - 4 / 29) - y = (l + 16) / 116 - x = y + a / 500 - z = y - b / 200 + y = (L + 16) / 116 + x = y + A / 500 + z = y - B / 200 # 使用D65参考白点 x_ref, y_ref, z_ref = 0.95047, 1.00000, 1.08883 @@ -636,23 +636,23 @@ def lab_to_rgb(l: float, a: float, b: float) -> Tuple[int, int, int]: return round(r * 255), round(g * 255), round(b_out * 255) -def hsl_to_rgb(h: float, s: float, l: float) -> Tuple[int, int, int]: +def hsl_to_rgb(H: float, S: float, L: float) -> Tuple[int, int, int]: """将HSL转换为RGB Args: - h: 色相 (0-360) - s: 饱和度 (0-100) - l: 亮度 (0-100) + H: 色相 (0-360) + S: 饱和度 (0-100) + L: 亮度 (0-100) Returns: tuple: (R 0-255, G 0-255, B 0-255) """ - h_norm = h / 360.0 - s_norm = s / 100.0 - l_norm = l / 100.0 + H_norm = H / 360.0 + S_norm = S / 100.0 + L_norm = L / 100.0 - r, g, b_out = colorsys.hls_to_rgb(h_norm, l_norm, s_norm) - return round(r * 255), round(g * 255), round(b_out * 255) + R, G, B_out = colorsys.hls_to_rgb(H_norm, L_norm, S_norm) + return round(R * 255), round(G * 255), round(B_out * 255) def cmyk_to_rgb(c: float, m: float, y: float, k: float) -> Tuple[int, int, int]: diff --git a/core/colorblind.py b/core/colorblind.py index 591fbce..92fa620 100644 --- a/core/colorblind.py +++ b/core/colorblind.py @@ -63,95 +63,95 @@ def rgb_to_lms(r: int, g: int, b: int) -> Tuple[float, float, float]: b_linear = gamma_correction(b_norm) # RGB 到 LMS 转换矩阵 (Bradford) - l = 0.8951 * r_linear + 0.2664 * g_linear - 0.1614 * b_linear - m = -0.7502 * r_linear + 1.7135 * g_linear + 0.0367 * b_linear - s = 0.0389 * r_linear - 0.0685 * g_linear + 1.0296 * b_linear - - return (l, m, s) + L = 0.8951 * r_linear + 0.2664 * g_linear - 0.1614 * b_linear + M = -0.7502 * r_linear + 1.7135 * g_linear + 0.0367 * b_linear + S = 0.0389 * r_linear - 0.0685 * g_linear + 1.0296 * b_linear + + return (L, M, S) -def lms_to_rgb(l: float, m: float, s: float) -> Tuple[int, int, int]: +def lms_to_rgb(L: float, M: float, S: float) -> Tuple[int, int, int]: """将 LMS 转换回 RGB 色彩空间 - + Args: - l: 长波视锥响应 - m: 中波视锥响应 - s: 短波视锥响应 - + L: 长波视锥响应 + M: 中波视锥响应 + S: 短波视锥响应 + Returns: - (r, g, b) 元组,范围 0-255 + (R, G, B) 元组,范围 0-255 """ # LMS 到 RGB 转换矩阵 (Bradford 逆矩阵) - r_linear = 0.986993 * l - 0.147054 * m + 0.159963 * s - g_linear = 0.432305 * l + 0.51836 * m + 0.049291 * s - b_linear = -0.008529 * l + 0.040043 * m + 0.968487 * s - + r_linear = 0.986993 * L - 0.147054 * M + 0.159963 * S + g_linear = 0.432305 * L + 0.51836 * M + 0.049291 * S + b_linear = -0.008529 * L + 0.040043 * M + 0.968487 * S + # 线性 RGB 到 sRGB 的伽马校正 def gamma_correction_inv(c): if c <= 0.0031308: return c * 12.92 else: return 1.055 * (c ** (1.0 / 2.4)) - 0.055 - + r_norm = gamma_correction_inv(r_linear) g_norm = gamma_correction_inv(g_linear) b_norm = gamma_correction_inv(b_linear) - + # 裁剪到 0-1 范围并转换为 0-255 - r = int(max(0, min(1, r_norm)) * 255) - g = int(max(0, min(1, g_norm)) * 255) - b = int(max(0, min(1, b_norm)) * 255) - - return (r, g, b) + R = int(max(0, min(1, r_norm)) * 255) + G = int(max(0, min(1, g_norm)) * 255) + B = int(max(0, min(1, b_norm)) * 255) + + return (R, G, B) -def simulate_protanopia(l: float, m: float, s: float) -> Tuple[float, float, float]: +def simulate_protanopia(L: float, M: float, S: float) -> Tuple[float, float, float]: """模拟红色盲 (Protanopia) - + 红色视锥细胞缺失,L 通道信息丢失。 使用红色盲模拟矩阵。 """ # 红色盲转换:L 通道由 M 和 S 估算 - l_blind = 0.0 * l + 2.02344 * m - 2.52581 * s - m_blind = m - s_blind = s - return (l_blind, m_blind, s_blind) + L_blind = 0.0 * L + 2.02344 * M - 2.52581 * S + M_blind = M + S_blind = S + return (L_blind, M_blind, S_blind) -def simulate_deuteranopia(l: float, m: float, s: float) -> Tuple[float, float, float]: +def simulate_deuteranopia(L: float, M: float, S: float) -> Tuple[float, float, float]: """模拟绿色盲 (Deuteranopia) - + 绿色视锥细胞缺失,M 通道信息丢失。 使用绿色盲模拟矩阵。 """ # 绿色盲转换:M 通道由 L 和 S 估算 - l_blind = l - m_blind = 0.49421 * l + 0.0 * m + 1.24827 * s - s_blind = s - return (l_blind, m_blind, s_blind) + L_blind = L + M_blind = 0.49421 * L + 0.0 * M + 1.24827 * S + S_blind = S + return (L_blind, M_blind, S_blind) -def simulate_tritanopia(l: float, m: float, s: float) -> Tuple[float, float, float]: +def simulate_tritanopia(L: float, M: float, S: float) -> Tuple[float, float, float]: """模拟蓝色盲 (Tritanopia) - + 蓝色视锥细胞缺失,S 通道信息丢失。 使用蓝色盲模拟矩阵。 """ # 蓝色盲转换:S 通道由 L 和 M 估算 - l_blind = l - m_blind = m - s_blind = -0.395913 * l + 0.801109 * m + 0.0 * s - return (l_blind, m_blind, s_blind) + L_blind = L + M_blind = M + S_blind = -0.395913 * L + 0.801109 * M + 0.0 * S + return (L_blind, M_blind, S_blind) -def simulate_achromatopsia(l: float, m: float, s: float) -> Tuple[float, float, float]: +def simulate_achromatopsia(L: float, M: float, S: float) -> Tuple[float, float, float]: """模拟全色盲 (Achromatopsia) - + 完全无法感知颜色,转换为灰度。 使用 LMS 到灰度的转换。 """ # 转换为灰度值 (使用亮度权重) - gray = 0.299 * l + 0.587 * m + 0.114 * s + gray = 0.299 * L + 0.587 * M + 0.114 * S return (gray, gray, gray) @@ -176,25 +176,25 @@ def simulate_colorblind( if colorblind_type == 'normal': return rgb - r, g, b = rgb - + R, G, B = rgb + # 转换为 LMS 空间 - l, m, s = rgb_to_lms(r, g, b) - + L, M, S = rgb_to_lms(R, G, B) + # 应用色盲模拟 if colorblind_type == 'protanopia': - l, m, s = simulate_protanopia(l, m, s) + L, M, S = simulate_protanopia(L, M, S) elif colorblind_type == 'deuteranopia': - l, m, s = simulate_deuteranopia(l, m, s) + L, M, S = simulate_deuteranopia(L, M, S) elif colorblind_type == 'tritanopia': - l, m, s = simulate_tritanopia(l, m, s) + L, M, S = simulate_tritanopia(L, M, S) elif colorblind_type == 'achromatopsia': - l, m, s = simulate_achromatopsia(l, m, s) + L, M, S = simulate_achromatopsia(L, M, S) else: return rgb - + # 转换回 RGB - return lms_to_rgb(l, m, s) + return lms_to_rgb(L, M, S) def get_colorblind_info(colorblind_type: str) -> Dict[str, str]: diff --git a/core/gradient.py b/core/gradient.py index 8f0cf2f..3f663c7 100644 --- a/core/gradient.py +++ b/core/gradient.py @@ -101,36 +101,36 @@ def _interpolate_lab(start_rgb: Tuple[int, int, int], end_rgb: Tuple[int, int, i List[Tuple[int, int, int]]: RGB颜色列表,包含起始色、中间色、结束色 """ # 转换为LAB - l1, a1, b1 = rgb_to_lab(*start_rgb) - l2, a2, b2 = rgb_to_lab(*end_rgb) + L1, A1, B1 = rgb_to_lab(*start_rgb) + L2, A2, B2 = rgb_to_lab(*end_rgb) colors = [start_rgb] total_segments = steps + 1 for i in range(1, steps + 1): t = i / total_segments - l = l1 + (l2 - l1) * t - a = a1 + (a2 - a1) * t - b = b1 + (b2 - b1) * t + L = L1 + (L2 - L1) * t + A = A1 + (A2 - A1) * t + B = B1 + (B2 - B1) * t # 转换回RGB - colors.append(_lab_to_rgb(l, a, b)) + colors.append(_lab_to_rgb(L, A, B)) colors.append(end_rgb) return colors -def _lab_to_rgb(l: float, a: float, b: float) -> Tuple[int, int, int]: +def _lab_to_rgb(L: float, A: float, B: float) -> Tuple[int, int, int]: """将LAB颜色空间转换为RGB 这是rgb_to_lab的逆运算 Args: - l: 亮度分量 (0-100) - a: 红绿对立分量 (-128-127) - b: 黄蓝对立分量 (-128-127) + L: 亮度分量 (0-100) + A: 红绿对立分量 (-128-127) + B: 黄蓝对立分量 (-128-127) Returns: - Tuple[int, int, int]: RGB颜色值 (r, g, b),每个值范围0-255 + Tuple[int, int, int]: RGB颜色值 (R, G, B),每个值范围0-255 """ # 步骤1: 从LAB转换到XYZ def f_inv(t: float) -> float: @@ -144,9 +144,9 @@ def _lab_to_rgb(l: float, a: float, b: float) -> Tuple[int, int, int]: x_ref, y_ref, z_ref = 0.95047, 1.00000, 1.08883 # 计算f(Y), f(X), f(Z) - fy = (l + 16) / 116 - fx = fy + a / 500 - fz = fy - b / 200 + fy = (L + 16) / 116 + fx = fy + A / 500 + fz = fy - B / 200 # 计算XYZ x = x_ref * f_inv(fx) diff --git a/core/svg_color_mapper.py b/core/svg_color_mapper.py index 07db65d..a36edef 100644 --- a/core/svg_color_mapper.py +++ b/core/svg_color_mapper.py @@ -771,8 +771,6 @@ class SVGColorMapper: # 为所有可见元素分配颜色 print("排序后的元素:") for i, elem in enumerate(visible_elements[:10]): - x = elem.attributes.get('x', 'N/A') - y = elem.attributes.get('y', 'N/A') fill = elem.fill_color or 'N/A' print(f" {i}: fill={fill}, area={elem.area:.2f}") diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py index 2298064..0219889 100644 --- a/dialogs/about_dialog.py +++ b/dialogs/about_dialog.py @@ -194,9 +194,6 @@ class AboutDialog(BaseFramelessDialog): def _get_about_text(self): """获取关于页面的文本内容""" - app_info = version_manager.get_app_info() - version = version_manager.get_version() - return """  取色卡(Color Card)是一款专为摄影师和设计师开发的图片分析及配色工具,旨在帮助摄影爱好者和专业人士快速分析图像的色彩分布、亮度信息等关键数据,并提供一站式的本地配色解决方案。 项目功能设计借鉴参考了Adobe Color、色采、palettemakel等优秀的在线配色工具。 diff --git a/dialogs/base_frameless_dialog.py b/dialogs/base_frameless_dialog.py index f589099..1049118 100644 --- a/dialogs/base_frameless_dialog.py +++ b/dialogs/base_frameless_dialog.py @@ -120,7 +120,6 @@ class BaseFramelessDialog(FramelessDialog): text_color = get_text_color() text_color_str = text_color.name() bg_color = get_dialog_bg_color() - bg_color_str = bg_color.name() # 使用 QPalette 设置窗口背景色 palette = self.palette() diff --git a/dialogs/contrast_dialog.py b/dialogs/contrast_dialog.py index 153648b..ae8cc89 100644 --- a/dialogs/contrast_dialog.py +++ b/dialogs/contrast_dialog.py @@ -74,9 +74,7 @@ class ColorSelector(QWidget): # 颜色下拉选择 self.color_combo = ComboBox() self.color_combo.setFixedWidth(100) - for i, color_data in enumerate(self._colors): - rgb = color_data.get('rgb', [128, 128, 128]) - hex_val = rgb_to_hex(tuple(rgb)) + for i, _ in enumerate(self._colors): self.color_combo.addItem(tr('dialogs.contrast.color_index', index=i+1)) self.color_combo.setItemData(i, i) self.color_combo.currentIndexChanged.connect(self._on_combo_changed) diff --git a/dialogs/edit_palette.py b/dialogs/edit_palette.py index 8deebdf..c875225 100644 --- a/dialogs/edit_palette.py +++ b/dialogs/edit_palette.py @@ -333,7 +333,7 @@ class ColorModeSliders(QWidget): self._sliders[2].set_gradient(gradient_b) elif self._mode == 'HSL': - h, s, l = rgb_to_hsl(r, g, b) + H, S, L = rgb_to_hsl(r, g, b) # H滑块:色相渐变 gradient_h = QLinearGradient(0, 0, 200, 0) for i in range(7): @@ -344,15 +344,15 @@ class ColorModeSliders(QWidget): # S滑块:从灰到纯色 gradient_s = QLinearGradient(0, 0, 200, 0) - gradient_s.setColorAt(0.0, QColor.fromHsl(int(h / 360 * 359), 0, int(l / 100 * 255))) - gradient_s.setColorAt(1.0, QColor.fromHsl(int(h / 360 * 359), 255, int(l / 100 * 255))) + gradient_s.setColorAt(0.0, QColor.fromHsl(int(H / 360 * 359), 0, int(L / 100 * 255))) + gradient_s.setColorAt(1.0, QColor.fromHsl(int(H / 360 * 359), 255, int(L / 100 * 255))) self._sliders[1].set_gradient(gradient_s) # L滑块:从黑到白 gradient_l = QLinearGradient(0, 0, 200, 0) - gradient_l.setColorAt(0.0, QColor.fromHsl(int(h / 360 * 359), int(s / 100 * 255), 0)) - gradient_l.setColorAt(0.5, QColor.fromHsl(int(h / 360 * 359), int(s / 100 * 255), 128)) - gradient_l.setColorAt(1.0, QColor.fromHsl(int(h / 360 * 359), int(s / 100 * 255), 255)) + gradient_l.setColorAt(0.0, QColor.fromHsl(int(H / 360 * 359), int(S / 100 * 255), 0)) + gradient_l.setColorAt(0.5, QColor.fromHsl(int(H / 360 * 359), int(S / 100 * 255), 128)) + gradient_l.setColorAt(1.0, QColor.fromHsl(int(H / 360 * 359), int(S / 100 * 255), 255)) self._sliders[2].set_gradient(gradient_l) elif self._mode == 'CMYK': diff --git a/dialogs/update_dialog.py b/dialogs/update_dialog.py index 5039b68..d49dc68 100644 --- a/dialogs/update_dialog.py +++ b/dialogs/update_dialog.py @@ -115,10 +115,10 @@ def compare_versions(current: str, latest: str) -> int: current_parts.extend([0] * (max_len - len(current_parts))) latest_parts.extend([0] * (max_len - len(latest_parts))) - for c, l in zip(current_parts, latest_parts): - if c > l: + for c, latest_part in zip(current_parts, latest_parts): + if c > latest_part: return 1 - elif c < l: + elif c < latest_part: return -1 if current_pre > latest_pre: diff --git a/ui/cards.py b/ui/cards.py index 184f962..3871c8f 100644 --- a/ui/cards.py +++ b/ui/cards.py @@ -80,9 +80,7 @@ class BaseCardPanel(QWidget): old_count = self._card_count self._card_count = count - - layout = self.layout() - + if count > old_count: self._add_cards(old_count, count) else: diff --git a/ui/color_generation.py b/ui/color_generation.py index ea3b7fd..9c83897 100644 --- a/ui/color_generation.py +++ b/ui/color_generation.py @@ -488,8 +488,7 @@ class ColorGenerationInterface(QWidget): self.random_btn.setText(tr('color_generation.random')) self.favorite_button.setText(tr('color_generation.favorite')) self.brightness_label.setText(tr('color_generation.brightness')) - - current_index = self.scheme_combo.currentIndex() + self.scheme_combo.setItemText(0, tr('color_generation.schemes.monochromatic')) self.scheme_combo.setItemText(1, tr('color_generation.schemes.analogous')) self.scheme_combo.setItemText(2, tr('color_generation.schemes.complementary')) @@ -613,7 +612,6 @@ class ColorGenerationInterface(QWidget): self._scheme_colors[index] = (h, s, b) # 转换为RGB并更新色块面板 - rgb = hsb_to_rgb(h, s, b) self.color_panel.set_colors([hsb_to_rgb(*c) for c in self._scheme_colors]) def on_brightness_changed(self, value): diff --git a/ui/gradient_extract.py b/ui/gradient_extract.py index c5f4a34..b5b4ddc 100644 --- a/ui/gradient_extract.py +++ b/ui/gradient_extract.py @@ -424,7 +424,6 @@ class GradientExtractInterface(QWidget): def _update_hex_input_style(self): """更新16进制输入框样式(与配色管理一致)""" primary_color = get_text_color(secondary=False) - secondary_color = get_text_color(secondary=True) border_color = get_border_color() input_style = f""" diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" index dd09dc7..407f2cb 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" @@ -235,6 +235,26 @@ from ui import MainWindow | `PreviewService` | 预览场景管理服务 | `core/preview_service.py` | | `HistogramService` | 直方图计算服务 | `core/histogram_service.py` | +### 3.3.2 颜色空间命名规范 + +**颜色空间分量使用行业标准大写简写命名:** + +| 颜色空间 | 命名 | +|---------|------| +| RGB | `R`, `G`, `B` | +| HSB/HSL | `H`, `S`, `B`/`L` | +| LAB | `L`, `A`, `B` | +| LMS | `L`, `M`, `S` | +| CMYK | `C`, `M`, `Y`, `K` | + +```python +# 示例 +L, A, B = rgb_to_lab(R, G, B) +H, S, L = rgb_to_hsl(R, G, B) +``` + +**注意:** 禁止使用小写 `l`(易与数字 1 混淆,违反 E741 规范) + ### 3.4 异常处理规范 **基本原则:** 避免使用裸 `except:` 或 `except Exception:`,应指定具体异常类型,提供详细的错误信息,便于调试。 -- Gitee From a749010824046d25b19e1ee694442813e07d35ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Thu, 19 Mar 2026 13:54:28 +0800 Subject: [PATCH 27/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E5=90=88=E5=B9=B6?= =?UTF-8?q?=20BaseCanvas=20=E4=B8=AD=E9=87=8D=E5=A4=8D=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E7=9A=84=20resizeEvent=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 合并两个 resizeEvent 方法为一个,解决方法覆盖问题 - 确保加载状态遮罩层在窗口大小改变时正确更新位置 - 保留图片调整和取色点位置更新功能 - 遵循代码清理原则,消除重复代码 --- ui/canvases.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ui/canvases.py b/ui/canvases.py index b525daa..6eae108 100644 --- a/ui/canvases.py +++ b/ui/canvases.py @@ -162,11 +162,19 @@ class BaseCanvas(QWidget): self.update() def resizeEvent(self, event) -> None: - """窗口大小改变时更新加载状态组件位置""" + """窗口大小改变时处理相关逻辑""" super().resizeEvent(event) + + # 更新加载状态组件位置 if self._is_loading: self._loading_widget.setGeometry(self.rect()) + # 重新调整图片 + if self._image and not self._image.isNull(): + self.update_picker_positions() + self.extract_all() + self.update() + def set_image(self, image_path: str) -> None: """异步加载并显示图片(使用ImageService分阶段加载,非阻塞) @@ -638,15 +646,6 @@ class BaseCanvas(QWidget): self.open_image_requested.emit() event.accept() - def resizeEvent(self, event) -> None: - """窗口大小改变时重新调整图片""" - super().resizeEvent(event) - if self._image and not self._image.isNull(): - # 窗口大小改变时,更新取色点位置并重新提取数据 - self.update_picker_positions() - self.extract_all() - self.update() - def contextMenuEvent(self, event) -> None: """右键菜单事件""" # 只有在有图片时才显示右键菜单 -- Gitee From 203c1eaa57f157e26719378ba6f2fe7822be42f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Thu, 19 Mar 2026 14:20:45 +0800 Subject: [PATCH 28/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=AE=A1=E6=9F=A5=E5=8F=91=E7=8E=B0=E7=9A=84?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=92=8C=E9=98=B2=E5=BE=A1=E6=80=A7=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dialogs/edit_palette.py: 添加 QDialog 导入,修复 F821 错误 - dialogs/update_dialog.py: 添加 QUrl 导入,修复 F821 错误 - core/color.py: 精简 NUMPY_AVAILABLE 防御性代码,删除 10 处冗余条件判断 - 核心原则.md: 添加"避免不必要的防御性策略"设计原则 - 消除静态检查警告,提高代码清晰度 --- core/color.py | 27 ++++++++----------- dialogs/edit_palette.py | 2 +- dialogs/update_dialog.py | 2 +- ...70\345\277\203\345\216\237\345\210\231.md" | 1 + 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/core/color.py b/core/color.py index 6cd5ae0..7276beb 100644 --- a/core/color.py +++ b/core/color.py @@ -3,11 +3,7 @@ import colorsys from typing import Any, Dict, List, Tuple # 第三方库导入 -try: - import numpy as np - NUMPY_AVAILABLE = True -except ImportError: - NUMPY_AVAILABLE = False +import numpy as np # 项目模块导入 from .color_scheme_cache import get_color_scheme_cache @@ -353,7 +349,7 @@ def calculate_histogram(image, sample_step: int = 4, gamma: float = 2.2) -> List width = image.width() height = image.height() - if NUMPY_AVAILABLE and hasattr(image, 'bits'): + if hasattr(image, 'bits'): try: return _calculate_histogram_numpy(image, width, height, sample_step, gamma) except Exception: @@ -468,8 +464,8 @@ def calculate_rgb_histogram(image, sample_step: int = 4) -> Tuple[List[int], Lis width = image.width() height = image.height() - # 使用NumPy向量化计算(如果可用) - if NUMPY_AVAILABLE and hasattr(image, 'bits'): + # 使用NumPy向量化计算 + if hasattr(image, 'bits'): try: return _calculate_rgb_histogram_numpy(image, width, height, sample_step) except Exception: @@ -1054,7 +1050,7 @@ class _ColorCube: use_numpy: 是否使用 numpy 优化 """ self.pixels = pixels - self._use_numpy = use_numpy and NUMPY_AVAILABLE + self._use_numpy = use_numpy self._cache_volume = None self._cache_avg_color = None self._cache_ranges = None @@ -1197,7 +1193,7 @@ def _mmcq_quantize(pixels: List[Tuple[int, int, int]], count: int) -> List[_Colo return [] # 判断是否使用 numpy 优化(像素数量较多时) - use_numpy = NUMPY_AVAILABLE and len(pixels) > 1000 + use_numpy = len(pixels) > 1000 # 初始立方体包含所有像素 cubes = [_ColorCube(pixels, use_numpy)] @@ -1249,7 +1245,7 @@ def _extract_pixels_fast(image, sample_step: int = 4) -> List[Tuple[int, int, in width = image.width() height = image.height() - if NUMPY_AVAILABLE and hasattr(image, 'bits'): + if hasattr(image, 'bits'): # 使用 numpy 批量读取像素(QImage 格式) try: # 将 QImage 转换为 numpy 数组 @@ -1292,10 +1288,9 @@ def _extract_pixels_fast(image, sample_step: int = 4) -> List[Tuple[int, int, in elif hasattr(image, 'size') and hasattr(image, 'getpixel'): width, height = image.size - if NUMPY_AVAILABLE and hasattr(image, 'convert'): + if hasattr(image, 'convert'): # 使用 numpy 批量读取像素(PIL Image 格式) try: - import numpy as np arr = np.array(image.convert('RGB')) # 采样像素 @@ -1383,7 +1378,7 @@ def _extract_pixels_with_positions_fast( width = image.width() height = image.height() - if NUMPY_AVAILABLE and hasattr(image, 'bits'): + if hasattr(image, 'bits'): # 使用 numpy 批量读取像素 try: image = image.convertToFormat(image.Format.Format_RGB888) @@ -1416,7 +1411,7 @@ def _extract_pixels_with_positions_fast( elif hasattr(image, 'size') and hasattr(image, 'getpixel'): width, height = image.size - if NUMPY_AVAILABLE and hasattr(image, 'convert'): + if hasattr(image, 'convert'): # 使用 numpy 批量读取像素 try: arr = np.array(image.convert('RGB')) @@ -1475,7 +1470,7 @@ def find_dominant_color_positions( return [(0.5, 0.5)] * len(dominant_colors) # 使用 numpy 加速聚类计算 - if NUMPY_AVAILABLE and len(pixel_data) > 100: + if len(pixel_data) > 100: try: # 转换为 numpy 数组 pixel_array = np.array(pixel_data, dtype=np.float32) # [x, y, r, g, b] diff --git a/dialogs/edit_palette.py b/dialogs/edit_palette.py index c875225..b28ec19 100644 --- a/dialogs/edit_palette.py +++ b/dialogs/edit_palette.py @@ -6,7 +6,7 @@ from typing import Dict, Any, Tuple, Optional # 第三方库导入 from PySide6.QtCore import Qt, QTimer, Signal, QPoint, QRect from PySide6.QtWidgets import ( - QHBoxLayout, QLabel, QVBoxLayout, QWidget, QGridLayout, QApplication + QHBoxLayout, QLabel, QVBoxLayout, QWidget, QGridLayout, QApplication, QDialog ) from PySide6.QtGui import QColor, QPainter, QLinearGradient, QBrush, QPen, QMouseEvent from qfluentwidgets import ( diff --git a/dialogs/update_dialog.py b/dialogs/update_dialog.py index d49dc68..6005e75 100644 --- a/dialogs/update_dialog.py +++ b/dialogs/update_dialog.py @@ -3,7 +3,7 @@ import re from typing import List, Tuple # 第三方库导入 -from PySide6.QtCore import Qt, QThread, Signal +from PySide6.QtCore import Qt, QThread, Signal, QUrl from PySide6.QtGui import QDesktopServices from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget from qfluentwidgets import InfoBar, InfoBarPosition, PrimaryPushButton, PushButton, qconfig diff --git "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" index 7219419..27fe3ec 100644 --- "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" +++ "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" @@ -101,6 +101,7 @@ splitter.setHandleWidth(0) # 必须隐藏分隔条 - **延迟抽象**:只有一种实现时不用工厂/策略模式 - **适度防御**:边界检查要有实际意义 - **单一职责**:一个类只负责一类功能 +- **避免不必要的防御性策略**:对于项目必需依赖(如 requirements.txt 中定义的包),无需编写防御性导入和回退代码 ### 多线程使用 -- Gitee From 4cdcd7f4d57e159f4f191948fd0fe932e37d772b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Thu, 19 Mar 2026 16:56:26 +0800 Subject: [PATCH 29/47] =?UTF-8?q?Revert=20"[=E4=BF=AE=E5=A4=8D]=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=BB=E7=AA=97=E5=8F=A3=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=97=B6=E6=9C=AA=E8=87=AA=E5=8A=A8=E5=BC=B9=E5=87=BA=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 6b12abc21c041d72008ffbe508386d4eb614bb2b. --- main.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/main.py b/main.py index b9880dc..9d00799 100644 --- a/main.py +++ b/main.py @@ -218,19 +218,7 @@ def main(): logger.info("创建主窗口...") window = MainWindow() - - # 根据保存的窗口状态决定如何显示 - # 如果窗口已经在 __init__ 中被最大化或全屏,则不需要再调用 show() - if not window.isMaximized() and not window.isFullScreen(): - window.show() - logger.info("主窗口以普通模式显示") - else: - logger.info(f"主窗口以 {'全屏' if window.isFullScreen() else '最大化'} 模式显示") - - # 确保窗口在前台显示 - window.raise_() - window.activateWindow() - + window.show() logger.info("主窗口显示完成") # 关闭启动画面并修复任务栏图标(在窗口显示后调用) -- Gitee From a69bc1381411077211ea4be00c9bf1fd375efd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Thu, 19 Mar 2026 17:10:38 +0800 Subject: [PATCH 30/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=8A=A8=E7=94=BB=E6=9C=9F=E9=97=B4=E5=8F=AF?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=85=B6=E4=BB=96=E7=AA=97=E5=8F=A3=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E4=B8=BB=E7=AA=97=E5=8F=A3=E4=B8=8D=E5=BC=B9=E5=87=BA?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 启动画面添加 WindowDoesNotAcceptFocus 标志,避免拦截用户焦点 - 主窗口显示时调用 activateWindow() 和 raise_() 强制激活并提升到最前 --- main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 9d00799..367bf8e 100644 --- a/main.py +++ b/main.py @@ -96,7 +96,8 @@ def _create_splash_screen(): splash.setWindowFlags( Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint | - Qt.WindowType.SplashScreen + Qt.WindowType.SplashScreen | + Qt.WindowType.WindowDoesNotAcceptFocus ) # 居中显示 @@ -226,6 +227,9 @@ def main(): if splash: splash.finish(window) fix_windows_taskbar_icon_for_window(window) + # 强制激活主窗口,确保在其他窗口操作后仍能弹出 + window.activateWindow() + window.raise_() QTimer.singleShot(100, _on_window_shown) -- Gitee From d7021850f9aa8d6406075339542740e4e2d929dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Fri, 20 Mar 2026 13:59:59 +0800 Subject: [PATCH 31/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?RYB=E6=A8=A1=E5=BC=8F=E4=B8=8B=E6=98=8E=E5=BA=A6=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E6=97=B6=E8=A7=92=E5=BA=A6=E8=BD=AC=E6=8D=A2=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 on_base_color_changed 方法中RYB模式的角度转换逻辑 - 将RGB的delta_h正确转换为RYB的delta_h后再应用到配色点 - 保持RYB模式下调整明度时色相不变 --- ui/color_generation.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/color_generation.py b/ui/color_generation.py index 9c83897..f41d719 100644 --- a/ui/color_generation.py +++ b/ui/color_generation.py @@ -571,11 +571,16 @@ class ColorGenerationInterface(QWidget): if delta_h != 0 and self._scheme_colors: if self._color_wheel_mode == 'RYB': # RYB模式下需要在RYB色轮上进行偏移,保持RYB角度关系 + # 将RGB的delta_h转换为RYB的delta_h + old_base_ryb = rgb_hue_to_ryb_hue(self._base_hue - delta_h) + new_base_ryb = rgb_hue_to_ryb_hue(self._base_hue) + ryb_delta_h = new_base_ryb - old_base_ryb + for i in range(len(self._scheme_colors)): old_h, old_s, old_b = self._scheme_colors[i] # RGB -> RYB -> 偏移 -> RGB ryb_h = rgb_hue_to_ryb_hue(old_h) - new_ryb_h = (ryb_h + delta_h) % 360 + new_ryb_h = (ryb_h + ryb_delta_h) % 360 new_h = ryb_hue_to_rgb_hue(new_ryb_h) self._scheme_colors[i] = (new_h, old_s, old_b) else: -- Gitee From a2781947c24f5587605cf64e0e4c9ae1cd89871f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Fri, 20 Mar 2026 15:44:37 +0800 Subject: [PATCH 32/47] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=AF=BC=E5=85=A5=E5=AF=B9=E8=AF=9D=E6=A1=86?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E6=89=93=E5=BC=80=E4=BD=8D=E7=BD=AE=E4=B8=BA?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=9B=BE=E7=89=87=E6=96=87=E4=BB=B6=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 utils/__init__.py 添加 get_default_image_directory() 函数 - 修改 ui/color_extract.py 使用用户图片文件夹作为默认路径 - 修改 ui/luminance_extract.py 使用用户图片文件夹作为默认路径 - 修改 ui/color_preview.py 使用用户图片文件夹作为默认路径 - 修改 ui/palette_management.py 使用用户图片文件夹作为默认路径 --- ui/color_extract.py | 4 ++-- ui/color_preview.py | 6 +++--- ui/luminance_extract.py | 4 ++-- ui/palette_management.py | 4 ++-- utils/__init__.py | 13 +++++++++++++ 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/ui/color_extract.py b/ui/color_extract.py index e624506..30f06d9 100644 --- a/ui/color_extract.py +++ b/ui/color_extract.py @@ -21,7 +21,7 @@ from qfluentwidgets import ( # 项目模块导入 from core import get_color_info, get_config_manager, ServiceFactory, log_user_action -from utils import tr, get_locale_manager +from utils import tr, get_locale_manager, get_default_image_directory from dialogs import EditPaletteDialog from .canvases import ImageCanvas from .cards import ColorCardPanel @@ -194,7 +194,7 @@ class ColorExtractInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('color_extract.select_image'), - "", + get_default_image_directory(), tr('color_extract.image_filter') ) diff --git a/ui/color_preview.py b/ui/color_preview.py index 860e810..0c6dfa1 100644 --- a/ui/color_preview.py +++ b/ui/color_preview.py @@ -35,7 +35,7 @@ from core.color import get_color_info from core.logger import get_logger, log_user_action from dialogs.edit_palette import EditPaletteDialog from dialogs.export_settings_dialog import ExportSettingsDialog -from utils import tr, get_locale_manager +from utils import tr, get_locale_manager, get_default_image_directory from utils.theme_colors import get_border_color, get_text_color logger = get_logger("color_preview") @@ -1882,7 +1882,7 @@ class ColorPreviewInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('color_preview.import_svg'), - "", + get_default_image_directory(), tr('color_preview.svg_filter') ) @@ -2081,7 +2081,7 @@ class ColorPreviewInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('color_preview.import_template'), - "", + get_default_image_directory(), tr('color_preview.svg_filter') ) diff --git a/ui/luminance_extract.py b/ui/luminance_extract.py index 9abaea6..a04f81f 100644 --- a/ui/luminance_extract.py +++ b/ui/luminance_extract.py @@ -12,7 +12,7 @@ from PySide6.QtWidgets import QFileDialog, QSplitter, QVBoxLayout, QWidget # 项目模块导入 from core import LuminanceService from core.logger import get_logger, log_user_action -from utils import tr, get_locale_manager +from utils import tr, get_locale_manager, get_default_image_directory from .canvases import LuminanceCanvas from .histograms import LuminanceHistogramWidget @@ -100,7 +100,7 @@ class LuminanceExtractInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('luminance_extract.select_image'), - "", + get_default_image_directory(), tr('luminance_extract.image_filter') ) diff --git a/ui/palette_management.py b/ui/palette_management.py index 25133e4..ba1d1ec 100644 --- a/ui/palette_management.py +++ b/ui/palette_management.py @@ -17,7 +17,7 @@ from qfluentwidgets import ( # 项目模块导入 from core import get_color_info, hex_to_rgb, get_config_manager, ServiceFactory -from utils import tr, get_locale_manager, calculate_grid_columns +from utils import tr, get_locale_manager, calculate_grid_columns, get_default_image_directory from core.async_loader import BaseBatchLoader from core.grouping import generate_groups from core.logger import get_logger, log_user_action, log_performance @@ -1285,7 +1285,7 @@ class PaletteManagementInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('palette_management.import_title'), - "", + get_default_image_directory(), tr('palette_management.json_filter') ) diff --git a/utils/__init__.py b/utils/__init__.py index 1fb9c18..926a5e4 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,5 +1,7 @@ """工具函数模块""" +from pathlib import Path + from .icon import load_icon_universal, get_icon_path, create_fallback_icon, get_base_path from .platform import set_app_user_model_id, fix_windows_taskbar_icon_for_window, set_window_title_bar_theme from .layout import calculate_grid_columns @@ -12,6 +14,16 @@ from .locale import ( get_supported_languages, ) + +def get_default_image_directory() -> str: + """获取默认图片导入目录 + + Returns: + str: 用户图片文件夹路径 + """ + return str(Path.home() / "Pictures") + + __all__ = [ 'load_icon_universal', 'get_icon_path', @@ -27,4 +39,5 @@ __all__ = [ 'set_language', 'get_current_language', 'get_supported_languages', + 'get_default_image_directory', ] -- Gitee From e29f975b075f016780ec7f9819919214f04d3f5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Fri, 20 Mar 2026 16:57:50 +0800 Subject: [PATCH 33/47] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=AF=BC=E5=85=A5=E5=AF=BC=E5=87=BA=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E8=B7=AF=E5=BE=84=E5=92=8C=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 get_default_image_directory() 和 get_default_data_directory() 工具函数 - 添加 get_last_directory() 和 set_last_directory() 实现记住上次目录功能 - 修改图片导入对话框默认路径为用户图片文件夹 - 修改配色数据导入导出对话框默认路径为用户文档文件夹 - 统一 Path 导入位置到文件顶部,移除函数内重复导入 - 修复 color_preview.py 中残留的局部 Path 导入导致的 UnboundLocalError --- ui/color_extract.py | 6 ++++-- ui/color_preview.py | 19 +++++++++++------ ui/luminance_extract.py | 6 ++++-- ui/palette_management.py | 14 +++++++++---- utils/__init__.py | 45 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 14 deletions(-) diff --git a/ui/color_extract.py b/ui/color_extract.py index 30f06d9..f9f5078 100644 --- a/ui/color_extract.py +++ b/ui/color_extract.py @@ -7,6 +7,7 @@ # 标准库导入 import uuid from datetime import datetime +from pathlib import Path # 第三方库导入 from PySide6.QtCore import Qt @@ -21,7 +22,7 @@ from qfluentwidgets import ( # 项目模块导入 from core import get_color_info, get_config_manager, ServiceFactory, log_user_action -from utils import tr, get_locale_manager, get_default_image_directory +from utils import tr, get_locale_manager, get_default_image_directory, get_last_directory, set_last_directory from dialogs import EditPaletteDialog from .canvases import ImageCanvas from .cards import ColorCardPanel @@ -194,7 +195,7 @@ class ColorExtractInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('color_extract.select_image'), - get_default_image_directory(), + get_last_directory("image_import", get_default_image_directory()), tr('color_extract.image_filter') ) @@ -204,6 +205,7 @@ class ColorExtractInterface(QWidget): params={"path": file_path, "source": "color_extract"}, result="success" ) + set_last_directory("image_import", str(Path(file_path).parent)) self.image_canvas.set_image(file_path) def on_image_loaded(self, file_path): diff --git a/ui/color_preview.py b/ui/color_preview.py index 0c6dfa1..a34c182 100644 --- a/ui/color_preview.py +++ b/ui/color_preview.py @@ -12,6 +12,7 @@ - 配色预览界面 """ # 标准库导入 +from pathlib import Path from typing import List, Optional, Dict, Any, Type # 第三方库导入 @@ -35,7 +36,7 @@ from core.color import get_color_info from core.logger import get_logger, log_user_action from dialogs.edit_palette import EditPaletteDialog from dialogs.export_settings_dialog import ExportSettingsDialog -from utils import tr, get_locale_manager, get_default_image_directory +from utils import tr, get_locale_manager, get_default_image_directory, get_last_directory, set_last_directory from utils.theme_colors import get_border_color, get_text_color logger = get_logger("color_preview") @@ -1882,13 +1883,15 @@ class ColorPreviewInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('color_preview.import_svg'), - get_default_image_directory(), + get_last_directory("svg_import", get_default_image_directory()), tr('color_preview.svg_filter') ) if not file_path: return + set_last_directory("svg_import", str(Path(file_path).parent)) + svg_preview = self.preview_panel.get_svg_preview() if svg_preview is None: InfoBar.warning( @@ -1960,12 +1963,13 @@ class ColorPreviewInterface(QWidget): return # 打开文件保存对话框(获取保存目录) + last_dir = get_last_directory("svg_export", get_default_image_directory()) if len(selected_indices) == 1: # 单张图片 - default_name = f"{filename_prefix}.{export_format}" + default_name = str(Path(last_dir) / f"{filename_prefix}.{export_format}") else: # 多张图片 - default_name = filename_prefix + default_name = str(Path(last_dir) / filename_prefix) file_path, _ = QFileDialog.getSaveFileName( self, @@ -1977,6 +1981,8 @@ class ColorPreviewInterface(QWidget): if not file_path: return + set_last_directory("svg_export", str(Path(file_path).parent)) + # 执行导出 success_count = 0 failed_messages = [] @@ -2007,7 +2013,6 @@ class ColorPreviewInterface(QWidget): base_path = base_path[:-len(ext)] break # 获取保存目录 - from pathlib import Path path_obj = Path(file_path) parent_dir = path_obj.parent # 生成带序号的文件名 @@ -2081,13 +2086,15 @@ class ColorPreviewInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('color_preview.import_template'), - get_default_image_directory(), + get_last_directory("svg_import", get_default_image_directory()), tr('color_preview.svg_filter') ) if not file_path: return + set_last_directory("svg_import", str(Path(file_path).parent)) + is_valid, error_msg = self._get_preview_service().validate_svg_file(file_path) if not is_valid: InfoBar.error( diff --git a/ui/luminance_extract.py b/ui/luminance_extract.py index a04f81f..1b737a0 100644 --- a/ui/luminance_extract.py +++ b/ui/luminance_extract.py @@ -4,6 +4,7 @@ """ # 标准库导入 +from pathlib import Path from typing import Dict, Any from PySide6.QtCore import Qt, QTimer, Signal @@ -12,7 +13,7 @@ from PySide6.QtWidgets import QFileDialog, QSplitter, QVBoxLayout, QWidget # 项目模块导入 from core import LuminanceService from core.logger import get_logger, log_user_action -from utils import tr, get_locale_manager, get_default_image_directory +from utils import tr, get_locale_manager, get_default_image_directory, get_last_directory, set_last_directory from .canvases import LuminanceCanvas from .histograms import LuminanceHistogramWidget @@ -100,12 +101,13 @@ class LuminanceExtractInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('luminance_extract.select_image'), - get_default_image_directory(), + get_last_directory("image_import", get_default_image_directory()), tr('luminance_extract.image_filter') ) if file_path: log_user_action("open_image", {"file_path": file_path}) + set_last_directory("image_import", str(Path(file_path).parent)) self._load_image(file_path) def change_image(self): diff --git a/ui/palette_management.py b/ui/palette_management.py index ba1d1ec..d49a790 100644 --- a/ui/palette_management.py +++ b/ui/palette_management.py @@ -1,6 +1,7 @@ # 标准库导入 import math from datetime import datetime +from pathlib import Path from typing import List, Dict, Any # 第三方库导入 @@ -17,7 +18,7 @@ from qfluentwidgets import ( # 项目模块导入 from core import get_color_info, hex_to_rgb, get_config_manager, ServiceFactory -from utils import tr, get_locale_manager, calculate_grid_columns, get_default_image_directory +from utils import tr, get_locale_manager, calculate_grid_columns, get_default_data_directory, get_last_directory, set_last_directory from core.async_loader import BaseBatchLoader from core.grouping import generate_groups from core.logger import get_logger, log_user_action, log_performance @@ -1285,13 +1286,14 @@ class PaletteManagementInterface(QWidget): file_path, _ = QFileDialog.getOpenFileName( self, tr('palette_management.import_title'), - get_default_image_directory(), + get_last_directory("palette_import", get_default_data_directory()), tr('palette_management.json_filter') ) if not file_path: return + set_last_directory("palette_import", str(Path(file_path).parent)) log_user_action("import_palette_start", {"file_path": file_path}) self._pending_import_path = file_path @@ -1365,7 +1367,8 @@ class PaletteManagementInterface(QWidget): def _on_export_clicked(self): """导出按钮点击""" - default_name = f"color_card_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + last_dir = get_last_directory("palette_export", get_default_data_directory()) + default_name = str(Path(last_dir) / f"color_card_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json") file_path, _ = QFileDialog.getSaveFileName( self, tr('palette_management.export_title'), @@ -1379,6 +1382,7 @@ class PaletteManagementInterface(QWidget): if not file_path.endswith('.json'): file_path += '.json' + set_last_directory("palette_export", str(Path(file_path).parent)) log_user_action("export_palette_start", {"file_path": file_path}) favorites = self._config_manager.get_favorites() @@ -1442,7 +1446,8 @@ class PaletteManagementInterface(QWidget): return palette_name = favorite_data.get('name', tr('palette_management.unnamed')) - default_name = f"{palette_name}.ase" + last_dir = get_last_directory("palette_export", get_default_data_directory()) + default_name = str(Path(last_dir) / f"{palette_name}.ase") file_path, _ = QFileDialog.getSaveFileName( self, @@ -1457,6 +1462,7 @@ class PaletteManagementInterface(QWidget): if not file_path.endswith('.ase'): file_path += '.ase' + set_last_directory("palette_export", str(Path(file_path).parent)) log_user_action("export_ase_start", {"file_path": file_path, "palette_name": palette_name}) with log_performance("export_ase", {"file_path": file_path, "color_count": len(colors)}): diff --git a/utils/__init__.py b/utils/__init__.py index 926a5e4..18b19b4 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,7 +1,12 @@ """工具函数模块""" +# 标准库导入 from pathlib import Path +# 第三方库导入 +from PySide6.QtCore import QSettings + +# 项目模块导入 from .icon import load_icon_universal, get_icon_path, create_fallback_icon, get_base_path from .platform import set_app_user_model_id, fix_windows_taskbar_icon_for_window, set_window_title_bar_theme from .layout import calculate_grid_columns @@ -24,6 +29,43 @@ def get_default_image_directory() -> str: return str(Path.home() / "Pictures") +def get_default_data_directory() -> str: + """获取默认数据文件目录(用于导入/导出配色数据) + + Returns: + str: 用户文档文件夹路径 + """ + return str(Path.home() / "Documents") + + +def get_last_directory(key: str, default_dir: str) -> str: + """获取用户上次选择的目录 + + Args: + key: 存储键名(区分不同功能) + default_dir: 默认目录(首次使用或记录不存在时返回) + + Returns: + str: 上次选择的目录或默认目录 + """ + settings = QSettings("ColorCard", "App") + last_dir = settings.value(f"last_directory/{key}", default_dir) + if last_dir and Path(last_dir).exists(): + return str(last_dir) + return default_dir + + +def set_last_directory(key: str, directory: str): + """记录用户选择的目录 + + Args: + key: 存储键名 + directory: 用户选择的目录路径 + """ + settings = QSettings("ColorCard", "App") + settings.setValue(f"last_directory/{key}", directory) + + __all__ = [ 'load_icon_universal', 'get_icon_path', @@ -40,4 +82,7 @@ __all__ = [ 'get_current_language', 'get_supported_languages', 'get_default_image_directory', + 'get_default_data_directory', + 'get_last_directory', + 'set_last_directory', ] -- Gitee From 93680b620af961f844f4d519f8376bad7d25bc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sat, 21 Mar 2026 17:42:32 +0800 Subject: [PATCH 34/47] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E4=B8=BA=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86=E6=B7=BB=E5=8A=A0=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=92=8C=E4=BC=98=E5=8C=96=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - main.py set_app_user_model_id: 添加 logger.debug 记录 Windows API 调用失败 - main.py create_splash_screen: 添加 logger.debug 记录启动画面创建失败 - color.py _extract_pixels_fast: 优化注释说明 NumPy 降级策略意图 --- .gitignore | 1 + core/color.py | 3 ++- main.py | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 7a85b3c..bd24cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,4 @@ md转docx.py 文档/color_card_20260313_161405.json /测试 文档/代码审查报告-26.03.md +/创作过程 diff --git a/core/color.py b/core/color.py index 7276beb..9caff92 100644 --- a/core/color.py +++ b/core/color.py @@ -1267,7 +1267,8 @@ def _extract_pixels_fast(image, sample_step: int = 4) -> List[Tuple[int, int, in return pixels except Exception: - pass # 失败时回退到普通方法 + # NumPy 加速失败,静默回退到普通方法 + pass # 普通方法(逐个读取) for y in range(0, height, sample_step): diff --git a/main.py b/main.py index 367bf8e..90e8656 100644 --- a/main.py +++ b/main.py @@ -16,7 +16,8 @@ def set_app_user_model_id(): app_id = 'HXiaoStudio.ColorCard.1.0.0' ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id) return True - except Exception: + except Exception as e: + logger.debug(f"设置 AppUserModelID 失败: {e}") return False @@ -119,7 +120,8 @@ def _create_splash_screen(): ) return splash - except Exception: + except Exception as e: + logger.debug(f"创建启动画面失败: {e}") return None -- Gitee From 62c213ae495bf000c3ed85cff731f403b66d7216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sat, 21 Mar 2026 19:00:20 +0800 Subject: [PATCH 35/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E7=AE=80=E5=8C=96?= =?UTF-8?q?SVG=E9=A2=9C=E8=89=B2=E6=98=A0=E5=B0=84=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E5=8C=96=E8=AF=86=E5=88=AB=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除id属性语义化检查,仅保留class属性识别 - 删除非语义化回退逻辑(按标签类型和面积判断) - 删除文字元素无fill的兼容处理 - 删除智能映射中的固定元素检测逻辑 - 更新开发规范9.7节,同步代码实现与文档描述 - 更新版本记录至3.44 --- core/svg_color_mapper.py | 66 ++----------------- ...00\345\217\221\350\247\204\350\214\203.md" | 15 +++-- 2 files changed, 15 insertions(+), 66 deletions(-) diff --git a/core/svg_color_mapper.py b/core/svg_color_mapper.py index a36edef..7246e34 100644 --- a/core/svg_color_mapper.py +++ b/core/svg_color_mapper.py @@ -35,7 +35,7 @@ class SVGElementInfo: stroke_color: Optional[str] = None # 原始 stroke 颜色 area: float = 0.0 # 元素面积(用于排序) is_visible: bool = True # 是否可见 - fixed_color: Optional[str] = None # 固定颜色设置(black/original) + fixed_color: Optional[str] = None # 固定颜色设置(black/original),仅语义化映射使用 is_semantic: bool = False # 是否通过语义化标识(class/id关键词)分类 is_transparent: bool = False # 是否是透明元素(无 fill 但有 stroke) z_index: int = 0 # 绘制顺序(文档顺序,越大越在上层) @@ -104,47 +104,12 @@ class SVGElementClassifier: """ tag = element.tag.split('}')[-1] if '}' in element.tag else element.tag - # 1. 检查 class 属性(最高优先级) + # 检查 class 属性 element_class = element.get('class', '') if element_class: for keyword, elem_type in self.CLASS_KEYWORDS.items(): if keyword in element_class.lower(): - return elem_type, True # 通过语义化标识分类 - - # 2. 检查 id 属性 - element_id = element.get('id', '') - if element_id: - for keyword, elem_type in self.CLASS_KEYWORDS.items(): - if keyword in element_id.lower(): - return elem_type, True # 通过语义化标识分类 - - # 3. 根据标签类型和上下文智能判断(非语义化,是启发式规则) - if tag == 'rect': - # 矩形分类策略: - # - 如果只有一个矩形或面积最大 -> 可能是背景 - # - 如果有多个矩形且不是最大的 -> 可能是装饰/主元素 - # - 大面积矩形(>10000)-> 背景 - if is_largest_rect and area > 5000: - return ElementType.BACKGROUND, False - elif area > 10000: - return ElementType.BACKGROUND, False - elif total_rect_count > 1: - # 多个矩形时,小矩形作为装饰元素 - return (ElementType.PRIMARY if area < 5000 else ElementType.BACKGROUND), False - else: - return ElementType.BACKGROUND, False - - elif tag in ['path', 'polygon', 'polyline']: - return ElementType.PRIMARY, False - - elif tag in ['circle', 'ellipse']: - return ElementType.PRIMARY, False - - elif tag == 'line': - return ElementType.SECONDARY, False - - elif tag in ['text', 'tspan']: - return ElementType.TEXT, False + return elem_type, True return ElementType.UNKNOWN, False @@ -492,13 +457,10 @@ class SVGColorMapper: for elem in sorted_elements: if elem.bounding_box is None: continue - + if elem.is_transparent: continue - - if elem.fixed_color: - continue - + # 只检测大面积元素,避免小元素被误判 if elem.area < 10000: continue @@ -602,14 +564,8 @@ class SVGColorMapper: continue # 应用颜色 - 直接设置内联属性,这会覆盖 CSS 类样式 - # 1. 处理 fill - should_apply_fill = ( - elem_info.fill_color is not None or # 有显式 fill - elem_info.tag in ['text', 'tspan'] or # 文字元素 - elem_info.element_type in [ElementType.PRIMARY, ElementType.SECONDARY, ElementType.ACCENT, ElementType.BACKGROUND] # 图形元素 - ) - - if should_apply_fill: + # 处理 fill + if elem_info.fill_color is not None: elem.set('fill', color) # 2. 处理 stroke @@ -844,14 +800,6 @@ class SVGColorMapper: if elem is None: continue - # 处理固定颜色元素 - if elem_info.fixed_color: - if elem_info.fixed_color == 'black': - # 固定为黑色 - self._apply_color_to_element(elem, elem_info, '#000000') - # fixed_color == 'original' 时保持原色,不做任何操作 - continue - # 应用 fill 颜色映射 if elem_info.fill_color: fill_lower = elem_info.fill_color.lower() diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" index 407f2cb..e55fa1a 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" @@ -1065,15 +1065,15 @@ SVG模板使用语义化 `class` 命名规范,支持智能配色映射。 #### 9.7.1 元素分类依据 -| 优先级 | 依据 | 示例 | -| :-: | ----------- | ------------------------- | -| 1 | class 属性关键词 | `class="background"` → 背景 | -| 2 | id 属性关键词 | `id="main-title"` → 文字 | -| 3 | 标签类型 + 面积 | 最大矩形 → 背景 | +语义化映射仅通过 `class` 属性识别元素类型: + +| 依据 | 示例 | +| ---- | ---- | +| class 属性关键词 | `class="background"` → 背景 | #### 9.7.2 支持的关键词 -| 元素类型 | class/id 关键词 | 颜色映射 | +| 元素类型 | class 关键词 | 颜色映射 | | ---- | -------------------------- | ------ | | 背景 | `background`, `bg`, `back` | 配色\[0] | | 主元素 | `primary`, `main` | 配色\[1] | @@ -1097,7 +1097,7 @@ else: ##### 语义化映射模式 -**适用条件**:SVG有语义化类型信息(通过class/id关键词识别) +**适用条件**:SVG有语义化类型信息(通过class关键词识别) **映射规则**: @@ -2026,6 +2026,7 @@ def _get_about_text(self): | 版本 | 日期 | 变更内容 | | :--: | :--------: | :------------------------------------------------------------------------: | +| 3.44 | 2026-03-21 | 更新SVG颜色映射规范(9.7.1节、9.7.2节、9.7.3节):简化语义化映射为仅通过class属性识别,删除id属性和标签类型回退的兼容处理 | | 3.43 | 2026-03-16 | 更新无边框对话框基类规范(5.1.1节):`closeEvent` 信号断开逻辑移至基类;删除 Windows DWM API 相关内容(3.8.5节) | | 3.42 | 2026-03-16 | 新增无边框对话框基类规范(5.1.1节):提取 BaseFramelessDialog 到独立文件,统一标题栏和主题适配;更新项目结构(1.3节) | | 3.41 | 2026-03-13 | 精简开发规范中的代码示例,删除冗余注释和重复内容,使文档更加简洁易读 | -- Gitee From db697611a163d89a21212491bd5ab219c23f8790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sat, 21 Mar 2026 19:04:14 +0800 Subject: [PATCH 36/47] =?UTF-8?q?[=E5=86=85=E5=AE=B9=E8=B0=83=E6=95=B4=20?= =?UTF-8?q?=E4=B8=BA=20color=5Fextract.py=20=E7=9A=84=20on=5Fimage=5Floade?= =?UTF-8?q?d=20=E6=96=B9=E6=B3=95=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补充文档字符串,说明方法由主窗口同步时调用 - 添加代码注释,说明图片数据处理已在 on_image_data_loaded 中完成 - 与 luminance_extract.py 的同类方法保持一致的注释风格 --- ui/color_extract.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/color_extract.py b/ui/color_extract.py index f9f5078..1c0fc18 100644 --- a/ui/color_extract.py +++ b/ui/color_extract.py @@ -209,7 +209,8 @@ class ColorExtractInterface(QWidget): self.image_canvas.set_image(file_path) def on_image_loaded(self, file_path): - """图片加载完成回调""" + """图片加载完成回调(由主窗口同步时调用)""" + # 图片数据处理已在 on_image_data_loaded 中完成 pass def on_image_data_loaded(self, pixmap, image): -- Gitee From fe97b790cf69259442159ad847db54bfe658cc11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sat, 21 Mar 2026 19:12:17 +0800 Subject: [PATCH 37/47] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=20core/color.py=20=E4=B8=AD=E6=9C=AA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=9A=84=20RYB=20=E8=89=B2=E7=9B=B8=E6=98=A0=E5=B0=84=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 RYB_HUE_MAP_RGB_TO_RYB 映射表(15行) - 删除 RYB_HUE_MAP_RYB_TO_RGB 映射表(15行) - 删除相关注释(4行) - 共清理34行未使用代码,减少维护负担 --- core/color.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/core/color.py b/core/color.py index 9caff92..a0cfed6 100644 --- a/core/color.py +++ b/core/color.py @@ -1536,41 +1536,6 @@ def find_dominant_color_positions( # ==================== RYB 色彩空间支持 ==================== -# RYB 色相映射表:RGB色相角度 -> RYB色相角度 -# 基于传统美术色轮的红-黄-蓝三原色系统 -RYB_HUE_MAP_RGB_TO_RYB = { - 0: 0, # 红 - 30: 30, # 橙红 - 60: 60, # 橙 - 90: 90, # 橙黄 - 120: 120, # 黄 - 150: 150, # 黄绿 - 180: 180, # 绿(RYB中绿在180度) - 210: 210, # 青绿 - 240: 240, # 青 - 270: 270, # 蓝 - 300: 300, # 紫 - 330: 330, # 品红 - 360: 360, # 红 -} - -# RYB 色相映射表:RYB色相角度 -> RGB色相角度 -RYB_HUE_MAP_RYB_TO_RGB = { - 0: 0, # 红 - 30: 30, # 橙红 - 60: 60, # 橙 - 90: 90, # 橙黄 - 120: 120, # 黄 - 150: 150, # 黄绿 - 180: 180, # 绿(RYB中绿在180度,RGB中绿在120度) - 210: 210, # 青绿 - 240: 240, # 青 - 270: 270, # 蓝 - 300: 300, # 紫 - 330: 330, # 品红 - 360: 360, # 红 -} - def rgb_hue_to_ryb_hue(rgb_hue: float) -> float: """将 RGB 色相转换为 RYB 色相 -- Gitee From 33a6aa9871bddbc7e08f786567da2aaaf8755f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sat, 21 Mar 2026 19:55:01 +0800 Subject: [PATCH 38/47] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A3=80=E6=9F=A5=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ .../\344\273\243\347\240\201\346\243\200\346\237\245.txt" | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index bd24cc4..5692f31 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,5 @@ md转docx.py /测试 文档/代码审查报告-26.03.md /创作过程 +bandit_report.html +bandit_project_report.html diff --git "a/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" "b/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" index 55f01ca..3ea5da1 100644 --- "a/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" +++ "b/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" @@ -9,5 +9,5 @@ mypy . radon cc . -a -s # 发布前检查 -bandit -r . -vulture . +bandit -r . -f html -o bandit_report.html +vulture core ui dialogs utils main.py --min-confidence 60 -- Gitee From dae7d25a30a29274f48611e2e9e10ed2f9fe1607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sat, 21 Mar 2026 19:59:51 +0800 Subject: [PATCH 39/47] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20main.py=20=E4=B8=AD=E6=9C=AA=E4=BD=BF=E7=94=A8=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 qt_message_handler 函数的 context 参数改为 _context - 遵循项目命名规范,使用下划线前缀标记未使用参数 - 消除代码审查报告中的问题22(高置信度) --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 90e8656..1ab97b9 100644 --- a/main.py +++ b/main.py @@ -169,7 +169,7 @@ def main(): sys.stdout = _old_stdout # 安装自定义 Qt 消息处理器以过滤 QFont 警告 - def qt_message_handler(mode, context, message): + def qt_message_handler(mode, _context, message): """自定义 Qt 消息处理器,过滤掉 QFont::setPointSize 警告""" if "QFont::setPointSize: Point size <= 0" in message: return -- Gitee From c2d5e4e5613e6efa1e2b9dacd5610ec3d1562046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sun, 22 Mar 2026 03:18:10 +0800 Subject: [PATCH 40/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=20theme=5Fcolors.py=20=E4=B8=AD=E6=9C=AA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E4=B8=BB=E9=A2=98=E9=A2=9C=E8=89=B2=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 get_card_background_color() 函数 - 删除 get_interface_background_color() 函数 - 删除 get_border_color_secondary() 函数 - 删除 get_picker_colors() 函数 - 删除 get_tooltip_border_color() 函数 - 删除 get_tooltip_text_color() 函数 - 删除 get_favorite_icon_color() 函数 - 清理过度设计的死代码,遵循 YAGNI 原则 --- utils/theme_colors.py | 46 ------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/utils/theme_colors.py b/utils/theme_colors.py index cc77ce2..b468a89 100644 --- a/utils/theme_colors.py +++ b/utils/theme_colors.py @@ -12,16 +12,6 @@ def get_canvas_background_color(): return QColor(42, 42, 42) -def get_card_background_color(): - """获取卡片背景颜色""" - return QColor(42, 42, 42) if isDarkTheme() else QColor(255, 255, 255) - - -def get_interface_background_color(): - """获取界面背景颜色(与FluentWindow一致)""" - return QColor(32, 32, 32) if isDarkTheme() else QColor(243, 243, 243) - - def get_histogram_background_color(): """获取直方图背景颜色 - 固定灰黑色 #2a2a2a""" return QColor(42, 42, 42) @@ -52,11 +42,6 @@ def get_border_color(): return QColor(80, 80, 80) if isDarkTheme() else QColor(221, 221, 221) -def get_border_color_secondary(): - """获取次要边框颜色""" - return QColor(120, 120, 120) if isDarkTheme() else QColor(200, 200, 200) - - # ========== 占位符/空状态颜色 ========== def get_placeholder_color(): """获取占位符颜色(空色块背景)""" @@ -201,36 +186,11 @@ def get_canvas_empty_text_color(): return QColor(150, 150, 150) -def get_picker_colors(): - """获取取色点颜色列表""" - return [ - QColor(0, 102, 255, 100), - QColor(0, 128, 255, 100), - QColor(0, 153, 255, 100), - QColor(0, 204, 102, 100), - QColor(102, 255, 102, 100), - QColor(255, 204, 0, 100), - QColor(255, 128, 0, 100), - QColor(255, 51, 102, 100), - QColor(200, 100, 255, 100), - ] - - def get_tooltip_bg_color(): """获取提示框背景颜色""" return QColor(0, 0, 0, 180) -def get_tooltip_border_color(): - """获取提示框边框颜色""" - return QColor(255, 255, 255) - - -def get_tooltip_text_color(): - """获取提示框文本颜色""" - return QColor(0, 0, 0) - - # ========== 缩放查看器颜色 ========== def get_zoom_grid_color(): """获取缩放查看器网格颜色""" @@ -274,12 +234,6 @@ def get_zone_text_color(): return QColor(255, 255, 255) if isDarkTheme() else QColor(0, 0, 0) -# ========== 收藏组件颜色 ========== -def get_favorite_icon_color(): - """获取收藏界面图标颜色""" - return QColor(153, 153, 153) - - # ========== 高饱和度区域高亮颜色 ========== def get_high_saturation_highlight_color(): """获取高饱和度区域高亮颜色 - 半透明品红色""" -- Gitee From a7813345da67520254e27ed8c0216095cf51096e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sun, 22 Mar 2026 14:21:34 +0800 Subject: [PATCH 41/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E9=A1=B5=E9=9D=A2=E5=AF=BC=E8=88=AA=E6=A0=8F?= =?UTF-8?q?=E7=BC=BA=E5=B0=91"=E4=BD=BF=E7=94=A8=E8=AF=B4=E6=98=8E"?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 changelog.html 导航栏中添加"使用说明"链接 - 链接指向 Bilibili 视频教程,与 index.html 保持一致 --- docs/changelog.html | 1 + website/changelog.html | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/changelog.html b/docs/changelog.html index 5f05242..e94a138 100644 --- a/docs/changelog.html +++ b/docs/changelog.html @@ -171,6 +171,7 @@ 项目起源 下载 更新日志 + 使用说明 反馈 关于我们
diff --git a/website/changelog.html b/website/changelog.html index 5f05242..e94a138 100644 --- a/website/changelog.html +++ b/website/changelog.html @@ -171,6 +171,7 @@ 项目起源 下载 更新日志 + 使用说明 反馈 关于我们
-- Gitee From 905e118341558fa0406e5f55c93163a80adb3723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sun, 22 Mar 2026 14:40:13 +0800 Subject: [PATCH 42/47] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A3=80=E6=9F=A5=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\344\273\243\347\240\201\346\243\200\346\237\245.txt" | 1 - 1 file changed, 1 deletion(-) diff --git "a/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" "b/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" index 3ea5da1..a5a90d9 100644 --- "a/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" +++ "b/\346\226\207\346\241\243/\344\273\243\347\240\201\346\243\200\346\237\245.txt" @@ -9,5 +9,4 @@ mypy . radon cc . -a -s # 发布前检查 -bandit -r . -f html -o bandit_report.html vulture core ui dialogs utils main.py --min-confidence 60 -- Gitee From 58630333afc34a4958cb7b5cc0374f022050b683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sun, 22 Mar 2026 14:50:47 +0800 Subject: [PATCH 43/47] =?UTF-8?q?[=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83]=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AF=BC=E5=85=A5=E4=BD=8D=E7=BD=AE=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ui/settings.py: 将 version_manager 和 get_title_color 导入移到项目模块导入区域 - core/image_service.py: 将 Qt 导入移到文件顶部第三方库区域,删除文件末尾重复导入 --- core/image_service.py | 5 +---- ui/settings.py | 5 ++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/core/image_service.py b/core/image_service.py index b3993d2..a110445 100644 --- a/core/image_service.py +++ b/core/image_service.py @@ -12,7 +12,7 @@ from typing import Optional # 第三方库导入 from PIL import Image -from PySide6.QtCore import QObject, QThread, Signal +from PySide6.QtCore import QObject, QThread, Signal, Qt from PySide6.QtGui import QImage, QPixmap # 项目模块导入 @@ -611,6 +611,3 @@ class ImageService(QObject): dict: 内存统计信息 """ return self._get_memory_manager().get_memory_stats() - - -from PySide6.QtCore import Qt diff --git a/ui/settings.py b/ui/settings.py index 7519ab7..35dea45 100644 --- a/ui/settings.py +++ b/ui/settings.py @@ -11,12 +11,11 @@ from qfluentwidgets import ( from core import get_config_manager from core.logger import get_logger, log_user_action from utils import tr, get_supported_languages, set_language, get_locale_manager - +from utils.theme_colors import get_title_color from dialogs import AboutDialog, UpdateAvailableDialog +from version import version_manager logger = get_logger("settings") -from version import version_manager -from utils.theme_colors import get_title_color AVAILABLE_COLOR_MODES = ['HSB', 'LAB', 'HSL', 'CMYK', 'RGB'] -- Gitee From e607a0f9ce04897a182cf56e62a965f700c798af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sun, 22 Mar 2026 14:59:17 +0800 Subject: [PATCH 44/47] =?UTF-8?q?[=E5=86=85=E5=AE=B9=E8=B0=83=E6=95=B4]=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- version.py | 2 +- version.txt | 2 +- version_info.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/version.py b/version.py index f9edd26..77daee7 100644 --- a/version.py +++ b/version.py @@ -11,7 +11,7 @@ class VersionManager: self.minor: int = 6 self.patch: int = 0 self.build: int = 0 - self.prerelease: str = "Beta" + self.prerelease: str = "" # 核心版本信息 self.version: str = f"{self.major}.{self.minor}.{self.patch}{self.prerelease}" diff --git a/version.txt b/version.txt index 9b107ef..5f427fb 100644 --- a/version.txt +++ b/version.txt @@ -1,5 +1,5 @@ 1.6.0 -2026.3.18.1 +2026.3.22.1 1.6.0.0 浮晓 HXiao Studio © 2026 浮晓 HXiao Studio diff --git a/version_info.txt b/version_info.txt index ac78628..484e954 100644 --- a/version_info.txt +++ b/version_info.txt @@ -1,6 +1,6 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(2026,3,18,1), + filevers=(2026,3,22,1), prodvers=(1,6,0,0), mask=0x3f, flags=0x0, -- Gitee From 72ad81dfe327c43f084d455037bc549c48cc6147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sun, 22 Mar 2026 15:08:55 +0800 Subject: [PATCH 45/47] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E7=BB=8F=E9=AA=8C=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...17\351\252\214\346\200\273\347\273\223.md" | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" index 357c4cf..d1d98cb 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" @@ -1166,3 +1166,245 @@ dialog.exec() #### 旧模板(直接使用 FramelessDialog,供参考) 如需自定义标题栏样式或实现特殊需求,可直接继承 `FramelessDialog`,参考 10.3-10.4 节实现相关方法。 + +--- + +## 11. 静态分析工具使用经验 + +### 11.1 Ruff 使用经验 + +**E402 导入位置问题处理原则:** + +- **故意设计的延迟导入**(如启动优化)→ 维持现状 +- **代码组织混乱**导致的导入位置问题 → 修复 + +**示例:** + +```python +# ❌ 代码组织混乱 +logger = get_logger("settings") +from version import version_manager + +# ✓ 正确的导入顺序 +from version import version_manager +logger = get_logger("settings") +``` + +**处理决策表:** + +| 场景 | 示例 | 处理方式 | +|------|------|----------| +| 启动优化延迟导入 | `main.py` 中延迟导入启动画面 | 维持现状 | +| 服务延迟加载 | `ServiceFactory` 中延迟导入服务类 | 维持现状 | +| 代码组织混乱 | `settings.py` 中导入在 logger 之后 | 修复 | + +### 11.2 Bandit 安全扫描使用经验 + +**对于纯本地桌面应用:** + +| 问题类型 | 典型场景 | 评估 | 原因 | +|----------|----------|------|------| +| MD5 哈希使用 | 缓存键生成 | ✅ 无需处理 | 非安全场景,仅影响缓存命中率 | +| XML 解析安全 | SVG 文件解析 | 🟢 低优先级 | 本地应用,用户主动行为 | +| random 模块使用 | UI 颜色生成 | ✅ 无需处理 | 非加密用途 | +| try-except-pass | 降级策略 | ✅ 无需处理 | 合理用法 | + +**结论:** 纯本地桌面应用的 Bandit 告警大部分是**误报**,仅供参考,不应作为代码质量门槛。 + +### 11.3 Vulture 死代码检测使用经验 + +**60% 置信度问题的局限性:** + +| 问题类型 | Vulture 报告 | 实际情况 | 评估 | +|----------|-------------|----------|------| +| 未使用属性 | `_nav_icon` | 动态设置使用 | 误报 | +| 未使用方法 | `get_elements` | 公共 API | 误报 | +| 未使用事件 | `mouseMoveEvent` | Qt 框架调用 | 误报 | +| 未使用函数 | `cancel_loading` | 预留功能 | 保留 | + +**建议:** +- 60% 置信度的问题需要**人工确认**,不建议批量删除 +- 100% 置信度的问题(如未使用变量)可以安全修复 +- 公共 API 方法即使暂时未使用也应保留 + +--- + +## 12. 代码审查经验 + +### 12.1 重复代码处理原则 + +**不应该抽象的重复:** + +- 标准库/框架的惯用写法(如 `try/except` 信号断开) +- 只有 6 处以下且场景简单 +- 抽象后反而降低可读性 + +**应该抽象的重复:** + +- 业务逻辑重复(如 RGB/RYB 配色生成) +- 重复代码超过 10 行 +- 抽象后能显著提升可维护性 + +**决策案例:** + +```python +# ❌ 过度设计:创建 safe_disconnect 工具函数 +# 22处重复,但每处只有4行,且是标准写法 + +def safe_disconnect(signal, slot): + try: + signal.disconnect(slot) + except (TypeError, RuntimeError): + pass + +# ✅ 维持现状:标准 try/except 写法 +try: + signal.disconnect(slot) +except (TypeError, RuntimeError): + pass +``` + +### 12.2 过度设计识别 + +**信号处理:** + +- ❌ 创建 `safe_disconnect` 工具函数(过度设计) +- ✅ 维持 `try/except` 标准写法(符合惯例) + +**配置管理:** + +- ❌ 统一 `COLOR_MODE_CONFIG` 和 `MODE_PARAMS`(用途不同) +- ✅ 维持各自模块独立(内聚性原则) + +**延迟抽象原则:** + +- 只有一种实现 → 不需要工厂/策略模式 +- 标准用法重复 → 不需要提取工具函数 +- 60% 置信度的工具告警 → 需要人工确认 + +### 12.3 评估原则 + +**代码审查检查清单:** + +- [ ] 是否过度抽象?(延迟抽象原则) +- [ ] 是否影响可读性?(标准写法 vs 自定义函数) +- [ ] 工具告警是否合理?(考虑实际场景) +- [ ] 修改收益是否大于成本?(行数节省 vs 复杂度增加) + +**常见误报识别:** + +| 工具 | 误报类型 | 识别方法 | +|------|----------|----------| +| Ruff E402 | 故意延迟导入 | 检查是否是启动优化或服务延迟加载 | +| Bandit B303 | MD5 用于缓存 | 确认非安全场景 | +| Vulture 60% | 动态属性/事件方法 | 检查实际使用情况 | +| Ruff F841 | 未使用变量 | 100% 置信度可安全修复 | + +--- + +## 13. 延迟加载设计模式总结 + +### 13.1 多层延迟加载架构 + +Color Card 项目实现了完整的延迟加载体系: + +**1. 服务层**(`ServiceFactory`) + +```python +@classmethod +def get_color_service(cls): + """延迟创建服务,双检锁保证线程安全""" + if 'color' not in cls._instances: + with cls._get_lock(): + if 'color' not in cls._instances: + from .color_service import ColorService + cls._instances['color'] = ColorService(None) + return cls._instances['color'] +``` + +**2. UI 层**(`_get_xxx_service`) + +```python +def _get_color_service(self): + """方法级延迟获取""" + if self._color_service is None: + from core import get_color_service + self._color_service = get_color_service() + return self._color_service +``` + +**3. 数据层**(`_ensure_loaded`) + +```python +def _ensure_loaded(self) -> None: + """懒加载标志 + 确保加载""" + if not self._loaded: + self._load_data() + self._loaded = True +``` + +**4. 界面层**(`on_tab_selected`) + +```python +def on_tab_selected(self): + """标签页选中时延迟加载""" + if not self._data_loaded: + self._load_data() +``` + +**5. 计算任务**(`QTimer.singleShot`) + +```python +# 延迟执行耗时操作,让 UI 先响应 +QTimer.singleShot(100, self.extract_all) +``` + +### 13.2 延迟加载与代码规范的关系 + +| 场景 | 是否延迟加载 | 处理方式 | +|------|-------------|----------| +| `main.py` 启动画面导入 | ✅ 是 | 维持现状,符合规范 | +| `ServiceFactory` 服务导入 | ✅ 是 | 维持现状,符合规范 | +| `settings.py` 导入位置混乱 | ❌ 否 | 修复,代码组织问题 | +| `image_service.py` 末尾导入 | ❌ 否 | 修复,代码组织问题 | + +**关键区分:** + +- **延迟加载**:有明确的性能优化目的,导入在函数/方法内部 +- **代码组织问题**:无实际目的,只是导入位置混乱 + +### 13.3 延迟加载最佳实践 + +**导入位置规范:** + +```python +# 标准库导入 +import sys +from pathlib import Path + +# 第三方库导入 +from PySide6.QtCore import QObject +from PIL import Image + +# 项目模块导入(非延迟加载) +from core import get_config_manager +from utils import tr + +# 延迟加载(在函数内部) +def _get_service(self): + if self._service is None: + from core import get_service # 延迟导入 + self._service = get_service() + return self._service +``` + +**性能优化效果:** + +从 Changelog 可以看到延迟加载的实际收益: + +> "实现业务服务延迟加载,减少应用启动时间" +> "优化直方图计算服务,缩短延迟计算时间,大图片加载性能显著提升" + +--- + +**文档更新日期**: 2026-03-22 -- Gitee From 3e9b62e31068bfa8640309f622bce776c9b70898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sun, 22 Mar 2026 15:37:44 +0800 Subject: [PATCH 46/47] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20v1.6.0=20=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 v1.6.0 版本更新日志到 docs/changelog.json - 新增 v1.6.0 版本更新日志到 website/public/changelog.json --- docs/changelog.json | 40 +++++++++++++++++++++++++++++++++++ website/public/changelog.json | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/docs/changelog.json b/docs/changelog.json index aa41324..c0545cb 100644 --- a/docs/changelog.json +++ b/docs/changelog.json @@ -1,5 +1,45 @@ { "versions": [ + { + "version": "v1.6.0", + "date": "2026-03-22", + "changes": [ + { + "category": "新增功能", + "items": [ + "新增「跟随系统」语言选项,应用语言可自动适配系统语言设置" + ] + }, + { + "category": "问题修复", + "items": [ + "修复启动动画期间操作其他窗口导致主窗口不弹出的问题", + "修复 Hex 值区域字重异常问题" + ] + }, + { + "category": "界面优化", + "items": [ + "HSB 色轮改为顺时针排列,符合常用色彩理论习惯", + "所有对话框统一采用无边框设计,视觉风格更加一致", + "禁用关于对话框文本选择高亮,避免误操作" + ] + }, + { + "category": "体验优化", + "items": [ + "优化文件导入导出目录路径,图片导入默认打开用户图片文件夹,配色文件导入导出路径更加便捷", + "统一色轮采样点在 RGB 和 RYB 模式下的行为一致性" + ] + }, + { + "category": "代码重构", + "items": [ + "优化内部代码结构,提升软件稳定性" + ] + } + ] + }, { "version": "v1.5.1", "date": "2026-03-15", diff --git a/website/public/changelog.json b/website/public/changelog.json index 65bb19f..8d5c58b 100644 --- a/website/public/changelog.json +++ b/website/public/changelog.json @@ -1,5 +1,45 @@ { "versions": [ + { + "version": "v1.6.0", + "date": "2026-03-22", + "changes": [ + { + "category": "新增功能", + "items": [ + "新增「跟随系统」语言选项,应用语言可自动适配系统语言设置" + ] + }, + { + "category": "问题修复", + "items": [ + "修复启动动画期间操作其他窗口导致主窗口不弹出的问题", + "修复 Hex 值区域字重异常问题" + ] + }, + { + "category": "界面优化", + "items": [ + "HSB 色轮改为顺时针排列,符合常用色彩理论习惯", + "所有对话框统一采用无边框设计,视觉风格更加一致", + "禁用关于对话框文本选择高亮,避免误操作" + ] + }, + { + "category": "体验优化", + "items": [ + "优化文件导入导出目录路径,图片导入默认打开用户图片文件夹,配色文件导入导出路径更加便捷", + "统一色轮采样点在 RGB 和 RYB 模式下的行为一致性" + ] + }, + { + "category": "代码重构", + "items": [ + "优化内部代码结构,提升软件稳定性" + ] + } + ] + }, { "version": "v1.5.1", "date": "2026-03-15", -- Gitee From 8e72ac543a9de4622ac49b0c169cbb5ae8e4bd65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?= Date: Sun, 22 Mar 2026 15:42:19 +0800 Subject: [PATCH 47/47] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20README.md=20=E5=BC=80=E5=8F=91=E5=8E=86=E7=A8=8B=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新开发历程统计至 v1.6.0(9 个版本,111 项更新) --- README.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index cd6b9b5..c2c02df 100644 --- a/README.md +++ b/README.md @@ -38,22 +38,23 @@ | 指标 | 数据 | | :---- | :--------------------- | -| 发布版本 | 8 个版本(v1.0.0 → v1.5.1) | -| 开发周期 | 38 天 | -| 总更新项 | **102 项** | -| 平均每版本 | 12.75 项 | +| 发布版本 | 9 个版本(v1.0.0 → v1.6.0) | +| 开发周期 | 45 天 | +| 总更新项 | **111 项** | +| 平均每版本 | 12.33 项 | **详细分类统计**: | 分类 | 数量 | 说明 | | :------- | :----: | :-------------- | -| ✨ 新增功能 | **32** | 包含首次发布的 9 项核心功能 | -| 🔧 问题修复 | **25** | 持续修复 Bug,提升稳定性 | -| 🎨 界面优化 | **25** | 用户体验打磨 | +| ✨ 新增功能 | **33** | 包含首次发布的 9 项核心功能 | +| 🔧 问题修复 | **27** | 持续修复 Bug,提升稳定性 | +| 🎨 界面优化 | **28** | 用户体验打磨 | | ⚡ 性能提升 | **8** | 缓存机制、启动优化等 | | 📝 内容调整 | **5** | 文本、名称等调整 | -| 🏗️ 代码重构 | **3** | 代码结构优化 | +| 🏗️ 代码重构 | **4** | 代码结构优化 | | 🔮 逻辑优化 | **2** | 算法逻辑改进 | +| ⚙️ 体验优化 | **2** | 交互体验改进 | | 📜 许可证完善 | **1** | 开源合规性 | | 🚀 功能优化 | **1** | 功能增强 | @@ -287,22 +288,23 @@ Since the release of v1.0.0 on 2026-02-05, the project has maintained a fast and | Metric | Data | | :------------------ | :--------------------------- | -| Released Versions | 8 versions (v1.0.0 → v1.5.1) | -| Development Period | 38 days | -| Total Updates | **102 items** | -| Average per Version | 12.75 items | +| Released Versions | 9 versions (v1.0.0 → v1.6.0) | +| Development Period | 45 days | +| Total Updates | **111 items** | +| Average per Version | 12.33 items | **Detailed Category Statistics(portion)**: | Category | Count | Description | | :--------------------- | :----: | :------------------------------------------- | -| ✨ New Features | **32** | Including 9 core features from v1.0.0 launch | -| 🔧 Bug Fixes | **25** | Continuous bug fixes for stability | -| 🎨 UI Improvements | **25** | User experience refinements | +| ✨ New Features | **33** | Including 9 core features from v1.0.0 launch | +| 🔧 Bug Fixes | **27** | Continuous bug fixes for stability | +| 🎨 UI Improvements | **28** | User experience refinements | | ⚡ Performance | **8** | Cache mechanism, startup optimization | | 📝 Content Adjustments | **5** | Text, naming adjustments | -| 🏗️ Code Refactoring | **3** | Code structure optimization | +| 🏗️ Code Refactoring | **4** | Code structure optimization | | 🔮 Logic Optimization | **2** | Algorithm improvements | +| ⚙️ Experience | **2** | Interaction improvements | | 📜 License Compliance | **1** | Open source compliance | | 🚀 Feature Enhancement | **1** | Feature enhancements | -- Gitee