Ollin

ViewBoxes

one piece under six seeds on one canvas: each withViewBox clips and remaps a cell, so the block inside is written as if it owned the window, with its own background and its own mouse

Run
swift run --package-path Examples Example-Rendering-ViewBoxes
Source
Examples/Rendering/ViewBoxes/Sketch.swift
import Ollin

/// Six versions of one piece, on one canvas, at the same time.
///
/// `withViewBox` runs a block inside a rectangle as if that rectangle were the
/// whole window: drawing is clipped to it and the coordinates are remapped, so
/// `petals()` below is written as though it owned the canvas and never learns
/// otherwise. Each box seeds itself differently, so the sheet is a contact sheet
/// of the same piece under six seeds.
///
/// Two things are remapped for the block so that code needs no changes. Its
/// `background` fills its own box rather than the frame, which belongs to all
/// six at once. And the mouse arrives in the box's own coordinates, which is why
/// the pale ring follows the pointer inside whichever box it is over and sits
/// out at the edge of the others.
///
/// Labels are drawn outside the boxes, in canvas coordinates, so they keep one
/// size while the pieces are scaled down.
///
/// See Docs/Core/Canvas.md.
@main
final class ViewBoxes: Sketch {
    static let columns = 3, rows = 2
    static let boxSize = 300.0, gap = 40.0, top = 300.0, rowGap = 100.0

    let paper = Color(hex: 0xF2EFE8)
    let ink = Color(hex: 0x22242C)

    static let palettes: [(Color, Color)] = [
        (Color(hex: 0xF6E7D8), Color(hex: 0xC1553C)),
        (Color(hex: 0xE7EDF2), Color(hex: 0x2F5D7C)),
        (Color(hex: 0xEDE9F2), Color(hex: 0x59417E)),
        (Color(hex: 0xF3EFE0), Color(hex: 0x6B7A3A)),
        (Color(hex: 0xF7E9E9), Color(hex: 0xA33B52)),
        (Color(hex: 0xE6EEEA), Color(hex: 0x2C6B5B)),
    ]

    override func setup() {
        textFont(OutlineFont.system)
    }

    override func draw() {
        background(paper)
        drawTitle()
        for seed in 0..<(ViewBoxes.columns * ViewBoxes.rows) {
            let frame = box(seed)
            withViewBox(frame) { petals(seed: seed) }
            label(seed, under: frame)
        }
    }

    func box(_ index: Int) -> Rectangle {
        let column = index % ViewBoxes.columns, row = index / ViewBoxes.columns
        let spanned = Double(ViewBoxes.columns) * ViewBoxes.boxSize
            + Double(ViewBoxes.columns - 1) * ViewBoxes.gap
        return Rectangle(x: (1080 - spanned) / 2 + Double(column) * (ViewBoxes.boxSize + ViewBoxes.gap),
                         y: ViewBoxes.top + Double(row) * (ViewBoxes.boxSize + ViewBoxes.rowGap),
                         width: ViewBoxes.boxSize, height: ViewBoxes.boxSize)
    }

    /// Drawn in canvas coordinates, outside the box, so it keeps its size.
    func label(_ seed: Int, under frame: Rectangle) {
        noStroke()
        fill(ink.withAlpha(0.5))
        textSize(22)
        textAlign(.center, .top)
        drawText("seed \(seed)", frame.center.x, frame.y + frame.height + 20)
    }

    func drawTitle() {
        noStroke()
        fill(ink)
        textSize(44)
        textAlign(.center, .top)
        drawText("One piece, six seeds, one window", 540, 130)
        fill(ink.withAlpha(0.45))
        textSize(22)
        drawText("each box is drawn as though it owned the canvas", 540, 192)
    }

    // MARK: The piece, written as though it owned the window

    /// A ring of petals over a wash. Nothing in here knows it is in a box: it
    /// reads `width`, `height` and `mouseX` and draws at canvas scale.
    func petals(seed: Int) {
        randomSeed(seed)
        noiseSeed(seed)
        let (wash, mark) = ViewBoxes.palettes[seed % ViewBoxes.palettes.count]
        background(wash)

        let count = 5 + seed % 6
        let turn = sway(over: 14, in: 0...(.tau), phase: Double(seed) / 12)

        withState {
            translate(center)
            rotate(turn)
            for i in 0..<count {
                withState {
                    rotate(Double(i) / Double(count) * .tau)
                    petal(mark, index: i)
                }
            }
        }

        // A ring on the pointer, in this box's own coordinates.
        noFill()
        stroke(mark.withAlpha(0.45))
        strokeWeight(6)
        drawCircle(mouseX, mouseY, 70)
    }

    func petal(_ mark: Color, index: Int) {
        let length = height * (0.22 + random(0.16))
        let waist = width * (0.05 + random(0.05))
        noStroke()
        fill(mark.withAlpha(0.55))
        drawShape { path in
            path.move(to: Vector2(0, 0))
            path.cubicCurve(to: Vector2(0, -length),
                            control1: Vector2(waist, -length * 0.25),
                            control2: Vector2(waist * 0.6, -length * 0.85))
            path.cubicCurve(to: Vector2(0, 0),
                            control1: Vector2(-waist * 0.6, -length * 0.85),
                            control2: Vector2(-waist, -length * 0.25))
            path.close()
        }
        fill(mark)
        drawCircle(0, -length, waist * 0.35)
    }
}

↑↓ move ↩ open