Browse Source

开始优化触屏滑动

张洋 4 weeks ago
parent
commit
ca98e22bf8

+ 47 - 2
zhipuzi_pos_windows/ui/CTouchWindowBase.cpp

@@ -34,6 +34,17 @@ void CTouchWindowBase::AddTouchSample(LONG y)
 		m_samples.pop_front();
 }
 
+void CTouchWindowBase::SynthesizeTap(HWND target, POINT pt)
+{
+	if (!target || !::IsWindow(target))
+		return;
+	LPARAM lParamPos = MAKELPARAM(pt.x, pt.y);
+	// 模拟一次完整的“轻触点击”。注意:这两条消息由 PostMessage 发出,ExtraInfo=0,
+	// 不带 MOUSEEVENTF_FROMTOUCH,因此会被 DuiLib 当作真实鼠标点击正常处理。
+	::PostMessage(target, WM_LBUTTONDOWN, MK_LBUTTON, lParamPos);
+	::PostMessage(target, WM_LBUTTONUP, 0, lParamPos);
+}
+
 void CTouchWindowBase::StartInertia(float velocityPxPerMs)
 {
 	if (m_inertiaActive)
@@ -68,8 +79,19 @@ LRESULT CTouchWindowBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
 	switch (uMsg)
 	{
 		case WM_CREATE:
-			// 注册接收触摸(移除:由外部窗口统一注册,避免重复注册)
-			// ::RegisterTouchWindow(m_hWnd, 0);
+			// 注册接收 WM_TOUCH。所有继承本类的窗口(主窗口、各种模态框)都会自动生效,
+			// 避免“主窗口能滑、弹框不能滑”的不一致。
+			::RegisterTouchWindow(m_hWnd, 0);
+			break;
+
+		case WM_LBUTTONDOWN:
+		case WM_LBUTTONUP:
+		case WM_LBUTTONDBLCLK:
+			// 关键修复:过滤 Windows 为触屏“模拟”出来的鼠标消息(带 MOUSEEVENTF_FROMTOUCH)。
+			// 否则手指在列表上滑动/轻触都会被当成真实鼠标点击 —— 这正是“滑动误触加入购物车”的根因。
+			// 真实鼠标的 ExtraInfo 不带该标记,因此不影响鼠标操作(含调试鼠标)。
+			if ((::GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH)
+				return 0;
 			break;
 
 		case WM_TOUCH:
@@ -96,11 +118,18 @@ LRESULT CTouchWindowBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
 						StopInertia();
 
 						m_touchDown = true;
+						m_lastX = pt.x;
 						m_lastY = pt.y;
 						m_accumWheel = 0;
 						m_samples.clear();
 						AddTouchSample(pt.y);
 
+						// 轻触/滑动判定初始化
+						m_touchStartPt = pt;
+						m_touchStartTime = NowMs();
+						m_bTouchMoved = false;
+						m_moveDistSq = 0;
+
 						// 保存最后触摸点(用于惯性期间发送位置)
 						m_lastTouchPt = pt;
 
@@ -112,9 +141,16 @@ LRESULT CTouchWindowBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
 					{
 						if (m_touchDown)
 						{
+							LONG dx = pt.x - m_lastX;
 							LONG dy = pt.y - m_lastY; // 按手机习惯:手指上滑(坐标减小) -> 负增量
+							m_lastX = pt.x;
 							m_lastY = pt.y;
 
+							// 累计移动距离平方,用于“轻触 vs 滑动”判定
+							m_moveDistSq += (std::int64_t)dx * dx + (std::int64_t)dy * dy;
+							if (m_moveDistSq > (std::int64_t)TAP_MOVE_THRESHOLD * TAP_MOVE_THRESHOLD)
+								m_bTouchMoved = true;
+
 							AddTouchSample(pt.y);
 
 							int wheelUnits = static_cast<int>(dy * PIXEL_TO_WHEEL);
@@ -160,6 +196,7 @@ LRESULT CTouchWindowBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
 							m_touchDown = false;
 							// 保存最后触摸点(确保惯性期间有位置可用)
 							m_lastTouchPt = pt;
+							m_lastX = 0;
 							m_lastY = 0;
 							m_accumWheel = 0;
 							m_samples.clear();
@@ -167,6 +204,14 @@ LRESULT CTouchWindowBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
 							//::PostMessage(target, WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y));
 							::ReleaseCapture();
 
+							// 关键修复(误触):本次触摸没有发生“滑动”才视为“轻触点击”,
+							// 模拟一次 down+up,让 DuiLib 正常触发 click(如点商品加入购物车)。
+							// 若发生过滑动,则只滚动、绝不触发点击,从而彻底避免误触。
+							if (!m_bTouchMoved)
+							{
+								SynthesizeTap(target, m_touchStartPt);
+							}
+
 							// 启动惯性滚动(如果速度足够大)
 							StartInertia(velocityPxPerMs);
 						}

+ 22 - 3
zhipuzi_pos_windows/ui/CTouchWindowBase.h

@@ -1,5 +1,12 @@
 #pragma once
 
+// Windows 为“触屏模拟鼠标”生成的鼠标消息,会在 GetMessageExtraInfo() 中带此标记。
+// 必须过滤掉这类消息,否则手指滑动/轻触会被 DuiLib 当成真实鼠标点击(误触加入购物车等)。
+// 真实鼠标的 ExtraInfo 不带该标记,因此过滤不会影响鼠标操作(含调试用鼠标)。
+#ifndef MOUSEEVENTF_FROMTOUCH
+#define MOUSEEVENTF_FROMTOUCH 0xFF515700
+#endif
+
 class CTouchWindowBase : public DuiLib::CWindowWnd
 {
 public:
@@ -19,15 +26,26 @@ protected:
 
 	// touch 状态
 	bool m_touchDown = false;
+	LONG m_lastX = 0;
 	LONG m_lastY = 0;
 	int m_accumWheel = 0; // 累积 wheel 值(单位 wheel delta)
 
+	// 轻触/滑动判定
+	bool m_bTouchMoved = false;        // 本次触摸是否发生了“滑动”(超过阈值)
+	POINT m_touchStartPt = { 0, 0 };   // 触摸起点(paint 窗口客户区坐标)
+	std::int64_t m_touchStartTime = 0; // 触摸起点时间(ms)
+	std::int64_t m_moveDistSq = 0;     // 累计移动距离的平方(用于阈值判定,避免频繁开方)
+
 	// 保存最后一次触摸的点(目标 paint 窗口的客户区坐标),用于惯性期间发送位置
 	POINT m_lastTouchPt = { 0, 0 };
 
 	// 每像素转多少 wheel units(可调节灵敏度)
 	// wheelUnits = dy * PIXEL_TO_WHEEL,后面按 WHEEL_DELTA(120) 做累积阈值
-	static constexpr float PIXEL_TO_WHEEL = 8.0f; // 约 15px -> 120,按需调整
+	static constexpr float PIXEL_TO_WHEEL = 12.0f; // 约 10px -> 120,越小越灵敏/越连贯
+
+	// 轻触/滑动判定阈值(像素)。一次触摸累计移动距离小于该值视为“轻触点击”,
+	// 否则视为“滑动滚动”(不触发点击)。用于区分“想点商品”与“想滑列表”。
+	static constexpr LONG TAP_MOVE_THRESHOLD = 10;
 
 	// 采样用于计算速度
 	struct TouchSample
@@ -47,13 +65,14 @@ protected:
 	static constexpr int INERTIA_TIMER_ID = 0xA001;
 	static constexpr int INERTIA_TIMER_INTERVAL_MS = 16; // 约 60 FPS
 	// 衰减系数按每毫秒指数衰减: velocity *= pow(INERTIA_DECAY_PER_MS, dt)
-	static constexpr float INERTIA_DECAY_PER_MS = 0.995f; // 值接近 1,越接近 1 惯性持续越久
-	static constexpr float MIN_INERTIA_VELOCITY = 0.02f; // px/ms,低于则停止
+	static constexpr float INERTIA_DECAY_PER_MS = 0.997f; // 值接近 1,越接近 1 惯性持续越久
+	static constexpr float MIN_INERTIA_VELOCITY = 0.008f; // px/ms,低于则停止(调小=惯性更持久)
 	static constexpr int SAMPLE_WINDOW_MS = 120; // 计算速度时回溯的时间窗口
 	static constexpr int MAX_SAMPLE_COUNT = 8;
 
 	// 内部方法
 	void AddTouchSample(LONG y);
+	void SynthesizeTap(HWND target, POINT pt); // 模拟一次“轻触点击”(down+up),仅用于判定为轻触时
 	void StartInertia(float velocityPxPerMs);
 	void StopInertia();
 	std::int64_t NowMs() const;