Class: Plushie::Animation::Tween

Inherits:
Object
  • Object
show all
Defined in:
lib/plushie/animation/tween.rb,
sig/plushie/animation.rbs

Overview

SDK-side animation interpolation.

Pure functions operating on structs for manual, host-side animations. The host computes interpolated values on each animation frame tick.

For most use cases, prefer renderer-side descriptors (Transition, Spring, Sequence) which animate with zero wire traffic. Use Tween when you need host-side control over the interpolated value (e.g., driving non-visual state from an animation).

Easing functions

All easing functions take a t value in 0.0..1.0 and return a curved t value. Available easings:

  • linear: identity
  • ease_in: cubic ease in
  • ease_out: cubic ease out
  • ease_in_out: cubic ease in-out
  • ease_in_quad: quadratic ease in
  • ease_out_quad: quadratic ease out
  • ease_in_out_quad: quadratic ease in-out
  • spring: spring with overshoot

Animation struct

The Tween tracks a single animated value over time. Create one with new, start it with start, and advance it on each frame with advance.

Examples:

anim = Plushie::Animation::Tween.new(0.0, 1.0, 300, easing: :ease_out)
anim = Plushie::Animation::Tween.start(anim, timestamp)
value, anim = Plushie::Animation::Tween.advance(anim, next_timestamp)

Defined Under Namespace

Classes: State

Constant Summary collapse

EASINGS =

-- Easing functions ---------------------------------------------------

