Files
msime-windows/ui/include/msimeui/Window.h
T
Lu Fan(SonnyCalcr) 53649d9d12 feat(server,ui,ui-html): 新增实验性的 Direct2D 设置窗口 (#999)
* feat(ui): 动画时钟、渲染剔除与文本框扩展,并优化 msimeui 性能

- 新增 Animation.h:cubic-bezier 缓动、Tween、颜色插值,Window 提供
  RequestAnimationFrame / GetFrameTime,动画帧随显示刷新率推进
- Window 可选 DirectComposition 渲染(含不透明模式以支持 ClearType)、
  双击按下、捕获丢失时结束按压、悬停刷新、可关闭销毁时退出消息循环
- Scene 把滚轮交给光标下最内层可滚动元素并逐级冒泡
- DeviceResources:裁剪栈驱动的离屏剔除、按帧回收的笔刷缓存、SVG 栅格缓存
- ScrollViewer 可配置外观与平滑滚动;Popup 支持阴影通道与出现动画;
  卡片阴影缓存抽成可复用的 ShadowCache
- TextBox 支持 SetText、密码掩码、多行滚动、字重/字间距/行高与外观设置
- 调试日志默认关闭(MSIMEUI_DEBUG_LOG=1 才写文件),不再在每次点击和
  TSF GetTextExt 时打开文件
- 字体回退在 CJK/emoji 映射之后保留系统回退,缺字不再显示方框
- 新增动画与剔除单元测试

* feat(server): 新增实验性的 Direct2D 设置窗口

设置窗口可通过 appearance.settings_ui_backend 选择渲染方式:默认 webview2,
d2d 为实验性的 msimeui 原生实现(server/src/settings/native/),1:1 复刻
网页设置页,包括侧栏切换时竖向高亮条的滑动动画。

- 两个前端共用同一套 webview 契约消息与 HandleClientMessage,配置读写
  映射仍只在 settings_app.cpp
- 切换渲染方式后设置进程自动以新方式重新打开;--d2d / --webview2 可临时指定
- 新配置项写入出厂模板与开发配置,UiBackendPolicy 与测试同步更新
- METASEQUOIA_IME_SETTINGS_INSTANCE 允许开发版与已安装的设置窗口并存
- 更新 ui-backends 文档与 server/AGENTS.md

* feat(ui-html): 外观页新增设置窗口渲染方式选项
2026-10-16 16:13:13 +08:00

124 lines
4.7 KiB
C++

#pragma once
#include "DeviceResources.h"
#include "Scene.h"
#include <memory>
#include <string>
namespace msimeui
{
enum class WindowInitialPlacement
{
Center,
BottomCenter,
};
class Window
{
public:
Window(std::wstring className, std::wstring title, int width, int height);
~Window();
bool Create();
bool AdoptExistingHwnd(HWND hwnd);
LRESULT DispatchImportedMessage(UINT message, WPARAM wParam, LPARAM lParam);
void SetStealFocusOnClick(bool enabled);
int Run(int nCmdShow);
HWND GetHandle() const;
HINSTANCE GetInstance() const;
Scene *GetScene() const;
DeviceResources &GetDeviceResources();
/// DPI override, mirrored into the window's own DeviceResources. Only for
/// cases where the system window DPI diverges from the content's real
/// scale (RDP client-scaling sync); see DeviceResources::SetDpiOverride.
/// GetDpi() and every pixels<->DIPs conversion (hit testing, drag region,
/// layout) honor it while set.
void SetDpiOverride(FLOAT dpi);
float GetDpi() const;
PointF ClientPixelsToDips(const POINT &point) const;
SizeF ClientPixelsToDips(const SIZE &size) const;
RECT DipsToClientPixels(const RectF &rect) const;
void Invalidate();
void Invalidate(const RectF &rect);
void InvalidateMeasure();
void InvalidateMeasure(Visual *source);
void InvalidateArrange();
void InvalidateArrange(Visual *source);
void Relayout();
void FocusVisual(Visual *visual);
void SetWindowStyle(DWORD style, DWORD extendedStyle = 0);
void SetDragRegionHeight(float height);
void SetRoundedCorners(bool enabled);
void SetInitialPlacement(WindowInitialPlacement placement, int bottomMargin = 12);
// Hosts that own the message loop (an adopted HWND in a larger application) keep
// running after this window goes away.
void SetQuitOnDestroy(bool quit);
// Replaces ThemeManager's windowBackground as the color each frame is cleared to.
void SetBackgroundColor(const D2D1_COLOR_F &color);
void SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE mode);
// Presents through a DirectComposition swap chain instead of an HWND render target.
// Needed by windows whose frame is drawn by the app over DWM's extended frame, where
// an HWND target's output is not composited; it also avoids the redirection copy.
// `opaque` ignores alpha, which an opaque window needs for ClearType text.
void SetCompositionRendering(bool enabled, bool opaque = false);
// Caret blinking for TextBox. Create() starts it; an adopted HWND has to ask.
void StartCaretTimer();
// Schedules another frame after the current one. Animated visuals call this from
// Render while they are still moving, so frames keep coming at the display rate
// and stop on their own when everything has settled.
void RequestAnimationFrame();
// Timestamp of the frame being rendered (NowMs() outside of painting). Every
// animation sampled in one frame sees the same time.
double GetFrameTime() const;
// Re-evaluates which visual is under the cursor, e.g. after content scrolled or
// was replaced without the mouse moving.
void RefreshHover();
void SetScene(std::unique_ptr<Scene> scene);
private:
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT HandleMessage(UINT message, WPARAM wParam, LPARAM lParam);
void OnPaint();
void OnSize();
bool OnMouseWheel(WPARAM wParam, LPARAM lParam);
LRESULT OnLeftButtonDown(WPARAM wParam, LPARAM lParam);
void UpdateHover(const POINT &point);
void SetFocusedVisual(Visual *visual);
bool UpdateCursorForClientPoint(const POINT &point);
std::wstring className_;
std::wstring title_;
int width_ = 0;
int height_ = 0;
HINSTANCE instance_ = nullptr;
HWND hwnd_ = nullptr;
FLOAT dpiOverride_ = 0.0f;
DeviceResources deviceResources_;
std::unique_ptr<Scene> scene_;
Visual *focusedVisual_ = nullptr;
Visual *hoveredVisual_ = nullptr;
Visual *capturedVisual_ = nullptr;
bool mouseLeaveTracking_ = false;
DWORD windowStyle_ = WS_OVERLAPPEDWINDOW;
DWORD extendedWindowStyle_ = 0;
float dragRegionHeight_ = 0.0f;
bool roundedCorners_ = false;
WindowInitialPlacement initialPlacement_ = WindowInitialPlacement::Center;
int initialBottomMargin_ = 12;
bool stealFocusOnClick_ = true;
bool adoptedHwnd_ = false;
bool quitOnDestroy_ = true;
bool hasBackgroundColor_ = false;
D2D1_COLOR_F backgroundColor_ = {};
D2D1_TEXT_ANTIALIAS_MODE textAntialiasMode_ = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
bool painting_ = false;
bool composition_ = false;
bool animationRequested_ = false;
double frameTimeMs_ = 0.0;
};
} // namespace msimeui