Files
一把伞南星andfanlusky 71960b8301 feat(server,ui): 悬浮工具栏图片模式与装饰图叠加候选词 (#1046)
* feat(server,ui): 悬浮工具栏图片模式与装饰图叠加候选词

在 #1046 的基础上按最新 develop 重做。

悬浮工具栏 [toolbar] mode = "image":一整张背景图,按钮按
[toolbar.buttons.<按钮>] 的 x_dip / y_dip / width_dip / height_dip 摆放,
可按状态换图([images]),没图时画矢量图标。窗口按背景图的透明区域裁成
异形,透明处点得穿;图上除按钮外都能拖动。v2 皮肤的动图会播放,工具栏
隐藏或淡出后停帧。

候选窗装饰图可用 candidate_overlay_* 叠加一个候选词,太宽时缩字号;
设置页的皮肤预览一并显示。

ui 新增 OverlayPanel(绝对定位)与 ImageMask(按 alpha 生成区域)。

* fix(ui): 动图工具栏的窗口形状取所有帧的并集

窗口区域连画面一起裁,只按首帧裁形状时,动画里伸出首帧轮廓的部分
(抬头、飞出的纸)会被裁掉。改为把所有帧的 alpha 合起来再生成区域。

* fix(server): 工具栏按钮框始终算在异形窗口里

窗口形状只取背景图不透明的部分时,摆在图片透明处的按钮会被一起裁掉、
点不到。形状改为背景图轮廓并上每个按钮的框,按钮可以放在人物旁边的空白处,
不必在背景图里垫底。

* feat(server): 工具栏按钮可单独设内置图标的字号

[toolbar.buttons.<按钮>] 新增 icon_size_dip,内置 Segoe Fluent Icons 图标的
字号不再只能跟着按钮框走:按钮框铺满背景图里的圆底、图标单独调大小。
不写时仍是按钮框短边的 0.82 倍。

* feat(server): 设置页按图片模式预览悬浮工具栏

皮肤页的卡片与悬浮工具栏页的预览原来只会画矩形条。皮肤是图片模式时,
改按工具栏的画法预览:背景图(v2 皮肤的动图会播放)、按皮肤坐标摆放的
按钮、按钮贴图或内置图标(含 icon_size_dip),设置页关掉的按钮不画,
缩放与图标尺寸设置一并生效。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-20 23:08:01 +08:00
..

Metasequoia IME skin example

水杉输入法(Metasequoia IME)外部皮肤的官方样例。一个皮肤就是一个文件夹,外观全部写在一份 skin.toml 里:候选框的装饰图、背景图、圆角和配色,以及悬浮工具栏的配色。不需要写 CSS,也不会加载任何脚本。

更多现成皮肤见仓库根目录的 skins/。

快速开始

  1. 复制 skins/niya-demo/,把文件夹名和 skin.toml 里的 id 改成同一个新名字。
  2. 替换 assets/ 里的图片:
    • character.png:卡片上方的人物装饰,建议透明 PNG。
    • background.png:卡片背景纹理。深浅两套主题共用这张图,建议大部分透明。
  3. 修改 skin.toml 中的名称、作者、尺寸和配色。
  4. 用浏览器打开 preview.html,大致确认横向、纵向两种候选框的效果。
  5. 把皮肤文件夹复制到 %LOCALAPPDATA%\metasequoiaime\skins\,打开设置里的“皮肤”页面(会自动扫描),或者点“刷新皮肤”。

也可以直接运行安装脚本装上样例皮肤:

./install.ps1            # 复制到 %LOCALAPPDATA%\metasequoiaime\skins\niya-demo\ 并启用
./install.ps1 -NoActivate  # 只复制,不改 config.toml

启用即把 config.toml 中的 appearance.candidate_skin 设为 niya-demo。装好后重启水杉输入法服务即可生效。

设置页会读取 skins\ 下每个一级子目录中的 skin.toml。目录名与 id 不一致或字段不合法时,该皮肤不会出现在可选列表里,原因会显示在页面底部的诊断信息中。

目录结构

examples/skin-examples/
├─ skins/
│  ├─ niya-demo/              # 一个皮肤一个文件夹,文件夹名 = id
│  │  ├─ skin.toml            # 元信息、候选框、工具栏、授权
│  │  └─ assets/
│  │     ├─ character.png     # 卡片上方的装饰图
│  │     └─ background.png    # 卡片背景图
│  └─ sakura-night/           # v2 实机演示「夜樱」:面板后的月亮、APNG 花瓣、GIF 星星
│     ├─ skin.toml
│     └─ assets/
│        ├─ panel.png         # 九宫格面板(2 倍图,四角小樱花不随拉伸变形)
│        ├─ moon.png          # 面板下层挂件
│        ├─ petals.apng       # 面板上层的循环 APNG 挂件
│        └─ stars.gif         # 面板上层的循环 GIF 挂件
├─ schema/                    # manifest 字段说明
├─ tools/                     # 生成样例图片的脚本(需要 Pillow)
├─ tests/                     # CI 用的 manifest 与安装脚本测试
├─ preview.html               # 本地预览
└─ install.ps1                # 样例安装脚本

只有 skins/<id>/ 会被安装,preview.html、schema/、tools/、tests/ 和安装脚本都只是制作工具。

样例皮肤能演示什么

niya-demo 的 skin.toml 每一段都带注释,下面是最常改的几处:

[candidate_window]
min_width_dip = 176
corner_radius_dip = 12      # 0–32,不写则沿用 base
item_corner_radius_dip = 6  # 高亮圆角,0–16
shadow = "soft"             # none | soft | strong

[candidate_window.decoration]
image = "assets/character.png"
top_inset_dip = 88          # 装饰框高度
width_dip = 102             # 装饰框宽度
align = "right"             # left | center | right

[candidate_window.background]
image = "assets/background.png"
fit = "cover"               # cover | contain | stretch
opacity = 0.3               # 0–1

[candidate.dark]
accent = "#e08aa8"          # 光标与选中胶囊条
selected = "rgba(224, 138, 168, 0.28)"
hover = "rgba(224, 138, 168, 0.16)"
preedit_text = "#f0b8cb"     # 细分配色都可省略,见 schema/README.md
preedit_divider = "rgba(224, 138, 168, 0.30)"

[candidate.dark.menu]         # 右键菜单
background = "#221a1e"
hover = "rgba(224, 138, 168, 0.20)"

[toolbar]
corner_radius_dip = 8

[toolbar.dark]
background = "#221a1e"
handle = "#e08aa8"
icon = "#f6e4eb"

几个要点:

  • 装饰图放在一个 width_dip × top_inset_dip 的框里等比缩放,框贴在卡片正上方,不会与卡片重叠。框的比例和原图一致时就不会留白,比如样例原图是 408×353,所以用了 102×88。
  • 卡片实际最小宽度取 min_width_dip 和装饰图 width_dip 中较大的那个。
  • 背景图画在底色之上、文字之下,并按圆角裁剪。
  • 细分配色(普通候选文字、预编辑文字与光标、选中行的文字 / 序号 / 翻译 / 竖条、预编辑底色与分隔线)不写时都回落到 text 或 accent,完整的回落规则见 schema/README.md。设置页里的「候选文字颜色」仍优先于皮肤的文字色。
  • 所有颜色键都可以写十六进制 #rgb / #rgba / #rrggbb / #rrggbbaa,也可以写 rgb() / rgba() 或 transparent。十六进制必须带 #,不透明度在末尾(#RRGGBBAA),详见 schema/README.md。
  • font_family 排在用户设置的字体前面,缺字时回落到用户字体;字号始终由用户设置决定。
  • page_arrows = true 可以打开候选框里的翻页箭头;不写时沿用 base 内置皮肤的设置(出厂为关闭)。
  • fit_content = true 让卡片严格按候选文字的宽度收紧,不再垫出厂最小宽度;min_width_dip 和装饰图宽度仍然生效。不写时沿用 base 内置皮肤的设置(出厂为关闭)。
  • [toolbar] 下的所有键都可以省略,没写的沿用 base 皮肤;只有拖动条 handle 例外,没写时跟同一主题的 accent。
  • [supports].themes 里声明的每套主题都要有对应的 [candidate.<theme>];写了 [toolbar] 的话,也要有对应的 [toolbar.<theme>]。

完整字段见 schema/README.md。

全贴图皮肤(schema_version 2)

sakura-night 和随安装包分发的 绿叶(原 canvas-demo)都是 v2 皮肤:schema_version = 2、base = "canvas",候选框的外观全部来自贴图。下面的片段取自绿叶。

schema_version = 2
base = "canvas"                  # v2 必须以 canvas 为 base

[candidate_window.border_image]
source = "assets/panel.png"      # 64×64 的 2 倍图
light_source = "assets/panel-light.png" # 可选:浅色主题换这张,几何与 source 相同
slice_dip = [20]                 # 切片线,按源图像素
width_dip = [10]                 # 边框绘制宽度,按 DIP:2 倍图写切片的一半,200% 缩放下逐像素清晰

[[candidate_window.decorations]]
image = "assets/sparkle.apng"    # APNG / GIF 动图会播放
anchor = "top-right"
offset_x_dip = 10
offset_y_dip = -12
width_dip = 24
height_dip = 24
z_index = 2                      # z_index < 1 画在面板之下,其余画在面板之上
  • canvas 本身透明、无描边、无圆角、无阴影,不能单独选用;面板图、挂件和配色都由皮肤给出。
  • 面板四角画死的花纹要落在切片之内,否则会被拉伸。中心块可以用 fill = false 留空。
  • 文字离面板外缘的距离用 content_insets_dip,挂件伸出卡片时用 outer_insets_dip 给窗口留出空白。
  • 动图在系统关闭「动画效果」时只显示第一帧。动图解码后合计不超过 64 MB、最多 1024 帧,超出时只显示第一帧。
  • 绿叶和夜樱的图都是程序生成的几何图形,以 MIT 授权,可以直接改用;绿叶的浅色面板由 tools/gen_green_leaf_panel.py 生成。

sakura-night(夜樱)是一个更完整的实机演示,把 v2 的能力都用上了:

  • 紫夜玻璃九宫格面板,四角的小樱花落在 24 像素切片内,候选框怎么伸缩都不变形。
  • 月亮挂在右上角、z_index = -1 画在面板之后,只露出伸出面板的那一截。
  • 右侧飘落的花瓣是 24 帧 × 70 ms 的循环 APNG,左下闪烁的星星是 8 帧 × 150 ms 的循环 GIF。
  • 用 content_insets_dip 让文字避开边框和四角花纹,用 outer_insets_dip 给伸出去的挂件留出窗口空间,用 line_gap_dip 拉开预编辑行与候选。
  • 左上角刻意不放挂件:预编辑文字从那里开始,挂件压上去会挡字。

它的图片都由 tools/gen_sakura_night.py 生成(需要 Pillow),重新运行会得到同样的文件:

python tools/gen_sakura_night.py skins/sakura-night

制作约束

  • 只用 skin.toml 和本地图片,不支持自定义 JavaScript,也不再读取 cand.css 或工具栏 CSS。
  • 图片路径相对于皮肤目录,不能指向目录之外。
  • id 只能包含小写字母、数字、点、下划线和连字符,最长 64 个字符。
  • 单张图片建议不超过 500 KB(CI 会检查 PNG、APNG、GIF 和 WebP)。

测试

python -m unittest discover -s tests -v   # 检查 manifest 与引用的图片
./tests/install.ps1                        # 在临时目录里测试安装、启用和重复安装

授权提示

本目录采用 MIT 授权(见 LICENSE),不随仓库其余部分的 GPL-3.0。这个目录就是拿来复制的:把它整个拷走,或者把 skins/niya-demo/ 整个拷走改成自己的皮肤,都不需要额外授权。之所以用 MIT 而不是产品代码的 GPL-3.0,就是为了让你做出来的皮肤可以按自己的意愿授权。

皮肤代码和图片资源可以用不同的许可,发布前请在 skin.toml 的 [license] 里分别写明 code 和 assets。样例中的 assets 标为 UNVERIFIED-DEMO-ONLY,意思是这些图片只用于演示、来源未经核实,请不要直接拿去发布,换成你有权使用的图片。