touch-event.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import { useRef, useCallback, useState, useEffect } from 'react';
  2. // 触摸事件处理逻辑:坐标转换和手势模拟
  3. export function useTouchEvents(currentDevice, imageRef) {
  4. const isDragging = useRef(false);
  5. const startPos = useRef({ x: 0, y: 0 });
  6. const lastPos = useRef({ x: 0, y: 0 });
  7. const touchStartTime = useRef(0);
  8. const [deviceResolution, setDeviceResolution] = useState({ width: 1280, height: 2400 });
  9. // 获取设备实际分辨率
  10. useEffect(() => {
  11. const fetchDeviceResolution = async () => {
  12. if (!currentDevice || !window.electronAPI || !window.electronAPI.getDeviceResolution) {
  13. // 如果 API 不可用,使用默认值
  14. return;
  15. }
  16. try {
  17. const result = await window.electronAPI.getDeviceResolution(currentDevice);
  18. if (result?.success && result.width && result.height) {
  19. setDeviceResolution({ width: result.width, height: result.height });
  20. }
  21. } catch (err) {
  22. // 获取设备分辨率失败,使用默认值
  23. }
  24. };
  25. if (currentDevice) {
  26. fetchDeviceResolution();
  27. }
  28. }, [currentDevice]);
  29. // 将鼠标坐标转换为设备坐标
  30. const convertToDeviceCoords = useCallback((clientX, clientY) => {
  31. if (!imageRef.current) {
  32. return null;
  33. }
  34. const img = imageRef.current;
  35. const rect = img.getBoundingClientRect();
  36. // 获取图片的实际显示尺寸(考虑 object-fit: contain)
  37. // 图片可能不会完全填满容器,需要计算实际显示区域
  38. let displayWidth, displayHeight, offsetX, offsetY;
  39. if (img.naturalWidth && img.naturalHeight) {
  40. // 获取图片的原始尺寸
  41. const imgAspect = img.naturalWidth / img.naturalHeight;
  42. const containerAspect = rect.width / rect.height;
  43. if (imgAspect > containerAspect) {
  44. // 图片更宽,以宽度为准(左右可能有黑边)
  45. displayWidth = rect.width;
  46. displayHeight = rect.width / imgAspect;
  47. offsetX = 0;
  48. offsetY = (rect.height - displayHeight) / 2;
  49. } else {
  50. // 图片更高,以高度为准(上下可能有黑边)
  51. displayHeight = rect.height;
  52. displayWidth = rect.height * imgAspect;
  53. offsetX = (rect.width - displayWidth) / 2;
  54. offsetY = 0;
  55. }
  56. } else {
  57. // 如果图片尺寸未知,使用容器尺寸
  58. displayWidth = rect.width;
  59. displayHeight = rect.height;
  60. offsetX = 0;
  61. offsetY = 0;
  62. }
  63. // 计算鼠标相对于实际显示区域的位置
  64. const relativeX = (clientX - rect.left - offsetX) / displayWidth;
  65. const relativeY = (clientY - rect.top - offsetY) / displayHeight;
  66. // 检查是否在有效显示区域内(0-1之间)
  67. if (relativeX < 0 || relativeX > 1 || relativeY < 0 || relativeY > 1) {
  68. // 点击在显示区域外(黑边区域),返回 null
  69. return null;
  70. }
  71. // 转换为设备坐标
  72. const deviceX = Math.round(relativeX * deviceResolution.width);
  73. const deviceY = Math.round(relativeY * deviceResolution.height);
  74. // 确保坐标在设备范围内
  75. const clampedX = Math.max(0, Math.min(deviceResolution.width - 1, deviceX));
  76. const clampedY = Math.max(0, Math.min(deviceResolution.height - 1, deviceY));
  77. return { x: clampedX, y: clampedY };
  78. }, [imageRef, deviceResolution]);
  79. // 发送 tap 事件到设备
  80. const sendTap = useCallback(async (x, y) => {
  81. if (!currentDevice || !window.electronAPI || !window.electronAPI.sendTap) {
  82. return;
  83. }
  84. try {
  85. const result = await window.electronAPI.sendTap(currentDevice, x, y);
  86. if (!result?.success) {
  87. // Tap 失败
  88. }
  89. } catch (err) {
  90. // Tap 异常
  91. }
  92. }, [currentDevice]);
  93. // 发送 swipe 事件到设备
  94. const sendSwipe = useCallback(async (x1, y1, x2, y2, duration = 300) => {
  95. if (!currentDevice || !window.electronAPI || !window.electronAPI.sendSwipe) {
  96. return;
  97. }
  98. try {
  99. const result = await window.electronAPI.sendSwipe(currentDevice, x1, y1, x2, y2, duration);
  100. if (!result?.success) {
  101. // Swipe 失败
  102. }
  103. } catch (err) {
  104. // Swipe 异常
  105. }
  106. }, [currentDevice]);
  107. // 处理鼠标按下
  108. const handleMouseDown = useCallback((e) => {
  109. if (!currentDevice || !imageRef.current) return;
  110. e.preventDefault();
  111. isDragging.current = true;
  112. touchStartTime.current = Date.now();
  113. const deviceCoords = convertToDeviceCoords(e.clientX, e.clientY);
  114. if (deviceCoords) {
  115. startPos.current = deviceCoords;
  116. lastPos.current = deviceCoords;
  117. }
  118. }, [currentDevice, imageRef, convertToDeviceCoords]);
  119. // 处理鼠标移动
  120. const handleMouseMove = useCallback((e) => {
  121. if (!isDragging.current || !currentDevice || !imageRef.current) return;
  122. e.preventDefault();
  123. const deviceCoords = convertToDeviceCoords(e.clientX, e.clientY);
  124. if (deviceCoords) {
  125. lastPos.current = deviceCoords;
  126. }
  127. }, [currentDevice, imageRef, convertToDeviceCoords]);
  128. // 处理鼠标释放
  129. const handleMouseUp = useCallback((e) => {
  130. if (!isDragging.current || !currentDevice) return;
  131. e.preventDefault();
  132. isDragging.current = false;
  133. const touchDuration = Date.now() - touchStartTime.current;
  134. const distance = Math.sqrt(
  135. Math.pow(lastPos.current.x - startPos.current.x, 2) +
  136. Math.pow(lastPos.current.y - startPos.current.y, 2)
  137. );
  138. // 判断是 tap 还是 swipe
  139. // 如果移动距离小于 10 像素且时间小于 300ms,认为是 tap
  140. if (distance < 10 && touchDuration < 300) {
  141. // Tap 操作
  142. sendTap(startPos.current.x, startPos.current.y);
  143. } else if (distance >= 10) {
  144. // Swipe 操作
  145. const swipeDuration = Math.max(100, Math.min(1000, touchDuration));
  146. sendSwipe(
  147. startPos.current.x,
  148. startPos.current.y,
  149. lastPos.current.x,
  150. lastPos.current.y,
  151. swipeDuration
  152. );
  153. }
  154. }, [currentDevice, sendTap, sendSwipe]);
  155. // 处理鼠标离开(防止拖拽到外部时无法触发 mouseup)
  156. const handleMouseLeave = useCallback((e) => {
  157. if (isDragging.current) {
  158. handleMouseUp(e);
  159. }
  160. }, [handleMouseUp]);
  161. return {
  162. handleMouseDown,
  163. handleMouseMove,
  164. handleMouseUp,
  165. handleMouseLeave,
  166. };
  167. }