| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- #!/usr/bin/env python3
- """
- Easing Functions - Timing functions for smooth animations.
- Provides various easing functions for natural motion and timing.
- All functions take a value t (0.0 to 1.0) and return eased value (0.0 to 1.0).
- """
- import math
- def linear(t: float) -> float:
- """Linear interpolation (no easing)."""
- return t
- def ease_in_quad(t: float) -> float:
- """Quadratic ease-in (slow start, accelerating)."""
- return t * t
- def ease_out_quad(t: float) -> float:
- """Quadratic ease-out (fast start, decelerating)."""
- return t * (2 - t)
- def ease_in_out_quad(t: float) -> float:
- """Quadratic ease-in-out (slow start and end)."""
- if t < 0.5:
- return 2 * t * t
- return -1 + (4 - 2 * t) * t
- def ease_in_cubic(t: float) -> float:
- """Cubic ease-in (slow start)."""
- return t * t * t
- def ease_out_cubic(t: float) -> float:
- """Cubic ease-out (fast start)."""
- return (t - 1) * (t - 1) * (t - 1) + 1
- def ease_in_out_cubic(t: float) -> float:
- """Cubic ease-in-out."""
- if t < 0.5:
- return 4 * t * t * t
- return (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
- def ease_in_bounce(t: float) -> float:
- """Bounce ease-in (bouncy start)."""
- return 1 - ease_out_bounce(1 - t)
- def ease_out_bounce(t: float) -> float:
- """Bounce ease-out (bouncy end)."""
- if t < 1 / 2.75:
- return 7.5625 * t * t
- elif t < 2 / 2.75:
- t -= 1.5 / 2.75
- return 7.5625 * t * t + 0.75
- elif t < 2.5 / 2.75:
- t -= 2.25 / 2.75
- return 7.5625 * t * t + 0.9375
- else:
- t -= 2.625 / 2.75
- return 7.5625 * t * t + 0.984375
- def ease_in_out_bounce(t: float) -> float:
- """Bounce ease-in-out."""
- if t < 0.5:
- return ease_in_bounce(t * 2) * 0.5
- return ease_out_bounce(t * 2 - 1) * 0.5 + 0.5
- def ease_in_elastic(t: float) -> float:
- """Elastic ease-in (spring effect)."""
- if t == 0 or t == 1:
- return t
- return -math.pow(2, 10 * (t - 1)) * math.sin((t - 1.1) * 5 * math.pi)
- def ease_out_elastic(t: float) -> float:
- """Elastic ease-out (spring effect)."""
- if t == 0 or t == 1:
- return t
- return math.pow(2, -10 * t) * math.sin((t - 0.1) * 5 * math.pi) + 1
- def ease_in_out_elastic(t: float) -> float:
- """Elastic ease-in-out."""
- if t == 0 or t == 1:
- return t
- t = t * 2 - 1
- if t < 0:
- return -0.5 * math.pow(2, 10 * t) * math.sin((t - 0.1) * 5 * math.pi)
- return math.pow(2, -10 * t) * math.sin((t - 0.1) * 5 * math.pi) * 0.5 + 1
- # Convenience mapping
- EASING_FUNCTIONS = {
- "linear": linear,
- "ease_in": ease_in_quad,
- "ease_out": ease_out_quad,
- "ease_in_out": ease_in_out_quad,
- "bounce_in": ease_in_bounce,
- "bounce_out": ease_out_bounce,
- "bounce": ease_in_out_bounce,
- "elastic_in": ease_in_elastic,
- "elastic_out": ease_out_elastic,
- "elastic": ease_in_out_elastic,
- }
- def get_easing(name: str = "linear"):
- """Get easing function by name."""
- return EASING_FUNCTIONS.get(name, linear)
- def interpolate(start: float, end: float, t: float, easing: str = "linear") -> float:
- """
- Interpolate between two values with easing.
- Args:
- start: Start value
- end: End value
- t: Progress from 0.0 to 1.0
- easing: Name of easing function
- Returns:
- Interpolated value
- """
- ease_func = get_easing(easing)
- eased_t = ease_func(t)
- return start + (end - start) * eased_t
- def ease_back_in(t: float) -> float:
- """Back ease-in (slight overshoot backward before forward motion)."""
- c1 = 1.70158
- c3 = c1 + 1
- return c3 * t * t * t - c1 * t * t
- def ease_back_out(t: float) -> float:
- """Back ease-out (overshoot forward then settle back)."""
- c1 = 1.70158
- c3 = c1 + 1
- return 1 + c3 * pow(t - 1, 3) + c1 * pow(t - 1, 2)
- def ease_back_in_out(t: float) -> float:
- """Back ease-in-out (overshoot at both ends)."""
- c1 = 1.70158
- c2 = c1 * 1.525
- if t < 0.5:
- return (pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
- return (pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2
- def apply_squash_stretch(
- base_scale: tuple[float, float], intensity: float, direction: str = "vertical"
- ) -> tuple[float, float]:
- """
- Calculate squash and stretch scales for more dynamic animation.
- Args:
- base_scale: (width_scale, height_scale) base scales
- intensity: Squash/stretch intensity (0.0-1.0)
- direction: 'vertical', 'horizontal', or 'both'
- Returns:
- (width_scale, height_scale) with squash/stretch applied
- """
- width_scale, height_scale = base_scale
- if direction == "vertical":
- # Compress vertically, expand horizontally (preserve volume)
- height_scale *= 1 - intensity * 0.5
- width_scale *= 1 + intensity * 0.5
- elif direction == "horizontal":
- # Compress horizontally, expand vertically
- width_scale *= 1 - intensity * 0.5
- height_scale *= 1 + intensity * 0.5
- elif direction == "both":
- # General squash (both dimensions)
- width_scale *= 1 - intensity * 0.3
- height_scale *= 1 - intensity * 0.3
- return (width_scale, height_scale)
- def calculate_arc_motion(
- start: tuple[float, float], end: tuple[float, float], height: float, t: float
- ) -> tuple[float, float]:
- """
- Calculate position along a parabolic arc (natural motion path).
- Args:
- start: (x, y) starting position
- end: (x, y) ending position
- height: Arc height at midpoint (positive = upward)
- t: Progress (0.0-1.0)
- Returns:
- (x, y) position along arc
- """
- x1, y1 = start
- x2, y2 = end
- # Linear interpolation for x
- x = x1 + (x2 - x1) * t
- # Parabolic interpolation for y
- # y = start + progress * (end - start) + arc_offset
- # Arc offset peaks at t=0.5
- arc_offset = 4 * height * t * (1 - t)
- y = y1 + (y2 - y1) * t - arc_offset
- return (x, y)
- # Add new easing functions to the convenience mapping
- EASING_FUNCTIONS.update(
- {
- "back_in": ease_back_in,
- "back_out": ease_back_out,
- "back_in_out": ease_back_in_out,
- "anticipate": ease_back_in, # Alias
- "overshoot": ease_back_out, # Alias
- }
- )
|