""" Android Gesture 扩展组件 — GestureLibraries, GestureBuilderActivity, GestureOverlayView (扩展), GestureStroke (扩展), GesturePoint (扩展), 各种手势识别器 (TapGesture, LongPressGesture, SwipeGesture 等) 方法签名完全对齐 Android SDK """ from __future__ import annotations import math import time from typing import Any, Callable, Dict, List, Optional, Tuple from .._core import Stub, AndroidOnlyError __all__ = [ "GestureLibraries", "GestureBuilderActivity", "GestureOverlayView", "GestureStroke", "GesturePoint", "OrientationGesture", "FlingGesture", "SwipeGesture", "PinchGesture", "TapGesture", "LongPressGesture", "DoubleTapGesture", "ScrollGesture", "DragGesture", "MultiFingerGesture", "GestureConfiguration", "GestureEvent", "GestureRecognizer", "GesturePerformedListener", "GestureDrawingListener", "GestureInputListener", "GestureFeedback", "GestureFeedbackProvider", ] # ═══════════════════════════════════════════════════════════════ # 手势常量 # ═══════════════════════════════════════════════════════════════ ORIENTATION_ANY = 0 ORIENTATION_HORIZONTAL = 1 ORIENTATION_VERTICAL = 2 ORIENTATION_DIAGONAL_UP = 3 ORIENTATION_DIAGONAL_DOWN = 4 ORIENTATION_LEFT = 5 ORIENTATION_RIGHT = 6 ORIENTATION_UP = 7 ORIENTATION_DOWN = 8 GESTURE_STATE_UNDEFINED = 0 GESTURE_STATE_STARTED = 1 GESTURE_STATE_IN_PROGRESS = 2 GESTURE_STATE_COMPLETED = 3 GESTURE_STATE_CANCELLED = 4 GESTURE_STATE_FAILED = 5 GESTURE_EVENT_TYPE_TAP = 0 GESTURE_EVENT_TYPE_DOUBLE_TAP = 1 GESTURE_EVENT_TYPE_LONG_PRESS = 2 GESTURE_EVENT_TYPE_SWIPE = 3 GESTURE_EVENT_TYPE_FLING = 4 GESTURE_EVENT_TYPE_PINCH = 5 GESTURE_EVENT_TYPE_SCROLL = 6 GESTURE_EVENT_TYPE_DRAG = 7 GESTURE_EVENT_TYPE_ROTATION = 8 GESTURE_EVENT_TYPE_MULTI_FINGER_TAP = 9 GESTURE_EVENT_TYPE_ORIENTATION = 10 GESTURE_LIBRARY_FILE = 0 GESTURE_LIBRARY_RESOURCE = 1 GESTURE_LIBRARY_RAW = 2 GESTURE_LIBRARY_PRIVATE = 3 GESTURE_DIRECTION_ANY = 0 GESTURE_DIRECTION_LEFT = 1 GESTURE_DIRECTION_RIGHT = 2 GESTURE_DIRECTION_UP = 3 GESTURE_DIRECTION_DOWN = 4 GESTURE_DIRECTION_HORIZONTAL = 5 GESTURE_DIRECTION_VERTICAL = 6 GESTURE_FEEDBACK_NONE = 0 GESTURE_FEEDBACK_VIBRATE = 1 GESTURE_FEEDBACK_SOUND = 2 GESTURE_FEEDBACK_VISUAL = 4 GESTURE_FEEDBACK_ALL = 7 # ═══════════════════════════════════════════════════════════════ # GesturePoint (扩展) # ═══════════════════════════════════════════════════════════════ class GesturePoint(Stub): """GesturePoint — 手势点 (完整版)""" def __init__(self, x: float = 0.0, y: float = 0.0, timestamp: int = 0, pressure: float = 1.0, size: float = 0.0, pointerId: int = 0): super().__init__() self._x = x self._y = y self._timestamp = timestamp self._pressure = pressure self._size = size self._pointer_id = pointerId def getX(self) -> float: return self._x def getY(self) -> float: return self._y def getTimestamp(self) -> int: return self._timestamp def getPressure(self) -> float: return self._pressure def getSize(self) -> float: return self._size def getPointerId(self) -> int: return self._pointer_id def distance(self, other: "GesturePoint") -> float: dx = self._x - other._x dy = self._y - other._y return math.sqrt(dx * dx + dy * dy) def velocity(self, other: "GesturePoint") -> float: dt = self._timestamp - other._timestamp if dt <= 0: return 0.0 dist = self.distance(other) return dist / dt * 1000.0 def toString(self) -> str: return f"GesturePoint({self._x:.1f}, {self._y:.1f})" def equals(self, other: Any) -> bool: if not isinstance(other, GesturePoint): return False return (self._x == other._x and self._y == other._y and self._timestamp == other._timestamp) def hashCode(self) -> int: return hash((self._x, self._y, self._timestamp)) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # GestureStroke (扩展) # ═══════════════════════════════════════════════════════════════ class GestureStroke(Stub): """GestureStroke — 手势笔画 (完整版)""" def __init__(self, points: List[GesturePoint] = None): super().__init__() self._points = points or [] self._length = 0.0 self._bounding_box = (0, 0, 0, 0) self._orientation = 0.0 self._recalculate() def _recalculate(self): if not self._points: self._length = 0.0 self._bounding_box = (0, 0, 0, 0) return min_x = min(p.getX() for p in self._points) max_x = max(p.getX() for p in self._points) min_y = min(p.getY() for p in self._points) max_y = max(p.getY() for p in self._points) self._bounding_box = (min_x, min_y, max_x - min_x, max_y - min_y) length = 0.0 for i in range(1, len(self._points)): length += self._points[i - 1].distance(self._points[i]) self._length = length def getPoints(self) -> List[GesturePoint]: return list(self._points) def getLength(self) -> float: return self._length def getBoundingBox(self) -> Tuple[float, float, float, float]: return self._bounding_box def getBoundingBoxCenter(self) -> Tuple[float, float]: x, y, w, h = self._bounding_box return (x + w / 2, y + h / 2) def getOrientation(self) -> float: return self._orientation def addPoint(self, point: GesturePoint): self._points.append(point) self._recalculate() def clear(self): self._points.clear() self._recalculate() def isLongEnough(self, minLength: float) -> bool: return self._length >= minLength def isShortEnough(self, maxLength: float) -> bool: return self._length <= maxLength def getDuration(self) -> int: if len(self._points) < 2: return 0 return self._points[-1].getTimestamp() - self._points[0].getTimestamp() def toString(self) -> str: return f"GestureStroke(points={len(self._points)}, length={self._length:.1f})" def equals(self, other: Any) -> bool: if not isinstance(other, GestureStroke): return False return (self._length == other._length and self._bounding_box == other._bounding_box) def hashCode(self) -> int: return hash(self._bounding_box) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # GestureConfiguration # ═══════════════════════════════════════════════════════════════ class GestureConfiguration(Stub): """GestureConfiguration — 手势配置""" def __init__(self): super().__init__() self._tap_timeout_ms = 300 self._double_tap_timeout_ms = 400 self._long_press_timeout_ms = 500 self._swipe_min_distance = 50.0 self._swipe_max_distance = 500.0 self._fling_min_velocity = 200.0 self._pinch_min_span = 20.0 self._multi_finger_min_count = 2 self._gesture_min_length = 10.0 self._gesture_max_length = 500.0 self._orientation_slop = 30.0 def getTapTimeoutMs(self) -> int: return self._tap_timeout_ms def setTapTimeoutMs(self, timeout: int): self._tap_timeout_ms = timeout def getDoubleTapTimeoutMs(self) -> int: return self._double_tap_timeout_ms def setDoubleTapTimeoutMs(self, timeout: int): self._double_tap_timeout_ms = timeout def getLongPressTimeoutMs(self) -> int: return self._long_press_timeout_ms def setLongPressTimeoutMs(self, timeout: int): self._long_press_timeout_ms = timeout def getSwipeMinDistance(self) -> float: return self._swipe_min_distance def setSwipeMinDistance(self, distance: float): self._swipe_min_distance = distance def getSwipeMaxDistance(self) -> float: return self._swipe_max_distance def setSwipeMaxDistance(self, distance: float): self._swipe_max_distance = distance def getFlingMinVelocity(self) -> float: return self._fling_min_velocity def setFlingMinVelocity(self, velocity: float): self._fling_min_velocity = velocity def getPinchMinSpan(self) -> float: return self._pinch_min_span def setPinchMinSpan(self, span: float): self._pinch_min_span = span def getMultiFingerMinCount(self) -> int: return self._multi_finger_min_count def setMultiFingerMinCount(self, count: int): self._multi_finger_min_count = count def getGestureMinLength(self) -> float: return self._gesture_min_length def setGestureMinLength(self, length: float): self._gesture_min_length = length def getGestureMaxLength(self) -> float: return self._gesture_max_length def setGestureMaxLength(self, length: float): self._gesture_max_length = length def getOrientationSlop(self) -> float: return self._orientation_slop def setOrientationSlop(self, slop: float): self._orientation_slop = slop def toString(self) -> str: return f"GestureConfiguration(tap={self._tap_timeout_ms}ms)" # ═══════════════════════════════════════════════════════════════ # GestureEvent # ═══════════════════════════════════════════════════════════════ class GestureEvent(Stub): """GestureEvent — 手势事件""" def __init__(self, eventType: int = GESTURE_EVENT_TYPE_TAP, position: Tuple[float, float] = (0.0, 0.0), velocity: float = 0.0, direction: int = GESTURE_DIRECTION_ANY, span: float = 0.0, fingerCount: int = 1, stroke: Optional[GestureStroke] = None, timestamp: int = 0, state: int = GESTURE_STATE_COMPLETED): super().__init__() self._event_type = eventType self._x, self._y = position self._velocity = velocity self._direction = direction self._span = span self._finger_count = fingerCount self._stroke = stroke self._timestamp = timestamp or int(time.time() * 1000) self._state = state def getEventType(self) -> int: return self._event_type def getX(self) -> float: return self._x def getY(self) -> float: return self._y def getVelocity(self) -> float: return self._velocity def getDirection(self) -> int: return self._direction def getSpan(self) -> float: return self._span def getFingerCount(self) -> int: return self._finger_count def getStroke(self) -> Optional[GestureStroke]: return self._stroke def getTimestamp(self) -> int: return self._timestamp def getState(self) -> int: return self._state def toString(self) -> str: return (f"GestureEvent(type={self._event_type}, " f"pos=({self._x:.0f},{self._y:.0f}))") def equals(self, other: Any) -> bool: if not isinstance(other, GestureEvent): return False return (self._event_type == other._event_type and self._timestamp == other._timestamp) def hashCode(self) -> int: return hash((self._event_type, self._timestamp)) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # 手势监听器 # ═══════════════════════════════════════════════════════════════ class GesturePerformedListener(Stub): """GesturePerformedListener — 手势执行监听器""" def onGesturePerformed(self, gesture: GestureStroke): pass def toString(self) -> str: return f"GesturePerformedListener@{hex(id(self))}" class GestureDrawingListener(Stub): """GestureDrawingListener — 手势绘制监听器""" def onGestureStarted(self, stroke: GestureStroke): pass def onGestureUpdated(self, stroke: GestureStroke): pass def onGestureCompleted(self, stroke: GestureStroke): pass def onGestureCancelled(self, stroke: GestureStroke): pass def toString(self) -> str: return f"GestureDrawingListener@{hex(id(self))}" class GestureInputListener(Stub): """GestureInputListener — 手势输入监听器""" def onGestureEvent(self, event: GestureEvent): pass def toString(self) -> str: return f"GestureInputListener@{hex(id(self))}" # ═══════════════════════════════════════════════════════════════ # GestureFeedback / GestureFeedbackProvider # ═══════════════════════════════════════════════════════════════ class GestureFeedback(Stub): """GestureFeedback — 手势反馈""" def __init__(self, type_: int = GESTURE_FEEDBACK_NONE, intensity: float = 1.0, durationMs: int = 50): super().__init__() self._type = type_ self._intensity = intensity self._duration_ms = durationMs def getType(self) -> int: return self._type def getIntensity(self) -> float: return self._intensity def getDurationMs(self) -> int: return self._duration_ms def toString(self) -> str: return f"GestureFeedback(type={self._type})" class GestureFeedbackProvider(Stub): """GestureFeedbackProvider — 手势反馈提供器""" def __init__(self): super().__init__() self._vibrate_enabled = True self._sound_enabled = False self._visual_enabled = False def provideFeedback(self, feedback: GestureFeedback): pass def isVibrateEnabled(self) -> bool: return self._vibrate_enabled def setVibrateEnabled(self, enabled: bool): self._vibrate_enabled = enabled def isSoundEnabled(self) -> bool: return self._sound_enabled def setSoundEnabled(self, enabled: bool): self._sound_enabled = enabled def isVisualEnabled(self) -> bool: return self._visual_enabled def setVisualEnabled(self, enabled: bool): self._visual_enabled = enabled def toString(self) -> str: return f"GestureFeedbackProvider(vibrate={self._vibrate_enabled})" # ═══════════════════════════════════════════════════════════════ # GestureRecognizer # ═══════════════════════════════════════════════════════════════ class GestureRecognizer(Stub): """GestureRecognizer — 手势识别器基类""" def __init__(self): super().__init__() self._listeners: List[GestureInputListener] = [] self._configuration = GestureConfiguration() self._enabled = True self._current_stroke: Optional[GestureStroke] = None def addListener(self, listener: GestureInputListener): self._listeners.append(listener) def removeListener(self, listener: GestureInputListener): self._listeners = [l for l in self._listeners if l is not listener] def setEnabled(self, enabled: bool): self._enabled = enabled def isEnabled(self) -> bool: return self._enabled def getConfiguration(self) -> GestureConfiguration: return self._configuration def setConfiguration(self, config: GestureConfiguration): self._configuration = config def reset(self): self._current_stroke = None def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: return None def toString(self) -> str: return f"GestureRecognizer@{hex(id(self))}" # ═══════════════════════════════════════════════════════════════ # TapGesture / DoubleTapGesture / LongPressGesture # ═══════════════════════════════════════════════════════════════ class TapGesture(GestureRecognizer): """TapGesture — 点击手势识别器""" def __init__(self): super().__init__() self._touch_down_x = 0.0 self._touch_down_y = 0.0 self._touch_down_time = 0 self._touch_down_id = -1 def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: if not self._enabled: return None now = int(time.time() * 1000) if action == 0: # DOWN self._touch_down_x = x self._touch_down_y = y self._touch_down_time = now self._touch_down_id = pointerId elif action == 1: # UP if pointerId != self._touch_down_id: return None dt = now - self._touch_down_time dx = x - self._touch_down_x dy = y - self._touch_down_y dist = math.sqrt(dx * dx + dy * dy) if dt <= self._configuration.getTapTimeoutMs() and \ dist <= 30.0: return GestureEvent( eventType=GESTURE_EVENT_TYPE_TAP, position=(x, y), fingerCount=1, state=GESTURE_STATE_COMPLETED, timestamp=now, ) return None class DoubleTapGesture(GestureRecognizer): """DoubleTapGesture — 双击手势识别器""" def __init__(self): super().__init__() self._first_tap_time = 0 self._first_tap_x = 0.0 self._first_tap_y = 0.0 self._touch_down_count = 0 self._touch_down_id = -1 def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: if not self._enabled: return None now = int(time.time() * 1000) if action == 1: # UP self._touch_down_count += 1 if self._touch_down_count == 1: self._first_tap_time = now self._first_tap_x = x self._first_tap_y = y self._touch_down_id = pointerId elif self._touch_down_count == 2: dt = now - self._first_tap_time dx = x - self._first_tap_x dy = y - self._first_tap_y dist = math.sqrt(dx * dx + dy * dy) if dt <= self._configuration.getDoubleTapTimeoutMs() and \ dist <= 40.0: self._touch_down_count = 0 return GestureEvent( eventType=GESTURE_EVENT_TYPE_DOUBLE_TAP, position=(x, y), fingerCount=1, state=GESTURE_STATE_COMPLETED, timestamp=now, ) self._touch_down_count = 1 self._first_tap_time = now self._first_tap_x = x self._first_tap_y = y return None class LongPressGesture(GestureRecognizer): """LongPressGesture — 长按手势识别器""" def __init__(self): super().__init__() self._press_start_x = 0.0 self._press_start_y = 0.0 self._press_start_time = 0 self._is_pressing = False self._has_fired = False def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: if not self._enabled: return None now = int(time.time() * 1000) if action == 0: self._press_start_x = x self._press_start_y = y self._press_start_time = now self._is_pressing = True self._has_fired = False elif action == 1: if self._is_pressing and not self._has_fired: dt = now - self._press_start_time if dt >= self._configuration.getLongPressTimeoutMs(): self._has_fired = True self._is_pressing = False return GestureEvent( eventType=GESTURE_EVENT_TYPE_LONG_PRESS, position=(self._press_start_x, self._press_start_y), state=GESTURE_STATE_COMPLETED, timestamp=now, ) self._is_pressing = False return None # ═══════════════════════════════════════════════════════════════ # SwipeGesture / ScrollGesture / FlingGesture # ═══════════════════════════════════════════════════════════════ class SwipeGesture(GestureRecognizer): """SwipeGesture — 滑动手势识别器""" def __init__(self, direction: int = GESTURE_DIRECTION_ANY): super().__init__() self._direction = direction self._start_x = 0.0 self._start_y = 0.0 self._start_time = 0 self._is_tracking = False def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: if not self._enabled: return None now = int(time.time() * 1000) if action == 0: self._start_x = x self._start_y = y self._start_time = now self._is_tracking = True elif action == 1 and self._is_tracking: dx = x - self._start_x dy = y - self._start_y dist = math.sqrt(dx * dx + dy * dy) min_dist = self._configuration.getSwipeMinDistance() max_dist = self._configuration.getSwipeMaxDistance() if min_dist <= dist <= max_dist: direction = self._compute_direction(dx, dy) if self._direction == GESTURE_DIRECTION_ANY or \ self._direction == direction: self._is_tracking = False return GestureEvent( eventType=GESTURE_EVENT_TYPE_SWIPE, position=(x, y), direction=direction, stroke=GestureStroke([ GesturePoint(self._start_x, self._start_y, self._start_time), GesturePoint(x, y, now), ]), state=GESTURE_STATE_COMPLETED, timestamp=now, ) self._is_tracking = False return None def _compute_direction(self, dx: float, dy: float) -> int: angle = math.degrees(math.atan2(dy, dx)) if -45 <= angle <= 45: return GESTURE_DIRECTION_RIGHT if 45 < angle <= 135: return GESTURE_DIRECTION_DOWN if -135 <= angle < -45: return GESTURE_DIRECTION_UP return GESTURE_DIRECTION_LEFT class ScrollGesture(GestureRecognizer): """ScrollGesture — 滚动手势识别器""" def __init__(self): super().__init__() self._start_x = 0.0 self._start_y = 0.0 self._last_x = 0.0 self._last_y = 0.0 self._is_scrolling = False def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: if not self._enabled: return None if action == 0: self._start_x = x self._start_y = y self._last_x = x self._last_y = y self._is_scrolling = False elif action == 2: # MOVE dx = x - self._last_x dy = y - self._last_y dist = math.sqrt(dx * dx + dy * dy) if dist > 5.0: self._is_scrolling = True self._last_x = x self._last_y = y return GestureEvent( eventType=GESTURE_EVENT_TYPE_SCROLL, position=(x, y), stroke=GestureStroke([ GesturePoint(self._start_x, self._start_y, 0), GesturePoint(x, y, int(time.time() * 1000)), ]), state=GESTURE_STATE_IN_PROGRESS, ) elif action == 1 and self._is_scrolling: self._is_scrolling = False return None class FlingGesture(GestureRecognizer): """FlingGesture — 甩动手势识别器""" def __init__(self): super().__init__() self._start_x = 0.0 self._start_y = 0.0 self._start_time = 0 self._is_tracking = False def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: if not self._enabled: return None now = int(time.time() * 1000) if action == 0: self._start_x = x self._start_y = y self._start_time = now self._is_tracking = True elif action == 1 and self._is_tracking: dt = now - self._start_time if dt <= 0: return None dx = x - self._start_x dy = y - self._start_y dist = math.sqrt(dx * dx + dy * dy) velocity = dist / dt * 1000.0 if velocity >= self._configuration.getFlingMinVelocity(): self._is_tracking = False return GestureEvent( eventType=GESTURE_EVENT_TYPE_FLING, position=(x, y), velocity=velocity, state=GESTURE_STATE_COMPLETED, timestamp=now, ) self._is_tracking = False return None # ═══════════════════════════════════════════════════════════════ # PinchGesture / DragGesture / MultiFingerGesture # ═══════════════════════════════════════════════════════════════ class PinchGesture(GestureRecognizer): """PinchGesture — 捏合手势识别器""" def __init__(self): super().__init__() self._initial_span = 0.0 self._current_span = 0.0 self._finger1_start: Optional[Tuple[float, float]] = None self._finger2_start: Optional[Tuple[float, float]] = None self._active = False def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: return None class DragGesture(GestureRecognizer): """DragGesture — 拖拽手势识别器""" def __init__(self): super().__init__() self._start_x = 0.0 self._start_y = 0.0 self._is_dragging = False def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: if not self._enabled: return None if action == 0: self._start_x = x self._start_y = y self._is_dragging = False elif action == 2: dx = x - self._start_x dy = y - self._start_y dist = math.sqrt(dx * dx + dy * dy) if dist > 15.0: self._is_dragging = True return GestureEvent( eventType=GESTURE_EVENT_TYPE_DRAG, position=(x, y), state=GESTURE_STATE_IN_PROGRESS, ) elif action == 1 and self._is_dragging: self._is_dragging = False return None class MultiFingerGesture(GestureRecognizer): """MultiFingerGesture — 多指手势识别器""" def __init__(self, minFingers: int = 2): super().__init__() self._min_fingers = minFingers self._active_pointers: Dict[int, Tuple[float, float]] = {} def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: if not self._enabled: return None if action == 0: self._active_pointers[pointerId] = (x, y) elif action == 1: self._active_pointers.pop(pointerId, None) if len(self._active_pointers) >= self._min_fingers: return GestureEvent( eventType=GESTURE_EVENT_TYPE_MULTI_FINGER_TAP, position=(x, y), fingerCount=len(self._active_pointers), state=GESTURE_STATE_COMPLETED, ) elif action == 2: self._active_pointers[pointerId] = (x, y) return None # ═══════════════════════════════════════════════════════════════ # OrientationGesture # ═══════════════════════════════════════════════════════════════ class OrientationGesture(GestureRecognizer): """OrientationGesture — 方向手势识别器""" def __init__(self, orientation: int = ORIENTATION_ANY): super().__init__() self._orientation = orientation self._start_x = 0.0 self._start_y = 0.0 self._is_tracking = False def processTouchEvent(self, x: float, y: float, action: int, pointerId: int = 0, pressure: float = 1.0) -> Optional[GestureEvent]: if not self._enabled: return None if action == 0: self._start_x = x self._start_y = y self._is_tracking = True elif action == 1 and self._is_tracking: dx = x - self._start_x dy = y - self._start_y orientation = self._compute_orientation(dx, dy) if self._orientation == ORIENTATION_ANY or self._orientation == orientation: self._is_tracking = False return GestureEvent( eventType=GESTURE_EVENT_TYPE_ORIENTATION, position=(x, y), state=GESTURE_STATE_COMPLETED, ) self._is_tracking = False return None def _compute_orientation(self, dx: float, dy: float) -> int: adx = abs(dx) ady = abs(dy) if adx < 10 and ady < 10: return ORIENTATION_ANY if adx > ady * 2: return ORIENTATION_HORIZONTAL if dx > 0 else ORIENTATION_LEFT elif ady > adx * 2: return ORIENTATION_VERTICAL if dy > 0 else ORIENTATION_UP elif dx > 0 and dy > 0: return ORIENTATION_DIAGONAL_DOWN elif dx > 0 and dy < 0: return ORIENTATION_DIAGONAL_UP elif dx < 0 and dy > 0: return ORIENTATION_DIAGONAL_DOWN return ORIENTATION_DIAGONAL_UP # ═══════════════════════════════════════════════════════════════ # GestureOverlayView (扩展) # ═══════════════════════════════════════════════════════════════ class GestureOverlayView(Stub): """GestureOverlayView — 手势叠加视图 (完整版)""" GESTURE_STROKE_TYPE_SINGLE = 0 GESTURE_STROKE_TYPE_MULTIPLE = 1 ORIENTATION_HORIZONTAL = ORIENTATION_HORIZONTAL ORIENTATION_VERTICAL = ORIENTATION_VERTICAL def __init__(self, context: Any = None): super().__init__() self._context = context self._current_stroke: Optional[GestureStroke] = None self._gestures: List[GestureStroke] = [] self._stroke_type = GestureOverlayView.GESTURE_STROKE_TYPE_SINGLE self._gesture_color = 0xFFFFFF00 self._gesture_length = 0.0 self._is_gesture_visible = True self._events_enabled = True self._performed_listeners: List[GesturePerformedListener] = [] self._drawing_listeners: List[GestureDrawingListener] = [] self._gesture_recognizer = GestureRecognizer() def getCurrentStroke(self) -> Optional[GestureStroke]: return self._current_stroke def getGestures(self) -> List[GestureStroke]: return list(self._gestures) def setGestureColor(self, color: int): self._gesture_color = color def getGestureColor(self) -> int: return self._gesture_color def setGestureStrokeType(self, type_: int): self._stroke_type = type_ def getGestureStrokeType(self) -> int: return self._stroke_type def setGestureStrokeLengthThreshold(self, length: float): self._gesture_length = length def getGestureStrokeLengthThreshold(self) -> float: return self._gesture_length def setGestureVisible(self, visible: bool): self._is_gesture_visible = visible def isGestureVisible(self) -> bool: return self._is_gesture_visible def setEventsEnabled(self, enabled: bool): self._events_enabled = enabled def isEventsEnabled(self) -> bool: return self._events_enabled def addOnGesturePerformedListener(self, listener: GesturePerformedListener): self._performed_listeners.append(listener) def removeOnGesturePerformedListener(self, listener: GesturePerformedListener): self._performed_listeners = [l for l in self._performed_listeners if l is not listener] def addOnGestureDrawingListener(self, listener: GestureDrawingListener): self._drawing_listeners.append(listener) def removeOnGestureDrawingListener(self, listener: GestureDrawingListener): self._drawing_listeners = [l for l in self._drawing_listeners if l is not listener] def clear(self): self._gestures.clear() self._current_stroke = None def cancelGesture(self): if self._current_stroke: for l in self._drawing_listeners: l.onGestureCancelled(self._current_stroke) self._current_stroke = None def toString(self) -> str: return f"GestureOverlayView(strokes={len(self._gestures)})" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # GestureBuilderActivity # ═══════════════════════════════════════════════════════════════ class GestureBuilderActivity(Stub): """GestureBuilderActivity — 手势构建器活动""" def __init__(self): super().__init__() self._overlay = GestureOverlayView() self._library: Optional["GestureLibraries"] = None self._current_name: str = "" def getGestureOverlayView(self) -> GestureOverlayView: return self._overlay def setGestureLibrary(self, library: "GestureLibraries"): self._library = library def getGestureLibrary(self) -> Optional["GestureLibraries"]: return self._library def setCurrentGestureName(self, name: str): self._current_name = name def getCurrentGestureName(self) -> str: return self._current_name def saveGesture(self): if self._library and self._current_name and self._overlay.getCurrentStroke(): stroke = self._overlay.getCurrentStroke() self._library.store(self._current_name, stroke) self._library.save() def toString(self) -> str: return f"GestureBuilderActivity(name={self._current_name})" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # GestureLibraries # ═══════════════════════════════════════════════════════════════ class GestureLibraries(Stub): """GestureLibraries — 手势库""" def __init__(self, storeType: int = GESTURE_LIBRARY_FILE, path: str = "", loadOnInit: bool = True): super().__init__() self._store_type = storeType self._path = path self._gestures: Dict[str, List[GestureStroke]] = {} self._loaded = False if loadOnInit else False def load(self) -> bool: self._loaded = True return True def save(self) -> bool: return True def isLoaded(self) -> bool: return self._loaded def getGestures(self, name: str) -> List[GestureStroke]: return self._gestures.get(name, []) def getGestureNames(self) -> List[str]: return list(self._gestures.keys()) def store(self, name: str, stroke: GestureStroke): if name not in self._gestures: self._gestures[name] = [] self._gestures[name].append(stroke) def remove(self, name: str): self._gestures.pop(name, None) def removeEntry(self, name: str, stroke: GestureStroke): strokes = self._gestures.get(name) if strokes: try: strokes.remove(stroke) except ValueError: pass if not strokes: self._gestures.pop(name, None) def getCount(self) -> int: return sum(len(s) for s in self._gestures.values()) @staticmethod def fromFile(path: str) -> "GestureLibraries": return GestureLibraries(storeType=GESTURE_LIBRARY_FILE, path=path) @staticmethod def fromResource(context: Any, resourceId: int) -> "GestureLibraries": return GestureLibraries(storeType=GESTURE_LIBRARY_RESOURCE, path=str(resourceId)) @staticmethod def fromRaw(context: Any, resourceId: int) -> "GestureLibraries": return GestureLibraries(storeType=GESTURE_LIBRARY_RAW, path=str(resourceId)) @staticmethod def fromPrivateFile(context: Any, name: str) -> "GestureLibraries": return GestureLibraries(storeType=GESTURE_LIBRARY_PRIVATE, path=name) def toString(self) -> str: return f"GestureLibraries(type={self._store_type}, gestures={self.getCount()})" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF