Camera
A camera zooms, pans and turns the view of everything added to it. Show a whole diagram, move in on one detail, follow something as it travels — while titles stay put.
Loading live preview…
Source of camera — edit in the playground
//! 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())
}World and screen
Add a Camera to the view and build the world inside it. Everything you add to the camera
is seen through it; everything you add to the view itself stays on screen, which is where titles, captions
and labels belong.
let cam = view.add(Camera::new());
let diagram = cam.add(Group::new()); // the world: seen through the camera
diagram.add(Rect::new().width(360.0).height(160.0));
view.add(Txt::new().text("Title").h2().to_edge(Edge::Top)); // on screen Moving the camera
A camera has four signals, animated like any other property: x and y are the point it looks at, zoom is how close it is (2 = twice as big),
and rotation turns the view (in degrees, clockwise).
cam.fit(diagram, 120.0, 0.0).await; // all of it in the frame, 120 px to spare
cam.center_on(api, 1.0).await; // bring one node to the middle
cam.zoom().to(2.5, 1.0).await; // move in
cam.rotation().to(15.0, 1.0).await; // tilt
cam.reset(1.2).await; // back to x = y = 0, zoom 1, no rotation fit measures the node when it is called, so a layout that has grown is framed at its
current size. A duration of 0.0 jumps there at once — handy for the first frame. To
move and zoom together, run both in all:
all((cam.center_on(api, 1.2), cam.zoom().to(1.6, 1.2))).await; Following a node
Bind the camera's position to a node and it follows it every frame. The next animation of the
camera's x or y releases it.
let (px, py) = (packet.x(), packet.y());
cam.x().bind(move || px.get());
cam.y().bind(move || py.get());
packet.x().to(900.0, 2.0).await; // the camera rides along
cam.fit(diagram, 120.0, 1.4).await; // and lets go here Sharp at any zoom
Nothing is stretched: every frame is drawn again at its final scale. Text and outlines stay crisp at ×10, and blurs, shadows and glows grow with the zoom instead of turning to mush — on the GPU and the CPU renderer alike.
Checking
oxframe check does not report nodes inside a camera as out of frame:
when you zoom in on a detail, the rest of the world is supposed to be outside. Text overlap and contrast
are still checked, and nodes added to the view are checked as usual.