use rux_layout::*;
fn boxed(style: Style, children: Vec<Node>) -> Node {
let mut n = Node::new(style);
n.children = children;
n
}
const CHAR_W: f32 = 10.0;
const LINE_H: f32 = 15.0;
fn plain_text(text: &str) -> TextContent {
TextContent {
text: text.to_string(),
font_size: 13.0,
weight: 400,
color: Rgba::new(1.0, 1.0, 1.0, 1.0),
align: TextAlign::Start,
wrap: TextWrap::Normal,
font_family: None,
letter_spacing: None,
word_spacing: None,
line_height: None,
italic: false,
underline: false,
strikethrough: false,
nowrap: false,
caret: None,
selection: None,
preedit: None,
}
}
fn wrapping_measure(tc: &TextContent, max: Option<f32>) -> (f32, f32) {
let width_of = |s: &str| s.chars().count() as f32 * CHAR_W;
let Some(max) = max else {
return (width_of(&tc.text), LINE_H); };
let mut lines = 1;
let mut widest: f32 = 0.0;
let mut line = String::new();
for word in tc.text.split_whitespace() {
let candidate = if line.is_empty() { word.to_string() } else { format!("{line} {word}") };
if width_of(&candidate) > max && !line.is_empty() {
widest = widest.max(width_of(&line));
line = word.to_string();
lines += 1;
} else {
line = candidate;
}
}
widest = widest.max(width_of(&line));
(widest, lines as f32 * LINE_H)
}
fn all_rects(root: &Node, w: f32, h: f32) -> Vec<(f32, f32, f32, f32)> {
let mut measure = |_: &rux_layout::TextContent, _: Option<f32>| (0.0, 0.0);
layout(root, w, h, &mut measure)
.paints
.iter()
.filter_map(|p| match p {
Paint::Rect(r) => Some((r.x, r.y, r.width, r.height)),
_ => None,
})
.collect()
}
#[test]
fn a_capped_box_is_tall_enough_for_the_text_it_draws() {
let text = "This card is styled entirely by the shared sheet. Nothing about it is written here.";
let card = boxed(
Style {
display: Display::Flex,
axis: Axis::Column,
padding: Sides { top: 16.0, right: 16.0, bottom: 16.0, left: 16.0 },
max_width: Some(Len::Px(260.0)),
background: Some(Background::Color(Rgba::new(0.2, 0.2, 0.3, 1.0))),
..Default::default()
},
vec![Node::text(Style::default(), plain_text(text))],
);
let screen = boxed(
Style { display: Display::Flex, axis: Axis::Row, ..Default::default() },
vec![card],
);
let mut measure = wrapping_measure;
let out = layout(&screen, 1000.0, 800.0, &mut measure);
let card_rect = out
.paints
.iter()
.find_map(|p| match p {
Paint::Rect(r) if r.background.is_some() => Some(r.clone()),
_ => None,
})
.expect("the card paints a background");
let text_rect = out
.paints
.iter()
.find_map(|p| match p {
Paint::Text(t) => Some(t.clone()),
_ => None,
})
.expect("the text paints");
let (_, drawn_height) = wrapping_measure(&text_rect.content, Some(text_rect.width));
assert!(
drawn_height > LINE_H,
"the width should force a wrap, or this proves nothing (width {})",
text_rect.width
);
assert!(
text_rect.y + drawn_height <= card_rect.y + card_rect.height + 0.5,
"text of {drawn_height}px starting at y={} spills past the card, which ends at {}",
text_rect.y,
card_rect.y + card_rect.height
);
}
#[test]
fn wrapped_grid_reserves_height_for_every_row() {
let thumb = || {
boxed(
Style {
width: Some(Len::Px(64.0)),
height: Some(Len::Px(64.0)),
shrink: 0.0,
background: Some(Background::Color(Rgba::new(0.5, 0.5, 0.5, 1.0))),
..Default::default()
},
vec![],
)
};
let grid = boxed(
Style {
display: Display::Flex,
axis: Axis::Row,
wrap: true,
gap: 8.0,
width: Some(Len::Pct(1.0)),
max_width: Some(Len::Px(520.0)),
..Default::default()
},
(0..8).map(|_| thumb()).collect(),
);
let sentinel = boxed(
Style {
width: Some(Len::Px(200.0)),
height: Some(Len::Px(20.0)),
background: Some(Background::Color(Rgba::new(1.0, 0.0, 0.0, 1.0))),
..Default::default()
},
vec![],
);
let screen = boxed(
Style {
display: Display::Flex,
axis: Axis::Column,
gap: 12.0,
..Default::default()
},
vec![grid, sentinel],
);
let rects = all_rects(&screen, 1260.0, 790.0);
let thumbs: Vec<_> = rects.iter().filter(|r| r.2 == 64.0).collect();
let sentinel = *rects.iter().find(|r| r.2 == 200.0).expect("sentinel");
assert_eq!(thumbs.len(), 8, "all eight thumbs should paint");
let thumbs_bottom = thumbs
.iter()
.map(|t| t.1 + t.3)
.fold(0.0_f32, f32::max);
assert!(
thumbs_bottom > 100.0,
"expected the thumbs to wrap onto a second row (bottom {thumbs_bottom})"
);
assert!(
sentinel.1 >= thumbs_bottom - 0.5,
"sentinel (y={}) overlaps the wrapped thumbnails (bottom {thumbs_bottom})",
sentinel.1
);
}