Files
Lu Fan(SonnyCalcr) cc868151a8 feat: 新增 canvas 基底,D2D 候选窗支持 v2 九宫格、挂件分层与 APNG/GIF 动图 (#1014)
* feat(ui): 新增 APNG/GIF 动图解码与动画帧时钟

AnimatedImage 按 APNG 规范解析 acTL/fcTL/fdAT 并校验序号、CRC 与帧边界,逐帧重建 PNG 交给 WIC 解码后按 dispose_op/blend_op 合成;GIF 走 WIC 多帧解码。延时规则与浏览器一致,超限或损坏的动画退回静态首帧。

DeviceResources::GetImageFrame 按动画时钟取帧并汇报下一次换帧时刻,动图帧按 64 MB 预算缓存;Scene 与 Window 据此按需重绘,可整体关闭动画。DrawNineSlice 按 CSS border-image 语义区分源切片与目标边宽、支持 fill,并用夹取画刷消除接缝渗色。

* feat(server): 新增 canvas 基础皮肤并要求 v2 皮肤基于它

canvas 是全贴图基底:透明面板、无描边、无圆角、无阴影,外观来自九宫格面板图与挂件。它只能作为外部皮肤的 base,不进内置皮肤列表;v1 皮肤用它直接报错。

schema_version = 2 而 base 不是 canvas 的皮肤按 v1 加载、忽略全部 v2 字段,并在扫描结果里记一条问题。v2 字段补齐校验:图片限 .png/.apng/.gif/.webp,fill 必须是布尔,偏移与 z_index 有范围,挂件最多 32 层。

* feat(server): D2D 候选窗补齐 v2 九宫格、挂件分层与动图

canvas 皮肤在 D2D 候选窗里按 CSS border-image 语义绘制九宫格面板:切片以源图像素计、边宽以 DIP 计、fill = false 不画中心;有面板图时卡片的底色、描边、圆角与阴影让给贴图。content_insets_dip 从面板外缘量起且不小于边框宽度,line_gap_dip 只作用于预编辑行与候选之间。

挂件拆成卡片前后两层:z_index < 1 在面板之下,其余在面板之上,同层按 z_index 与声明顺序稳定排序;图片在框内等比居中。动图按场景给出的下一次换帧时刻排一次性定时器续帧,只在候选窗显示时运行,隐藏、换皮肤时停;系统关闭动画效果时只显示首帧,v1 皮肤保持静态。

设置页原生预览认得 canvas 基底并绘制面板图。

* feat(examples): 新增基于 canvas 的 v2 全贴图皮肤样例

canvas-demo 用 2 倍九宫格面板、面板下层的叶子挂件和面板上层循环播放的 APNG 星光演示 v2 皮肤,素材均为程序生成的几何图形,随目录以 MIT 授权。样例测试同步校验 schema 2 与 canvas 互为前提、v2 图片扩展名与数值范围。

* docs(examples): 说明 canvas 基底、v2 九宫格与挂件分层及动图规则

schema 说明去掉「仅 WebView2」,写明 v2 必须以 canvas 为 base、canvas 只能当 base;九宫格切片按源图像素、边宽按 DIP,content_insets_dip 不小于边宽,line_gap_dip 只管预编辑行与候选之间;挂件以 z_index 1 为界分在面板上下两层;动图格式、帧时长、上限与系统减少动画时的行为。server/docs/ui-backends.md 与 skins/README.md 同步更新。

* docs(server): 候选窗与皮肤目录的注释不再引用已删除的 WebView2 页面
2026-10-17 02:57:33 +08:00

64 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UI ownership
| Surface | Renderer | Owner |
|---|---|---|
| Candidate, floating toolbar, tray menu | msimeui native Direct2D/DirectWrite | `src/window/` presenters in the Server process |
| Settings | msimeui native Direct2D/DirectWrite | `src/settings/native/` in the separate `MetasequoiaImeSettings.exe` |
| Keyboard, handwriting, emoji and voice panels | Their existing native application/presenter | Shared actions/configuration stay in Server; panels do not own input sessions |
Direct2D is the only renderer. The WebView2 small windows, the WebView2 settings host and
the `ui-html/` pages have been removed, together with the `appearance.ui_backend` and
`appearance.settings_ui_backend` configuration keys; existing values are dropped by the
template merge on upgrade.
The candidate worker resolves output text, auxiliary codes, source badges and
translations once into `CandidateViewItem`, and the native renderer consumes these
values as plain text. Clicks enter the existing `EnqueueCandidateUiAction` queue with the
focus owner checks and action policy. The ten-entry mouse range is distinct from the
1–9 keyboard shortcuts. New business behavior belongs in shared actions/session/view
models, not in a presenter.
## Settings window
`MetasequoiaImeSettings.exe` draws its window with msimeui in `src/settings/native/`.
The window speaks the message contract in `engine/contracts/webview/` (the name is
historical): it receives `configSnapshot` and the response messages as JSON and posts
`configUpdate` and the request messages through `HandleClientMessage` in
`settings_app.cpp`, so config paths are mapped in one place. Its image assets live in
`server/assets/settings/` and are installed to `<data dir>\assets\settings\`.
The pages were ported one to one from the former HTML/CSS settings page: each page
builder in `native_page_*.cpp` names the partial and module it came from, and the
controls in `native_settings_controls.*` name the CSS rule they mirror. Those sources
remain in git history under `ui-html/webview2/settings/ime-settings/src/`.
Sciter is not an active product backend. The msimeui library owns generic controls and
rendering, never Server configuration, pipe sessions, dictionary access, or global
input state.
## v2 skins
`schema_version = 2` skins must use `base = "canvas"` (see `skin/candidate_skin_catalog.h`);
the fields are documented in `examples/skin-examples/schema/README.md`. `canvas` is the
full-texture base: transparent panel, no stroke, square corners, no shadow by default. It is
only usable as a base and is not part of `BuiltInIds()`.
- Geometry lives in `window/candidate_skin_layers.*` (pure arithmetic, no rendering) and is
shared by the candidate window and the settings preview: `ResolveNineSlice` follows CSS
`border-image` (slices in source-image pixels, widths in DIP); `ResolveContentPadding`
measures `content_insets_dip` from the panel's outer edge and never below the border width;
`SplitDecorations` stably sorts decorations and splits them at `kDecorationFrontZIndex` (1)
into a layer below and a layer above the card.
- In `CandidatePresenter`'s scene the card and two `CandidateSkinOverlay` layers share one
cell: back decorations, card, front decorations. Overlay layers measure 0 and do not hit
test. The panel is drawn by `msimeui::Card::SetBackgroundNineSlice`; with a panel image the
card's fill, stroke, radius and shadow are cleared. `line_gap_dip` becomes the preedit row's
bottom margin, so list/pager spacing is unaffected. `outer_insets_dip` becomes extra window
space around the card. `anchor` and `offset_*_dip`: positive values move right and down.
- Animation: images are fetched through `DeviceResources::GetImageFrame`. `Present` samples
the animation clock with `BeginFrame`, renders, then reads `Scene::GetNextFrameDeadline()`
and, only while the candidate window is shown, arms one one-shot coalescable timer
(`kCandidateAnimationTimerId`) that presents again. Hiding, a deferred show and a skin change
stop it; the clock restarts when the window goes from hidden to shown. With
`SPI_GETCLIENTAREAANIMATION` off, images are drawn still (first frame), as for v1 skins.