* feat(ui): 滚轮平滑滚动贴近浏览器手感 - 每次滚轮的平滑滚动改为 cubic ease-out,接续当前速度,不再有指数逼近的长尾 - 滚动距离跟随系统「一次滚动的行数」,「一次滚动一屏」时按整页滚动 - 合成交换链的帧时间对齐到 DWM 的刷新网格,消除绘制开始时刻抖动带来的位移抖动 * fix(server): 设置页滚动到 Niya Demo 皮肤时不再卡顿 Niya Demo 的 background.png 实际是 1280x720 的 WebP,预览第一次滚入视野时在界面线程同步解码,首次加载 WebP 解码器要 100-200ms。皮肤预览的静态图改为打开页面时就在后台线程解码,界面线程只创建位图。 设置页与词库表格的滚轮步长同时调到 100(与 Chrome 一致),平滑时长 260ms。 * feat(server): 设置窗口加入启动画面与开关 打开设置时先在窗口中央显示齿轮图标(Win11 用 Segoe Fluent Icons U+E713,Win10 回退 Segoe MDL2 Assets),等首个 configSnapshot 填好控件后再淡入主界面,避免输入方案先显示全拼再跳到双拼。齿轮至少停留 200ms,淡出 140ms。 「关于」页新增「启动动画」开关(appearance.settings_launch_animation,默认开启),关闭后只短暂显示纯色背景。新键已写入出厂模板和开发配置,并补了默认值与读写往返测试。
129 lines
5.0 KiB
C++
129 lines
5.0 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): when it will
|
|
// reach the screen, as far as the compositor tells. 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:
|
|
// Repaints when the next frame of an animated image is due.
|
|
static constexpr UINT_PTR kImageAnimationTimerId = 0x4D49;
|
|
|
|
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
|
LRESULT HandleMessage(UINT message, WPARAM wParam, LPARAM lParam);
|
|
void OnPaint();
|
|
double NextFrameTimeMs() const;
|
|
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
|