""" Android gesture 包 — Gesture, GestureOverlayView, GesturePoint, GestureStroke, GestureLibrary, GestureStore, Prediction, OrientedBoundingBox, GestureUtils 方法签名完全对齐 Android SDK """ from __future__ import annotations import math import sys from typing import Any, Dict, List, Optional, Tuple from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) __all__ = [ "Gesture", "GestureOverlayView", "GesturePoint", "GestureStroke", "GestureLibrary", "GestureStore", "Prediction", "OrientedBoundingBox", "GestureUtils", ] # ═══════════════════════════════════════════════════════════════ # GesturePoint # ═══════════════════════════════════════════════════════════════ class GesturePoint(Stub): """GesturePoint — 手势点""" def __init__(self, x: float = 0.0, y: float = 0.0, timestamp: int = 0): super().__init__() self.x = x self.y = y self.timestamp = timestamp def toString(self) -> str: return f"GesturePoint({self.x}, {self.y})" # ═══════════════════════════════════════════════════════════════ # OrientedBoundingBox # ═══════════════════════════════════════════════════════════════ class OrientedBoundingBox(Stub): """OrientedBoundingBox — 有向包围盒""" def __init__(self, center_x: float = 0.0, center_y: float = 0.0, width: float = 0.0, height: float = 0.0, orientation: float = 0.0): super().__init__() self.centerX = center_x self.centerY = center_y self.width = width self.height = height self.orientation = orientation def toRect(self) -> Tuple[float, float, float, float]: """转换为矩形""" hw = self.width / 2.0 hh = self.height / 2.0 return (self.centerX - hw, self.centerY - hh, self.centerX + hw, self.centerY + hh) # ═══════════════════════════════════════════════════════════════ # Prediction # ═══════════════════════════════════════════════════════════════ class Prediction(Stub): """Prediction — 预测结果""" def __init__(self, name: str = "", score: float = 0.0): super().__init__() self.name = name self.score = score def toString(self) -> str: return f"Prediction(name={self.name}, score={self.score})" # ═══════════════════════════════════════════════════════════════ # GestureStroke # ═══════════════════════════════════════════════════════════════ class GestureStroke(Stub): """GestureStroke — 手势笔划""" def __init__(self, points: Optional[List[GesturePoint]] = None): super().__init__() self._points = points or [] self._path: Optional[Any] = None self._length = self._compute_length() self._bounding_box = OrientedBoundingBox() def _compute_length(self) -> float: length = 0.0 for i in range(1, len(self._points)): dx = self._points[i].x - self._points[i - 1].x dy = self._points[i].y - self._points[i - 1].y length += math.sqrt(dx * dx + dy * dy) return length def getLength(self) -> float: """获取长度""" return self._length def getPoints(self) -> List[GesturePoint]: """获取点列表""" return self._points.copy() def getPath(self) -> Optional[Any]: """获取路径""" return self._path def toPath(self, width: float = 1.0, height: float = 1.0, num_sample: int = 0) -> Any: """转换为路径""" return self._path def clearPath(self) -> None: """清除路径""" self._path = None def computeOrientedBoundingBox(self) -> OrientedBoundingBox: """计算有向包围盒""" return self._bounding_box # ═══════════════════════════════════════════════════════════════ # Gesture # ═══════════════════════════════════════════════════════════════ class Gesture(Stub): """Gesture — 手势""" def __init__(self): super().__init__() self._strokes: List[GestureStroke] = [] self._id = 0 self._bounding_box: Optional[Tuple[float, float, float, float]] = None self._length = 0.0 def getStrokes(self) -> List[GestureStroke]: """获取笔划列表""" return self._strokes.copy() def getLength(self) -> float: """获取手势长度""" return sum(s.getLength() for s in self._strokes) def getBoundingBox(self) -> Tuple[float, float, float, float]: """获取包围盒""" if self._bounding_box: return self._bounding_box if not self._strokes: return (0.0, 0.0, 0.0, 0.0) min_x = min(p.x for s in self._strokes for p in s._points) if self._strokes else 0 min_y = min(p.y for s in self._strokes for p in s._points) if self._strokes else 0 max_x = max(p.x for s in self._strokes for p in s._points) if self._strokes else 0 max_y = max(p.y for s in self._strokes for p in s._points) if self._strokes else 0 return (min_x, min_y, max_x, max_y) def getID(self) -> int: """获取 ID""" return self._id def setID(self, id_: int) -> None: """设置 ID""" self._id = id_ def toPath(self) -> Optional[Any]: """转换为路径""" return None def toTempPath(self) -> Optional[Any]: """转换为临时路径""" return None def toBitmap(self) -> Optional[Any]: """转换为位图""" return None def getStrokesCount(self) -> int: """获取笔划数量""" return len(self._strokes) def getGestureStroke(self, index: int) -> Optional[GestureStroke]: """获取指定索引的笔划""" if 0 <= index < len(self._strokes): return self._strokes[index] return None def addStroke(self, stroke: GestureStroke) -> None: """添加笔划""" self._strokes.append(stroke) def toString(self) -> str: return f"Gesture(id={self._id}, strokes={len(self._strokes)})" def equals(self, other: Any) -> bool: if not isinstance(other, Gesture): return False return self._id == other._id def hashCode(self) -> int: return hash(self._id) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # GestureOverlayView # ═══════════════════════════════════════════════════════════════ class GestureOverlayView(Stub): """GestureOverlayView — 手势覆盖视图 (桌面存根)""" GESTURE_STROKE_TYPE_SINGLE = 0 GESTURE_STROKE_TYPE_MULTIPLE = 1 ORIENTATION_HORIZONTAL = 0 ORIENTATION_VERTICAL = 1 def __init__(self, context: Any = None): super().__init__() self._context = context self._gesture: Optional[Gesture] = None self._gesture_color = 0xFFFFFF00 self._uncertain_gesture_color = 0xFFFF0000 self._gesture_stroke_width = 4.0 self._gesture_stroke_type = GestureOverlayView.GESTURE_STROKE_TYPE_SINGLE self._gesture_stroke_length_threshold = 50.0 self._gesture_stroke_squareness_threshold = 0.5 self._fade_enabled = True self._fade_offset = 0 self._fade_duration = 150 self._events_interception_enabled = True self._gesture_visible = True self._gesture_listeners: List[Any] = [] self._gesture_performed_listeners: List[Any] = [] self._gesturing_listeners: List[Any] = [] def getGesture(self) -> Optional[Gesture]: return self._gesture def setGesture(self, gesture: Gesture) -> None: self._gesture = gesture def setGestureColor(self, color: int) -> None: self._gesture_color = color def getGestureColor(self) -> int: return self._gesture_color def setUncertainGestureColor(self, color: int) -> None: self._uncertain_gesture_color = color def getUncertainGestureColor(self) -> int: return self._uncertain_gesture_color def setGestureStrokeWidth(self, width: float) -> None: self._gesture_stroke_width = width def getGestureStrokeWidth(self) -> float: return self._gesture_stroke_width def setGestureStrokeType(self, type_: int) -> None: self._gesture_stroke_type = type_ def getGestureStrokeType(self) -> int: return self._gesture_stroke_type def setGestureStrokeLengthThreshold(self, threshold: float) -> None: self._gesture_stroke_length_threshold = threshold def getGestureStrokeLengthThreshold(self) -> float: return self._gesture_stroke_length_threshold def setGestureStrokeSquarenessThreshold(self, threshold: float) -> None: self._gesture_stroke_squareness_threshold = threshold def getGestureStrokeSquarenessThreshold(self) -> float: return self._gesture_stroke_squareness_threshold def setFadeEnabled(self, enabled: bool) -> None: self._fade_enabled = enabled def isFadeEnabled(self) -> bool: return self._fade_enabled def setFadeOffset(self, offset: int) -> None: self._fade_offset = offset def getFadeOffset(self) -> int: return self._fade_offset def setFadeDuration(self, duration: int) -> None: self._fade_duration = duration def getFadeDuration(self) -> int: return self._fade_duration def setEventsInterceptionEnabled(self, enabled: bool) -> None: self._events_interception_enabled = enabled def isEventsInterceptionEnabled(self) -> bool: return self._events_interception_enabled def setGestureVisible(self, visible: bool) -> None: self._gesture_visible = visible def isGestureVisible(self) -> bool: return self._gesture_visible def addOnGestureListener(self, listener: Any) -> None: self._gesture_listeners.append(listener) def removeOnGestureListener(self, listener: Any) -> None: if listener in self._gesture_listeners: self._gesture_listeners.remove(listener) def removeAllOnGestureListeners(self) -> None: self._gesture_listeners.clear() def addOnGesturePerformedListener(self, listener: Any) -> None: self._gesture_performed_listeners.append(listener) def removeOnGesturePerformedListener(self, listener: Any) -> None: if listener in self._gesture_performed_listeners: self._gesture_performed_listeners.remove(listener) def removeAllOnGesturePerformedListeners(self) -> None: self._gesture_performed_listeners.clear() def addOnGesturingListener(self, listener: Any) -> None: self._gesturing_listeners.append(listener) def removeOnGesturingListener(self, listener: Any) -> None: if listener in self._gesturing_listeners: self._gesturing_listeners.remove(listener) def removeAllOnGesturingListeners(self) -> None: self._gesturing_listeners.clear() def cancelGesture(self) -> None: self._gesture = None def clear(self) -> None: self._gesture = None # ═══════════════════════════════════════════════════════════════ # GestureLibrary # ═══════════════════════════════════════════════════════════════ class GestureLibrary(Stub): """GestureLibrary — 手势库 (桌面存根)""" ORIENTATION_INVARIANT = 0 ORIENTATION_SENSITIVE = 1 ORIENTATION_SEQUENTIAL_SENSITIVE = 2 SEQUENCE_INVARIANT = 0 SEQUENCE_SENSITIVE = 1 def __init__(self): super().__init__() self._gestures: Dict[str, List[Gesture]] = {} self._read_only = False self._orientation_style = GestureLibrary.ORIENTATION_SENSITIVE self._sequence_type = GestureLibrary.SEQUENCE_SENSITIVE def store(self) -> bool: """存储手势库""" return True def load(self) -> bool: """加载手势库""" return True def getGestureEntries(self) -> List[str]: """获取手势条目""" return list(self._gestures.keys()) def getGestures(self, entry_name: str) -> List[Gesture]: """获取手势列表""" return self._gestures.get(entry_name, []) def recognize(self, gesture: Gesture) -> List[Prediction]: """识别手势""" return [] def removeGesture(self, entry_name: str, gesture: Gesture) -> None: """移除手势""" if entry_name in self._gestures and gesture in self._gestures[entry_name]: self._gestures[entry_name].remove(gesture) def removeEntry(self, entry_name: str) -> None: """移除条目""" self._gestures.pop(entry_name, None) def addGesture(self, entry_name: str, gesture: Gesture) -> None: """添加手势""" if entry_name not in self._gestures: self._gestures[entry_name] = [] self._gestures[entry_name].append(gesture) def isReadOnly(self) -> bool: """检查是否只读""" return self._read_only def setOrientationStyle(self, style: int) -> None: self._orientation_style = style def getOrientationStyle(self) -> int: return self._orientation_style def setSequenceType(self, type_: int) -> None: self._sequence_type = type_ def getSequenceType(self) -> int: return self._sequence_type # ═══════════════════════════════════════════════════════════════ # GestureStore # ═══════════════════════════════════════════════════════════════ class GestureStore(Stub): """GestureStore — 手势存储 (桌面存根)""" def __init__(self): super().__init__() self._gestures: Dict[str, List[Gesture]] = {} self._orientation_style = GestureLibrary.ORIENTATION_SENSITIVE self._sequence_type = GestureLibrary.SEQUENCE_SENSITIVE self._change_listener: Optional[Any] = None def addGesture(self, entry_name: str, gesture: Gesture) -> None: if entry_name not in self._gestures: self._gestures[entry_name] = [] self._gestures[entry_name].append(gesture) def removeGesture(self, entry_name: str, gesture: Gesture) -> None: if entry_name in self._gestures and gesture in self._gestures[entry_name]: self._gestures[entry_name].remove(gesture) def removeEntry(self, entry_name: str) -> None: self._gestures.pop(entry_name, None) def getGestureEntries(self) -> List[str]: return list(self._gestures.keys()) def getGestures(self, entry_name: str) -> List[Gesture]: return self._gestures.get(entry_name, []) def recognize(self, gesture: Gesture) -> List[Prediction]: return [] def recognizeAsync(self, gesture: Gesture) -> None: pass def setOrientationStyle(self, style: int) -> None: self._orientation_style = style def getOrientationStyle(self) -> int: return self._orientation_style def setSequenceType(self, type_: int) -> None: self._sequence_type = type_ def getSequenceType(self) -> int: return self._sequence_type def load(self, store_file: Any) -> None: pass def save(self, store_file: Any) -> None: pass def setChangeListener(self, listener: Any) -> None: self._change_listener = listener def getChangeListener(self) -> Any: return self._change_listener # ═══════════════════════════════════════════════════════════════ # GestureUtils # ═══════════════════════════════════════════════════════════════ class GestureUtils(Stub): """GestureUtils — 手势工具类""" def __init__(self): super().__init__() @staticmethod def copy(gesture: Gesture) -> Gesture: """复制手势""" new_gesture = Gesture() for stroke in gesture.getStrokes(): new_stroke = GestureStroke(stroke.getPoints()) new_gesture.addStroke(new_stroke) return new_gesture @staticmethod def boundingBox(gesture: Gesture) -> OrientedBoundingBox: """计算手势包围盒""" bbox = gesture.getBoundingBox() cx = (bbox[0] + bbox[2]) / 2.0 if bbox[2] != bbox[0] else 0.0 cy = (bbox[1] + bbox[3]) / 2.0 if bbox[3] != bbox[1] else 0.0 w = bbox[2] - bbox[0] h = bbox[3] - bbox[1] return OrientedBoundingBox(cx, cy, w, h, 0.0) @staticmethod def boundingBox_stroke(stroke: GestureStroke) -> OrientedBoundingBox: """计算笔划包围盒""" return stroke.computeOrientedBoundingBox() @staticmethod def spatialSampling(gesture: Gesture, num_samples: int) -> List[float]: """空间采样""" return [] @staticmethod def temporalSampling(gesture: Gesture, num_samples: int) -> List[float]: """时间采样""" return [] @staticmethod def computeOrientedBoundingBox(points: List[GesturePoint]) -> OrientedBoundingBox: """计算有向包围盒""" if not points: return OrientedBoundingBox() xs = [p.x for p in points] ys = [p.y for p in points] cx = sum(xs) / len(xs) cy = sum(ys) / len(ys) w = max(xs) - min(xs) h = max(ys) - min(ys) return OrientedBoundingBox(cx, cy, w, h, 0.0)