张洋 4 weeks ago
parent
commit
6dd5f44463
2 changed files with 47 additions and 21 deletions
  1. 45 20
      DuiLib/Core/UIManager.cpp
  2. 2 1
      DuiLib/Core/UIManager.h

+ 45 - 20
DuiLib/Core/UIManager.cpp

@@ -581,9 +581,22 @@ namespace DuiLib
 	void CPaintManagerUI::OnTouchInput(WPARAM wParam, LPARAM lParam)
 	{
 		// 对照 NIM Duilib 的 Window::HandleMessage case WM_TOUCH(Window.cpp:1139-1199):
-		//   解析触点 -> 换算坐标 -> FindControl -> 向控件派发真正的触摸事件。
-		// 这里不合成任何鼠标消息:点击由控件自己吃到 TOUCHDOWN/TOUCHUP 后 Activate() 产生,
+		//   解析触点 -> 换算坐标 -> FindControl -> 向控件派发真正的鼠标语义事件。
+		// 这里不合成任何鼠标消息:点击由控件自己吃到 BUTTONDOWN/BUTTONUP 后 Activate() 产生,
 		// 不经 Windows 消息队列,因此主窗口与模态弹框行为完全一致。
+		//
+		// ★ 关键:触摸的 DOWN/UP 必须 1:1 复用 DuiLib 既有的“鼠标点击状态机”
+		//   (CPaintManagerUI::SetCapture / m_pEventClick / ReleaseCapture),
+		//   绝不能用 raw ::SetCapture + 私有 m_pTouchTarget 另搞一套。原因:
+		//   所有控件(CButtonUI/COptionUI/...)的 BUTTONUP 都依赖“当初 BUTTONDOWN 时
+		//   已被 SetCapture 且 m_pEventClick 指向自己”这一前提,才能在抬手时正确 Activate(),
+		//   并把 m_pEventClick 清空、m_bMouseCapture 复位,使下一拍干净。
+		//   之前用 raw ::SetCapture + 私有 m_pTouchTarget,与鼠标状态机失配,导致:
+		//     (1) 按下去有 PUSHED 悬浮态、抬起来 BUTTONUP 丢拍 -> 不触发点击;
+		//     (2) 下一拍点别的控件时,上一拍残留的按下态才被结算 -> 点击延迟一拍;
+		//     (3) 多窗口(主窗 + 模态弹框)下 raw ::SetCapture 的 HWND 归属与 m_pEventClick
+		//         两套状态互相打架,弹框按钮始终收不到完整的 DOWN/UP 周期 -> 全无响应。
+		//   所以这里直接走与 WM_LBUTTONDOWN / WM_LBUTTONUP 完全一致的代码路径。
 		TouchApi::PFN_GetTouchInputInfo pfnGetInfo = TouchApi::GetGetTouchInputInfo();
 		if (pfnGetInfo == NULL) return;
 
@@ -605,6 +618,7 @@ namespace DuiLib
 			if (ti.dwFlags & TouchApi::TOUCHEVTFLAG_DOWN)
 			{
 				m_bTouchDown = true;
+				m_ptTouchDownPos = pt;
 				m_ptLastTouchPos = pt;
 				m_ptLastMousePos = pt;
 				m_moveDistSq = 0;
@@ -614,10 +628,13 @@ namespace DuiLib
 				if (m_pTouchTarget != NULL && m_pTouchTarget->GetManager() != this)
 					m_pTouchTarget = NULL;
 
-				::SetCapture(m_hWndPaint);
-
 				if (m_pTouchTarget != NULL)
 				{
+					// —— 复用鼠标点击状态机(与 WM_LBUTTONDOWN 完全一致)——
+					SetCapture();                    // mgr:m_bMouseCapture=true + ::SetCapture(hWndPaint)
+					m_pEventClick = m_pTouchTarget;  // 点击目标槽,UP 时据此派发 BUTTONUP
+					m_pTouchTarget->SetFocus();
+
 					TEventUI event = { 0 };
 					// 归一化:直接派发等价鼠标按下事件,使重写了 DoEvent 的控件(CButtonUI 等)也能原生响应手指。
 					event.Type = UIEVENT_BUTTONDOWN;
@@ -637,7 +654,11 @@ namespace DuiLib
 				m_ptLastTouchPos = pt;
 				m_ptLastMousePos = pt;
 
-				m_moveDistSq += (std::int64_t)dx * dx + (std::int64_t)dy * dy;
+				// tap/drag 判定用“距按下点的净位移平方”,而非逐包位移累加:
+				// 触屏抖动手指会来回小幅移动,累加会让净位移≈0 的轻触被误判成滑动。
+				LONG netDx = pt.x - m_ptTouchDownPos.x;
+				LONG netDy = pt.y - m_ptTouchDownPos.y;
+				m_moveDistSq = (std::int64_t)netDx * netDx + (std::int64_t)netDy * netDy;
 				if (m_moveDistSq > (std::int64_t)TOUCH_TAP_SLOP * TOUCH_TAP_SLOP)
 					m_bTouchMoved = true;
 
@@ -667,31 +688,35 @@ namespace DuiLib
 			{
 				if (!m_bTouchDown) continue;
 
-				if (::GetCapture() == m_hWndPaint) ::ReleaseCapture();
-
-				// 抬起仍派发给按下时命中的同一个控件(NIM 语义)。
-				// 一旦判定为滑动手势,就把触点坐标挪到控件之外的 (-1,-1):
-				// 按钮的 UIEVENT_BUTTONUP 分支只在 PtInRect 通过时才 Activate(),
-				// 这样既能正常清除 PUSHED/CAPTURED 态,又不会误触发 click。
-				POINT ptUp = pt;
-				if (m_bTouchMoved)
+				// —— 复用鼠标点击状态机(与 WM_LBUTTONUP 完全一致)——
+				if (m_pEventClick != NULL)
 				{
-					ptUp.x = -1;
-					ptUp.y = -1;
-				}
+					// 一旦判定为滑动手势,就把触点坐标挪到控件之外的 (-1,-1):
+					// 按钮的 UIEVENT_BUTTONUP 分支只在 PtInRect 通过时才 Activate(),
+					// 这样既能正常清除 PUSHED/CAPTURED 态,又不会误触发 click。
+					POINT ptUp = pt;
+					if (m_bTouchMoved)
+					{
+						ptUp.x = -1;
+						ptUp.y = -1;
+					}
+
+					ReleaseCapture();                // mgr:m_bMouseCapture=false + ::ReleaseCapture
 
-				if (m_pTouchTarget != NULL)
-				{
 					TEventUI event = { 0 };
 					// 归一化:直接派发等价鼠标抬起事件(MOVE 仍保持 UIEVENT_TOUCHMOVE 留给 CContainerUI 滚动)。
 					event.Type = UIEVENT_BUTTONUP;
-					event.pSender = m_pTouchTarget;
+					event.pSender = m_pEventClick;
 					event.ptMouse = ptUp;
 					event.dwTimestamp = ::GetTickCount();
 					event.wKeyState = MapKeyState();
-					m_pTouchTarget->Event(event);
+
+					CControlUI* pClick = m_pEventClick;
+					m_pEventClick = NULL;           // 抬手即清空,保证下一拍干净(消除“点击延迟一拍”)
+					pClick->Event(event);
 				}
 
+				// 无论如何复位触摸手势态,避免状态泄漏(如手势被系统取消、未收到 UP 等)
 				m_bTouchDown = false;
 				m_pTouchTarget = NULL;
 				m_moveDistSq = 0;

+ 2 - 1
DuiLib/Core/UIManager.h

@@ -517,8 +517,9 @@ namespace DuiLib {
 		// 触屏状态(库级统一接管后,每个窗口的 CPaintManagerUI 各持一份)
 		bool m_bTouchDown = false;          // 手指是否按下
 		POINT m_ptLastTouchPos = { 0, 0 };  // 上一次触点:与本次触点差分,得到跟手滚动的平移增量
+		POINT m_ptTouchDownPos = { 0, 0 };  // 按下时的触点:与本次触点算“净位移”,用于 tap/drag 判定
 		CControlUI* m_pTouchTarget = NULL;  // 按下时命中的控件,抬起时复用同一目标(NIM 语义)
-		std::int64_t m_moveDistSq = 0;      // 累计位移平方,用于 tap slop(轻触/滑动)判定
+		std::int64_t m_moveDistSq = 0;      // 距按下点的“净位移平方”,用于 tap slop(轻触/滑动)判定
 		bool m_bTouchMoved = false;         // 是否已超过 tap slop:超过则不再判为点击
 
 		//