tiramisu/tween

Time-based value interpolation (tweening) for smooth animations.

Tweens smoothly interpolate between two values over time, with configurable easing functions. Use them for movement, fading, color changes, and any time-based animation.

Creating Tweens

import gleam/time/duration

// Float tween (opacity, score counter, etc.)
let fade = tween.tween_float(
  start: 0.0,
  end: 1.0,
  duration: duration.seconds(2),
  easing: fn(t) { t },  // Linear
)

// Vec3 tween (position, scale, etc.)
let move = tween.tween_vec3(
  start: vec3.Vec3(0.0, 0.0, 0.0),
  end: vec3.Vec3(10.0, 5.0, 0.0),
  duration: duration.seconds(1),
  easing: fn(t) { t *. t },  // Ease-in quadratic
)

// Transform tween (full movement + rotation + scale)
let transform_tween = tween.tween_transform(start, end, duration, easing)

Updating Tweens

Call update each frame with ctx.delta_time to advance the tween:

fn update(model: Model, msg: Msg, ctx: Context) {
  let updated_tween = tween.update(model.tween, ctx.delta_time)
  let current_value = tween.get_value(updated_tween)

  case tween.is_complete(updated_tween) {
    True -> // Animation finished
    False -> // Still animating
  }
}

Easing Functions

Easing functions transform linear time (0→1) into curved progress:

// Linear (constant speed)
fn(t) { t }

// Ease-in quadratic (slow start)
fn(t) { t *. t }

// Ease-out quadratic (slow end)
fn(t) { 1.0 -. { 1.0 -. t } *. { 1.0 -. t } }

// Smooth step (slow start and end)
fn(t) { t *. t *. { 3.0 -. 2.0 *. t } }

Types

Easing function that takes a normalized time value (0.0 to 1.0) and returns an eased value (typically 0.0 to 1.0).

Common easing functions can be found in animation libraries or you can write your own.

pub type Easing =
  fn(Float) -> Float

A tween that interpolates between two values over time.

Tweens are generic over any type a that can be interpolated (Float, Vec3, Transform, etc.). Use the convenience constructors like tween_float(), tween_vec3(), or tween_transform() for common types.

The duration is a Duration type, and elapsed tracks the current progress.

pub type Tween(a) {
  Tween(
    start_value: a,
    end_value: a,
    duration: duration.Duration,
    elapsed: duration.Duration,
    easing: fn(Float) -> Float,
    lerp_fn: fn(a, a, Float) -> a,
  )
}

Constructors

  • Tween(
      start_value: a,
      end_value: a,
      duration: duration.Duration,
      elapsed: duration.Duration,
      easing: fn(Float) -> Float,
      lerp_fn: fn(a, a, Float) -> a,
    )

    Arguments

    duration

    Duration of the tween

    elapsed

    Time elapsed since tween started

Values

pub fn get_value(tween: Tween(a)) -> a

Get the current interpolated value of a tween.

Applies the easing function and returns the value at the current elapsed time. Once the tween completes, this returns the end value.

Example

let my_tween = tween.tween_float(0.0, 100.0, duration.seconds(1), fn(t) { t })
  |> tween.update(duration.milliseconds(500))  // Halfway through

let value = tween.get_value(my_tween)  // => 50.0
pub fn is_complete(tween: Tween(a)) -> Bool

Check if a tween has finished playing.

Example

fn update(model: Model, msg: Msg, ctx: Context) {
  let updated_tween = tween.update(model.tween, ctx.delta_time)

  case tween.is_complete(updated_tween) {
    True -> {
      // Tween finished, trigger next animation or event
      #(Model(..model, tween: tween.reset(updated_tween)), effect.none())
    }
    False -> #(Model(..model, tween: updated_tween), effect.none())
  }
}
pub fn new(
  start: a,
  end: a,
  duration duration: duration.Duration,
  easing easing: fn(Float) -> Float,
  lerp_fn lerp_fn: fn(a, a, Float) -> a,
) -> Tween(a)

Create a new tween with a custom interpolation function.

For most use cases, prefer tween_float(), tween_vec3(), or tween_transform().

Example

// Custom tween for a color value
type Color {
  Color(r: Float, g: Float, b: Float)
}

