|
|
@@ -17,10 +17,9 @@ namespace DuiLib
|
|
|
#ifndef TOUCH_COORD_TO_PIXEL
|
|
|
#define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)
|
|
|
#endif
|
|
|
- // tap slop:手指位移超过该距离即判为滑动手势,抬起时不再产生点击。
|
|
|
- // NIM 原实现只靠「抬起点是否仍落在控件内」来判定是否 click;此处额外加滑动阈值,
|
|
|
- // 是因为 POS 收银场景下手指微颤/小幅拖动极易被判成点击,从而误把商品加进购物车。
|
|
|
- static constexpr LONG TOUCH_TAP_SLOP = 16;
|
|
|
+ // [触屏模型变更] 不再使用 tap-slop 阈值:100% 照搬 NIM Duilib,触摸“点击”交给 Windows
|
|
|
+ // 为触摸模拟的 WM_LBUTTONDOWN/UP(原生鼠标路径),滚动交给原生 UIEVENT_TOUCHMOVE,
|
|
|
+ // 二者由 m_pEventTouch 守卫互斥,无需自定滑动阈值。
|
|
|
|
|
|
// WM_TOUCH 消息 ID 同样受 `#if _WIN32_WINNT >= 0x0601` 保护,DuiLib 工程未定义
|
|
|
// _WIN32_WINNT(StdAfx.h 兜底为 XP)→ 必须自行声明,否则 switch 里引用该消息 ID
|
|
|
@@ -580,23 +579,16 @@ namespace DuiLib
|
|
|
|
|
|
void CPaintManagerUI::OnTouchInput(WPARAM wParam, LPARAM lParam)
|
|
|
{
|
|
|
- // 对照 NIM Duilib 的 Window::HandleMessage case WM_TOUCH(Window.cpp:1139-1199):
|
|
|
- // 解析触点 -> 换算坐标 -> 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 完全一致的代码路径。
|
|
|
+ // 100% 照搬 NIM Duilib 的 Window::HandleMessage case WM_TOUCH(Window.cpp:1139-1199)。
|
|
|
+ // 设计要点(不要自创归一化 / 状态机):
|
|
|
+ // 1) 独立的 m_pEventTouch 追踪“本次触摸命中控件”,DOWN 设定、UP 清空;绝不复用 m_pEventClick。
|
|
|
+ // 2) 只向控件派发“原生触摸事件” UIEVENT_TOUCHDOWN / UIEVENT_TOUCHMOVE / UIEVENT_TOUCHUP,
|
|
|
+ // 由 CContainerUI::DoEvent 把 TOUCHMOVE 当作 1:1 跟手滚动(对标 NIM ScrollableBox)。
|
|
|
+ // 3) 按钮“点击”不在这里合成!它来自 Windows 为触摸自动模拟的 WM_LBUTTONDOWN/UP,
|
|
|
+ // 走 MessageHandler 里原生的鼠标点击路径(CButtonUI 等据此 Activate())。NIM 从不过滤
|
|
|
+ // 这些模拟鼠标,只在 WM_LBUTTON* 分支加了 m_pEventTouch!=NULL 守卫,保证一次手指手势
|
|
|
+ // 要么滚动、要么点击,不会双触发。
|
|
|
+ // 4) 守卫:若 m_pEventClick 已存在(鼠标/指针交互进行中),直接忽略本次触摸,互不打架。
|
|
|
TouchApi::PFN_GetTouchInputInfo pfnGetInfo = TouchApi::GetGetTouchInputInfo();
|
|
|
if (pfnGetInfo == NULL) return;
|
|
|
|
|
|
@@ -610,6 +602,9 @@ namespace DuiLib
|
|
|
{
|
|
|
TouchApi::TOUCHINPUT& ti = inputs[i];
|
|
|
|
|
|
+ // 仅处理主触点(dwID==0 为保留值,NIM 同款跳过)
|
|
|
+ if (ti.dwID == 0) continue;
|
|
|
+
|
|
|
POINT pt;
|
|
|
pt.x = TOUCH_COORD_TO_PIXEL(ti.x);
|
|
|
pt.y = TOUCH_COORD_TO_PIXEL(ti.y);
|
|
|
@@ -617,122 +612,64 @@ namespace DuiLib
|
|
|
|
|
|
if (ti.dwFlags & TouchApi::TOUCHEVTFLAG_DOWN)
|
|
|
{
|
|
|
- m_bTouchDown = true;
|
|
|
- m_ptTouchDownPos = pt;
|
|
|
+ // 鼠标/指针交互进行中 -> 忽略触摸(NIM 同款守卫)
|
|
|
+ if (m_pEventClick != NULL) break;
|
|
|
+
|
|
|
+ ::SetFocus(m_hWndPaint);
|
|
|
m_ptLastTouchPos = pt;
|
|
|
- m_ptLastMousePos = pt;
|
|
|
- m_moveDistSq = 0;
|
|
|
- m_bTouchMoved = false;
|
|
|
|
|
|
- m_pTouchTarget = FindControl(pt);
|
|
|
- if (m_pTouchTarget != NULL && m_pTouchTarget->GetManager() != this)
|
|
|
- m_pTouchTarget = NULL;
|
|
|
+ CControlUI* pControl = FindControl(pt);
|
|
|
+ if (pControl == NULL) break;
|
|
|
+ if (pControl->GetManager() != this) break;
|
|
|
|
|
|
- if (m_pTouchTarget != NULL)
|
|
|
- {
|
|
|
- // —— 复用鼠标点击状态机(与 WM_LBUTTONDOWN 完全一致)——
|
|
|
- SetCapture(); // mgr:m_bMouseCapture=true + ::SetCapture(hWndPaint)
|
|
|
- m_pEventClick = m_pTouchTarget; // 点击目标槽,UP 时据此派发 BUTTONUP
|
|
|
- m_pTouchTarget->SetFocus();
|
|
|
+ m_pEventTouch = pControl;
|
|
|
+ pControl->SetFocus();
|
|
|
+ SetCapture(); // mgr 级:m_bMouseCapture=true + ::SetCapture
|
|
|
|
|
|
- TEventUI event = { 0 };
|
|
|
- // 归一化:直接派发等价鼠标按下事件,使重写了 DoEvent 的控件(CButtonUI 等)也能原生响应手指。
|
|
|
- event.Type = UIEVENT_BUTTONDOWN;
|
|
|
- event.pSender = m_pTouchTarget;
|
|
|
- event.ptMouse = pt;
|
|
|
- event.dwTimestamp = ::GetTickCount();
|
|
|
- event.wKeyState = MapKeyState();
|
|
|
- m_pTouchTarget->Event(event);
|
|
|
- }
|
|
|
+ TEventUI event = { 0 };
|
|
|
+ event.Type = UIEVENT_TOUCHDOWN;
|
|
|
+ event.pSender = pControl;
|
|
|
+ event.ptMouse = pt;
|
|
|
+ event.dwTimestamp = ::GetTickCount();
|
|
|
+ event.wKeyState = MapKeyState();
|
|
|
+ pControl->Event(event);
|
|
|
}
|
|
|
else if (ti.dwFlags & TouchApi::TOUCHEVTFLAG_MOVE)
|
|
|
{
|
|
|
- if (!m_bTouchDown) continue;
|
|
|
-
|
|
|
+ // 鼠标/指针交互进行中 -> 忽略触摸
|
|
|
+ if (m_pEventClick != NULL) break;
|
|
|
+ // 与上一包完全相同的点 -> 去重,避免无谓派发(NIM 同款守卫)
|
|
|
+ if (m_ptLastTouchPos.x == pt.x && m_ptLastTouchPos.y == pt.y) break;
|
|
|
+ // 本次位移增量(相对上一包),随事件下发,滚动容器据此做 1:1 跟手平移
|
|
|
LONG dx = pt.x - m_ptLastTouchPos.x;
|
|
|
LONG dy = pt.y - m_ptLastTouchPos.y;
|
|
|
m_ptLastTouchPos = pt;
|
|
|
- m_ptLastMousePos = pt;
|
|
|
+ if (m_pEventTouch == NULL) break;
|
|
|
|
|
|
- // 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_bTouchMoved)
|
|
|
- {
|
|
|
- if (m_moveDistSq > (std::int64_t)TOUCH_TAP_SLOP * TOUCH_TAP_SLOP)
|
|
|
- {
|
|
|
- // 越过“轻触死区”-> 判定为滑动。吞掉越过死区这一包的移动量,
|
|
|
- // 让滚动从死区边界干净起步,避免突然跳动;后续包才真正跟手滚动。
|
|
|
- m_bTouchMoved = true;
|
|
|
- continue;
|
|
|
- }
|
|
|
- // 仍在轻触死区内:吸收位移,不滚动、不派发 TOUCHMOVE
|
|
|
- // (否则“点一下不滑动”也会被抖动的 MOVE 包驱动成滚动,且抬起时点击被吞掉)。
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- if (dx == 0 && dy == 0) continue;
|
|
|
-
|
|
|
- // 滑动已确认:派发给当前触点下的控件。绝大多数控件不认 UIEVENT_TOUCHMOVE,
|
|
|
- // 会沿 CControlUI::DoEvent 末尾的 m_pParent->DoEvent 天然冒泡到最近的滚动容器
|
|
|
- // (等价于 NIM 里 ScrollableBox 收到 kEventTouchMove),由它做 1:1 跟手平移。
|
|
|
- CControlUI* pTarget = FindControl(pt);
|
|
|
- if (pTarget != NULL && pTarget->GetManager() != this) pTarget = NULL;
|
|
|
- if (pTarget == NULL) pTarget = m_pTouchTarget;
|
|
|
- if (pTarget != NULL)
|
|
|
- {
|
|
|
- TEventUI event = { 0 };
|
|
|
- event.Type = UIEVENT_TOUCHMOVE;
|
|
|
- event.pSender = pTarget;
|
|
|
- event.ptMouse = pt;
|
|
|
- event.dwTimestamp = ::GetTickCount();
|
|
|
- event.wKeyState = MapKeyState();
|
|
|
- // 位移增量随事件下发,滚动容器无需各自维护"上一次触点",避免多容器之间失步
|
|
|
- event.wParam = (WPARAM)(int)dx;
|
|
|
- event.lParam = (LPARAM)(int)dy;
|
|
|
- pTarget->Event(event);
|
|
|
- }
|
|
|
+ TEventUI event = { 0 };
|
|
|
+ event.Type = UIEVENT_TOUCHMOVE;
|
|
|
+ event.pSender = m_pEventTouch;
|
|
|
+ event.ptMouse = pt;
|
|
|
+ event.dwTimestamp = ::GetTickCount();
|
|
|
+ event.wKeyState = MapKeyState();
|
|
|
+ event.wParam = (WPARAM)(int)dx;
|
|
|
+ event.lParam = (LPARAM)(int)dy;
|
|
|
+ m_pEventTouch->Event(event);
|
|
|
}
|
|
|
else if (ti.dwFlags & TouchApi::TOUCHEVTFLAG_UP)
|
|
|
{
|
|
|
- if (!m_bTouchDown) continue;
|
|
|
-
|
|
|
- // —— 复用鼠标点击状态机(与 WM_LBUTTONUP 完全一致)——
|
|
|
- if (m_pEventClick != NULL)
|
|
|
- {
|
|
|
- // 一旦判定为滑动手势,就把触点坐标挪到控件之外的 (-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
|
|
|
-
|
|
|
- TEventUI event = { 0 };
|
|
|
- // 归一化:直接派发等价鼠标抬起事件(MOVE 仍保持 UIEVENT_TOUCHMOVE 留给 CContainerUI 滚动)。
|
|
|
- event.Type = UIEVENT_BUTTONUP;
|
|
|
- event.pSender = m_pEventClick;
|
|
|
- event.ptMouse = ptUp;
|
|
|
- event.dwTimestamp = ::GetTickCount();
|
|
|
- event.wKeyState = MapKeyState();
|
|
|
-
|
|
|
- CControlUI* pClick = m_pEventClick;
|
|
|
- m_pEventClick = NULL; // 抬手即清空,保证下一拍干净(消除“点击延迟一拍”)
|
|
|
- pClick->Event(event);
|
|
|
- }
|
|
|
+ m_ptLastTouchPos = pt;
|
|
|
+ ReleaseCapture();
|
|
|
+ if (m_pEventTouch == NULL) break;
|
|
|
|
|
|
- // 无论如何复位触摸手势态,避免状态泄漏(如手势被系统取消、未收到 UP 等)
|
|
|
- m_bTouchDown = false;
|
|
|
- m_pTouchTarget = NULL;
|
|
|
- m_moveDistSq = 0;
|
|
|
- m_bTouchMoved = false;
|
|
|
+ TEventUI event = { 0 };
|
|
|
+ event.Type = UIEVENT_TOUCHUP;
|
|
|
+ event.pSender = m_pEventTouch;
|
|
|
+ event.ptMouse = pt;
|
|
|
+ event.dwTimestamp = ::GetTickCount();
|
|
|
+ event.wKeyState = MapKeyState();
|
|
|
+ m_pEventTouch->Event(event);
|
|
|
+ m_pEventTouch = NULL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1300,28 +1237,11 @@ namespace DuiLib
|
|
|
{
|
|
|
if (m_hWndPaint == NULL) return false;
|
|
|
|
|
|
- // [触屏兼容修复] 过滤 Windows 为“触摸手势”模拟出来的鼠标消息(ExtraInfo 带 MOUSEEVENTF_FROMTOUCH 标记)。
|
|
|
- // 背景:调用 RegisterTouchWindow 后,Windows 在把 WM_TOUCH 交给我们的同时,还会“模拟”出一套
|
|
|
- // WM_LBUTTONDOWN / WM_LBUTTONMOVE / WM_LBUTTONUP(以及可能的 WM_LBUTTONDBLCLK)。
|
|
|
- // 这些模拟消息若进入 DuiLib,会导致:
|
|
|
- // * 手指刚按下准备滑动就被当成“点击” —— 误把商品加入购物车;
|
|
|
- // * 滑动时经过的每个商品 item 都被置上悬浮态(UISTATE_HOT) 且无人清除 —— 多个 item 同时悬浮;
|
|
|
- // * 抬起后悬浮态卡死不消失。
|
|
|
- // 触摸手势的实际处理(跟手滚动 / 轻触点击)由 CPaintManagerUI 通过 WM_TOUCH 完成,
|
|
|
- // 派发成等价鼠标事件 UIEVENT_BUTTONDOWN/BUTTONUP 交给控件;控件按原生鼠标语义判定 Activate() 触发 click,
|
|
|
-// 全程不再合成任何鼠标消息(旧的 SynthesizeTap 方案已废弃)。
|
|
|
- //
|
|
|
- // 作用域天然正确:只有调用过 RegisterTouchWindow 的窗口(所有窗口已在 CPaintManagerUI::Init 注册)才会产生带
|
|
|
- // FROMTOUCH 标记的模拟鼠标;DuiLib 的下拉框/菜单等未注册触摸的弹出窗口不会产生该标记,照常进入
|
|
|
- // DuiLib,触摸点选不受影响。因此不必再用窗口属性做额外限定。
|
|
|
- // 注:未用 IsTouchWindow,因其声明要求 _WIN32_WINNT>=0x0601,本项目部分目标 SDK 没有该符号。
|
|
|
- if ((uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK
|
|
|
- || uMsg == WM_MOUSEMOVE)
|
|
|
- && (::GetMessageExtraInfo() & 0xFF515700) == 0xFF515700)
|
|
|
- {
|
|
|
- lRes = 0;
|
|
|
- return true; // 标记为已处理,阻止 DuiLib 继续处理
|
|
|
- }
|
|
|
+ // [触屏兼容] 不再过滤 Windows 为触摸模拟的鼠标消息(MOUSEEVENTF_FROMTOUCH)。
|
|
|
+ // 100% 照搬 NIM Duilib:触摸的“点击”正来自这些模拟的 WM_LBUTTONDOWN/UP,
|
|
|
+ // 由 MessageHandler 原生鼠标路径交给 CButtonUI 等控件 Activate() 触发。
|
|
|
+ // 为防止一次手指手势既滚动又点击,仅在 WM_LBUTTON*/WM_MOUSEMOVE 分支加了
|
|
|
+ // `if (m_pEventTouch != NULL) return;` 守卫(手势进行中 m_pEventTouch 非空)。
|
|
|
|
|
|
// Cycle through listeners
|
|
|
for (int i = 0; i < m_aMessageFilters.GetSize(); i++)
|
|
|
@@ -1918,6 +1838,8 @@ namespace DuiLib
|
|
|
break;
|
|
|
case WM_MOUSEMOVE:
|
|
|
{
|
|
|
+ // 触屏手势进行中 -> 忽略模拟鼠标移动(NIM 同款守卫)
|
|
|
+ if (m_pEventTouch != NULL) { lRes = 0; return true; }
|
|
|
// Start tracking this entire window again...
|
|
|
if (!m_bMouseTracking)
|
|
|
{
|
|
|
@@ -2057,6 +1979,8 @@ namespace DuiLib
|
|
|
break;
|
|
|
case WM_LBUTTONDOWN:
|
|
|
{
|
|
|
+ // 触屏手势进行中 -> 忽略模拟鼠标,避免“既滚动又点击”双触发(NIM 同款守卫)
|
|
|
+ if (m_pEventTouch != NULL) { lRes = 0; return true; }
|
|
|
// We alway set focus back to our app (this helps
|
|
|
// when Win32 child windows are placed on the dialog
|
|
|
// and we need to remove them on focus change).
|
|
|
@@ -2100,6 +2024,8 @@ namespace DuiLib
|
|
|
break;
|
|
|
case WM_LBUTTONDBLCLK:
|
|
|
{
|
|
|
+ // 触屏手势进行中 -> 忽略模拟鼠标(NIM 同款守卫)
|
|
|
+ if (m_pEventTouch != NULL) { lRes = 0; return true; }
|
|
|
if (!m_bNoActivate) ::SetFocus(m_hWndPaint);
|
|
|
|
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
@@ -2122,6 +2048,8 @@ namespace DuiLib
|
|
|
break;
|
|
|
case WM_LBUTTONUP:
|
|
|
{
|
|
|
+ // 触屏手势进行中 -> 忽略模拟鼠标(NIM 同款守卫)
|
|
|
+ if (m_pEventTouch != NULL) { lRes = 0; return true; }
|
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
m_ptLastMousePos = pt;
|
|
|
if (m_pEventClick == NULL) break;
|