* 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>
外部皮肤 manifest(TOML)
每个皮肤目录需要 skin.toml,id 必须与文件夹名一致。schema_version 支持 1 与 2:v2 是面向全贴图与复杂立绘皮肤的规范,必须以 canvas 为 base,由 Direct2D 候选窗完整绘制(九宫格面板、上下两层挂件、动图)。
候选框和悬浮工具栏都由 skin.toml 声明,不要再提供 cand.css 或工具栏 CSS。所有图片路径都相对于皮肤目录,且不能指向目录之外。
顶层字段
- 必填:
schema_version(1或2)、id、name、version、base(继承的内置皮肤:fluent、wechat、graphite、willow_green、autumn_osmanthus、microsoft;或与 msime 共享的基底:system、shuishan、light、paper、night、ink,按fluent的结构绘制并补上基底配色,固定明暗的基底只在对应的themes下生效;或全贴图基底canvas,只用于 v2) schema_version与base的搭配:schema_version = 2必须写base = "canvas"。写了 2 而base是别的基底时,皮肤仍可使用,但按schema_version = 1加载,下面标「v2」的表和键全部忽略(连同其中的错误),设置页的诊断信息里会多一条说明。schema_version = 1不能用canvas:没有面板贴图它只是一张透明卡片,这种皮肤不会出现在可选列表里。
- 可选:
author、description [supports]:layouts(horizontal/vertical)、themes(dark/light)[license]:code、assets
[candidate_window]
min_width_dip:卡片最小宽度;实际最小宽度还会被装饰图的width_dip撑大corner_radius_dip:卡片圆角,0–32;不写则沿用baseborder_width_dip:卡片外框线宽,0–4;颜色取border,不写border时取base的边框色(杨柳青、秋桂没有外框,此时仍不可见)item_corner_radius_dip:选中 / 悬停高亮的圆角,0–16;贴着卡片四角的那几个角仍跟corner_radius_dipshadow:卡片阴影,none|soft|strong;不写则沿用base。只影响候选卡片,不影响右键菜单backdrop:卡片背后的系统材质,none|acrylic|mica;不写等于none。acrylic模糊卡片后面的窗口;mica模糊桌面壁纸,只透出壁纸色调(需要 Windows 11,更早的系统按acrylic画)。材质按 Fluent 配方叠「模糊背景 → 亮度层 → 色调」,色调颜色取surface,两个不透明度见下面配色表里的backdrop_opacity/backdrop_luminosity。系统关闭「透明效果」或不支持(低于 Windows 10 1903)时,surface按不透明画。写了border_image时不生效font_family:候选字体名,UTF-8 编码后最长 64 字节(约 21 个汉字),不能含引号、反斜杠、逗号、分号、尖括号、花括号和反引号。排在用户设置的字体前面,缺字时仍回落到用户字体;字号不由皮肤决定page_arrows:候选框里的翻页箭头,true|false。横排时是候选右侧贴着最后一行的一小列,竖排时是最后一行下方的一矮行、“‹”与序号左对齐;颜色取number,悬停底色取hover。不写则沿用base的默认皮肤设置(数据目录skins\default\<base>\skin.toml,出厂为false)fit_content:卡片是否严格按候选文字所需的宽度收紧,true|false。打开后不再垫出厂的最小宽度(160 DIP),同一次输入里后到的翻译等内容消失时宽度也立刻缩回;min_width_dip和装饰图width_dip仍然生效。不写则沿用base的默认皮肤设置(出厂为false)
[candidate_window.decoration](可选)
卡片上方的装饰图。写了这张表就必须同时给出 image、top_inset_dip、width_dip。
image:图片路径top_inset_dip/width_dip:装饰框的高和宽,框贴在卡片上方,不与卡片重叠;图片在框内等比缩放align:left|center|right,相对卡片
装饰图上还可以叠加一个候选词(比如把首选词写在立绘的对话框里)。candidate_overlay = true 时下面这组键才生效,都可省略:
candidate_overlay:true|false,默认falsecandidate_overlay_index:叠加哪个候选,0–100的整数。0跟随高亮,1是当前页第一个,依此类推,默认1。当前页没有这一个时不画candidate_overlay_font_size_dip:字号,大于0、不超过200,默认16。字体与候选相同candidate_overlay_max_width_dip:词最多占多宽,0–2000,默认0即装饰框的宽度。超出时按比例缩小字号candidate_overlay_min_font_size_dip:缩字号的下限,大于0、不超过200,默认8。缩到下限仍放不下时就让它超出,不折行candidate_overlay_color:文字颜色,写法见颜色格式,默认白色candidate_overlay_align_h:left|center|right;candidate_overlay_align_v:top|center|bottom。相对装饰框对齐,默认都是centercandidate_overlay_offset_x_dip/candidate_overlay_offset_y_dip:对齐之后再平移,-2000–2000,正数向右、向下candidate_overlay_strip_badge:true时只画词本身,不带候选后面的标注(如整句模型名),默认true
[candidate_window.background](可选)
卡片背景图,画在底色之上、文字之下,按圆角裁剪,对深浅两套主题都生效。
image:图片路径fit:cover(铺满裁切)|contain(完整显示)|stretch(拉伸)opacity:0–1
canvas 基底(v2)
全贴图皮肤的地基:面板透明、没有描边、直角、默认没有阴影,候选框长什么样完全由下面的九宫格面板图和挂件决定。它只能作为 base,不能在设置页单独选用。
- 文字与高亮用不依赖底色的中性色:深色主题近白文字(
#f2f2f2)、白色半透明的选中 / 悬停底,浅色主题近黑文字(#1a1a1a)、黑色半透明的选中 / 悬停底;高亮圆角 4,可用item_corner_radius_dip改。贴图偏深或偏浅时用[candidate.dark]/[candidate.light]改配色。 - v1 的键照样可用:配色、
font_family、page_arrows、fit_content、min_width_dip、corner_radius_dip、border_width_dip、shadow(写soft/strong才有阴影)、[candidate_window.decoration]、[candidate_window.background]、[toolbar]。写了border_image时,卡片的底色、描边、圆角、阴影和background背景图都让给面板图。 - 右键菜单没有贴图,沿用
fluent的样式,可用[candidate.*.menu]改;悬浮工具栏同样沿用fluent,可用[toolbar]改配色,或用图片模式整个换成贴图。翻页箭头、fit_content的出厂值也取fluent的默认皮肤设置。
[candidate_window.border_image](v2 可选)
九宫格面板图,语义同 CSS border-image(border-image-repeat: stretch):四角按边宽缩放,四边沿长度方向拉伸,中心双向拉伸。
source:图片路径,.png、.apng、.gif或.webplight_source:可选,浅色主题换用的面板图,格式要求同source。切片、边宽和fill与source共用,所以两张图要按同一套几何画;不写时深浅主题都用sourceslice_dip:切片线位置,按源图像素计(同 CSSborder-image-slice的数值,键名里的_dip是历史写法),1、2 或 4 个0–500的数,按 CSS 简写展开成上右下左(1 个值四边相同,2 个值是「上下 左右」)stretch_dip:可选,同为源图像素,给出时代替slice_dip。面板图里画死的装饰必须落在切片之内(可拉伸区之外),否则会被拉长;例如帆船画在右侧,就把右侧切片放到船身左侧width_dip:可选,四边在屏幕上画多宽,按 DIP 计(同 CSSborder-image-width),写法同上。不写时取切片的数值当 DIP。给 2 倍图时写成切片的一半,200% 缩放下逐像素清晰、100% 下是缩小一半的同一张图。两条对边加起来比面板还宽时四边按同一比例缩小fill:可选,布尔值,是否画中心块,默认true;false时中心留空、透出桌面
面板图画在卡片的底色之上、文字之下,不按圆角裁剪(同 CSS)。动图面板也会播放,但面板一般建议用静态图,把动画留给挂件。
[candidate_window.content_insets_dip](v2 可选)
候选框内容与文字的安全内边距,从面板外缘量起,用来避开贴图四周自带的花纹或立绘。某一边小于九宫格该边的边宽(width_dip)时按边宽算,文字不会压在边框上(同 CSS 的 border + padding)。不写时是边宽再加 5 DIP。
top/right/bottom/left:四边内边距,top/bottom0–200,left/right0–500
[candidate_window] 的 line_gap_dip(v2 可选)
预编辑行与候选之间的距离,0–100。只影响这一处,竖排时候选与翻页箭头之间的间距不变。不写则沿用 base 的行距。
[candidate_window.outer_insets_dip](v2 可选)
挂件挂在卡片外沿时,窗口需要额外预留的透明空白,从卡片四边往外量。
top/right/bottom/left:四边预留量浮点数
不声明时窗口只按卡片大小开,挂到卡片外面的立绘会被窗口边界裁掉。
[[candidate_window.decorations]](v2 可选,数组表)
自由锚定的立绘挂件,可以叠加角色立绘与动图,最多 32 层。
image:图片路径,.png、.apng、.gif或.webpanchor:挂件框贴着面板的哪个位置:top-left|top-right|bottom-left|bottom-right|left|right|top|bottom|center,默认rightwidth_dip/height_dip:挂件框的尺寸,0–2000(不含 0);图片在框里等比缩放、居中(同 CSSbackground-size: contain)offset_x_dip/offset_y_dip:相对锚点的平移量,-2000–2000,正数向右、向下z_index:层级,-1000–1000的整数,默认2。面板自身相当于1:z_index < 1的挂件画在面板之下(只有伸出面板的部分看得见),其余画在面板与文字之上。同层按z_index从小到大、相同时按书写顺序叠放
挂件不参与布局,也不接收点击;伸到面板外面时要用 outer_insets_dip 给窗口留出空白。
动图(v2)
v2 皮肤里的图片可以是动图:APNG(按 APNG 规范 解码)、GIF,以及系统 WebP 解码器能逐帧读出的动画 WebP(每帧 100 ms)。
- 帧时长与浏览器一致:
delay_den为 0 按 100 算,10 ms 及以下按 100 ms;APNG 的num_plays、GIF 的 NETSCAPE 循环次数生效,播完停在最后一帧。 - 动画从候选窗出现时开始,同一次显示里打字不会让它重头开始;候选窗隐藏或换皮肤后,下一次显示从第一帧开始。
- 系统关闭了动画效果(设置 → 辅助功能 → 视觉效果 → 动画效果)时显示静态图:APNG 显示它的默认图(通常就是第一帧),GIF 显示第一帧。
- 一张动图超过 1024 帧或解码后超过 64 MB、文件损坏或不合规范时只显示第一帧(静态图)。所有动图的帧合计受 64 MB 显存预算约束。
- v1 皮肤里的图片始终是静态首帧,与以往一致。
[candidate.dark] / [candidate.light]
accent、selected、hover、surface、border、text、number、translation、show_selected_bar
candidate_window.backdrop 不是 none 时还可以写两个 0–1 的数,深浅主题各自设置:
backdrop_opacity:surface色调盖在材质上的不透明度,越高越接近纯色。不写时取surface自带的 alphabackdrop_luminosity:亮度层的不透明度。越高,卡片明暗越不受后面画面影响,亮背景上的浅色字也能保住对比度;调低更通透,对比度跟着后面走。不写时acrylic取0.85、mica取1
Windows 11 的默认值可以作参考:亚克力深色 #2c2c2c / 0.15 / 0.96、浅色 #fcfcfc / 0 / 0.85;云母深色 #202020 / 0.8 / 1、浅色 #f3f3f3 / 0.5 / 1(依次是 surface、backdrop_opacity、backdrop_luminosity)。
细分配色,都可省略,不写时按右列回落:
| 键 | 作用 | 不写时 |
|---|---|---|
candidate_text |
普通候选的文字 | text |
preedit_text |
预编辑(拼音)文字 | text |
preedit_caret |
预编辑光标 | accent |
selected_text |
选中候选的文字 | base 的选中行文字色 |
selected_number |
选中候选的序号 | base 的选中行序号色 |
translation_pos |
翻译里的词性(n.、adj.、proper n. 等) |
与翻译同色 |
translation_separator |
翻译里义项之间的分隔符(;、;) |
与翻译同色 |
selected_translation |
选中候选的翻译 | base 的选中行翻译色(微信、杨柳青、秋桂铺实色高亮,各有一组);base 没有时用 translation,再不写则取选中文字色的 62% 不透明度 |
selected_translation_pos |
选中候选翻译里的词性 | 写了 selected_translation 时与它同色;否则 base 的选中行词性色,再回落到 translation_pos |
selected_translation_separator |
选中候选翻译里的分隔符 | 写了 selected_translation 时与它同色;否则 base 的选中行分隔符色,再回落到 translation_separator |
selected_bar |
选中项左侧竖条 | accent |
preedit_background |
预编辑行底色,圆角同高亮圆角 | 透明 |
preedit_divider |
预编辑行下方 1px 分隔线 | 无 |
设置页里的「候选文字颜色」优先于 text、candidate_text 和 preedit_text。
内置皮肤的翻译用 Notion 调色板:单词分别是 fluent 紫、微信蓝、杨柳青绿、石墨棕、微软粉,秋桂浅色橙、深色黄;词性换成相邻的另一个 Notion 色(紫配粉、蓝配绿、绿配蓝、棕配橙、橙配红、黄配橙、粉配紫)再加 80 透明度调淡,与单词区分开;分隔符是单词色加 73 透明度。包写了 translation 而没写 translation_pos / translation_separator 时,base 的词性和分隔符配色一并作废,跟随新的翻译色。想要同样的效果,词性取另一个色相、在色值后补两位十六进制透明度即可:
translation = "#529cca"
translation_pos = "#4dab9a80"
translation_separator = "#529cca73"
[candidate.dark.menu] / [candidate.light.menu](可选)
候选窗右键菜单:background、border、text、hover。
[toolbar]
悬浮工具栏样式。所有键都可省略,不写的沿用 base。
corner_radius_dip:0–32[toolbar.dark]/[toolbar.light]:background、border、handle(左侧拖动条)、divider、icon、hover。handle不写时取同一主题的[candidate.*].accent,与选中竖条、预编辑光标同色;accent也没写才沿用base。写的话也请与accent一致mode:bar(默认,矩形条)|image(图片模式,见下)
图片模式(mode = "image")
工具栏整个画成一张图,按钮按坐标摆在图上,类似搜狗的状态栏皮肤。不画底色、描边、圆角、阴影、拖动条和分隔线;窗口按图片的透明区域裁成异形,完全透明的地方点击会落到后面的窗口上。按钮框总是算在窗口里,所以按钮也可以摆在背景图透明的地方(比如人物旁边的空白处),不必在背景图里给它垫底。图上除按钮以外的地方都能按住拖动。
[toolbar]
mode = "image"
image = "assets/toolbar.png"
image_width_dip = 160
image_height_dip = 40
[toolbar.buttons.status]
x_dip = 10
y_dip = 7
width_dip = 26
height_dip = 26
[toolbar.buttons.status.images]
cn = "assets/cn.png"
en = "assets/en.png"
[toolbar.buttons.settings]
x_dip = 124
y_dip = 7
image = "assets/settings.png"
image:背景图,.png、.apng、.gif或.webpimage_width_dip/image_height_dip:背景图的显示尺寸,大于0、不超过2000。窗口就开这么大,图片拉伸到这个尺寸;给 2 倍图时写成像素的一半[toolbar.buttons.<按钮>]:每个按钮一张表,按钮名见下表,所有键都可省略x_dip/y_dip:按钮框左上角相对背景图左上角的位置,0–2000。两个都写了按钮才会出现,没写坐标的按钮不画(设置仍可从托盘菜单打开)width_dip/height_dip:按钮框的宽高,大于0、不超过500,默认26icon_size_dip:没有贴图时内置图标(Segoe Fluent Icons)的字号,大于0、不超过500。不写时是按钮框短边的0.82倍;按钮框画在一块圆底之类的图案里、又想让图标比框大或小时单独设它visible:false时把这个按钮藏起来。皮肤只能藏,用户在设置页关掉的按钮写true也不会出现image:按钮图,在按钮框里等比缩放;各个状态共用,images里缺某个状态时也用它[toolbar.buttons.<按钮>.images]:状态名 → 按钮图,只有有状态的按钮能写- 既没有
image也没有对应状态的图时,画内置的矢量图标,颜色取[toolbar.*].icon。鼠标悬停和按下时按钮上盖一层[toolbar.*].hover
| 按钮 | 作用 | 状态名 |
|---|---|---|
status |
中 / 英切换 | cn(中文)、en(英文)、ja(日文)、caps(大写锁定)、en-candidate(英文候选输入) |
fullwidth |
全 / 半角 | full、half |
punctuation |
中 / 英标点 | cn、en |
character_set |
简 / 繁 | simplified、traditional |
emoji |
表情面板 | 无 |
screen_keyboard |
屏幕键盘 | 无 |
settings |
设置 | 无 |
- 设置页「悬浮工具栏」里的缩放会同时放大背景图、按钮位置和尺寸。
- 用户在设置页关掉某个按钮后,图上它的位置就空着,背景图不会因此变短。
- 动图只在
schema_version = 2的皮肤里播放,规则同动图;工具栏隐藏或自动隐藏淡出后停止播放。窗口形状按所有帧合起来的轮廓裁,任何一帧画到的地方都不会被裁掉,也都能点到、能拖动。 - 未知的按钮名、状态名,以及不在
mode = "image"时写的image/buttons,不会让皮肤读不出来,只在设置页的诊断信息里提示一条。
颜色格式
所有颜色键——[candidate.dark] / [candidate.light] 里除 show_selected_bar 外的键、[candidate.*.menu] 和 [toolbar.dark] / [toolbar.light] 的全部键——都接受以下写法。数字、布尔、枚举和路径类的键不是颜色,不能写成颜色值。
| 写法 | 示例 | 说明 |
|---|---|---|
#rgb / #rrggbb |
#e8a、#e08aa8 |
不透明 |
#rgba / #rrggbbaa |
#e8a4、#e08aa847 |
末尾一位或两位是不透明度 |
rgb(r, g, b) |
rgb(224, 138, 168) |
r、g、b 为 0–255 |
rgba(r, g, b, a) |
rgba(224, 138, 168, 0.28) |
a 为 0–1 |
transparent |
transparent |
全透明,等同 #0000 |
- 十六进制大小写均可,但必须带
#:这是清单格式的约定,与 CSS 写法一致;渲染器对不带#的写法有容错,但不要依赖它。 - 不透明度在末尾(
#RRGGBBAA),不是 Windows 常见的#AARRGGBB。例如#e9e8e89d约为 62% 不透明,rgba(224, 138, 168, 0.28)与#e08aa847等价。