A camera tour of a system diagram

explainer

The brief. Tour a system diagram: show the whole path, zoom onto one step, follow a request, then pull back. Keep the title fixed on screen.

Open in the playground Source on GitHub

What it uses

The code

examples/src/camera.rs in oxframe-sdk, for oxframe 0.20.2. In the playground it compiles in the browser and you can change it.

//! A camera tour of a system diagram: see all of it, zoom onto one step,
//! follow a request as it travels, then back out. The title stays put: it
//! is added to the view, not to the camera.

use oxframe::prelude::*;

const STEPS: [&str; 6] = ["Browser", "CDN", "API", "Queue", "Worker", "Database"];

/// Where step `i` sits in the world the camera looks at (wider than a frame).
fn spot(i: usize) -> (f64, f64) {
    let x = -1500.0 + i as f64 * 600.0;
    let y = if i.is_multiple_of(2) { -140.0 } else { 140.0 };
    (x, y)
}

#[oxframe::scene]
pub async fn camera(view: View) {
    let cam = view.add(Camera::new().name("camera"));
    let world = cam.add(Group::new().name("diagram"));
    for i in 1..STEPS.len() {
        let (a, b) = (spot(i - 1), spot(i));
        world.add(
            Line::new()
                .points([(a.0 + 180.0, a.1), (b.0 - 180.0, b.1)])
                .stroke(Muted)
                .line_width(6.0)
                .end_arrow(true),
        );
    }
    let boxes: Vec<Rect> = STEPS
        .iter()
        .enumerate()
        .map(|(i, name)| {
            let (x, y) = spot(i);
            world.add(
                Rect::new()
                    .width(360.0)
                    .height(160.0)
                    .radius(Md)
                    .fill(Surface)
                    .stroke(Surface)
                    .line_width(6.0)
                    .position((x, y))
                    .name(*name)
                    .child(Txt::new().text(*name).h2()),
            )
        })
        .collect();
    let title = view.add(
        Txt::new()
            .text("One request, end to end")
            .h2()
            .to_edge(Edge::Top)
            .name("title"),
    );

    // The whole diagram, then the steps appear one by one.
    cam.fit(world, 140.0, 0.0).await;
    title.fade_in().await;
    stagger(0.12, &boxes, |b| b.pop_in()).await;
    wait_for(0.4).await;

    // A closer look at the API.
    all((cam.center_on(boxes[2], 1.2), cam.zoom().to(1.6, 1.2))).await;
    wait_for(0.6).await;

    // A request travels on; the camera follows it.
    let (x0, y0) = spot(2);
    let packet = world.add(
        Circle::new()
            .size(44.0)
            .fill(Accent)
            .position((x0, y0 + 110.0)),
    );
    packet.pop_in().await;
    let (px, py) = (packet.x(), packet.y());
    cam.x().bind(move || px.get());
    cam.y().bind(move || py.get() - 110.0);
    // Each step it reaches lights up (an outline: text on a solid accent
    // fill would be hard to read).
    for (i, step) in boxes.iter().enumerate().skip(3) {
        let (x, y) = spot(i);
        all((packet.x().to(x, 1.0), packet.y().to(y + 110.0, 1.0))).await;
        step.stroke().to(Accent, 0.3).await;
    }

    // Back out to the whole picture.
    cam.fit(world, 140.0, 1.4).await;
    wait_for(1.0).await;
}

pub fn project() -> Project {
    Project::new("camera").scene(camera())
}

More examples