主要变更: 1. 新增桌面端托盘图标支持深色/浅色主题切换 2. 重构应用锁、动画配置、小组件导航服务职责 3. 修复Riverpod初始化断言、防重复点击、工作台模式残留选中态问题 4. 优化诗词服务、阅读进度、搜索结果空状态体验 5. 完善macOS打包配置与错误静默处理逻辑 6. 新增快速卡片多语言适配与动画退出队列管理
158 lines
5.6 KiB
Python
158 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
闲言APP — 系统托盘图标生成脚本
|
||
创建时间: 2026-06-18
|
||
更新时间: 2026-06-22
|
||
作用: 用 Pillow 生成 macOS/Windows 托盘图标 PNG + ICO(浅色+深色两套)
|
||
设计: 对话气泡 + 三条横线,体现"记录言语"主题
|
||
输出:
|
||
- assets/images/tray_icon_light.png (黑色图标,用于 macOS template + Win 浅色主题)
|
||
- assets/images/tray_icon_dark.png (白色图标,用于 Win 深色主题)
|
||
- assets/images/tray_icon_light_32.png (32x32 高清版)
|
||
- assets/images/tray_icon_dark_32.png (32x32 高清版)
|
||
- windows/runner/resources/tray_icon_light.ico (黑色图标,Win 浅色主题)
|
||
- windows/runner/resources/tray_icon_dark.ico (白色图标,Win 深色/纯黑主题)
|
||
"""
|
||
|
||
import os
|
||
from PIL import Image, ImageDraw
|
||
|
||
# ============================================================
|
||
# 配置
|
||
# ============================================================
|
||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
OUTPUT_DIR = os.path.join(PROJECT_ROOT, 'assets', 'images')
|
||
WIN_ICO_DIR = os.path.join(PROJECT_ROOT, 'windows', 'runner', 'resources')
|
||
SIZES = [16, 32] # 生成 16x16 和 32x32 两种尺寸
|
||
ICO_SIZES = [16, 24, 32, 48, 64] # ICO 文件包含多种尺寸
|
||
|
||
# 颜色定义
|
||
COLOR_LIGHT = (0, 0, 0, 255) # 黑色(浅色主题用)
|
||
COLOR_DARK = (255, 255, 255, 255) # 白色(深色主题用)
|
||
COLOR_TRANSPARENT = (0, 0, 0, 0)
|
||
|
||
|
||
def draw_tray_icon(size: int, color: tuple) -> Image.Image:
|
||
"""绘制托盘图标
|
||
|
||
设计:
|
||
- 对话气泡(圆角矩形)+ 三条横线 + 气泡尾巴
|
||
- 单色设计,简洁现代
|
||
"""
|
||
# 创建透明背景
|
||
img = Image.new('RGBA', (size, size), COLOR_TRANSPARENT)
|
||
draw = ImageDraw.Draw(img)
|
||
|
||
# 缩放因子(基于 16x16 设计)
|
||
scale = size / 16.0
|
||
|
||
def s(v):
|
||
"""缩放坐标"""
|
||
return int(v * scale)
|
||
|
||
# ============================================================
|
||
# 1. 对话气泡主体(圆角矩形)
|
||
# ============================================================
|
||
# 气泡位置:左上 (1, 1) 到右下 (15, 11)
|
||
bubble_x1, bubble_y1 = s(1), s(1)
|
||
bubble_x2, bubble_y2 = s(15), s(11)
|
||
bubble_radius = s(2)
|
||
|
||
# 绘制圆角矩形气泡
|
||
draw.rounded_rectangle(
|
||
[bubble_x1, bubble_y1, bubble_x2, bubble_y2],
|
||
radius=bubble_radius,
|
||
fill=color,
|
||
)
|
||
|
||
# ============================================================
|
||
# 2. 气泡尾巴(左下角,指向说话者)
|
||
# ============================================================
|
||
tail_points = [
|
||
(s(3), s(11)), # 尾巴起点(气泡底部)
|
||
(s(3), s(14)), # 尾巴尖
|
||
(s(6), s(11)), # 尾巴终点(气泡底部)
|
||
]
|
||
draw.polygon(tail_points, fill=color)
|
||
|
||
# ============================================================
|
||
# 3. 气泡内三条横线(代表文字)
|
||
# ============================================================
|
||
line_color = COLOR_TRANSPARENT # 用透明色"挖出"横线
|
||
line_thickness = max(1, s(1))
|
||
|
||
# 三条横线的位置(在气泡内部居中)
|
||
line_x1 = s(3)
|
||
line_x2_short = s(10) # 短线结束
|
||
line_x2_long = s(13) # 长线结束
|
||
|
||
# 第一条横线(短)
|
||
draw.rectangle(
|
||
[line_x1, s(4), line_x2_short, s(4) + line_thickness - 1],
|
||
fill=line_color,
|
||
)
|
||
# 第二条横线(长)
|
||
draw.rectangle(
|
||
[line_x1, s(6), line_x2_long, s(6) + line_thickness - 1],
|
||
fill=line_color,
|
||
)
|
||
# 第三条横线(中)
|
||
draw.rectangle(
|
||
[line_x1, s(8), s(11), s(8) + line_thickness - 1],
|
||
fill=line_color,
|
||
)
|
||
|
||
return img
|
||
|
||
|
||
def main():
|
||
"""主函数:生成所有图标"""
|
||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||
os.makedirs(WIN_ICO_DIR, exist_ok=True)
|
||
|
||
for size in SIZES:
|
||
# 浅色版(黑色图标)
|
||
light_img = draw_tray_icon(size, COLOR_LIGHT)
|
||
light_name = f'tray_icon_light{"" if size == 16 else f"_{size}"}.png'
|
||
light_path = os.path.join(OUTPUT_DIR, light_name)
|
||
light_img.save(light_path, 'PNG')
|
||
print(f'✅ 生成: {light_path} ({size}x{size})')
|
||
|
||
# 深色版(白色图标)
|
||
dark_img = draw_tray_icon(size, COLOR_DARK)
|
||
dark_name = f'tray_icon_dark{"" if size == 16 else f"_{size}"}.png'
|
||
dark_path = os.path.join(OUTPUT_DIR, dark_name)
|
||
dark_img.save(dark_path, 'PNG')
|
||
print(f'✅ 生成: {dark_path} ({size}x{size})')
|
||
|
||
# ============================================================
|
||
# 生成 Windows ICO 文件(包含多尺寸)
|
||
# ============================================================
|
||
# 浅色 ICO(黑色图标,白天模式用)
|
||
light_ico_images = [draw_tray_icon(s, COLOR_LIGHT) for s in ICO_SIZES]
|
||
light_ico_path = os.path.join(WIN_ICO_DIR, 'tray_icon_light.ico')
|
||
light_ico_images[0].save(
|
||
light_ico_path,
|
||
format='ICO',
|
||
sizes=[(s, s) for s in ICO_SIZES],
|
||
append_images=light_ico_images[1:],
|
||
)
|
||
print(f'✅ 生成: {light_ico_path} (ICO 多尺寸)')
|
||
|
||
# 深色 ICO(白色图标,深色/纯黑模式用)
|
||
dark_ico_images = [draw_tray_icon(s, COLOR_DARK) for s in ICO_SIZES]
|
||
dark_ico_path = os.path.join(WIN_ICO_DIR, 'tray_icon_dark.ico')
|
||
dark_ico_images[0].save(
|
||
dark_ico_path,
|
||
format='ICO',
|
||
sizes=[(s, s) for s in ICO_SIZES],
|
||
append_images=dark_ico_images[1:],
|
||
)
|
||
print(f'✅ 生成: {dark_ico_path} (ICO 多尺寸)')
|
||
|
||
print('\n🎉 所有托盘图标生成完成!')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|