|
|
@@ -22,11 +22,11 @@ namespace DuiLib
|
|
|
#define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)
|
|
|
#endif
|
|
|
// [触屏模型] 触摸只做两件事,不做任何“触摸→鼠标”的归一化:
|
|
|
- // ①点击:交给 Windows 为触摸自动模拟的 WM_LBUTTONDOWN/UP(原生鼠标路径),
|
|
|
+ // ①点击:由 TOUCHUP 在确认未拖动后派发 BUTTONDOWN/UP,
|
|
|
// 由 CButtonUI 等控件原生 Activate();
|
|
|
// ②滚动:WM_TOUCH 直接派发 UIEVENT_TOUCHDOWN/MOVE/UP(见 OnTouchInput),
|
|
|
// 容器据此 1:1 跟手平移、滚动条据此拖滑块。
|
|
|
- // “轻点 vs 拖拽”不用距离阈值互斥,而是在 WM_LBUTTONUP 用 m_bTouchMoved 抑制误点。
|
|
|
+ // “轻点 vs 拖拽”使用 m_bTouchMoved 区分,拖动不会激活控件。
|
|
|
|
|
|
// WM_TOUCH 消息 ID 同样受 `#if _WIN32_WINNT >= 0x0601` 保护,DuiLib 工程未定义
|
|
|
// _WIN32_WINNT(StdAfx.h 兜底为 XP)→ 必须自行声明,否则 switch 里引用该消息 ID
|
|
|
@@ -38,22 +38,13 @@ namespace DuiLib
|
|
|
#endif
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
- // [触屏诊断] 临时打点:DuiLib 自身无日志库,故用无依赖的追加文件法。
|
|
|
- // 输出到 exe 同目录 touch_trace.log(每行带 tick/线程)。定位完成后可整段删除。
|
|
|
- // 开关:环境变量 ZP_TOUCH_TRACE=0 可关闭;默认开启。
|
|
|
+ // [触屏诊断] 保留日志入口以便后续定位,但当前默认关闭。
|
|
|
// ---------------------------------------------------------------------------
|
|
|
namespace TouchTrace
|
|
|
{
|
|
|
inline bool Enabled()
|
|
|
{
|
|
|
- static int s_state = -1;
|
|
|
- if (s_state < 0)
|
|
|
- {
|
|
|
- TCHAR buf[8] = { 0 };
|
|
|
- DWORD n = ::GetEnvironmentVariable(_T("ZP_TOUCH_TRACE"), buf, 8);
|
|
|
- s_state = (n > 0 && buf[0] == _T('0')) ? 0 : 1;
|
|
|
- }
|
|
|
- return s_state == 1;
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
// 极简 TCHAR* → UTF-8 char* 转换(仅用于日志路径;非线程安全,够用即可)
|
|
|
@@ -589,9 +580,9 @@ namespace DuiLib
|
|
|
// 名字必须避开 SDK 的同名宏:部分 SDK 把它们定义成【宏】,且不受 _WIN32_WINNT 版本段保护,
|
|
|
// 同名会被预处理器展开——声明变成 `const DWORD 0x0002 = 0x0002;`(C2059),
|
|
|
// 限定使用变成 `TouchApi::0x0004`(C2589)。故改用 TOUCHEVTFLAG 前缀。
|
|
|
- const DWORD TOUCHEVTFLAG_MOVE = 0x0002;
|
|
|
- const DWORD TOUCHEVTFLAG_DOWN = 0x0004;
|
|
|
- const DWORD TOUCHEVTFLAG_UP = 0x0008;
|
|
|
+ const DWORD TOUCHEVTFLAG_MOVE = 0x0001;
|
|
|
+ const DWORD TOUCHEVTFLAG_DOWN = 0x0002;
|
|
|
+ const DWORD TOUCHEVTFLAG_UP = 0x0004;
|
|
|
|
|
|
typedef BOOL (WINAPI *PFN_RegisterTouchWindow)(HWND, ULONG);
|
|
|
typedef BOOL (WINAPI *PFN_GetTouchInputInfo)(HANDLE, UINT, PTOUCHINPUT, int);
|
|
|
@@ -666,7 +657,7 @@ namespace DuiLib
|
|
|
// 消息本身绝不丢弃——历史教训:整条吞掉会让点击状态机(m_pEventClick)失效、点击全灭。
|
|
|
inline bool IsPromotedTouchMouseMessage()
|
|
|
{
|
|
|
- return (::GetMessageExtraInfo() & 0xFF515700) == 0xFF515700;
|
|
|
+ return (::GetMessageExtraInfo() & 0xFFFFFF80) == 0xFF515700;
|
|
|
}
|
|
|
|
|
|
// 把触点收进滚动条自身 rect:垂直滚动条主要收敛 x(y 原样保留,纵向拖拽量完好)。
|
|
|
@@ -698,6 +689,45 @@ namespace DuiLib
|
|
|
}
|
|
|
return NULL;
|
|
|
}
|
|
|
+
|
|
|
+ inline CScrollBarUI* Touch_FindScrollBar(CControlUI* pStart, POINT pt)
|
|
|
+ {
|
|
|
+ for (CControlUI* p = pStart; p != NULL; p = p->GetParent())
|
|
|
+ {
|
|
|
+ CContainerUI* pContainer = static_cast<CContainerUI*>(p->GetInterface(DUI_CTR_CONTAINER));
|
|
|
+ if (pContainer == NULL) continue;
|
|
|
+
|
|
|
+ CScrollBarUI* bars[2] = {
|
|
|
+ pContainer->GetVerticalScrollBar(),
|
|
|
+ pContainer->GetHorizontalScrollBar()
|
|
|
+ };
|
|
|
+ for (int i = 0; i < 2; ++i)
|
|
|
+ {
|
|
|
+ if (bars[i] == NULL || !bars[i]->IsVisible() || !bars[i]->IsEnabled())
|
|
|
+ continue;
|
|
|
+ RECT rc = bars[i]->GetPos();
|
|
|
+ ::InflateRect(&rc, kScrollBarTouchSlop, kScrollBarTouchSlop);
|
|
|
+ if (::PtInRect(&rc, pt)) return bars[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ inline CContainerUI* Touch_FindScrollContainer(CControlUI* pStart)
|
|
|
+ {
|
|
|
+ for (CControlUI* p = pStart; p != NULL; p = p->GetParent())
|
|
|
+ {
|
|
|
+ CContainerUI* pContainer = static_cast<CContainerUI*>(p->GetInterface(DUI_CTR_CONTAINER));
|
|
|
+ if (pContainer == NULL) continue;
|
|
|
+
|
|
|
+ CScrollBarUI* pVertical = pContainer->GetVerticalScrollBar();
|
|
|
+ CScrollBarUI* pHorizontal = pContainer->GetHorizontalScrollBar();
|
|
|
+ if ((pVertical != NULL && pVertical->IsVisible() && pVertical->IsEnabled())
|
|
|
+ || (pHorizontal != NULL && pHorizontal->IsVisible() && pHorizontal->IsEnabled()))
|
|
|
+ return pContainer;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void CPaintManagerUI::InitTouchSupport()
|
|
|
@@ -729,17 +759,24 @@ namespace DuiLib
|
|
|
if (pfnGetInfo == NULL) return;
|
|
|
|
|
|
UINT cInputs = LOWORD(wParam);
|
|
|
- if (cInputs == 0) return;
|
|
|
+ TouchTrace::Log(_T("WM_TOUCH begin inputs=%u handle=%p"), cInputs, (HANDLE)lParam);
|
|
|
+ if (cInputs == 0)
|
|
|
+ {
|
|
|
+ TouchTrace::Log(_T("WM_TOUCH ignored: inputs=0"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
std::vector<TouchApi::TOUCHINPUT> inputs(cInputs);
|
|
|
if (!pfnGetInfo((HANDLE)lParam, cInputs, inputs.data(), (int)sizeof(TouchApi::TOUCHINPUT)))
|
|
|
+ {
|
|
|
+ TouchTrace::Log(_T("WM_TOUCH GetTouchInputInfo failed error=%u"), ::GetLastError());
|
|
|
return;
|
|
|
+ }
|
|
|
|
|
|
for (UINT i = 0; i < cInputs; ++i)
|
|
|
{
|
|
|
TouchApi::TOUCHINPUT& ti = inputs[i];
|
|
|
-
|
|
|
- // 仅处理主触点(dwID==0 为保留值,NIM 同款跳过)
|
|
|
- if (ti.dwID == 0) continue;
|
|
|
+ TouchTrace::Log(_T("WM_TOUCH input[%u] id=%u flags=0x%08X screen=%d,%d"),
|
|
|
+ i, ti.dwID, ti.dwFlags, TOUCH_COORD_TO_PIXEL(ti.x), TOUCH_COORD_TO_PIXEL(ti.y));
|
|
|
|
|
|
POINT pt;
|
|
|
pt.x = TOUCH_COORD_TO_PIXEL(ti.x);
|
|
|
@@ -748,6 +785,7 @@ namespace DuiLib
|
|
|
|
|
|
if (ti.dwFlags & TouchApi::TOUCHEVTFLAG_DOWN)
|
|
|
{
|
|
|
+ TouchTrace::Log(_T("TOUCHDOWN begin id=%u client=%d,%d"), ti.dwID, pt.x, pt.y);
|
|
|
// 这里【不再】用 `if (m_pEventClick != NULL) break;` 做通用守卫:Windows 会在 WM_TOUCH
|
|
|
// 前后补发合成的 WM_LBUTTONDOWN(同样设置 m_pEventClick),通用守卫会让 m_pEventTouch
|
|
|
// 永不设置 → 容器收不到 TOUCHMOVE,列表滚不动。
|
|
|
@@ -759,21 +797,36 @@ namespace DuiLib
|
|
|
|
|
|
::SetFocus(m_hWndPaint);
|
|
|
m_ptLastTouchPos = pt;
|
|
|
+ // 触摸没有“悬浮”语义。新手势开始前先清掉上一次鼠标路径遗留的 HOT 状态,
|
|
|
+ // 防止首个触摸 DOWN 恰好发生在合成 WM_MOUSEMOVE 之前。
|
|
|
+ ClearTouchHoverState(pt);
|
|
|
// 先把手势状态复位:即使这一按没命中任何控件,也不能让上一轮的 m_pEventTouch 残留
|
|
|
// 下去(否则后续 TOUCHMOVE 会发给一个早已松开的手指所对应的控件)。
|
|
|
m_pEventTouch = NULL;
|
|
|
+ m_pTouchScrollContainer = NULL;
|
|
|
m_bTouchMoved = false;
|
|
|
m_ptTouchStart = pt;
|
|
|
|
|
|
CControlUI* pControl = FindControl(pt);
|
|
|
- if (pControl == NULL) break;
|
|
|
- if (pControl->GetManager() != this) break;
|
|
|
+ if (pControl == NULL)
|
|
|
+ {
|
|
|
+ TouchTrace::Log(_T("TOUCHDOWN no control client=%d,%d"), pt.x, pt.y);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (pControl->GetManager() != this)
|
|
|
+ {
|
|
|
+ TouchTrace::Log(_T("TOUCHDOWN foreign control=%s manager=%p self=%p"),
|
|
|
+ pControl->GetName().GetData(), (void*)pControl->GetManager(), (void*)this);
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
// [触屏] 手指容差改判:直接命中的不是滚动条时,看看是不是“手指离滚动条滑块很近”。
|
|
|
// 典型场景:列表右侧竖直滚动条仅 16px 宽,用户想拖滑块却按到了旁边的列表项上。
|
|
|
if (pControl->GetInterface(DUI_CTR_SCROLLBAR) == NULL)
|
|
|
{
|
|
|
- CControlUI* pBar = Touch_ResolveScrollBarThumb(pControl, pt);
|
|
|
+ CControlUI* pBar = Touch_FindScrollBar(pControl, pt);
|
|
|
+ if (pBar == NULL)
|
|
|
+ pBar = Touch_ResolveScrollBarThumb(pControl, pt);
|
|
|
if (pBar != NULL)
|
|
|
{
|
|
|
TouchTrace::Log(_T("TOUCHDOWN-FATFINGER hit=%s -> scrollbar=%s pt=%d,%d"),
|
|
|
@@ -783,6 +836,9 @@ namespace DuiLib
|
|
|
}
|
|
|
|
|
|
bool bTouchIsScrollBar = (pControl->GetInterface(DUI_CTR_SCROLLBAR) != NULL);
|
|
|
+ TouchTrace::Log(_T("TOUCHDOWN hit name=%s class=%s scrollbar=%d scrollContainer=%p"),
|
|
|
+ pControl->GetName().GetData(), pControl->GetClass(), (int)bTouchIsScrollBar,
|
|
|
+ (void*)Touch_FindScrollContainer(pControl));
|
|
|
// 命中滚动条时把触点收进滚动条 rect(横向按偏也能拖滑块);事件点用收敛后的,
|
|
|
// 但 m_ptLastTouchPos / m_ptTouchStart 仍记真实坐标(去重与位移判定按真实手指轨迹走)。
|
|
|
POINT ptEvent = bTouchIsScrollBar ? Touch_SnapIntoScrollBar(pControl, pt) : pt;
|
|
|
@@ -794,8 +850,13 @@ namespace DuiLib
|
|
|
}
|
|
|
|
|
|
m_pEventTouch = pControl;
|
|
|
+ m_pTouchScrollContainer = bTouchIsScrollBar ? NULL : Touch_FindScrollContainer(pControl);
|
|
|
+ if (!bTouchIsScrollBar)
|
|
|
+ m_pEventClick = pControl;
|
|
|
m_ptTouchStart = pt;
|
|
|
- m_bSuppressHoverAfterTouch = false; // 新手势开始,解除上一轮抬起后的悬浮抑制
|
|
|
+ // 触摸没有悬浮语义;整个手势期间以及抬起后的合成鼠标移动都不应
|
|
|
+ // 重建 HOT。真实鼠标移动时在 WM_MOUSEMOVE 中解除该抑制。
|
|
|
+ m_bSuppressHoverAfterTouch = true;
|
|
|
pControl->SetFocus();
|
|
|
SetCapture(); // mgr 级:m_bMouseCapture=true + ::SetCapture
|
|
|
TouchTrace::Log(_T("TOUCHDOWN hit=%s cls=%s pt=%d,%d click=%p capture=%d"),
|
|
|
@@ -809,9 +870,18 @@ namespace DuiLib
|
|
|
event.dwTimestamp = ::GetTickCount();
|
|
|
event.wKeyState = MapKeyState();
|
|
|
pControl->Event(event);
|
|
|
+
|
|
|
+ // 触摸按下只建立候选点击,不立即发送 BUTTONDOWN。
|
|
|
+ // 用户可能正在从商品项或空白区域开始拖动滚动容器;若此时先发送
|
|
|
+ // BUTTONDOWN,控件会先进入按下态,随后很容易被提升的鼠标消息激活。
|
|
|
+ // 轻点的 BUTTONDOWN/BUTTONUP 在 TOUCHUP 且未越过阈值时成对发送。
|
|
|
}
|
|
|
else if (ti.dwFlags & TouchApi::TOUCHEVTFLAG_MOVE)
|
|
|
{
|
|
|
+ TouchTrace::Log(_T("TOUCHMOVE begin id=%u client=%d,%d active=%s scrollContainer=%p"),
|
|
|
+ ti.dwID, pt.x, pt.y,
|
|
|
+ m_pEventTouch != NULL ? m_pEventTouch->GetName().GetData() : _T("null"),
|
|
|
+ (void*)m_pTouchScrollContainer);
|
|
|
// 【不要】在这里对滚动条让路。滚动条现在完全由 WM_TOUCH 驱动:DOWN 已把 m_pEventTouch
|
|
|
// 设为"滚动条自身"(不再交回原生鼠标),CScrollBarUI 也已把 TOUCHDOWN/MOVE/UP 与鼠标
|
|
|
// 事件等价处理。若此处仍 `m_pEventClick->GetInterface("ScrollBar") != NULL` 就 break,
|
|
|
@@ -834,7 +904,9 @@ namespace DuiLib
|
|
|
|
|
|
TEventUI event = { 0 };
|
|
|
event.Type = UIEVENT_TOUCHMOVE;
|
|
|
- event.pSender = m_pEventTouch;
|
|
|
+ CControlUI* pTouchTarget = m_pTouchScrollContainer != NULL
|
|
|
+ ? static_cast<CControlUI*>(m_pTouchScrollContainer) : m_pEventTouch;
|
|
|
+ event.pSender = pTouchTarget;
|
|
|
// 滚动条:触点同样收进滚动条 rect(与 DOWN 保持一致,避免首帧跳变)。
|
|
|
event.ptMouse = (m_pEventTouch->GetInterface(DUI_CTR_SCROLLBAR) != NULL)
|
|
|
? Touch_SnapIntoScrollBar(m_pEventTouch, pt) : pt;
|
|
|
@@ -846,11 +918,16 @@ namespace DuiLib
|
|
|
m_pEventTouch->GetName().GetData(), m_pEventTouch->GetClass(),
|
|
|
(int)dx, (int)dy, (int)m_bTouchMoved,
|
|
|
m_pEventClick != NULL ? m_pEventClick->GetName().GetData() : _T("null"));
|
|
|
- m_pEventTouch->Event(event);
|
|
|
+ pTouchTarget->Event(event);
|
|
|
}
|
|
|
else if (ti.dwFlags & TouchApi::TOUCHEVTFLAG_UP)
|
|
|
{
|
|
|
+ TouchTrace::Log(_T("TOUCHUP begin id=%u client=%d,%d active=%s moved=%d"),
|
|
|
+ ti.dwID, pt.x, pt.y,
|
|
|
+ m_pEventTouch != NULL ? m_pEventTouch->GetName().GetData() : _T("null"),
|
|
|
+ (int)m_bTouchMoved);
|
|
|
m_ptLastTouchPos = pt;
|
|
|
+ ClearTouchHoverState(pt);
|
|
|
|
|
|
// 触屏抬起模拟“鼠标离开”:清除悬浮态。触摸永远不会生成 WM_MOUSELEAVE,
|
|
|
// 若只清 m_pEventHover,m_aNeedMouseLeaveNeeded 数组里登记过的控件
|
|
|
@@ -894,7 +971,12 @@ namespace DuiLib
|
|
|
m_bMouseTracking = false;
|
|
|
|
|
|
// 本函数未接管该次触摸(如 DOWN 时 FindControl 落空)→ 沿旧路径交给原生鼠标,这里不释放。
|
|
|
- if (m_pEventTouch == NULL) break;
|
|
|
+ if (m_pEventTouch == NULL)
|
|
|
+ {
|
|
|
+ m_pTouchScrollContainer = NULL;
|
|
|
+ m_bTouchMoved = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
ReleaseCapture();
|
|
|
|
|
|
@@ -910,7 +992,31 @@ namespace DuiLib
|
|
|
m_pEventTouch->GetName().GetData(), (int)m_bTouchMoved, (void*)m_pEventHover,
|
|
|
m_aNeedMouseLeaveNeeded.GetSize(), (void*)m_pEventClick, (int)m_bMouseCapture);
|
|
|
m_pEventTouch->Event(event);
|
|
|
+
|
|
|
+ if (m_pEventClick != NULL && !m_bTouchMoved
|
|
|
+ && m_pEventClick->GetInterface(DUI_CTR_SCROLLBAR) == NULL)
|
|
|
+ {
|
|
|
+ CControlUI* pClick = m_pEventClick;
|
|
|
+ m_pEventClick = NULL;
|
|
|
+ TEventUI clickEvent = { 0 };
|
|
|
+ clickEvent.Type = UIEVENT_BUTTONDOWN;
|
|
|
+ clickEvent.pSender = pClick;
|
|
|
+ clickEvent.ptMouse = pt;
|
|
|
+ clickEvent.dwTimestamp = ::GetTickCount();
|
|
|
+ clickEvent.wKeyState = MapKeyState();
|
|
|
+ pClick->Event(clickEvent);
|
|
|
+
|
|
|
+ clickEvent.Type = UIEVENT_BUTTONUP;
|
|
|
+ pClick->Event(clickEvent);
|
|
|
+ }
|
|
|
+ else if (m_pEventClick != NULL)
|
|
|
+ {
|
|
|
+ // 拖动或滚动条触摸从未发送 BUTTONDOWN,直接丢弃候选点击。
|
|
|
+ m_pEventClick = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
m_pEventTouch = NULL;
|
|
|
+ m_pTouchScrollContainer = NULL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -919,6 +1025,45 @@ namespace DuiLib
|
|
|
pfnClose((HANDLE)lParam);
|
|
|
}
|
|
|
|
|
|
+ void CPaintManagerUI::ClearTouchHoverState(POINT pt)
|
|
|
+ {
|
|
|
+ TouchTrace::Log(_T("CLEAR-HOVER pt=%d,%d hover=%s leaveCount=%d"),
|
|
|
+ pt.x, pt.y,
|
|
|
+ m_pEventHover != NULL ? m_pEventHover->GetName().GetData() : _T("null"),
|
|
|
+ m_aNeedMouseLeaveNeeded.GetSize());
|
|
|
+ TEventUI event = { 0 };
|
|
|
+ event.Type = UIEVENT_MOUSELEAVE;
|
|
|
+ event.ptMouse = pt;
|
|
|
+ event.wKeyState = MapKeyState();
|
|
|
+ event.dwTimestamp = ::GetTickCount();
|
|
|
+
|
|
|
+ CStdPtrArray controls(m_aNeedMouseLeaveNeeded.GetSize());
|
|
|
+ controls.Resize(m_aNeedMouseLeaveNeeded.GetSize());
|
|
|
+ ::CopyMemory(controls.GetData(), m_aNeedMouseLeaveNeeded.GetData(),
|
|
|
+ m_aNeedMouseLeaveNeeded.GetSize() * sizeof(LPVOID));
|
|
|
+
|
|
|
+ for (int i = 0; i < controls.GetSize(); ++i)
|
|
|
+ {
|
|
|
+ CControlUI* pControl = static_cast<CControlUI*>(controls[i]);
|
|
|
+ if (pControl != NULL)
|
|
|
+ {
|
|
|
+ event.pSender = pControl;
|
|
|
+ pControl->Event(event);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m_pEventHover != NULL)
|
|
|
+ {
|
|
|
+ event.pSender = m_pEventHover;
|
|
|
+ m_pEventHover->Event(event);
|
|
|
+ m_pEventHover = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ m_aNeedMouseLeaveNeeded.Empty();
|
|
|
+ if (m_hwndTooltip != NULL)
|
|
|
+ ::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&m_ToolTip);
|
|
|
+ }
|
|
|
+
|
|
|
void CPaintManagerUI::DeletePtr(void* ptr)
|
|
|
{
|
|
|
if (ptr)
|
|
|
@@ -1478,6 +1623,15 @@ namespace DuiLib
|
|
|
{
|
|
|
if (m_hWndPaint == NULL) return false;
|
|
|
|
|
|
+ // 触摸点击由 WM_TOUCH 直接驱动 BUTTONDOWN/BUTTONUP。
|
|
|
+ // 部分收银机仍会补发鼠标消息,但这些消息不能再次进入普通鼠标状态机,
|
|
|
+ // 否则会造成重复点击或覆盖触摸手势状态。
|
|
|
+ if ((uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK)
|
|
|
+ && IsPromotedTouchMouseMessage())
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
// [触屏兼容] 不再过滤 Windows 为触摸模拟的鼠标消息(MOUSEEVENTF_FROMTOUCH)。
|
|
|
// 触摸的“点击”正来自这些模拟的 WM_LBUTTONDOWN/UP,由原生鼠标路径交给 CButtonUI 等控件
|
|
|
// Activate() 触发——而 DuiLib 的点击状态机依赖 WM_LBUTTONDOWN 设置 m_pEventClick,故绝不能
|
|
|
@@ -2011,6 +2165,7 @@ namespace DuiLib
|
|
|
break;
|
|
|
case WM_MOUSEHOVER:
|
|
|
{
|
|
|
+ if (m_bSuppressHoverAfterTouch) return true;
|
|
|
m_bMouseTracking = false;
|
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
CControlUI* pHover = FindControl(pt);
|
|
|
@@ -2086,6 +2241,40 @@ namespace DuiLib
|
|
|
break;
|
|
|
case WM_MOUSEMOVE:
|
|
|
{
|
|
|
+ const bool bPromotedTouchMove = IsPromotedTouchMouseMessage();
|
|
|
+ if (bPromotedTouchMove && m_pEventTouch != NULL)
|
|
|
+ {
|
|
|
+ POINT ptTouch = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
+ const int kTouchSlop = 8;
|
|
|
+ const int dxTouch = ptTouch.x - m_ptTouchStart.x;
|
|
|
+ const int dyTouch = ptTouch.y - m_ptTouchStart.y;
|
|
|
+ if (dxTouch > kTouchSlop || dxTouch < -kTouchSlop
|
|
|
+ || dyTouch > kTouchSlop || dyTouch < -kTouchSlop)
|
|
|
+ m_bTouchMoved = true;
|
|
|
+ m_ptLastMousePos = ptTouch;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (m_bSuppressHoverAfterTouch)
|
|
|
+ {
|
|
|
+ m_ptLastMousePos = POINT{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((INT_PTR)lParam == -1)
|
|
|
+ break;
|
|
|
+
|
|
|
+ POINT ptCheck = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
+ if (m_bSuppressHoverAfterTouch)
|
|
|
+ {
|
|
|
+ // 合成消息通常仍停在触摸结束点;只有明显离开该点的鼠标移动
|
|
|
+ // 才能重新建立鼠标悬浮语义。
|
|
|
+ const int dx = ptCheck.x - m_ptLastTouchPos.x;
|
|
|
+ const int dy = ptCheck.y - m_ptLastTouchPos.y;
|
|
|
+ if (dx <= 8 && dx >= -8 && dy <= 8 && dy >= -8)
|
|
|
+ break;
|
|
|
+ m_bSuppressHoverAfterTouch = false;
|
|
|
+ }
|
|
|
+
|
|
|
// Start tracking this entire window again...
|
|
|
if (!m_bMouseTracking)
|
|
|
{
|
|
|
@@ -2098,34 +2287,6 @@ namespace DuiLib
|
|
|
m_bMouseTracking = true;
|
|
|
}
|
|
|
|
|
|
- // 触屏抬起后的“悬浮态复活”抑制:
|
|
|
- // 抬起瞬间会置 m_bSuppressHoverAfterTouch=true。此后 Windows 补发的合成 MOUSEMOVE
|
|
|
- // (以及 WM_MOUSELEAVE 触发的自回发 WM_MOUSEMOVE)一律不再参与悬浮态 enter/leave,
|
|
|
- // 只更新坐标。真实鼠标移动(与上次触摸落点明显不同)才解除抑制。
|
|
|
- {
|
|
|
- POINT ptCheck = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
- if ((INT_PTR)lParam == -1)
|
|
|
- {
|
|
|
- // (LPARAM)-1 = “鼠标离开窗口”的自回发,坐标为垃圾值,直接丢弃
|
|
|
- break;
|
|
|
- }
|
|
|
- if (m_bSuppressHoverAfterTouch)
|
|
|
- {
|
|
|
- const int kResumeSlop = 3; // 与上次触摸落点的最小差异,超过视为真实鼠标移动
|
|
|
- int ddx = ptCheck.x - m_ptLastTouchPos.x;
|
|
|
- int ddy = ptCheck.y - m_ptLastTouchPos.y;
|
|
|
- if (ddx > kResumeSlop || ddx < -kResumeSlop || ddy > kResumeSlop || ddy < -kResumeSlop)
|
|
|
- {
|
|
|
- m_bSuppressHoverAfterTouch = false; // 真实鼠标移动,恢复悬浮逻辑
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- m_ptLastMousePos = ptCheck;
|
|
|
- break; // 吞掉这次合成移动,不给控件改悬浮态
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
// Generate the appropriate mouse messages
|
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
// 是否移动
|
|
|
@@ -2213,7 +2374,6 @@ namespace DuiLib
|
|
|
// “点过的控件一直高亮 / 连点几个控件后多个同时高亮、怎么点都清不掉”的顽疾
|
|
|
// (按钮的 UISTATE_HOT、列表项的 UISTATE_HOT 全靠 MOUSEENTER/MOUSELEAVE 维护)。
|
|
|
// 该判断只影响悬浮段,消息依旧往下走 → 点击/拖拽状态机不受影响。
|
|
|
- const bool bPromotedTouchMove = IsPromotedTouchMouseMessage();
|
|
|
if (!IsCaptured() && !bPromotedTouchMove)
|
|
|
{
|
|
|
pNewHover = FindControl(pt);
|
|
|
@@ -2264,6 +2424,14 @@ namespace DuiLib
|
|
|
break;
|
|
|
case WM_LBUTTONDOWN:
|
|
|
{
|
|
|
+ TouchTrace::Log(_T("WM_LBUTTONDOWN promoted=%d pt=%d,%d eventTouch=%s eventClick=%s"),
|
|
|
+ (int)IsPromotedTouchMouseMessage(), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
|
|
|
+ m_pEventTouch != NULL ? m_pEventTouch->GetName().GetData() : _T("null"),
|
|
|
+ m_pEventClick != NULL ? m_pEventClick->GetName().GetData() : _T("null"));
|
|
|
+ // WM_TOUCH 后 Windows 可能补发 WM_LBUTTONDOWN。触摸点击由 TOUCHUP
|
|
|
+ // 的 tap 判定统一处理,不能让这条合成消息提前建立鼠标点击状态。
|
|
|
+ if (m_pEventTouch != NULL)
|
|
|
+ break;
|
|
|
// 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).
|
|
|
@@ -2303,6 +2471,7 @@ namespace DuiLib
|
|
|
event.wKeyState = (WORD)wParam;
|
|
|
event.dwTimestamp = ::GetTickCount();
|
|
|
pControl->Event(event);
|
|
|
+
|
|
|
}
|
|
|
break;
|
|
|
case WM_LBUTTONDBLCLK:
|
|
|
@@ -2329,32 +2498,65 @@ namespace DuiLib
|
|
|
break;
|
|
|
case WM_LBUTTONUP:
|
|
|
{
|
|
|
- // 触屏拖拽手势(已发生位移)-> 松开不触发“点击”,避免“拖拽又误点”。
|
|
|
- // 但滚动条滑块拖拽依赖 BUTTONUP 来解除 UISTATE_CAPTURED 并停掉 50ms 定时器,
|
|
|
- // 绝不能在此提前清空 m_pEventClick,否则滚动条卡死在捕获态。故对滚动条放行 BUTTONUP。
|
|
|
+ TouchTrace::Log(_T("WM_LBUTTONUP promoted=%d pt=%d,%d eventTouch=%s eventClick=%s moved=%d"),
|
|
|
+ (int)IsPromotedTouchMouseMessage(), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
|
|
|
+ m_pEventTouch != NULL ? m_pEventTouch->GetName().GetData() : _T("null"),
|
|
|
+ m_pEventClick != NULL ? m_pEventClick->GetName().GetData() : _T("null"),
|
|
|
+ (int)m_bTouchMoved);
|
|
|
+ const bool bPromotedTouchUp = IsPromotedTouchMouseMessage();
|
|
|
+ // TOUCHUP 已经负责 tap/drag 判定;若提升的鼠标抬起先到,不能
|
|
|
+ // 绕过该判定触发 BUTTONUP。
|
|
|
+ if (m_pEventTouch != NULL)
|
|
|
+ break;
|
|
|
+ POINT ptButtonUp = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
+ if (!bPromotedTouchUp)
|
|
|
+ {
|
|
|
+ // 普通鼠标点击也不会天然产生 MOUSELEAVE。
|
|
|
+ // 点击后光标仍停留在控件上时,必须主动清除 HOT 状态,
|
|
|
+ // 否则直到移动到下一个控件前都会保持悬浮态。
|
|
|
+ ClearTouchHoverState(ptButtonUp);
|
|
|
+ }
|
|
|
+ if (bPromotedTouchUp)
|
|
|
+ {
|
|
|
+ ClearTouchHoverState(ptButtonUp);
|
|
|
+ m_bSuppressHoverAfterTouch = true;
|
|
|
+ m_bMouseTracking = false;
|
|
|
+ }
|
|
|
+
|
|
|
TouchTrace::Log(_T("LBUTTONUP moved=%d click=%s hover=%s leaveArr=%d capture=%d"),
|
|
|
(int)m_bTouchMoved,
|
|
|
m_pEventClick != NULL ? m_pEventClick->GetName().GetData() : _T("null"),
|
|
|
m_pEventHover != NULL ? m_pEventHover->GetName().GetData() : _T("null"),
|
|
|
m_aNeedMouseLeaveNeeded.GetSize(), (int)m_bMouseCapture);
|
|
|
- if (m_bTouchMoved && m_pEventClick != NULL && m_pEventClick->GetInterface(_T("ScrollBar")) == NULL)
|
|
|
- m_pEventClick = NULL;
|
|
|
- m_bTouchMoved = false; // 用完即清,绝不影响后续真实鼠标点击
|
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
|
m_ptLastMousePos = pt;
|
|
|
- if (m_pEventClick == NULL) break;
|
|
|
+ CControlUI* pClick = m_pEventClick;
|
|
|
+ const bool bTouchDragged = m_bTouchMoved;
|
|
|
+ m_pEventClick = NULL;
|
|
|
+ m_bTouchMoved = false; // 用完即清,绝不影响后续真实鼠标点击
|
|
|
+ if (pClick == NULL)
|
|
|
+ {
|
|
|
+ ReleaseCapture();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
ReleaseCapture();
|
|
|
TEventUI event = { 0 };
|
|
|
event.Type = UIEVENT_BUTTONUP;
|
|
|
- event.pSender = m_pEventClick;
|
|
|
+ event.pSender = pClick;
|
|
|
event.wParam = wParam;
|
|
|
event.lParam = lParam;
|
|
|
event.ptMouse = pt;
|
|
|
+ if (bTouchDragged && pClick->GetInterface(DUI_CTR_SCROLLBAR) == NULL)
|
|
|
+ {
|
|
|
+ // 清除按钮的 PUSHED/CAPTURED 状态,但让 CButtonUI 的命中测试失败,
|
|
|
+ // 从而不会在滑动结束时调用 Activate。
|
|
|
+ event.ptMouse.x = -1;
|
|
|
+ event.ptMouse.y = -1;
|
|
|
+ }
|
|
|
event.wKeyState = (WORD)wParam;
|
|
|
event.dwTimestamp = ::GetTickCount();
|
|
|
|
|
|
- CControlUI* pClick = m_pEventClick;
|
|
|
- m_pEventClick = NULL;
|
|
|
pClick->Event(event);
|
|
|
}
|
|
|
break;
|
|
|
@@ -5135,4 +5337,4 @@ namespace DuiLib
|
|
|
}
|
|
|
} // namespace DuiLib
|
|
|
|
|
|
-#pragma warning(pop)
|
|
|
+#pragma warning(pop)
|