张洋 vor 3 Tagen
Ursprung
Commit
75722f7ddb

+ 229 - 0
zhipuzi_pos_windows/ui/TouchWindowBase.cpp

@@ -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);
+}

+ 61 - 0
zhipuzi_pos_windows/ui/TouchWindowBase.h

@@ -0,0 +1,61 @@
+#pragma once
+
+#include "../pch/pch.h"
+#include <deque>
+#include <chrono>
+
+class TouchWindowBase : public DuiLib::CWindowWnd
+{
+public:
+	TouchWindowBase();
+	virtual ~TouchWindowBase();
+
+	// 必须设置为 DuiLib 的 paint 窗口(CPaintManagerUI 创建的窗口)
+	void SetPaintWindow(HWND hWndPaint)
+	{
+		m_hWndPaint = hWndPaint;
+	}
+
+	virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) override;
+
+protected:
+	HWND m_hWndPaint = NULL;
+
+	// touch 状态
+	bool m_touchDown = false;
+	LONG m_lastY = 0;
+	int m_accumWheel = 0; // 累积 wheel 值(单位 wheel delta)
+
+	// 每像素转多少 wheel units(可调节灵敏度)
+	// wheelUnits = dy * PIXEL_TO_WHEEL,后面按 WHEEL_DELTA(120) 做累积阈值
+	static constexpr float PIXEL_TO_WHEEL = 8.0f; // 约 15px -> 120,按需调整
+
+	// 采样用于计算速度
+	struct TouchSample
+	{
+		std::int64_t tms; // ms
+		LONG y;
+	};
+	std::deque<TouchSample> m_samples; // 保留最近若干样本
+
+	// 惯性滚动状态
+	bool m_inertiaActive = false;
+	float m_inertiaVelocityPxPerMs = 0.0f; // 像素每毫秒
+	UINT_PTR m_inertiaTimerId = 0;
+	std::int64_t m_lastInertiaTms = 0;
+
+	// 惯性参数(可调)
+	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 int SAMPLE_WINDOW_MS = 120; // 计算速度时回溯的时间窗口
+	static constexpr int MAX_SAMPLE_COUNT = 8;
+
+	// 内部方法
+	void AddTouchSample(LONG y);
+	void StartInertia(float velocityPxPerMs);
+	void StopInertia();
+	std::int64_t NowMs() const;
+};

+ 11 - 2
zhipuzi_pos_windows/wnd/CMainWnd.cpp

@@ -187,12 +187,21 @@ LRESULT CMainWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandl
     ASSERT(pRoot && "Failed to parse XML");
 
     m_pm.AttachDialog(pRoot);
-
     m_pm.AddNotifier(this);
 
 	//执行完初始化,再绑定事件,避免事件触发时,页面还没有完全
 	Init();
 
+	this->SetPaintWindow(m_pm.GetPaintWindow()); // 或类似方式获取 paint 窗口句柄
+
+	// 注册触摸支持(Windows 7+ 使用 WM_TOUCH)
+	// RegisterTouchWindow 需要在窗口创建后调用,放在 OnCreate 合适位置
+	if (!::RegisterTouchWindow(m_hWnd, 0))
+	{
+		// 可选:记录错误,便于调试没有触摸设备或注册失败的情况
+		LOG_INFO("RegisterTouchWindow failed:" << GetLastError());
+	}
+
     return 0;
 }
 
@@ -421,7 +430,7 @@ LRESULT CMainWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
         return lRes;
     }
 
-    return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
+    return TouchWindowBase::HandleMessage(uMsg, wParam, lParam);
 }
 
 bool CMainWnd::HandleCustomMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)

+ 3 - 1
zhipuzi_pos_windows/wnd/CMainWnd.h

@@ -7,9 +7,11 @@
 
 #include "../page/CBasePageUI.h"
 
+#include "../ui/TouchWindowBase.h"
+
 class CMessagePush;
 
-class CMainWnd : public CWindowWnd, public INotifyUI, public IMessageFilterUI
+class CMainWnd : public TouchWindowBase, public INotifyUI, public IMessageFilterUI
 {
 public:
 	enum MainPageName

+ 2 - 0
zhipuzi_pos_windows/zhipuzi_pos_windows.vcxproj

@@ -245,6 +245,7 @@ copy $(ProjectDir)conf\ $(SolutionDir)bin\$(Platform)\$(Configuration)\conf\</Co
     <ClInclude Include="ai\YoloClassName.h" />
     <ClInclude Include="ai\YoloFeatureManager.h" />
     <ClInclude Include="page\CAIxuexiPageUI.h" />
+    <ClInclude Include="ui\TouchWindowBase.h" />
     <ClInclude Include="worker\CDiandanAIShibieWorker.h" />
     <ClInclude Include="worker\CVideoCaptureWorker.h" />
     <ClInclude Include="sqlite3\sqlite-vec.h" />
@@ -376,6 +377,7 @@ copy $(ProjectDir)conf\ $(SolutionDir)bin\$(Platform)\$(Configuration)\conf\</Co
     <ClCompile Include="ai\test.h" />
     <ClCompile Include="ai\YoloFeatureManager.cpp" />
     <ClCompile Include="page\CAIxuexiPageUI.cpp" />
+    <ClCompile Include="ui\TouchWindowBase.cpp" />
     <ClCompile Include="worker\CDiandanAIShibieWorker.cpp" />
     <ClCompile Include="worker\CVideoCaptureWorker.cpp" />
     <ClCompile Include="sqlite3\shell.c" />

+ 6 - 0
zhipuzi_pos_windows/zhipuzi_pos_windows.vcxproj.filters

@@ -399,6 +399,9 @@
     <ClInclude Include="page\CAIxuexiPageUI.h">
       <Filter>头文件</Filter>
     </ClInclude>
+    <ClInclude Include="ui\TouchWindowBase.h">
+      <Filter>头文件</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="pch\pch.cpp">
@@ -758,6 +761,9 @@
     <ClCompile Include="page\CAIxuexiPageUI.cpp">
       <Filter>源文件</Filter>
     </ClCompile>
+    <ClCompile Include="ui\TouchWindowBase.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <Image Include="resource\zhipuzi.ico">