* 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 页面
62 lines
2.0 KiB
C++
62 lines
2.0 KiB
C++
#pragma once
|
|
|
|
#include "Layout.h"
|
|
|
|
#include <chrono>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <vector>
|
|
|
|
namespace msimeui
|
|
{
|
|
class Scene
|
|
{
|
|
public:
|
|
struct PopupEntry
|
|
{
|
|
std::shared_ptr<Visual> visual;
|
|
std::function<void()> onClose;
|
|
};
|
|
|
|
void SetRoot(std::shared_ptr<Visual> root);
|
|
void Attach(Window *window);
|
|
void EnsureLayout(const SizeF &size);
|
|
void Layout(const SizeF &size);
|
|
void AddPopup(std::shared_ptr<Visual> popup, std::function<void()> onClose = {});
|
|
void RemovePopup(Visual *popup, bool notify = true);
|
|
void ClearPopups(bool notify = true);
|
|
bool DismissPopupsForClick(const PointF &point, Visual *target);
|
|
void RelayoutPopups();
|
|
void InvalidateMeasure();
|
|
void InvalidateMeasure(Visual *source);
|
|
void InvalidateArrange();
|
|
void InvalidateArrange(Visual *source);
|
|
void InvalidateVisual();
|
|
// Hosts call DeviceResources::BeginFrame before this, so the animation clock and the
|
|
// next-frame deadline belong to this frame.
|
|
void Render(DeviceResources &deviceResources);
|
|
// When the scene rendered last needs its next frame: the earliest change of an animated
|
|
// image it drew. Empty when nothing on screen animates. Hosts that render on demand
|
|
// schedule one repaint for it (msimeui::Window does so itself).
|
|
std::optional<std::chrono::steady_clock::time_point> GetNextFrameDeadline() const;
|
|
Visual *FindVisualAt(const PointF &point);
|
|
Visual *FindFocusableAt(const PointF &point);
|
|
bool OnMouseWheel(const POINT &point, short delta, WPARAM keyState);
|
|
|
|
bool OnTimer(UINT_PTR timerId);
|
|
|
|
private:
|
|
std::shared_ptr<Visual> root_;
|
|
std::vector<PopupEntry> popups_;
|
|
Window *window_ = nullptr;
|
|
Visual *measureDirtySource_ = nullptr;
|
|
Visual *arrangeDirtyRoot_ = nullptr;
|
|
SizeF lastLayoutSize_ = {};
|
|
bool hasLastLayoutSize_ = false;
|
|
bool measureDirty_ = true;
|
|
bool arrangeDirty_ = true;
|
|
std::optional<std::chrono::steady_clock::time_point> nextFrameDeadline_;
|
|
};
|
|
} // namespace msimeui
|