Repository navigation
Description
On #223 you mentioned adding optional flex/grid layout later and kept layout free for it. I'd like to help build it and wanted to agree the shape first.
Options I looked at:
- No layout system
- Small helpers
- Layout attributes in
svgr! - Taffy behind a
layoutfeature (my current recommendation)
1. No layout system
Positions stay absolute. The single-scene template already adapts to formats with UNIT and proportional positions, and a fit loop picks font sizes. What it can't express is flow: blocks that push each other down as text wraps, boxes that hug their text, and a row that becomes a column in portrait. SpeakerScene does this by hand: the abstract starts at TITLE_Y + occupied_height(), the corner bracket hangs off it with - 110, and the name pill is sized with estimate_text_width + 32 and centred at 1534 while the avatar sits at 1540. Every new block or format means redoing those numbers.
2. Small helpers
Something like stack and a bounding box on WrappedTextStructure. Small and dependency-free, and enough for SpeakerScene, but no wrapping, grow/shrink or grid. #80 tried stacks before text measurement existed and got stuck on child sizes, which text_width (and #256) now provide.
3. Layout attributes in svgr!
The nicest to write, but the macro can't see fonts or {} children, so it would be a large change. It could be added later on top of option 4.
4. Taffy behind a layout feature
Taffy does flex, grid and block in pure Rust, is MIT licensed, has no required dependencies, and is used by Bevy, Servo and Zed. Text is measured with fframes' own text measurement.
I built a working example on my fork using Taffy directly. One layout tree gives a lower third in 16:9 and a centred card in 9:16, and a long name wraps and grows the card:
A small wrapper in fframes could look like this:
// in render_frame: built and solved on first use, cached on the scene
let solved = self.layout.get_or_solve(&mut frame, ctx, (1920.0, 1080.0), |t| {
let title = t.text(self.talk_title, title_font);
let name = t.text(self.speaker_name, name_font);
t.column(&[title, name], Style { gap: 18.0, ..Default::default() })
});
solved.rect(name) // (x, y, w, h), already Animatable
solved.text_svgr(title) // wrapped text at its solved position- Layout is solved once, not per frame, and cached like
Captions' layout. - Motion doesn't change: layout gives resting positions, and you animate a wrapper
<g transform>withframe.animate. - It works with today's
text_width; fix(text): measure shaped text width instead of summing rounded advances #256 makes the widths exact. - The PR would add the example to e2e in both aspect ratios, with no unit tests.
Questions
- Does a runtime Taffy API match what you had in mind, or would you rather start from
svgr!attributes? - How should a scene know its design size: an explicit size argument, a generic
V: Video, or a field onctx?

Activity