|
@@ -0,0 +1,229 @@
|
|
|
|
|
+#include "TouchWindowBase.h"
|
|
|
|
|
+#include <vector>
|
|
|
|
|
+#include <cmath>
|
|
|
|
|
+
|
|
|
|
|
+#ifndef TOUCH_COORD_TO_PIXEL
|
|
|
|
|
+// TOUCH 输入单位是 1/100 英寸,通常使用这个宏转换为像素
|
|
|
|
|
+#define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+TouchWindowBase::TouchWindowBase()
|
|
|
|
|
+{
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TouchWindowBase::~TouchWindowBase()
|
|
|
|
|
+{
|
|
|
|
|
+ StopInertia();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::int64_t TouchWindowBase::NowMs() const
|
|
|
|
|
+{
|
|
|
|
|
+ using namespace std::chrono;
|
|
|
|
|
+ return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void TouchWindowBase::AddTouchSample(LONG y)
|
|
|
|
|
+{
|
|
|
|
|
+ std::int64_t t = NowMs();
|
|
|
|
|
+ m_samples.push_back({ t, y });
|
|
|
|
|
+
|
|
|
|
|
+ // 保留时间窗口或最大条目数
|
|
|
|
|
+ while (!m_samples.empty() && (t - m_samples.front().tms) > SAMPLE_WINDOW_MS)
|
|
|
|
|
+ m_samples.pop_front();
|
|
|
|
|
+
|
|
|
|
|
+ while ((int)m_samples.size() > MAX_SAMPLE_COUNT)
|
|
|
|
|
+ m_samples.pop_front();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void TouchWindowBase::StartInertia(float velocityPxPerMs)
|
|
|
|
|
+{
|
|
|
|
|
+ if (m_inertiaActive)
|
|
|
|
|
+ StopInertia();
|
|
|
|
|
+
|
|
|
|
|
+ m_inertiaVelocityPxPerMs = velocityPxPerMs;
|
|
|
|
|
+ if (std::abs(m_inertiaVelocityPxPerMs) < MIN_INERTIA_VELOCITY)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 速度太小,不启动惯性
|
|
|
|
|
+ m_inertiaActive = false;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ m_lastInertiaTms = NowMs();
|
|
|
|
|
+ m_inertiaTimerId = ::SetTimer(m_hWnd, INERTIA_TIMER_ID, INERTIA_TIMER_INTERVAL_MS, NULL);
|
|
|
|
|
+ m_inertiaActive = (m_inertiaTimerId != 0);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void TouchWindowBase::StopInertia()
|
|
|
|
|
+{
|
|
|
|
|
+ if (m_inertiaActive && m_inertiaTimerId)
|
|
|
|
|
+ {
|
|
|
|
|
+ ::KillTimer(m_hWnd, m_inertiaTimerId);
|
|
|
|
|
+ m_inertiaTimerId = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ m_inertiaActive = false;
|
|
|
|
|
+ m_inertiaVelocityPxPerMs = 0.0f;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+LRESULT TouchWindowBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
|
|
|
+{
|
|
|
|
|
+ switch (uMsg)
|
|
|
|
|
+ {
|
|
|
|
|
+ case WM_CREATE:
|
|
|
|
|
+ // 注册接收触摸(Win7+)
|
|
|
|
|
+ ::RegisterTouchWindow(m_hWnd, 0);
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case WM_TOUCH:
|
|
|
|
|
+ {
|
|
|
|
|
+ UINT cInputs = LOWORD(wParam);
|
|
|
|
|
+ std::vector<TOUCHINPUT> inputs(cInputs);
|
|
|
|
|
+ if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, inputs.data(), sizeof(TOUCHINPUT)))
|
|
|
|
|
+ {
|
|
|
|
|
+ for (UINT i = 0; i < cInputs; ++i)
|
|
|
|
|
+ {
|
|
|
|
|
+ TOUCHINPUT& ti = inputs[i];
|
|
|
|
|
+ POINT pt;
|
|
|
|
|
+ pt.x = TOUCH_COORD_TO_PIXEL(ti.x);
|
|
|
|
|
+ pt.y = TOUCH_COORD_TO_PIXEL(ti.y);
|
|
|
|
|
+
|
|
|
|
|
+ // 把屏幕坐标转换为目标 paint 窗口客户区坐标
|
|
|
|
|
+ HWND target = m_hWndPaint ? m_hWndPaint : m_hWnd;
|
|
|
|
|
+ ScreenToClient(target, &pt);
|
|
|
|
|
+
|
|
|
|
|
+ // 触摸按下
|
|
|
|
|
+ if (ti.dwFlags & TOUCHEVENTF_DOWN)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 停止可能正在进行的惯性
|
|
|
|
|
+ StopInertia();
|
|
|
|
|
+
|
|
|
|
|
+ m_touchDown = true;
|
|
|
|
|
+ m_lastY = pt.y;
|
|
|
|
|
+ m_accumWheel = 0;
|
|
|
|
|
+ m_samples.clear();
|
|
|
|
|
+ AddTouchSample(pt.y);
|
|
|
|
|
+
|
|
|
|
|
+ ::PostMessage(target, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(pt.x, pt.y));
|
|
|
|
|
+ ::SetCapture(target);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 触摸移动
|
|
|
|
|
+ else if (ti.dwFlags & TOUCHEVENTF_MOVE)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (m_touchDown)
|
|
|
|
|
+ {
|
|
|
|
|
+ LONG dy = m_lastY - pt.y; // 向上移动产生正的滚轮增量
|
|
|
|
|
+ m_lastY = pt.y;
|
|
|
|
|
+
|
|
|
|
|
+ AddTouchSample(pt.y);
|
|
|
|
|
+
|
|
|
|
|
+ int wheelUnits = static_cast<int>(dy * PIXEL_TO_WHEEL);
|
|
|
|
|
+ m_accumWheel += wheelUnits;
|
|
|
|
|
+
|
|
|
|
|
+ // 每当累积达到 WHEEL_DELTA 就发送一次 WM_MOUSEWHEEL
|
|
|
|
|
+ while (abs(m_accumWheel) >= WHEEL_DELTA)
|
|
|
|
|
+ {
|
|
|
|
|
+ int sendDelta = (m_accumWheel > 0) ? WHEEL_DELTA : -WHEEL_DELTA;
|
|
|
|
|
+ m_accumWheel -= sendDelta;
|
|
|
|
|
+
|
|
|
|
|
+ WPARAM wParamWheel = (WPARAM)(((unsigned short)0) | ((unsigned short)sendDelta << 16));
|
|
|
|
|
+ LPARAM lParamPos = MAKELPARAM(pt.x, pt.y);
|
|
|
|
|
+ ::PostMessage(target, WM_MOUSEWHEEL, wParamWheel, lParamPos);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 仍然发送鼠标移动,兼容需要拖拽的控件
|
|
|
|
|
+ ::PostMessage(target, WM_MOUSEMOVE, MK_LBUTTON, MAKELPARAM(pt.x, pt.y));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 触摸抬起
|
|
|
|
|
+ else if (ti.dwFlags & TOUCHEVENTF_UP)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (m_touchDown)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 计算速度(px/ms)
|
|
|
|
|
+ float velocityPxPerMs = 0.0f;
|
|
|
|
|
+ if (m_samples.size() >= 2)
|
|
|
|
|
+ {
|
|
|
|
|
+ TouchSample first = m_samples.front();
|
|
|
|
|
+ TouchSample last = m_samples.back();
|
|
|
|
|
+ std::int64_t dt = last.tms - first.tms;
|
|
|
|
|
+ if (dt > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 使用 earliest - latest,保持上方移动为正值
|
|
|
|
|
+ velocityPxPerMs = static_cast<float>(first.y - last.y) / static_cast<float>(dt);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ m_touchDown = false;
|
|
|
|
|
+ m_lastY = 0;
|
|
|
|
|
+ m_accumWheel = 0;
|
|
|
|
|
+ m_samples.clear();
|
|
|
|
|
+
|
|
|
|
|
+ ::PostMessage(target, WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y));
|
|
|
|
|
+ ::ReleaseCapture();
|
|
|
|
|
+
|
|
|
|
|
+ // 启动惯性滚动(如果速度足够大)
|
|
|
|
|
+ StartInertia(velocityPxPerMs);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ CloseTouchInputHandle((HTOUCHINPUT)lParam);
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // 可添加日志:GetTouchInputInfo 失败
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0; // 已处理
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case WM_TIMER:
|
|
|
|
|
+ {
|
|
|
|
|
+ if (wParam == INERTIA_TIMER_ID && m_inertiaActive)
|
|
|
|
|
+ {
|
|
|
|
|
+ HWND target = m_hWndPaint ? m_hWndPaint : m_hWnd;
|
|
|
|
|
+ std::int64_t now = NowMs();
|
|
|
|
|
+ std::int64_t dt = now - m_lastInertiaTms;
|
|
|
|
|
+ if (dt <= 0) dt = INERTIA_TIMER_INTERVAL_MS;
|
|
|
|
|
+ m_lastInertiaTms = now;
|
|
|
|
|
+
|
|
|
|
|
+ // 计算本帧移动的像素
|
|
|
|
|
+ float pixels = m_inertiaVelocityPxPerMs * static_cast<float>(dt);
|
|
|
|
|
+
|
|
|
|
|
+ // 将像素转换为 wheel units 累积并发送
|
|
|
|
|
+ int wheelUnits = static_cast<int>(pixels * PIXEL_TO_WHEEL);
|
|
|
|
|
+ m_accumWheel += wheelUnits;
|
|
|
|
|
+
|
|
|
|
|
+ POINT pt;
|
|
|
|
|
+ ::GetCursorPos(&pt);
|
|
|
|
|
+ ::ScreenToClient(target, &pt);
|
|
|
|
|
+ LPARAM lParamPos = MAKELPARAM(pt.x, pt.y);
|
|
|
|
|
+
|
|
|
|
|
+ while (abs(m_accumWheel) >= WHEEL_DELTA)
|
|
|
|
|
+ {
|
|
|
|
|
+ int sendDelta = (m_accumWheel > 0) ? WHEEL_DELTA : -WHEEL_DELTA;
|
|
|
|
|
+ m_accumWheel -= sendDelta;
|
|
|
|
|
+ WPARAM wParamWheel = (WPARAM)(((unsigned short)0) | ((unsigned short)sendDelta << 16));
|
|
|
|
|
+ ::PostMessage(target, WM_MOUSEWHEEL, wParamWheel, lParamPos);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 衰减速度(指数衰减)
|
|
|
|
|
+ // velocity *= pow(INERTIA_DECAY_PER_MS, dt)
|
|
|
|
|
+ m_inertiaVelocityPxPerMs *= std::pow(INERTIA_DECAY_PER_MS, static_cast<float>(dt));
|
|
|
|
|
+
|
|
|
|
|
+ // 停止条件
|
|
|
|
|
+ if (std::abs(m_inertiaVelocityPxPerMs) < MIN_INERTIA_VELOCITY)
|
|
|
|
|
+ {
|
|
|
|
|
+ StopInertia();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case WM_DESTROY:
|
|
|
|
|
+ StopInertia();
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 若未处理,交给基类
|
|
|
|
|
+ return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
|
|
|
|
|
+}
|