From 2c5dd1c9429428dfeb72ed94bf79aaf39e1332be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=A8=E5=8F=B6=E6=A2=A6=E6=98=A5?= Date: Tue, 4 Aug 2026 02:53:09 +0800 Subject: [PATCH] feat(ui): add about window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 晨叶梦春 --- THIRD_PARTY_NOTICES.md | 4 +- assets/README.md | 4 +- assets/information_3d.png | Bin 0 -> 29009 bytes assets/information_flat.svg | 5 + src/tchmaterial_parser/app.py | 20 ++- src/tchmaterial_parser/images.py | 9 +- src/tchmaterial_parser/ui/about_window.py | 146 ++++++++++++++++++++ src/tchmaterial_parser/ui/download_panel.py | 2 +- src/tchmaterial_parser/ui/theme.py | 17 ++- src/tchmaterial_parser/ui/token_window.py | 4 +- tchMaterial-parser.spec | 3 +- 11 files changed, 190 insertions(+), 24 deletions(-) create mode 100644 assets/information_3d.png create mode 100644 assets/information_flat.svg create mode 100644 src/tchmaterial_parser/ui/about_window.py diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 444e98d..dffdf62 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -9,10 +9,10 @@ Copyright (c) Microsoft Corporation. Licensed under the MIT License. Source: -https://github.com/microsoft/fluentui-emoji + Modifications: None The full license text is included in: -licenses/Microsoft-Fluent-Emoji.txt +[licenses/Microsoft-Fluent-Emoji.txt](./licenses/Microsoft-Fluent-Emoji.txt) diff --git a/assets/README.md b/assets/README.md index fa0af04..63bec28 100644 --- a/assets/README.md +++ b/assets/README.md @@ -9,8 +9,8 @@ | `logo.icns` | macOS `.app` 应用包图标 | `tchMaterial-parser.spec` 中的 `BUNDLE(icon=...)` | | `icon.ico` | Windows 可执行文件图标 | `tchMaterial-parser.spec` 中的 `EXE(icon=...)` | | `window_icon.png` | 程序运行时的窗口图标(窗口左上角、Alt+Tab,以及主界面标题旁) | `src/tchmaterial_parser/app.py` 直接读取,`tchMaterial-parser.spec` 负责打包 | -| `sun_3d.png`、`crescent_moon_3d.png`、`last_quarter_moon_3d.png`(来自 [microsoft/fluentui-emoji](https://github.com/microsoft/fluentui-emoji)) | 程序右上角选择主题图标 | `src/tchmaterial_parser/images.py` 直接读取,`tchMaterial-parser.spec` 负责打包 | -| `sun_flat.svg`、`crescent_moon_flat.svg`、`last_quarter_moon_flat.svg`(来自 [microsoft/fluentui-emoji](https://github.com/microsoft/fluentui-emoji)) | 矢量图 | 仅供设计使用,不参与构建 | +| `last_quarter_moon_3d.png`、`sun_3d.png`、`crescent_moon_3d.png`、`information_3d.png`(来自 [microsoft/fluentui-emoji](https://github.com/microsoft/fluentui-emoji)) | 程序右上角选择主题、关于图标 | `src/tchmaterial_parser/images.py` 直接读取,`tchMaterial-parser.spec` 负责打包 | +| `last_quarter_moon_flat.svg`、`sun_flat.svg`、`crescent_moon_flat.svg`、`information_flat.svg`(来自 [microsoft/fluentui-emoji](https://github.com/microsoft/fluentui-emoji)) | 矢量图 | 仅供设计使用,不参与构建 | ## 更换图标时的操作 diff --git a/assets/information_3d.png b/assets/information_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..02fbb08c4a0637bb93631a984db9d35e76185a3c GIT binary patch literal 29009 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 pZ)DRY;r}D~?|)+i;dZtlx2BQMZo}#bz7h~2 + + + + diff --git a/src/tchmaterial_parser/app.py b/src/tchmaterial_parser/app.py index ddf8011..b152e9d 100644 --- a/src/tchmaterial_parser/app.py +++ b/src/tchmaterial_parser/app.py @@ -10,9 +10,10 @@ from PIL import Image, ImageTk from . import __version__ from .catalog import ResourceHelper from .config import load_access_token, load_config, save_config -from .images import make_theme_icon_image, render_system_emoji +from .images import make_icon_image, render_system_emoji from .platform_utils import ctypes, os_name, print_error, resource_path, win32api, win32con, win32gui, win32print from .ui import download_panel, runtime, theme +from .ui.about_window import show_about_window from .ui.resource_tree import build_resource_tree from .ui.runtime import scaled from .ui.token_window import show_access_token_window @@ -130,7 +131,7 @@ def main() -> None: # 程序入口:初始化界面并进入主循环 container_frame = ttk.Frame(root, padding=(scaled(24), scaled(18))) # 通过内边距在窗口四周留白 container_frame.pack(expand=True, fill="both") - # 顶部:左侧为图标与标题,右侧为主题切换按钮 + # 顶部:左侧为图标与标题,右侧为关于、主题切换按钮 header_frame = ttk.Frame(container_frame) header_frame.pack(fill="x") @@ -154,7 +155,7 @@ def main() -> None: # 程序入口:初始化界面并进入主循环 def update_theme_button() -> None: # 更新按钮文字与图标,使其表示点击后将切换到的主题 if theme.switched_theme not in theme_icons: icon_size = max(scaled(16), 16) - theme_icons[theme.switched_theme] = ImageTk.PhotoImage(make_theme_icon_image(theme.switched_theme, icon_size)) + theme_icons[theme.switched_theme] = ImageTk.PhotoImage(make_icon_image(theme.switched_theme, icon_size)) theme_btn.config( text=" 浅色" if theme.switched_theme == "light" else " 深色" if theme.switched_theme == "dark" else " 跟随系统", image=theme_icons[theme.switched_theme], @@ -167,8 +168,17 @@ def main() -> None: # 程序入口:初始化界面并进入主循环 theme.apply_theme(target_theme) update_theme_button() - theme_btn = ttk.Button(header_frame, style="Toolbutton", command=switch_theme) - theme_btn.pack(side="right", anchor="n") + header_actions = ttk.Frame(header_frame) + header_actions.pack(side="right", anchor="n") + + # 关于按钮 + about_image = ImageTk.PhotoImage(make_icon_image("about", max(scaled(16), 16))) + about_btn = ttk.Button(header_actions, text=" 关于", image=about_image, compound="left", style="Toolbutton", command=show_about_window) + about_btn.pack(side="left", padx=(0, scaled(8))) + + # 切换主题按钮 + theme_btn = ttk.Button(header_actions, style="Toolbutton", command=switch_theme) + theme_btn.pack(side="left") update_theme_button() # 功能说明 diff --git a/src/tchmaterial_parser/images.py b/src/tchmaterial_parser/images.py index ee7d03f..e175ed6 100644 --- a/src/tchmaterial_parser/images.py +++ b/src/tchmaterial_parser/images.py @@ -93,14 +93,13 @@ def render_system_emoji(symbol: str, icon_size: int) -> Image.Image | None: # continue return None -def make_theme_icon_image(target_theme: Literal["system", "light", "dark"], icon_size: int) -> Image.Image: # 优先使用系统 Emoji 的原始字形,无法渲染时使用图片 - symbol = "☀️" if target_theme == "light" else "🌙" if target_theme == "dark" else "🌗" - emoji_icon = render_system_emoji(symbol, icon_size) +def make_icon_image(icon_name: Literal["system", "light", "dark", "about"], icon_size: int) -> Image.Image: # 优先使用系统 Emoji 的原始字形,无法渲染时使用图片 + icon_mapping = { "system": ("🌗", "last_quarter_moon_3d.png"), "light": ("☀️", "sun_3d.png"), "dark": ("🌙", "crescent_moon_3d.png"), "about": ("ℹ️", "information_3d.png") } + emoji_icon = render_system_emoji(icon_mapping[icon_name][0], icon_size) if emoji_icon is not None: return emoji_icon - icon_mapping = { "system": "last_quarter_moon_3d.png", "light": "sun_3d.png", "dark": "crescent_moon_3d.png" } - with Image.open(resource_path("assets", icon_mapping[target_theme])) as icon: + with Image.open(resource_path("assets", icon_mapping[icon_name][1])) as icon: icon_image = icon.copy() icon_image.thumbnail((icon_size, icon_size), Image.Resampling.LANCZOS) return icon_image diff --git a/src/tchmaterial_parser/ui/about_window.py b/src/tchmaterial_parser/ui/about_window.py new file mode 100644 index 0000000..acc04b6 --- /dev/null +++ b/src/tchmaterial_parser/ui/about_window.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# 关于窗口:展示程序、作者、项目地址与第三方许可证信息 + +import tkinter as tk +from tkinter import messagebox, ttk +import webbrowser + +from PIL import Image, ImageTk + +from .. import __version__ +from ..platform_utils import resource_path +from . import runtime, theme +from .runtime import scaled +from .widgets import center_window, make_card + +PROJECT_URL = "https://github.com/happycola233/tchMaterial-parser" +LICENSE_URL = "https://github.com/happycola233/tchMaterial-parser/blob/main/LICENSE" +FLUENT_EMOJI_LICENSE_URL = "https://github.com/microsoft/fluentui-emoji/blob/main/LICENSE" + +def open_url(url: str, parent: tk.Toplevel) -> None: # 使用系统默认浏览器打开链接,并在失败时向用户给出明确提示 + try: + if not webbrowser.open_new_tab(url): + messagebox.showerror("无法打开链接", f"请复制链接后在浏览器中打开:\n{url}", parent=parent) + except Exception as e: + messagebox.showerror("无法打开链接", f"请复制链接后在浏览器中打开:\n{url}\n\n{e}", parent=parent) + +def make_link(parent: tk.Widget, text: str, url: str, window: tk.Toplevel) -> ttk.Label: # 创建可通过鼠标或键盘打开的链接标签 + link = ttk.Label(parent, text=text, style="AboutLink.TLabel", cursor="hand2", takefocus=True) + link.bind("", lambda _event: open_url(url, window)) # 鼠标左键 + link.bind("", lambda _event: open_url(url, window)) # 回车键 + link.bind("", lambda _event: open_url(url, window)) # 空格键 + return link + +def show_about_window() -> None: # 打开关于窗口 + about_window = tk.Toplevel(runtime.root) + about_window.title("关于") + about_window.resizable(False, False) + about_window.focus() + about_window.transient(runtime.root) + about_window.bind("", lambda _event: about_window.destroy()) + + # 卡片内的标签需要使用与卡片一致的底色;链接通过下划线和强调色表明可点击 + style = ttk.Style(about_window) + style.configure( + "AboutCard.TLabel", + background=theme.current_colors["surface"], + foreground=theme.current_colors["fg"], + ) + style.configure( + "AboutCardMuted.TLabel", + font="AppCaptionFont", + background=theme.current_colors["surface"], + foreground=theme.current_colors["muted"], + ) + style.configure( + "AboutCardStrong.TLabel", + font="AppStrongFont", + background=theme.current_colors["surface"], + foreground=theme.current_colors["fg"], + ) + style.configure( + "AboutLink.TLabel", + font="AppLinkFont", + background=theme.current_colors["surface"], + foreground="#3973e6" if theme.current_theme == "light" else "#75a7ff", + ) + + frame = ttk.Frame(about_window, padding=scaled(24)) + frame.pack(fill="both", expand=True) + + # 程序图标与名称 + header = ttk.Frame(frame) + header.pack(fill="x") + + with Image.open(resource_path("assets", "window_icon.png")) as icon: + icon_image = icon.copy() + icon_image.thumbnail((scaled(64), scaled(64)), Image.Resampling.LANCZOS) + icon_photo = ImageTk.PhotoImage(icon_image) + icon_label = ttk.Label(header, image=icon_photo) + icon_label.pack(side="left", padx=(0, scaled(16))) + setattr(icon_label, "_image_ref", icon_photo) + + title_frame = ttk.Frame(header) + title_frame.pack(side="left", fill="x", expand=True) + ttk.Label(title_frame, text= "国家中小学智慧教育平台 资源下载工具", style="Title.TLabel").pack(anchor="w") + ttk.Label(title_frame, text=f"版本 {__version__}", style="Caption.TLabel").pack(anchor="w", pady=(scaled(3), 0)) + + # 作者与项目地址 + info_card = make_card(frame, padding=(scaled(16), scaled(12))) + info_card.pack(fill="x", pady=(scaled(18), 0)) + info_card.columnconfigure(1, weight=1) + + ttk.Label(info_card, text="作者", style="AboutCardMuted.TLabel").grid( + row=0, column=0, sticky="nw", padx=(0, scaled(16)) + ) + ttk.Label(info_card, text="肥宅水水呀、晨叶梦春 及其他贡献者", style="AboutCard.TLabel").grid( + row=0, column=1, sticky="nw" + ) + + ttk.Label(info_card, text="仓库", style="AboutCardMuted.TLabel").grid( + row=1, column=0, sticky="nw", padx=(0, scaled(16)), pady=(scaled(8), 0) + ) + project_link = make_link(info_card, "happycola233/tchMaterial-parser", PROJECT_URL, about_window) + project_link.grid(row=1, column=1, sticky="nw", pady=(scaled(8), 0)) + + ttk.Label(info_card, text="许可证", style="AboutCardMuted.TLabel").grid( + row=2, column=0, sticky="nw", padx=(0, scaled(16)), pady=(scaled(8), 0) + ) + license_link = make_link(info_card, "MIT License", LICENSE_URL, about_window) + license_link.grid(row=2, column=1, sticky="nw", pady=(scaled(8), 0)) + + # 第三方资源与许可证说明 + license_card = make_card(frame, padding=(scaled(16), scaled(12))) + license_card.pack(fill="x", pady=(scaled(12), 0)) + ttk.Label(license_card, text="第三方许可证", style="AboutCardStrong.TLabel").pack(anchor="w") + ttk.Label( + license_card, + text="本软件使用了 Microsoft Fluent Emoji 的部分图像资源,依据 MIT 许可证授权使用。\n相关图像资源及其版权归 Microsoft 所有。", + style="AboutCard.TLabel", + justify="left", + wraplength=scaled(600), + ).pack(anchor="w", pady=(scaled(6), 0)) + license_link = make_link(license_card, "查看 Microsoft Fluent Emoji 许可证 ↗", FLUENT_EMOJI_LICENSE_URL, about_window) + license_link.pack(anchor="w", pady=(scaled(8), 0)) + + # 声明 + notice_card = make_card(frame, padding=(scaled(16), scaled(12))) + notice_card.pack(fill="x", pady=(scaled(12), 0)) + ttk.Label(notice_card, text="声明", style="AboutCardStrong.TLabel").pack(anchor="w") + ttk.Label( + notice_card, + text="本软件采用 MIT 许可证开源,官方版本永久免费。\n谨防第三方付费倒卖、捆绑软件或冒充官方等行为;任何再分发行为均须保留原有版权及许可证声明。", + style="AboutCard.TLabel", + justify="left", + wraplength=scaled(600), + ).pack(anchor="w", pady=(scaled(6), 0)) + + button_frame = ttk.Frame(frame) + button_frame.pack(fill="x", pady=(scaled(16), 0)) + ttk.Button( + button_frame, text="关闭", style=theme.ACCENT_BUTTON_STYLE, command=about_window.destroy + ).pack(side="right") + + center_window(about_window, runtime.root) + theme.apply_titlebar_theme(about_window) + about_window.lift() diff --git a/src/tchmaterial_parser/ui/download_panel.py b/src/tchmaterial_parser/ui/download_panel.py index 0e63a82..962123d 100644 --- a/src/tchmaterial_parser/ui/download_panel.py +++ b/src/tchmaterial_parser/ui/download_panel.py @@ -121,7 +121,7 @@ def download_file(url: str, save_path: str, chapters: list[dict] | None = None) ui_call(progress_label.config, text=f"{format_bytes(all_downloaded_size)}/{format_bytes(all_total_size)} ({download_progress:.2f}%) 已下载 {downloaded_number}/{total_number}") # 更新标签以显示当前下载进度 if current_state["total_size"] > 0 and current_state["downloaded_size"] != current_state["total_size"]: # 文件下载不完整 - current_state["failed_reason"] = f"文件下载不完整,需下载 {current_state["total_size"]} 字节,实际下载 {current_state["downloaded_size"]} 字节" + current_state["failed_reason"] = f"文件下载不完整,需下载 {current_state['total_size']} 字节,实际下载 {current_state['downloaded_size']} 字节" current_state["downloaded_size"], current_state["total_size"] = 0, 0 current_state["finished"] = True try: diff --git a/src/tchmaterial_parser/ui/theme.py b/src/tchmaterial_parser/ui/theme.py index c75a757..8155fe6 100644 --- a/src/tchmaterial_parser/ui/theme.py +++ b/src/tchmaterial_parser/ui/theme.py @@ -27,12 +27,17 @@ THEME_COLORS = { ACCENT_BUTTON_STYLE = "Accent.TButton" SWITCH_STYLE = "Switch.TCheckbutton" -# 本程序使用的命名字体,格式为 字体名称: (基准字号(像素), 是否加粗) -APP_FONTS = { "AppCaptionFont": (12, False), "AppBodyFont": (14, False), "AppStrongFont": (14, True), "AppTitleFont": (20, True) } +# 本程序使用的命名字体,格式为 字体名称: (基准字号(像素), 是否加粗, 是否添加下划线) +APP_FONTS = { + "AppCaptionFont": (12, False, False), "AppBodyFont": (14, False, False), "AppStrongFont": (14, True, False), + "AppTitleFont": (20, True, False), "AppLinkFont": (12, False, True) +} # sv-ttk 内置的命名字体,需要一并改为中文字体(其默认字体不含中文字形),基准字号与 sv-ttk 原始取值保持一致 SV_FONTS = { - "SunValleyCaptionFont": (12, False), "SunValleyBodyFont": (14, False), "SunValleyBodyStrongFont": (14, True), "SunValleyBodyLargeFont": (18, False), - "SunValleySubtitleFont": (20, True), "SunValleyTitleFont": (28, True), "SunValleyTitleLargeFont": (40, True), "SunValleyDisplayFont": (68, True), + "SunValleyCaptionFont": (12, False, False), "SunValleyBodyFont": (14, False, False), + "SunValleyBodyStrongFont": (14, True, False), "SunValleyBodyLargeFont": (18, False, False), + "SunValleySubtitleFont": (20, True, False), "SunValleyTitleFont": (28, True, False), + "SunValleyTitleLargeFont": (40, True, False), "SunValleyDisplayFont": (68, True, False), } def bind_font_family(family: str) -> None: # 由 app.py 在选定界面字体后写入,供 setup_fonts() 使用 @@ -57,9 +62,9 @@ def pick_ui_font_family() -> str: # 选择一个合适的字体 def setup_fonts() -> None: # 创建(或更新)所有命名字体,使其使用中文字体并跟随缩放因子 # 此处直接调用 Tcl 命令而不使用 tkinter.font.Font,因为后者创建的字体会随 Python 对象被垃圾回收而一并删除 existing_fonts: tuple[str, ...] = runtime.root.tk.splitlist(runtime.root.tk.call("font", "names")) - for name, (size, bold) in { **APP_FONTS, **SV_FONTS }.items(): + for name, (size, bold, underline) in { **APP_FONTS, **SV_FONTS }.items(): # 字号取负值表示以像素为单位,从而避开 tk scaling 的二次缩放,与 sv-ttk 的取值方式保持一致 - options = ("-family", ui_font_family, "-size", -scaled(size), "-weight", "bold" if bold else "normal") + options = ("-family", ui_font_family, "-size", -scaled(size), "-weight", "bold" if bold else "normal", "-underline", 1 if underline else 0) runtime.root.tk.call("font", "configure" if name in existing_fonts else "create", name, *options) def detect_system_theme() -> Literal["light", "dark"]: # 获取系统当前使用的是浅色还是深色模式 diff --git a/src/tchmaterial_parser/ui/token_window.py b/src/tchmaterial_parser/ui/token_window.py index 5f787ab..2baf642 100644 --- a/src/tchmaterial_parser/ui/token_window.py +++ b/src/tchmaterial_parser/ui/token_window.py @@ -17,6 +17,7 @@ def show_access_token_window() -> None: # 打开输入 Access Token 的窗口 # 让窗口自动根据控件自适应尺寸;如需最小尺寸可用 token_window.minsize(...) token_window.focus() # 自动获得焦点 token_window.grab_set() # 阻止主窗口操作 + token_window.transient(runtime.root) # 使窗口依赖于主窗口 token_window.bind("", lambda event: token_window.destroy()) # 绑定 Esc 键关闭窗口 # 设置一个 Frame 用于留白,使布局更美观 @@ -64,9 +65,8 @@ def show_access_token_window() -> None: # 打开输入 Access Token 的窗口 help_win.title("获取 Access Token 方法") help_win.resizable(False, False) # 禁止调整窗口大小 help_win.focus() # 自动获得焦点 - help_win.grab_set() # 阻止主窗口操作 + help_win.transient(token_window) # 使窗口依赖于主窗口 help_win.bind("", lambda event: help_win.destroy()) # 绑定 Esc 键关闭窗口 - help_win.bind("", lambda event: token_window.grab_set()) help_frame = ttk.Frame(help_win, padding=scaled(20)) help_frame.pack(fill="both", expand=True) diff --git a/tchMaterial-parser.spec b/tchMaterial-parser.spec index 2a22f7a..52dc681 100644 --- a/tchMaterial-parser.spec +++ b/tchMaterial-parser.spec @@ -9,9 +9,10 @@ is_mac = sys.platform.startswith('darwin') # 下面 4 个图标文件是程序运行时读取的自有资源,目标目录保持为 assets/,与源码中的相对路径一致 data_files = collect_data_files('sv_ttk') + [ ('assets/window_icon.png', 'assets'), + ('assets/last_quarter_moon_3d.png', 'assets'), ('assets/sun_3d.png', 'assets'), ('assets/crescent_moon_3d.png', 'assets'), - ('assets/last_quarter_moon_3d.png', 'assets'), + ('assets/information_3d.png', 'assets'), ] a = Analysis(