1 """

2 Decorators and validators for interaction change detection.

3 """

4

5 from functools import wraps

6 from typing import Optional, Tuple, Any

7

8

9 def significant_change(threshold: float = 0.1):

10 """

11 Decorator that validates if a change is significant enough to warrant a command.

12

13 Args:

14 threshold: Minimum change magnitude to be considered significant

15

16 Returns:

17 None if change is insignificant, otherwise returns the command builder result

18 """

19

20 def decorator(func):

21 @wraps(func)

22 def wrapper(*args, **kwargs):

23 result = func(*args, **kwargs)

24 if result is None:

25 return None

26 return result

27

28 return wrapper

29

30 return decorator

31

32

33 class ChangeValidator:

34 """Validates whether changes are significant enough to create commands."""

35

36 @staticmethod

37 def position_changed(

38 old_pos: Optional[Tuple[float, float]], new_pos: Optional[Tuple[float, float]], threshold: float = 0.1

39 ) -> bool:

40 """Check if position changed significantly."""

41 if old_pos is None or new_pos is None:

42 return False

43

44 dx = abs(new_pos[0] - old_pos[0])

45 dy = abs(new_pos[1] - old_pos[1])

46 return dx > threshold or dy > threshold

47

48 @staticmethod

49 def size_changed(

50 old_size: Optional[Tuple[float, float]], new_size: Optional[Tuple[float, float]], threshold: float = 0.1

51 ) -> bool:

52 """Check if size changed significantly."""

53 if old_size is None or new_size is None:

54 return False

55

56 dw = abs(new_size[0] - old_size[0])

57 dh = abs(new_size[1] - old_size[1])

58 return dw > threshold or dh > threshold

59

60 @staticmethod

61 def rotation_changed(old_rotation: Optional[float], new_rotation: Optional[float], threshold: float = 0.1) -> bool:

62 """Check if rotation changed significantly."""

63 if old_rotation is None or new_rotation is None:

64 return False

65

66 return abs(new_rotation - old_rotation) > threshold

67

68 @staticmethod

69 def crop_changed(

70 old_crop: Optional[Tuple[float, float, float, float]],

71 new_crop: Optional[Tuple[float, float, float, float]],

72 threshold: float = 0.001,

73 ) -> bool:

74 """Check if crop info changed significantly."""

75 if old_crop is None or new_crop is None:

76 return False

77

78 if old_crop == new_crop:

79 return False

80

81 return any(abs(new_crop[i] - old_crop[i]) > threshold for i in range(4))

82

83

84 class InteractionChangeDetector:

85 """Detects and quantifies changes in element properties."""

86

87 def __init__(self, threshold: float = 0.1):

88 self.threshold = threshold

89 self.validator = ChangeValidator()

90

91 def detect_position_change(self, old_pos: Tuple[float, float], new_pos: Tuple[float, float]) -> Optional[dict]:

92 """

93 Detect position change and return change info.

94

95 Returns:

96 Dict with change info if significant, None otherwise

97 """

98 if not self.validator.position_changed(old_pos, new_pos, self.threshold):

99 return None

100

101 return {

102 "old_position": old_pos,

103 "new_position": new_pos,

104 "delta_x": new_pos[0] - old_pos[0],

105 "delta_y": new_pos[1] - old_pos[1],

106 }

107

108 def detect_size_change(self, old_size: Tuple[float, float], new_size: Tuple[float, float]) -> Optional[dict]:

109 """

110 Detect size change and return change info.

111

112 Returns:

113 Dict with change info if significant, None otherwise

114 """

115 if not self.validator.size_changed(old_size, new_size, self.threshold):

116 return None

117

118 return {

119 "old_size": old_size,

120 "new_size": new_size,

121 "delta_width": new_size[0] - old_size[0],

122 "delta_height": new_size[1] - old_size[1],

123 }

124

125 def detect_rotation_change(self, old_rotation: float, new_rotation: float) -> Optional[dict]:

126 """

127 Detect rotation change and return change info.

128

129 Returns:

130 Dict with change info if significant, None otherwise

131 """

132 if not self.validator.rotation_changed(old_rotation, new_rotation, self.threshold):

133 return None

134

135 return {"old_rotation": old_rotation, "new_rotation": new_rotation, "delta_angle": new_rotation - old_rotation}

136

137 def detect_crop_change(

138 self, old_crop: Tuple[float, float, float, float], new_crop: Tuple[float, float, float, float]

139 ) -> Optional[dict]:

140 """

141 Detect crop change and return change info.

142

143 Returns:

144 Dict with change info if significant, None otherwise

145 """

146 if not self.validator.crop_changed(old_crop, new_crop, threshold=0.001):

147 return None

148

149 return {"old_crop": old_crop, "new_crop": new_crop, "delta": tuple(new_crop[i] - old_crop[i] for i in range(4))}