handanim 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. handanim/__init__.py +96 -0
  2. handanim/animations/__init__.py +20 -0
  3. handanim/animations/camera.py +79 -0
  4. handanim/animations/color_transition.py +46 -0
  5. handanim/animations/fade.py +60 -0
  6. handanim/animations/rotate.py +31 -0
  7. handanim/animations/sketch.py +143 -0
  8. handanim/animations/translate.py +66 -0
  9. handanim/animations/zoom.py +52 -0
  10. handanim/core/__init__.py +16 -0
  11. handanim/core/animation.py +106 -0
  12. handanim/core/cache.py +90 -0
  13. handanim/core/draw_ops.py +571 -0
  14. handanim/core/drawable.py +205 -0
  15. handanim/core/scene.py +797 -0
  16. handanim/core/styles.py +103 -0
  17. handanim/core/tikz_renderer.py +290 -0
  18. handanim/core/utils.py +144 -0
  19. handanim/core/viewport.py +59 -0
  20. handanim/primitives/__init__.py +39 -0
  21. handanim/primitives/arrow.py +195 -0
  22. handanim/primitives/curves.py +131 -0
  23. handanim/primitives/ellipse.py +291 -0
  24. handanim/primitives/eraser.py +58 -0
  25. handanim/primitives/flowchart.py +382 -0
  26. handanim/primitives/hershey_constants.py +269 -0
  27. handanim/primitives/lines.py +164 -0
  28. handanim/primitives/math.py +379 -0
  29. handanim/primitives/polygons.py +202 -0
  30. handanim/primitives/raster_image.py +102 -0
  31. handanim/primitives/table.py +265 -0
  32. handanim/primitives/text.py +236 -0
  33. handanim/primitives/vector_svg.py +172 -0
  34. handanim/stylings/__init__.py +0 -0
  35. handanim/stylings/color.py +52 -0
  36. handanim/stylings/fillpatterns.py +209 -0
  37. handanim/stylings/fonts.py +40 -0
  38. handanim/stylings/strokes.py +88 -0
  39. handanim/stylings/utils.py +140 -0
  40. handanim-0.1.0.dist-info/LICENSE +21 -0
  41. handanim-0.1.0.dist-info/METADATA +243 -0
  42. handanim-0.1.0.dist-info/RECORD +47 -0
  43. handanim-0.1.0.dist-info/WHEEL +4 -0
  44. handanim_ai/__init__.py +0 -0
  45. handanim_ai/models.py +42 -0
  46. handanim_ai/prompts/big_picture.txt +44 -0
  47. handanim_ai/prompts/codegen.txt +116 -0