let color_tween = tween.new(
  start: Color(1.0, 0.0, 0.0),  // Red
  end: Color(0.0, 0.0, 1.0),    // Blue
  duration: duration.seconds(1),
  easing: fn(t) { t *. t },     // Ease in quad
  lerp_fn: fn(a, b, t) {
    Color(
      r: a.r +. { b.r -. a.r } *. t,
      g: a.g +. { b.g -. a.g } *. t,
      b: a.b +. { b.b -. a.b } *. t,
    )
  },
)
pub fn reset(tween: Tween(a)) -> Tween(a)

Reset a tween back to the beginning (elapsed time = 0).

Example

// Play the tween again from the start
let reset_tween = tween.reset(completed_tween)
pub fn reverse(tween: Tween(a)) -> Tween(a)

Reverse a tween by swapping its start and end values.

The elapsed time is preserved, so the tween continues from where it was but in the opposite direction.

Example

// Create a bouncing animation
fn update(model: Model, msg: Msg, ctx: Context) {
  let updated_tween = tween.update(model.tween, ctx.delta_time)

  case tween.is_complete(updated_tween) {
    True -> {
      // Bounce back by reversing the tween
      let reversed = tween.reverse(updated_tween)
        |> tween.reset()
      Model(..model, tween: reversed)
    }
    False -> Model(..model, tween: updated_tween)
  }
}
pub fn tween_float(
  start: Float,
  end: Float,
  duration duration: duration.Duration,
  easing easing: fn(Float) -> Float,
) -> Tween(Float)

Create a tween that interpolates a Float value.

The duration is a Duration type.

Example

// Fade from 0.0 to 1.0 over 2 seconds
let fade_tween = tween.tween_float(
  start: 0.0,
  end: 1.0,
  duration: duration.seconds(2),
  easing: fn(t) { t *. t *. t },  // Ease in cubic
)
pub fn tween_quaternion(
  start: quaternion.Quaternion,
  end: quaternion.Quaternion,
  duration duration: duration.Duration,
  easing easing: fn(Float) -> Float,
) -> Tween(quaternion.Quaternion)

Create a tween that interpolates between two quaternions using spherical linear interpolation (slerp).

The duration is a Duration type.

Example

import quaternion
import vec/vec3

let start_rotation = quaternion.from_euler(vec3.Vec3(0.0, 0.0, 0.0))
let end_rotation = quaternion.from_euler(vec3.Vec3(0.0, 3.14159, 0.0))

let rotation_tween = tween.tween_quaternion(
  start: start_rotation,
  end: end_rotation,
  duration: duration.seconds(2),
  easing: fn(t) { t },  // Linear
)
pub fn tween_transform(
  start: transform.Transform,
  end: transform.Transform,
  duration duration: duration.Duration,
  easing easing: fn(Float) -> Float,
) -> Tween(transform.Transform)

Create a tween that interpolates a Transform (position, rotation, and scale).

The duration is a Duration type.

Example

import tiramisu/transform
import vec/vec3

let start_transform = transform.at(position: vec3.Vec3(0.0, 0.0, 0.0))
  |> transform.scale_uniform(1.0)

let end_transform = transform.at(position: vec3.Vec3(5.0, 0.0, 0.0))
  |> transform.scale_uniform(2.0)
  |> transform.rotate_y(3.14159)

let transform_tween = tween.tween_transform(
  start: start_transform,
  end: end_transform,
  duration: duration.seconds(1.5),
  easing: fn(t) { t *. t *. { 3.0 -. 2.0 *. t } },  // Smooth step
)
pub fn tween_vec3(
  start: vec3.Vec3(Float),
  end: vec3.Vec3(Float),
  duration duration: duration.Duration,
  easing easing: fn(Float) -> Float,
) -> Tween(vec3.Vec3(Float))

Create a tween that interpolates a Vec3 value (useful for positions).

The duration is a Duration type.

Example

import vec/vec3

// Move from origin to (10, 5, 0) over 3 seconds
let position_tween = tween.tween_vec3(
  start: vec3.Vec3(0.0, 0.0, 0.0),
  end: vec3.Vec3(10.0, 5.0, 0.0),
  duration: duration.seconds(3),
  easing: fn(t) { 1.0 -. { 1.0 -. t } *. { 1.0 -. t } },  // Ease out quad
)
pub fn update(
  tween: Tween(a),
  delta delta: duration.Duration,
) -> Tween(a)

Update a tween by advancing its elapsed time.

The delta parameter is a Duration (typically from ctx.delta_time).

Example

fn update(model: Model, msg: Msg, ctx: Context) {
  // Advance the tween by the frame delta
  let updated_tween = tween.update(model.tween, ctx.delta_time)
  Model(..model, tween: updated_tween)
}
Search Document