Files
endorphin-tessera-gui/src/layout.rs
T
codex-bot f0fae1792f
Verify Tessera GUI / verify (push) Successful in 13m28s
feat: shape and antialias Tessera text with system fonts
2026-10-20 14:25:28 +08:00

128 lines
3.5 KiB
Rust

//! Layout consumes only owned native UI data, after the candidate VM is gone.
use anyhow::{Result, bail};
use crate::{
document::{Frame, RenderCommand},
text::{self, TextStyle},
ui_bridge::{MAX_DIMENSION, UiKind, UiNode},
};
const MAX_LAYOUT_VISITS: usize = 10_000;
#[derive(Default, Clone, Copy)]
struct Size {
width: f32,
height: f32,
}
pub fn frame(root: &UiNode) -> Result<Frame> {
let mut frame = Frame::default();
let mut visits = 0;
layout(root, 0.0, 0.0, &mut frame.commands, &mut visits)?;
Ok(frame)
}
fn bounded(value: f32) -> Result<f32> {
if !value.is_finite() || !(0.0..=MAX_DIMENSION as f32).contains(&value) {
bail!("UI layout exceeds the 1000000 coordinate/size limit");
}
Ok(value)
}
fn layout(
node: &UiNode,
x: f32,
y: f32,
commands: &mut Vec<RenderCommand>,
visits: &mut usize,
) -> Result<Size> {
*visits += 1;
if *visits > MAX_LAYOUT_VISITS {
bail!("expanded UI layout exceeds 10000 nodes");
}
bounded(x)?;
bounded(y)?;
let size = match &node.kind {
UiKind::Text { value, size, color } => {
commands.push(RenderCommand::Text {
x,
y,
value: value.clone(),
size: *size,
color: color.clone(),
});
let measured = text::measure(value, TextStyle::ui(*size));
Size {
width: measured.width,
height: measured.height,
}
}
UiKind::Rect {
width,
height,
radius,
color,
} => {
commands.push(RenderCommand::RoundedRect {
x,
y,
width: *width,
height: *height,
radius: *radius,
color: color.clone(),
});
Size {
width: *width,
height: *height,
}
}
UiKind::Spacer(size) => Size {
width: *size,
height: *size,
},
UiKind::Column {
padding,
gap,
children,
}
| UiKind::Row {
padding,
gap,
children,
} => {
let row = matches!(node.kind, UiKind::Row { .. });
let mut cursor = *padding;
let mut cross: f32 = 0.0;
for (index, child) in children.iter().enumerate() {
if index > 0 {
cursor = bounded(cursor + gap)?;
}
let (child_x, child_y) = if row {
(x + cursor, y + padding)
} else {
(x + padding, y + cursor)
};
let size = layout(child, child_x, child_y, commands, visits)?;
cursor = bounded(cursor + if row { size.width } else { size.height })?;
cross = cross.max(if row { size.height } else { size.width });
}
let along = bounded(cursor + padding)?;
let across = bounded(cross + padding * 2.0)?;
if row {
Size {
width: along,
height: across,
}
} else {
Size {
width: across,
height: along,
}
}
}
};
bounded(x + size.width)?;
bounded(y + size.height)?;
Ok(size)
}