handanim/__init__.py ADDED
@@ -0,0 +1,96 @@
1
+ from .core import (
2
+ AnimationEvent,
3
+ AnimationEventType,
4
+ CompositeAnimationEvent,
5
+ Drawable,
6
+ DrawableGroup,
7
+ FillStyle,
8
+ Scene,
9
+ SketchStyle,
10
+ StrokeStyle,
11
+ )
12
+ from .primitives import (
13
+ Arrow,
14
+ Circle,
15
+ Curve,
16
+ CurvedArrow,
17
+ Ellipse,
18
+ Eraser,
19
+ Flowchart,
20
+ FlowchartConnector,
21
+ FlowchartDiamond,
22
+ FlowchartNode,
23
+ Line,
24
+ LinearPath,
25
+ Math,
26
+ NGon,
27
+ Polygon,
28
+ Rectangle,
29
+ RoundedRectangle,
30
+ RoundedSquare,
31
+ Square,
32
+ Table,
33
+ TableRevealEvent,
34
+ Text,
35
+ VectorSVG,
36
+ )
37
+ from .animations import (
38
+ CameraAnimation,
39
+ ColorTransitionAnimation,
40
+ FadeInAnimation,
41
+ FadeOutAnimation,
42
+ RotateAnimation,
43
+ SketchAnimation,
44
+ TranslateFromAnimation,
45
+ TranslateToAnimation,
46
+ ZoomInAnimation,
47
+ ZoomOutAnimation,
48
+ )
49
+
50
+ __all__ = [
51
+ # Core
52
+ "Scene",
53
+ "StrokeStyle",
54
+ "SketchStyle",
55
+ "FillStyle",
56
+ "Drawable",
57
+ "DrawableGroup",
58
+ "AnimationEvent",
59
+ "AnimationEventType",
60
+ "CompositeAnimationEvent",
61
+ # Primitives
62
+ "Line",
63
+ "LinearPath",
64
+ "Arrow",
65
+ "CurvedArrow",
66
+ "Curve",
67
+ "Rectangle",
68
+ "RoundedRectangle",
69
+ "Square",
70
+ "RoundedSquare",
71
+ "Polygon",
72
+ "NGon",
73
+ "Ellipse",
74
+ "Circle",
75
+ "Text",
76
+ "Math",
77
+ "Eraser",
78
+ "VectorSVG",
79
+ "FlowchartNode",
80
+ "FlowchartDiamond",
81
+ "FlowchartConnector",
82
+ "Flowchart",
83
+ "Table",
84
+ "TableRevealEvent",
85
+ # Animations
86
+ "SketchAnimation",
87
+ "FadeInAnimation",
88
+ "FadeOutAnimation",
89
+ "ZoomInAnimation",
90
+ "ZoomOutAnimation",
91
+ "TranslateFromAnimation",
92
+ "TranslateToAnimation",
93
+ "RotateAnimation",
94
+ "ColorTransitionAnimation",
95
+ "CameraAnimation",
96
+ ]
@@ -0,0 +1,20 @@
1
+ from .camera import CameraAnimation
2
+ from .color_transition import ColorTransitionAnimation
3
+ from .fade import FadeInAnimation, FadeOutAnimation
4
+ from .rotate import RotateAnimation
5
+ from .sketch import SketchAnimation
6
+ from .translate import TranslateFromAnimation, TranslateToAnimation
7
+ from .zoom import ZoomInAnimation, ZoomOutAnimation
8
+
9
+ __all__ = [
10
+ "SketchAnimation",
11
+ "FadeInAnimation",
12
+ "FadeOutAnimation",
13
+ "ZoomInAnimation",
14
+ "ZoomOutAnimation",
15
+ "TranslateFromAnimation",
16
+ "TranslateToAnimation",
17
+ "RotateAnimation",
18
+ "ColorTransitionAnimation",
19
+ "CameraAnimation",
20
+ ]
@@ -0,0 +1,79 @@
1
+
2
+ from ..core.animation import AnimationEvent, AnimationEventType
3
+ from ..core.draw_ops import OpsSet
4
+ from ..core.viewport import Viewport
5
+
6
+
7
+ class CameraAnimation(AnimationEvent):
8
+ """
9
+ Animates the Scene viewport (pan and/or zoom) over time.
10
+
11
+ Instead of touching any drawable's OpsSet, this animation changes the world
12
+ ranges that the Viewport maps to the screen — the "camera" moves, not the content.
13
+
14
+ Args:
15
+ start_time: When the animation begins (seconds).
16
+ duration: Length of the animation (seconds).
17
+ easing_fun: Optional easing function applied to progress.
18
+ data: Dict with optional keys:
19
+ - "from_xrange" (tuple[float, float]): World x range at progress=0.
20
+ Defaults to wherever the camera currently is.
21
+ - "from_yrange" (tuple[float, float]): World y range at progress=0.
22
+ Defaults to wherever the camera currently is.
23
+ - "to_xrange" (tuple[float, float]): World x range at progress=1.
24
+ Defaults to from_xrange (no movement on x).
25
+ - "to_yrange" (tuple[float, float]): World y range at progress=1.
26
+ Defaults to from_yrange (no movement on y).
27
+
28
+ Usage::
29
+
30
+ scene.add_camera(CameraAnimation(
31
+ start_time=5, duration=3,
32
+ data={
33
+ "to_xrange": (400, 800),
34
+ "to_yrange": (200, 600),
35
+ }
36
+ ))
37
+ """
38
+
39
+ def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
40
+ super().__init__(AnimationEventType.MUTATION, start_time, duration, easing_fun, data)
41
+
42
+ def _apply(self, opsset: OpsSet, progress: float) -> OpsSet:
43
+ # CameraAnimation operates on the Viewport, not on OpsSets.
44
+ # Returning the opsset unchanged makes it safe if mistakenly used with scene.add().
45
+ return opsset
46
+
47
+ def apply_to_viewport(self, current: Viewport, progress: float) -> Viewport:
48
+ """
49
+ Return a new Viewport interpolated toward the target world ranges.
50
+
51
+ Args:
52
+ current: The viewport state immediately before this event (used as
53
+ the from_* default when not explicitly specified).
54
+ progress: Animation progress from 0.0 to 1.0.
55
+
56
+ Returns:
57
+ A new Viewport instance with interpolated world ranges.
58
+ """
59
+ from_xrange: tuple[float, float] = self.data.get("from_xrange", current.world_xrange)
60
+ from_yrange: tuple[float, float] = self.data.get("from_yrange", current.world_yrange)
61
+ to_xrange: tuple[float, float] = self.data.get("to_xrange", from_xrange)
62
+ to_yrange: tuple[float, float] = self.data.get("to_yrange", from_yrange)
63
+
64
+ new_xrange = (
65
+ (1 - progress) * from_xrange[0] + progress * to_xrange[0],
66
+ (1 - progress) * from_xrange[1] + progress * to_xrange[1],
67
+ )
68
+ new_yrange = (
69
+ (1 - progress) * from_yrange[0] + progress * to_yrange[0],
70
+ (1 - progress) * from_yrange[1] + progress * to_yrange[1],
71
+ )
72
+
73
+ return Viewport(
74
+ world_xrange=new_xrange,
75
+ world_yrange=new_yrange,
76
+ screen_width=current.screen_width,
77
+ screen_height=current.screen_height,
78
+ margin=current.margin,
79
+ )
@@ -0,0 +1,46 @@
1
+
2
+ from ..core.animation import AnimationEvent, AnimationEventType
3
+ from ..core.draw_ops import Ops, OpsSet, OpsType
4
+
5
+
6
+ def _lerp_color(
7
+ a: tuple[float, float, float],
8
+ b: tuple[float, float, float],
9
+ t: float,
10
+ ) -> tuple[float, float, float]:
11
+ return tuple((1 - t) * ca + t * cb for ca, cb in zip(a, b, strict=False)) # type: ignore[return-value]
12
+
13
+
14
+ class ColorTransitionAnimation(AnimationEvent):
15
+ """
16
+ Interpolates every SET_PEN color in an OpsSet from `start_color` to `end_color`.
17
+
18
+ At progress=0 all strokes are rendered with `start_color`; at progress=1 they
19
+ use `end_color`. The fill color (if present) is interpolated identically.
20
+
21
+ Args:
22
+ start_time: When the animation begins (seconds).
23
+ duration: Length of the animation (seconds).
24
+ easing_fun: Optional easing function.
25
+ data: Dict with keys:
26
+ - "start_color" (tuple[float,float,float]): RGB at progress 0. Required.
27
+ - "end_color" (tuple[float,float,float]): RGB at progress 1. Required.
28
+ """
29
+
30
+ def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
31
+ super().__init__(AnimationEventType.MUTATION, start_time, duration, easing_fun, data)
32
+ self.start_color: tuple[float, float, float] = self.data.get("start_color", (0.0, 0.0, 0.0))
33
+ self.end_color: tuple[float, float, float] = self.data.get("end_color", (1.0, 1.0, 1.0))
34
+
35
+ def _apply(self, opsset: OpsSet, progress: float) -> OpsSet:
36
+ current_color = _lerp_color(self.start_color, self.end_color, progress)
37
+ new_ops = []
38
+ for op in opsset.opsset:
39
+ if op.type == OpsType.SET_PEN:
40
+ pen_data = dict(op.data).copy()
41
+ if "color" in pen_data:
42
+ pen_data["color"] = current_color
43
+ new_ops.append(Ops(type=OpsType.SET_PEN, data=pen_data))
44
+ else:
45
+ new_ops.append(op)
46
+ return OpsSet(initial_set=new_ops)
@@ -0,0 +1,60 @@
1
+ from ..core.animation import AnimationEvent, AnimationEventType
2
+ from ..core.draw_ops import Ops, OpsSet, OpsType
3
+
4
+
5
+ class FadeInAnimation(AnimationEvent):
6
+ """
7
+ A class representing a fade-in animation event that gradually increases the opacity of elements.
8
+
9
+ This animation applies a progressive opacity change from 0 to 1 over a specified duration,
10
+ creating a smooth fade-in effect for graphical elements.
11
+
12
+ Args:
13
+ start_time (float, optional): The time at which the animation begins. Defaults to 0.
14
+ duration (float, optional): The total duration of the animation. Defaults to 0.
15
+ easing_fun (callable, optional): An optional function to modify the animation's progress curve. Defaults to None.
16
+ data (dict, optional): Additional data associated with the animation. Defaults to None.
17
+ """
18
+
19
+ def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
20
+ super().__init__(AnimationEventType.CREATION, start_time, duration, easing_fun, data)
21
+
22
+ def _opsset_apply(self, opsset: OpsSet, progress: float):
23
+ current_ops_list = []
24
+ for op in opsset.opsset:
25
+ if op.type == OpsType.SET_PEN:
26
+ modified_data = {k: progress if k == "opacity" else v for k, v in op.data.items()}
27
+ current_ops_list.append(Ops(type=OpsType.SET_PEN, data=modified_data, partial=op.partial, meta=op.meta))
28
+ elif op.type == OpsType.IMAGE and isinstance(op.data, dict):
29
+ modified_data = dict(op.data)
30
+ modified_data["opacity"] = progress
31
+ current_ops_list.append(Ops(type=OpsType.IMAGE, data=modified_data, partial=op.partial, meta=op.meta))
32
+ else:
33
+ current_ops_list.append(op)
34
+ new_opsset = OpsSet(initial_set=current_ops_list)
35
+ return new_opsset
36
+
37
+ def _apply(self, opsset: OpsSet, progress: float):
38
+ return self._opsset_apply(opsset, progress)
39
+
40
+
41
+ class FadeOutAnimation(FadeInAnimation):
42
+ """
43
+ A class representing a fade-out animation event that gradually decreases the opacity of elements.
44
+
45
+ This animation applies a progressive opacity change from 1 to 0 over a specified duration,
46
+ creating a smooth fade-out effect for graphical elements.
47
+
48
+ Args:
49
+ start_time (float, optional): The time at which the animation begins. Defaults to 0.
50
+ duration (float, optional): The total duration of the animation. Defaults to 0.
51
+ easing_fun (callable, optional): An optional function to modify the animation's progress curve. Defaults to None.
52
+ data (dict, optional): Additional data associated with the animation. Defaults to None.
53
+ """
54
+
55
+ def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
56
+ super().__init__(start_time, duration, easing_fun, data)
57
+ self.type = AnimationEventType.DELETION # override the type
58
+
59
+ def _apply(self, opsset, progress):
60
+ return super()._apply(opsset, 1 - progress)
@@ -0,0 +1,31 @@
1
+
2
+ from ..core.animation import AnimationEvent, AnimationEventType
3
+ from ..core.draw_ops import OpsSet
4
+
5
+
6
+ class RotateAnimation(AnimationEvent):
7
+ """
8
+ Animates a rotation of an OpsSet from 0 to `angle` degrees over the duration.
9
+
10
+ The rotation is applied around the center of gravity by default, or around
11
+ a fixed `center` point if provided in data.
12
+
13
+ Args:
14
+ start_time: When the animation begins (seconds).
15
+ duration: Length of the animation (seconds).
16
+ easing_fun: Optional easing function.
17
+ data: Dict with optional keys:
18
+ - "angle" (float): Total rotation angle in degrees. Default 360.
19
+ - "center" (tuple[float, float]): Fixed pivot point. Default: center of gravity.
20
+ """
21
+
22
+ def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
23
+ super().__init__(AnimationEventType.MUTATION, start_time, duration, easing_fun, data)
24
+ self.angle = self.data.get("angle", 360)
25
+ self.center: tuple[float, float] | None = self.data.get("center", None)
26
+
27
+ def _apply(self, opsset: OpsSet, progress: float) -> OpsSet:
28
+ new_opsset = OpsSet(initial_set=opsset.opsset)
29
+ pivot = self.center if self.center is not None else new_opsset.get_center_of_gravity()
30
+ new_opsset.rotate(self.angle * progress, center_of_rotation=pivot)
31
+ return new_opsset
@@ -0,0 +1,143 @@
1
+ import numpy as np
2
+
3
+ from ..core.animation import AnimationEvent, AnimationEventType
4
+ from ..core.draw_ops import Ops, OpsSet, OpsType
5
+ from ..core.styles import FillStyle
6
+ from ..primitives.ellipse import GlowDot
7
+
8
+
9
+ class SketchAnimation(AnimationEvent):
10
+ """
11
+ A class representing a sketch animation event that progressively renders drawing operations.
12
+
13
+ This animation supports partial rendering of drawing and fill operations with configurable timing
14
+ and optional glowing dot effects. It allows for smooth, incremental visualization of drawing sequences.
15
+
16
+ Attributes:
17
+ wait_before_fill (float): Delay in seconds before starting fill animation, capped at half the total duration.
18
+
19
+ Methods:
20
+ get_partial_sketch: Calculates a partial OpsSet representing the current sketching progress.
21
+ apply: Applies the sketch animation to a given OpsSet at a specific progress point.
22
+ """
23
+
24
+ def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
25
+ super().__init__(AnimationEventType.CREATION, start_time, duration, easing_fun, data)
26
+ if data is None:
27
+ data = {}
28
+ self.wait_before_fill = data.get(
29
+ "wait_before_fill", 0
30
+ ) # seconds to wait before starting fill animation, if any
31
+ self.wait_before_fill = min(self.wait_before_fill, self.duration / 2) # maximum wait is half of the duration
32
+
33
+ def get_partial_sketch(self, opsset: OpsSet, progress: float) -> OpsSet:
34
+ """
35
+ Calculate a partial OpsSet representing the sketching progress of an operation set.
36
+
37
+ Args:
38
+ OpsSet: The full drawn opsset on which to apply partial sketching
39
+ progress (float): The progress of sketching, ranging from 0.0 to 1.0.
40
+
41
+ Returns:
42
+ OpsSet: A new OpsSet containing the operations up to the specified progress point,
43
+ with the last operation potentially being partially completed.
44
+ """
45
+ base_ops = opsset.opsset
46
+ draw_ops_count = 0 # number of opssets used for drawing
47
+ fill_ops_count = 0 # number of opssets used for filling
48
+ fill_mode = False
49
+ for op in base_ops:
50
+ if op.type not in Ops.SETUP_OPS_TYPES:
51
+ if fill_mode:
52
+ fill_ops_count += 1
53
+ else:
54
+ draw_ops_count += 1
55
+ elif op.type is OpsType.METADATA and op.data.get("drawing_mode") == "fill":
56
+ fill_mode = True
57
+ total_ops_count = draw_ops_count + fill_ops_count # total count of drawing ops
58
+
59
+ # calculate per ops how much seconds is assigned
60
+ per_op_time = (self.duration - self.wait_before_fill) / total_ops_count
61
+
62
+ # calculate the drawing and filling times
63
+ draw_end_time = per_op_time * draw_ops_count
64
+ fill_start_time = draw_end_time + self.wait_before_fill
65
+
66
+ # based on progress, find out the total number of ops to be drawn
67
+ if progress * self.duration <= draw_end_time:
68
+ # drawing is not completed yet
69
+ draw_progress = progress * self.duration / draw_end_time
70
+ n_active = int(draw_progress * draw_ops_count)
71
+ elif progress * self.duration <= fill_start_time:
72
+ # drawing is completed, but filling is not started yet
73
+ n_active = draw_ops_count
74
+ else:
75
+ # filling is in progress
76
+ fill_progress = (progress * self.duration - fill_start_time) / (self.duration - fill_start_time)
77
+ n_active = draw_ops_count + int(fill_progress * fill_ops_count)
78
+
79
+ # create a new opsset with the partial ops
80
+ counter = 0
81
+ last_op = None
82
+ new_opsset = OpsSet(initial_set=[]) # initially start with blank opsset
83
+ for op in base_ops:
84
+ if op.type not in Ops.SETUP_OPS_TYPES and counter < n_active:
85
+ new_opsset.add(op)
86
+ counter += 1
87
+ elif counter < n_active:
88
+ # other operations keep adding, but don't increase counter
89
+ new_opsset.add(op)
90
+ else:
91
+ last_op = op # the last operation for which it stopped
92
+ break
93
+ if last_op is not None and (progress * total_ops_count - n_active) > 0:
94
+ # need to calculate it partially
95
+ new_opsset.add(
96
+ Ops(
97
+ type=last_op.type,
98
+ data=last_op.data,
99
+ partial=progress * total_ops_count - n_active,
100
+ )
101
+ )
102
+ return new_opsset
103
+
104
+ def _apply(self, opsset: OpsSet, progress: float):
105
+ """
106
+ Apply the sketch animation to an operation set with a given progress.
107
+
108
+ This method progressively renders the operations in the opsset based on the current animation progress,
109
+ with an optional glowing dot effect to highlight the current drawing point.
110
+
111
+ Args:
112
+ opsset (OpsSet): The set of operations to animate
113
+ progress (float): Animation progress from 0.0 to 1.0
114
+
115
+ Returns:
116
+ OpsSet: A new operation set with partially or fully rendered operations
117
+ """
118
+ new_opsset = OpsSet(initial_set=[])
119
+ if progress > 0:
120
+ sketching_opssets = self.get_partial_sketch(opsset, progress)
121
+ new_opsset.extend(sketching_opssets)
122
+ # now we can optionally add a glowing dot for the sketching operation
123
+ if self.data.get("glowing_dot") and progress < 1:
124
+ # the glowing dot should disappear at the end of the sketch
125
+ glow_dot_data = self.data.get("glowing_dot")
126
+ if not isinstance(glow_dot_data, dict):
127
+ glow_dot_data = {}
128
+ # we need to draw glowing dot
129
+ cx, cy = sketching_opssets.get_current_point() # get the current point based on sketching
130
+
131
+ breathing_factor = 1 + 0.05 * np.sin(
132
+ 2 * np.pi * progress * glow_dot_data.get("frequency", 5)
133
+ ) # have a subtle breathing effect to increase or decrease glow
134
+ dot = GlowDot(
135
+ center=(cx, cy),
136
+ radius=glow_dot_data.get("radius", 5) * breathing_factor,
137
+ fill_style=FillStyle(color=glow_dot_data.get("color", (0.5, 0.5, 0.5))),
138
+ )
139
+ new_opsset.extend(dot.draw()) # add this dot at the end of current path
140
+ else:
141
+ # progress is 0, so nothing should be drawn
142
+ pass
143
+ return new_opsset
@@ -0,0 +1,66 @@
1
+ from ..core.animation import AnimationEvent, AnimationEventType
2
+ from ..core.draw_ops import OpsSet
3
+
4
+
5
+ class TranslateToAnimation(AnimationEvent):
6
+ """
7
+ A class representing a translate to a point animation event.
8
+
9
+ This animation translates an OpsSet from its current center of gravity to a specified point
10
+ over the course of the animation's duration, using an optional easing function.
11
+
12
+ Args:
13
+ start_time (float, optional): The start time of the animation. Defaults to 0.
14
+ duration (float, optional): The duration of the animation. Defaults to 0.
15
+ easing_fun (callable, optional): An optional easing function to modify animation progress. Defaults to None.
16
+ data (dict, optional): A dictionary containing animation data, including the target 'point'. Defaults to None.
17
+
18
+ Methods:
19
+ _opsset_apply: Calculates and applies the translation of the OpsSet.
20
+ apply: Applies the translation to the given OpsSet at the specified progress.
21
+ """
22
+
23
+ def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
24
+ super().__init__(
25
+ AnimationEventType.MUTATION, start_time, duration, easing_fun, data
26
+ )
27
+
28
+ def _opsset_apply(self, opsset: OpsSet, progress: float):
29
+ new_opsset = OpsSet(initial_set=opsset.opsset)
30
+ # calculate the center of gravity for the opsset
31
+ gravity_x, gravity_y = opsset.get_center_of_gravity()
32
+ point_x, point_y = self.data.get("point", (0, 0))
33
+ cur_x, cur_y = (
34
+ progress * point_x + (1 - progress) * gravity_x,
35
+ progress * point_y + (1 - progress) * gravity_y,
36
+ )
37
+ new_opsset.translate(cur_x - gravity_x, cur_y - gravity_y)
38
+ return new_opsset
39
+
40
+ def _apply(self, opsset: OpsSet, progress: float):
41
+ return self._opsset_apply(opsset, progress)
42
+
43
+ class TranslateFromAnimation(TranslateToAnimation):
44
+ """
45
+ A class representing a translate from a point animation event.
46
+
47
+ This animation translates an OpsSet from a specified point to its current center of gravity
48
+ over the course of the animation's duration, using an optional easing function.
49
+
50
+ Inherits from TranslateToAnimation and reverses the progress to achieve the "from" translation effect.
51
+
52
+ Args:
53
+ start_time (float, optional): The start time of the animation. Defaults to 0.
54
+ duration (float, optional): The duration of the animation. Defaults to 0.
55
+ easing_fun (callable, optional): An optional easing function to modify animation progress. Defaults to None.
56
+ data (dict, optional): A dictionary containing animation data, including the starting 'point'. Defaults to None.
57
+
58
+ Methods:
59
+ apply: Applies the translation from the specified point to the OpsSet's center of gravity.
60
+ """
61
+
62
+ def __init__(self, start_time=0, duration=0, easing_fun=None, data=None):
63
+ super().__init__(start_time, duration, easing_fun, data)
64
+
65
+ def _apply(self, opsset, progress):
66
+ return super()._apply(opsset, 1 - progress)
@@ -0,0 +1,52 @@
1
+ from ..core.animation import AnimationEvent, AnimationEventType
2
+ from ..core.draw_ops import OpsSet
3
+
4
+
5
+ class ZoomInAnimation(AnimationEvent):
6
+ """
7
+ A class representing a zoom-in animation event that scales an OpsSet progressively.
8
+
9
+ This animation scales the operations set from its original size to a larger size
10
+ based on the provided progress value. It is typically used for creating or
11
+ expanding visual elements.
12
+
13
+ Args:
14
+ start_time (int, optional): The start time of the animation. Defaults to 0.
15
+ duration (int, optional): The duration of the animation. Defaults to 0.
16
+ easing_fun (callable, optional): An optional easing function to modify the animation progress. Defaults to None.
17
+ data (Any, optional): Additional data associated with the animation. Defaults to None.
18
+ """
19
+
20
+ def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
21
+ super().__init__(AnimationEventType.CREATION, start_time, duration, easing_fun, data)
22
+
23
+ def _apply_opsset(self, opsset: OpsSet, progress: float):
24
+ new_opsset = OpsSet(initial_set=opsset.opsset)
25
+ new_opsset.scale(progress)
26
+ return new_opsset
27
+
28
+ def _apply(self, opsset, progress):
29
+ return self._apply_opsset(opsset, progress)
30
+
31
+
32
+ class ZoomOutAnimation(ZoomInAnimation):
33
+ """
34
+ A class representing a zoom-out animation event that scales an OpsSet progressively.
35
+
36
+ This animation scales the operations set from its current size to a smaller size
37
+ based on the provided progress value. It is typically used for shrinking or
38
+ removing visual elements.
39
+
40
+ Args:
41
+ start_time (int, optional): The start time of the animation. Defaults to 0.
42
+ duration (int, optional): The duration of the animation. Defaults to 0.
43
+ easing_fun (callable, optional): An optional easing function to modify the animation progress. Defaults to None.
44
+ data (Any, optional): Additional data associated with the animation. Defaults to None.
45
+ """
46
+
47
+ def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
48
+ super().__init__(start_time, duration, easing_fun, data)
49
+ self.type = AnimationEventType.DELETION
50
+
51
+ def _apply(self, opsset, progress):
52
+ return super()._apply(opsset, 1 - progress)
@@ -0,0 +1,16 @@
1
+ from .animation import AnimationEvent, AnimationEventType, CompositeAnimationEvent
2
+ from .drawable import Drawable, DrawableGroup
3
+ from .scene import Scene
4
+ from .styles import FillStyle, SketchStyle, StrokeStyle
5
+
6
+ __all__ = [
7
+ "AnimationEvent",
8
+ "AnimationEventType",
9
+ "Scene",
10
+ "StrokeStyle",
11
+ "SketchStyle",
12
+ "FillStyle",
13
+ "Drawable",
14
+ "DrawableGroup",
15
+ "CompositeAnimationEvent",
16
+ ]