1#!/usr/bin/env python3
  2"""
  3Slide Animation - Slide elements in from edges with overshoot/bounce.
  4
  5Creates smooth entrance and exit animations.
  6"""
  7
  8import sys
  9from pathlib import Path
 10
 11sys.path.append(str(Path(__file__).parent.parent))
 12
 13from PIL import Image
 14from core.gif_builder import GIFBuilder
 15from core.frame_composer import create_blank_frame, draw_emoji_enhanced
 16from core.easing import interpolate
 17
 18
 19def create_slide_animation(
 20    object_type: str = 'emoji',
 21    object_data: dict | None = None,
 22    num_frames: int = 30,
 23    direction: str = 'left',  # 'left', 'right', 'top', 'bottom'
 24    slide_type: str = 'in',  # 'in', 'out', 'across'
 25    easing: str = 'ease_out',
 26    overshoot: bool = False,
 27    final_pos: tuple[int, int] | None = None,
 28    frame_width: int = 480,
 29    frame_height: int = 480,
 30    bg_color: tuple[int, int, int] = (255, 255, 255)
 31) -> list[Image.Image]:
 32    """
 33    Create slide animation.
 34
 35    Args:
 36        object_type: 'emoji', 'text'
 37        object_data: Object configuration
 38        num_frames: Number of frames
 39        direction: Direction of slide
 40        slide_type: Type of slide (in/out/across)
 41        easing: Easing function
 42        overshoot: Add overshoot/bounce at end
 43        final_pos: Final position (None = center)
 44        frame_width: Frame width
 45        frame_height: Frame height
 46        bg_color: Background color
 47
 48    Returns:
 49        List of frames
 50    """
 51    frames = []
 52
 53    # Default object data
 54    if object_data is None:
 55        if object_type == 'emoji':
 56            object_data = {'emoji': '➡️', 'size': 100}
 57
 58    if final_pos is None:
 59        final_pos = (frame_width // 2, frame_height // 2)
 60
 61    # Calculate start and end positions based on direction
 62    size = object_data.get('size', 100) if object_type == 'emoji' else 100
 63    margin = size
 64
 65    if direction == 'left':
 66        start_pos = (-margin, final_pos[1])
 67        end_pos = final_pos if slide_type == 'in' else (frame_width + margin, final_pos[1])
 68    elif direction == 'right':
 69        start_pos = (frame_width + margin, final_pos[1])
 70        end_pos = final_pos if slide_type == 'in' else (-margin, final_pos[1])
 71    elif direction == 'top':
 72        start_pos = (final_pos[0], -margin)
 73        end_pos = final_pos if slide_type == 'in' else (final_pos[0], frame_height + margin)
 74    elif direction == 'bottom':
 75        start_pos = (final_pos[0], frame_height + margin)
 76        end_pos = final_pos if slide_type == 'in' else (final_pos[0], -margin)
 77    else:
 78        start_pos = (-margin, final_pos[1])
 79        end_pos = final_pos
 80
 81    # For 'out' type, swap start and end
 82    if slide_type == 'out':
 83        start_pos, end_pos = final_pos, end_pos
 84    elif slide_type == 'across':
 85        # Slide all the way across
 86        if direction == 'left':
 87            start_pos = (-margin, final_pos[1])
 88            end_pos = (frame_width + margin, final_pos[1])
 89        elif direction == 'right':
 90            start_pos = (frame_width + margin, final_pos[1])
 91            end_pos = (-margin, final_pos[1])
 92        elif direction == 'top':
 93            start_pos = (final_pos[0], -margin)
 94            end_pos = (final_pos[0], frame_height + margin)
 95        elif direction == 'bottom':
 96            start_pos = (final_pos[0], frame_height + margin)
 97            end_pos = (final_pos[0], -margin)
 98
 99    # Use overshoot easing if requested
100    if overshoot and slide_type == 'in':
101        easing = 'back_out'
102
103    for i in range(num_frames):
104        t = i / (num_frames - 1) if num_frames > 1 else 0
105        frame = create_blank_frame(frame_width, frame_height, bg_color)
106
107        # Calculate current position
108        x = int(interpolate(start_pos[0], end_pos[0], t, easing))
109        y = int(interpolate(start_pos[1], end_pos[1], t, easing))
110
111        # Draw object
112        if object_type == 'emoji':
113            size = object_data['size']
114            draw_emoji_enhanced(
115                frame,
116                emoji=object_data['emoji'],
117                position=(x - size // 2, y - size // 2),
118                size=size,
119                shadow=object_data.get('shadow', True)
120            )
121
122        elif object_type == 'text':
123            from core.typography import draw_text_with_outline
124            draw_text_with_outline(
125                frame,
126                text=object_data.get('text', 'SLIDE'),
127                position=(x, y),
128                font_size=object_data.get('font_size', 50),
129                text_color=object_data.get('text_color', (0, 0, 0)),
130                outline_color=object_data.get('outline_color', (255, 255, 255)),
131                outline_width=3,
132                centered=True
133            )
134
135        frames.append(frame)
136
137    return frames
138
139
140def create_multi_slide(
141    objects: list[dict],
142    num_frames: int = 30,
143    stagger_delay: int = 3,
144    frame_width: int = 480,
145    frame_height: int = 480,
146    bg_color: tuple[int, int, int] = (255, 255, 255)
147) -> list[Image.Image]:
148    """
149    Create animation with multiple objects sliding in sequence.
150
151    Args:
152        objects: List of object configs with 'type', 'data', 'direction', 'final_pos'
153        num_frames: Number of frames
154        stagger_delay: Frames between each object starting
155        frame_width: Frame width
156        frame_height: Frame height
157        bg_color: Background color
158
159    Returns:
160        List of frames
161    """
162    frames = []
163
164    for i in range(num_frames):
165        frame = create_blank_frame(frame_width, frame_height, bg_color)
166
167        for idx, obj in enumerate(objects):
168            # Calculate when this object starts moving
169            start_frame = idx * stagger_delay
170            if i < start_frame:
171                continue  # Object hasn't started yet
172
173            # Calculate progress for this object
174            obj_frame = i - start_frame
175            obj_duration = num_frames - start_frame
176            if obj_duration <= 0:
177                continue
178
179            t = obj_frame / obj_duration
180
181            # Get object properties
182            obj_type = obj.get('type', 'emoji')
183            obj_data = obj.get('data', {'emoji': '➡️', 'size': 80})
184            direction = obj.get('direction', 'left')
185            final_pos = obj.get('final_pos', (frame_width // 2, frame_height // 2))
186            easing = obj.get('easing', 'back_out')
187
188            # Calculate position
189            size = obj_data.get('size', 80)
190            margin = size
191
192            if direction == 'left':
193                start_x = -margin
194                end_x = final_pos[0]
195                y = final_pos[1]
196            elif direction == 'right':
197                start_x = frame_width + margin
198                end_x = final_pos[0]
199                y = final_pos[1]
200            elif direction == 'top':
201                x = final_pos[0]
202                start_y = -margin
203                end_y = final_pos[1]
204            elif direction == 'bottom':
205                x = final_pos[0]
206                start_y = frame_height + margin
207                end_y = final_pos[1]
208            else:
209                start_x = -margin
210                end_x = final_pos[0]
211                y = final_pos[1]
212
213            # Interpolate position
214            if direction in ['left', 'right']:
215                x = int(interpolate(start_x, end_x, t, easing))
216            else:
217                y = int(interpolate(start_y, end_y, t, easing))
218
219            # Draw object
220            if obj_type == 'emoji':
221                draw_emoji_enhanced(
222                    frame,
223                    emoji=obj_data['emoji'],
224                    position=(x - size // 2, y - size // 2),
225                    size=size,
226                    shadow=False
227                )
228
229        frames.append(frame)
230
231    return frames
232
233
234# Example usage
235if __name__ == '__main__':
236    print("Creating slide animations...")
237
238    builder = GIFBuilder(width=480, height=480, fps=20)
239
240    # Example 1: Slide in from left with overshoot
241    frames = create_slide_animation(
242        object_type='emoji',
243        object_data={'emoji': '➡️', 'size': 100},
244        num_frames=30,
245        direction='left',
246        slide_type='in',
247        overshoot=True
248    )
249    builder.add_frames(frames)
250    builder.save('slide_in_left.gif', num_colors=128)
251
252    # Example 2: Slide across
253    builder.clear()
254    frames = create_slide_animation(
255        object_type='emoji',
256        object_data={'emoji': '🚀', 'size': 80},
257        num_frames=40,
258        direction='left',
259        slide_type='across',
260        easing='ease_in_out'
261    )
262    builder.add_frames(frames)
263    builder.save('slide_across.gif', num_colors=128)
264
265    # Example 3: Multiple objects sliding in
266    builder.clear()
267    objects = [
268        {
269            'type': 'emoji',
270            'data': {'emoji': '🎯', 'size': 60},
271            'direction': 'left',
272            'final_pos': (120, 240)
273        },
274        {
275            'type': 'emoji',
276            'data': {'emoji': '🎪', 'size': 60},
277            'direction': 'right',
278            'final_pos': (240, 240)
279        },
280        {
281            'type': 'emoji',
282            'data': {'emoji': '🎨', 'size': 60},
283            'direction': 'top',
284            'final_pos': (360, 240)
285        }
286    ]
287    frames = create_multi_slide(objects, num_frames=50, stagger_delay=5)
288    builder.add_frames(frames)
289    builder.save('slide_multi.gif', num_colors=128)
290
291    print("Created slide animations!")