Returns:

  • (Hash[Symbol, ^(Float) -> Float])
{
  linear: ->(t) { t },

  ease_in: ->(t) { t * t * t },

  ease_out: ->(t) {
    inv = 1.0 - t
    1.0 - inv * inv * inv
  },

  ease_in_out: ->(t) {
    if t < 0.5
      4.0 * t * t * t
    else
      inv = -2.0 * t + 2.0
      1.0 - inv * inv * inv / 2.0
    end
  },

  ease_in_quad: ->(t) { t * t },

  ease_out_quad: ->(t) { 1.0 - (1.0 - t) * (1.0 - t) },

  ease_in_out_quad: ->(t) {
    if t < 0.5
      2.0 * t * t
    else
      1.0 - (-2.0 * t + 2.0)**2 / 2.0
    end
  },

  spring: ->(t) {
    if t <= 0.0
      0.0
    elsif t >= 1.0
      1.0
    else
      c4 = 2.0 * Math::PI / 3.0
      2.0**(-10.0 * t) * Math.sin((t * 10.0 - 0.75) * c4) + 1.0
    end
  }
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#auto_reverse [Boolean] swap from/to on each cycle([Boolean]) ⇒ Object (readonly)

Immutable animation state.



45
46
47
48
49
50
51
52
53
54
# File 'lib/plushie/animation/tween.rb', line 45

State = ::Data.define(:from, :to, :duration, :started_at, :easing, :value,
  :repeat, :auto_reverse) do
  include Plushie::Model::Extensions

  def initialize(from:, to:, duration:, started_at: nil, easing: :linear,
    value: nil, repeat: nil, auto_reverse: false)
    super(from: from, to: to, duration: duration, started_at: started_at,
          easing: easing, value: value.nil? ? from : value, repeat: repeat, auto_reverse: auto_reverse)
  end
end

#repeat [Integer, :forever, nil] remaining repeat count([Integer,: forever, nil]) ⇒ Object (readonly)

Immutable animation state.



45
46
47
48
49
50
51
52
53
54
# File 'lib/plushie/animation/tween.rb', line 45

State = ::Data.define(:from, :to, :duration, :started_at, :easing, :value,
  :repeat, :auto_reverse) do
  include Plushie::Model::Extensions

  def initialize(from:, to:, duration:, started_at: nil, easing: :linear,
    value: nil, repeat: nil, auto_reverse: false)
    super(from: from, to: to, duration: duration, started_at: started_at,
          easing: easing, value: value.nil? ? from : value, repeat: repeat, auto_reverse: auto_reverse)
  end
end

Class Method Details

.advance(anim, timestamp) ⇒ Array(Numeric, State), Array(Numeric, Symbol)

Advance the animation to the given frame timestamp.

Returns [current_value, updated_animation] while the animation is in progress, or [final_value, :finished] when it completes.

Parameters:

  • anim (State)
  • timestamp (Integer)

Returns:

  • (Array(Numeric, State), Array(Numeric, Symbol))


179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/plushie/animation/tween.rb', line 179

def self.advance(anim, timestamp)
  return [anim.value, anim] if anim.started_at.nil?

  elapsed = timestamp - anim.started_at
  t = clamp(elapsed.to_f / anim.duration)
  current = interpolate(anim.from, anim.to, t, anim.easing)

  if t >= 1.0
    handle_cycle_end(anim)
  else
    [current, anim.with(value: current)]
  end
end

.ease_inFloat

Parameters:

  • t (Float)

Returns:

  • (Float)


23
# File 'sig/plushie/animation.rbs', line 23

def self.ease_in: (Float t) -> Float

.ease_in_outFloat

Parameters:

  • t (Float)

Returns:

  • (Float)


25
# File 'sig/plushie/animation.rbs', line 25

def self.ease_in_out: (Float t) -> Float

.ease_in_out_quadFloat

Parameters:

  • t (Float)

Returns:

  • (Float)


28
# File 'sig/plushie/animation.rbs', line 28

def self.ease_in_out_quad: (Float t) -> Float

.ease_in_quadFloat

Parameters:

  • t (Float)

Returns:

  • (Float)


26
# File 'sig/plushie/animation.rbs', line 26

def self.ease_in_quad: (Float t) -> Float

.ease_outFloat

Parameters:

  • t (Float)

Returns:

  • (Float)


24
# File 'sig/plushie/animation.rbs', line 24

def self.ease_out: (Float t) -> Float

.ease_out_quadFloat

Parameters:

  • t (Float)

Returns:

  • (Float)


27
# File 'sig/plushie/animation.rbs', line 27

def self.ease_out_quad: (Float t) -> Float

.finished?(anim) ⇒ Boolean

Returns true if the animation has run to completion.

Parameters:

Returns:

  • (Boolean)


197
198
199
200
# File 'lib/plushie/animation/tween.rb', line 197

def self.finished?(anim)
  return false if anim.started_at.nil?
  anim.value == anim.to
end

.interpolate(from, to, t, easing = :linear) ⇒ Float

Linearly interpolate between from and to at progress t, with an optional easing function applied to t first.

Parameters:

  • from (Numeric)

    start value

  • to (Numeric)

    end value

  • t (Numeric)

    progress (0.0 to 1.0)

  • easing (Proc, Symbol) (defaults to: :linear)

    easing function or symbol name

Returns:

  • (Float)


116
117
118
119
120
121
# File 'lib/plushie/animation/tween.rb', line 116

def self.interpolate(from, to, t, easing = :linear)
  easing_fn = resolve_easing(easing)
  clamped = clamp(t)
  eased = easing_fn.call(clamped)
  from + (to - from) * eased
end

.linearFloat

Parameters:

  • t (Float)

Returns:

  • (Float)


22
# File 'sig/plushie/animation.rbs', line 22

def self.linear: (Float t) -> Float

.looping(from, to, duration_ms, **opts) ⇒ State

Create a looping tween that repeats forever with auto-reverse.

Convenience for common back-and-forth animations (pulsing, breathing, oscillating).

Examples:

anim = Tween.looping(0.0, 1.0, 500, easing: :ease_in_out)

Parameters:

  • from (Numeric)
  • to (Numeric)
  • duration_ms (Integer)
  • opts (Hash)

    additional options (e.g. easing:)

Returns:



158
159
160
# File 'lib/plushie/animation/tween.rb', line 158

def self.looping(from, to, duration_ms, **opts)
  new(from, to, duration_ms, repeat: :forever, auto_reverse: true, **opts)
end

.new(from, to, duration_ms, easing: :linear, repeat: nil, auto_reverse: false) ⇒ State

Create a new animation.

Parameters:

  • from (Numeric)

    start value

  • to (Numeric)

    end value

  • duration_ms (Integer)

    duration in milliseconds (must be > 0)

  • easing (Proc, Symbol) (defaults to: :linear)

    easing function or name (default: :linear)

  • easing: (Symbol, ^(Float) -> Float) (defaults to: :linear)
  • repeat: (Integer, Symbol, nil) (defaults to: nil)
  • auto_reverse: (Boolean) (defaults to: false)

Returns:

Raises:

  • (ArgumentError)


132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/plushie/animation/tween.rb', line 132

def self.new(from, to, duration_ms, easing: :linear, repeat: nil, auto_reverse: false)
  raise ArgumentError, "duration_ms must be positive" unless duration_ms.is_a?(Integer) && duration_ms > 0

  State.new(
    from: from,
    to: to,
    duration: duration_ms,
    easing: easing,
    repeat: repeat,
    auto_reverse: auto_reverse
  )
end

.springFloat

Parameters:

  • t (Float)

Returns:

  • (Float)


29
# File 'sig/plushie/animation.rbs', line 29

def self.spring: (Float t) -> Float

.start(anim, timestamp) ⇒ State

Start (or restart) the animation at the given frame timestamp.

Parameters:

  • anim (State)
  • timestamp (Integer)

    frame timestamp in milliseconds

Returns:



167
168
169
# File 'lib/plushie/animation/tween.rb', line 167

def self.start(anim, timestamp)
  anim.with(started_at: timestamp, value: anim.from)
end

.value(anim) ⇒ Numeric

Return the current interpolated value.

Parameters:

Returns:

  • (Numeric)


205
# File 'lib/plushie/animation/tween.rb', line 205

def self.value(anim) = anim.value