126 lines
3.4 KiB
Rust
126 lines
3.4 KiB
Rust
//! Layout consumes only owned native UI data, after the candidate VM is gone.
|
|
use anyhow::{Result, bail};
|
|
|
|
use crate::{
|
|
document::{Frame, RenderCommand},
|
|
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(),
|
|
});
|
|
Size {
|
|
width: value.chars().count() as f32 * 8.0 * *size as f32,
|
|
height: 8.0 * *size as f32,
|
|
}
|
|
}
|
|
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)
|
|
}
|