Ollin

EasingGallery

all thirty named Easing curves plotted in a grid, each with a dot that follows its shape, and the back, elastic, and bounce rows overshoot their cells

Run
swift run --package-path Examples Example-Motion-EasingGallery
Source
Examples/Motion/EasingGallery/Sketch.swift
import Foundation
import Ollin

/// The full easing catalog at a glance: all thirty named `Easing` curves plotted
/// in a grid, each with a dot moving along its shape as a shared phase sweeps `0→1→0`,
/// back and forth. The back, elastic, and bounce rows visibly overshoot their
/// cells — that's the point of them.
///
/// Each curve is sampled into a polyline (`x = u`, `y = curve(u)`), so the plot
/// is the curve itself; the dot sits on it at the current phase.
@main
final class EasingGallery: Sketch {
    let curves: [Easing] = [
        .easeInSine, .easeOutSine, .easeInOutSine,
        .easeInQuad, .easeOutQuad, .easeInOutQuad,
        .easeInCubic, .easeOutCubic, .easeInOutCubic,
        .easeInQuart, .easeOutQuart, .easeInOutQuart,
        .easeInQuint, .easeOutQuint, .easeInOutQuint,
        .easeInExpo, .easeOutExpo, .easeInOutExpo,
        .easeInCirc, .easeOutCirc, .easeInOutCirc,
        .easeInBack, .easeOutBack, .easeInOutBack,
        .easeInElastic, .easeOutElastic, .easeInOutElastic,
        .easeInBounce, .easeOutBounce, .easeInOutBounce,
    ]
    let names = [
        "easeInSine", "easeOutSine", "easeInOutSine",
        "easeInQuad", "easeOutQuad", "easeInOutQuad",
        "easeInCubic", "easeOutCubic", "easeInOutCubic",
        "easeInQuart", "easeOutQuart", "easeInOutQuart",
        "easeInQuint", "easeOutQuint", "easeInOutQuint",
        "easeInExpo", "easeOutExpo", "easeInOutExpo",
        "easeInCirc", "easeOutCirc", "easeInOutCirc",
        "easeInBack", "easeOutBack", "easeInOutBack",
        "easeInElastic", "easeOutElastic", "easeInOutElastic",
        "easeInBounce", "easeOutBounce", "easeInOutBounce",
    ]

    override func draw() {
        background(Color(white: 0.08))

        let cols = 6, rows = 5
        let margin = width * 0.045
        let cellW = (width - margin * 2) / Double(cols)
        let cellH = (height - margin * 2) / Double(rows)
        let inset = cellW * 0.18
        // Ping-pong 0→1→0 (triangle wave) so the dot moves along each curve forward,
        // then back, instead of snapping to the start.
        let sweep = (time * 0.45).truncatingRemainder(dividingBy: 2)    // 0...2
        let phase = sweep < 1 ? sweep : 2 - sweep                       // 0→1→0

        for (i, curve) in curves.enumerated() {
            let col = i % cols, row = i / cols
            let ox = margin + Double(col) * cellW
            let oy = margin + Double(row) * cellH
            let x0 = ox + inset
            let baseline = oy + cellH - inset          // curve == 0 sits here
            let plotW = cellW - inset * 2
            let plotH = cellH - inset * 2

            func point(_ u: Double) -> Vector2 {
                Vector2(x0 + u * plotW, baseline - curve(u) * plotH)
            }

            let tint = Colormap.turbo.color(at: Double(i) / Double(curves.count - 1))

            // Faint frame and the 0/1 guide lines.
            stroke(Color(white: 0.18)); strokeWeight(1.5 * scale); noFill()
            drawLine(x0, baseline, x0 + plotW, baseline)
            drawLine(x0, baseline - plotH, x0 + plotW, baseline - plotH)

            // The curve.
            noFill(); stroke(tint); strokeWeight(3 * scale)
            drawPolyline((0...48).map { point(Double($0) / 48) })

            // The dot on it at the current phase.
            noStroke(); fill(tint)
            drawCircle(center: point(phase), radius: 8 * scale)

            // The curve's name, tucked into the cell's top-left above the plot.
            fill(Color(white: 0.7)); textAlign(.left, .top); textSize(13 * scale)
            drawText(names[i], ox + inset * 0.5, oy + inset * 0.35)
        }
    }
}

↑↓ move ↩ open