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

4.1 KiB
Raw Permalink Blame History

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.