1 """

2 Factory for creating interaction commands based on interaction type.

3

4 This implements the Strategy pattern, allowing different command builders

5 to be registered and used based on the interaction type.

6 """

7

8 from typing import Optional, Dict, Any

9 from pyPhotoAlbum.models import BaseLayoutElement

10 from .interaction_command_builders import (

11 CommandBuilder,

12 MoveCommandBuilder,

13 ResizeCommandBuilder,

14 RotateCommandBuilder,

15 ImagePanCommandBuilder,

16 )

17

18

19 class InteractionCommandFactory:

20 """

21 Factory for creating commands from interaction data.

22

23 Uses the Strategy pattern to delegate command creation to

24 specialized builder classes based on interaction type.

25 """

26

27 def __init__(self):

28 """Initialize factory with default builders."""

29 self._builders: Dict[str, CommandBuilder] = {}

30 self._register_default_builders()

31

32 def _register_default_builders(self):

33 """Register the default command builders."""

34 self.register_builder("move", MoveCommandBuilder())

35 self.register_builder("resize", ResizeCommandBuilder())

36 self.register_builder("rotate", RotateCommandBuilder())

37 self.register_builder("image_pan", ImagePanCommandBuilder())

38

39 def register_builder(self, interaction_type: str, builder: CommandBuilder):

40 """

41 Register a command builder for an interaction type.

42

43 Args:

44 interaction_type: The type of interaction (e.g., 'move', 'resize')

45 builder: The CommandBuilder instance to handle this type

46 """

47 self._builders[interaction_type] = builder

48

49 def create_command(

50 self, interaction_type: str, element: BaseLayoutElement, start_state: dict, **kwargs

51 ) -> Optional[Any]:

52 """

53 Create a command based on interaction type and state changes.

54

55 Args:

56 interaction_type: Type of interaction ('move', 'resize', etc.)

57 element: The element that was interacted with

58 start_state: Dictionary containing initial state values

59 **kwargs: Additional context for command creation

60

61 Returns:

62 Command object if changes warrant it, None otherwise

63 """

64 builder = self._builders.get(interaction_type)

65

66 if builder is None:

67 print(f"Warning: No builder registered for interaction type '{interaction_type}'")

68 return None

69

70 if not builder.can_build(element, start_state, **kwargs):

71 return None

72

73 return builder.build(element, start_state, **kwargs)

74

75 def has_builder(self, interaction_type: str) -> bool:

76 """Check if a builder is registered for the given interaction type."""

77 return interaction_type in self._builders

78

79 def get_supported_types(self) -> list:

80 """Get list of supported interaction types."""

81 return list(self._builders.keys())

82

83

84 class InteractionState:

85 """

86 Value object representing the state of an interaction.

87

88 This simplifies passing interaction data around and makes

89 the code more maintainable.

90 """

91

92 def __init__(

93 self,

94 element: Optional[BaseLayoutElement] = None,

95 interaction_type: Optional[str] = None,

96 position: Optional[tuple] = None,

97 size: Optional[tuple] = None,

98 rotation: Optional[float] = None,

99 crop_info: Optional[tuple] = None,

100 ):

101 """

102 Initialize interaction state.

103

104 Args:

105 element: The element being interacted with

106 interaction_type: Type of interaction

107 position: Initial position

108 size: Initial size

109 rotation: Initial rotation

110 crop_info: Initial crop info (for images)

111 """

112 self.element = element

113 self.interaction_type = interaction_type

114 self.position = position

115 self.size = size

116 self.rotation = rotation

117 self.crop_info = crop_info

118

119 def to_dict(self) -> dict:

120 """

121 Convert state to dictionary for command builders.

122

123 Returns:

124 Dict with non-None state values

125 """

126 state: Dict[str, Any] = {}

127 if self.position is not None:

128 state["position"] = self.position

129 if self.size is not None:

130 state["size"] = self.size

131 if self.rotation is not None:

132 state["rotation"] = self.rotation

133 if self.crop_info is not None:

134 state["crop_info"] = self.crop_info

135 return state

136

137 def is_valid(self) -> bool:

138 """Check if state has required fields for command creation."""

139 return self.element is not None and self.interaction_type is not None

140

141 def clear(self):

142 """Clear all state values."""

143 self.element = None

144 self.interaction_type = None

145 self.position = None

146 self.size = None

147 self.rotation = None

148 self.crop_info = None