use super::layout_tests::layout_app;
use bevy::app::PropagateSet;
use bevy::prelude::*;
use bevy::ui::{ComputedNode, ComputedUiRenderTargetInfo, UiGlobalTransform, ui_layout_system};
fn text_app() -> App {
use bevy::text::{DEFAULT_FONT_DATA, Font};
let mut app = layout_app();
let mut fonts = Assets::<Font>::default();
fonts
.insert(
AssetId::default(),
Font::from_bytes(DEFAULT_FONT_DATA.to_vec()),
)
.unwrap();
app.insert_resource(fonts);
app.init_resource::<bevy::text::LayoutCx>();
app.add_systems(
PostUpdate,
(
bevy::text::load_font_assets_into_font_collection,
bevy::ui::widget::measure_text_system,
)
.chain()
.after(PropagateSet::<ComputedUiRenderTargetInfo>::default())
.before(ui_layout_system),
);
app
}
const PROSE: &str = "the quick brown fox jumps over the lazy dog and keeps \
running past the fence until the words run out";
fn size(app: &App, e: Entity) -> Vec2 {
app.world().entity(e).get::<ComputedNode>().unwrap().size
}
fn root(app: &mut App, flex_direction: FlexDirection) -> Entity {
app.world_mut()
.spawn(Node {
width: Val::Vw(100.0),
height: Val::Vh(100.0),
flex_direction,
align_items: AlignItems::FlexStart,
..default()
})
.id()
}
fn wrapped_height(width: f32) -> f32 {
let mut app = text_app();
let root = root(&mut app, FlexDirection::Column);
let text = app
.world_mut()
.spawn((
Text::new(PROSE),
Node {
width: Val::Px(width),
..default()
},
ChildOf(root),
))
.id();
app.update();
app.update();
let size = size(&app, text);
assert!(
size.y > 60.0,
"the reference wraps onto several lines: {size}"
);
size.y
}
#[test]
fn text_with_max_width_in_a_row_reserves_its_wrapped_height() {
let expected = wrapped_height(200.0);
for on_text in [true, false] {
let mut app = text_app();
let root = root(&mut app, FlexDirection::Row);
let clamp = Node {
max_width: Val::Px(200.0),
..default()
};
let (card, text) = if on_text {
let text = app
.world_mut()
.spawn((Text::new(PROSE), clamp, ChildOf(root)))
.id();
(text, text)
} else {
let card = app.world_mut().spawn((clamp, ChildOf(root))).id();
let text = app
.world_mut()
.spawn((Text::new(PROSE), Node::default(), ChildOf(card)))
.id();
(card, text)
};
app.update();
app.update();
assert_eq!(size(&app, text), Vec2::new(200.0, expected), "{on_text}");
assert_eq!(size(&app, card), Vec2::new(200.0, expected), "{on_text}");
}
}
#[test]
fn text_with_a_width_in_a_centered_row_is_measured_at_that_width() {
let expected = wrapped_height(400.0);
let mut app = text_app();
let root = root(&mut app, FlexDirection::Column);
let row = Node {
align_items: AlignItems::Center,
..default()
};
let row = app.world_mut().spawn((row, ChildOf(root))).id();
let width = Node {
width: Val::Px(400.0),
..default()
};
let text = app
.world_mut()
.spawn((Text::new(PROSE), width, ChildOf(row)))
.id();
app.update();
app.update();
assert_eq!(size(&app, text), Vec2::new(400.0, expected));
assert_eq!(size(&app, row).y, expected);
}
fn clamped_page(
app: &mut App,
stage_direction: FlexDirection,
container: Node,
) -> (Entity, Entity, Entity) {
let column = || Node {
flex_direction: FlexDirection::Column,
..default()
};
let root = app
.world_mut()
.spawn(Node {
width: Val::Vw(100.0),
height: Val::Vh(100.0),
..column()
})
.id();
let card = app.world_mut().spawn((column(), ChildOf(root))).id();
let stage = app
.world_mut()
.spawn((
Node {
flex_direction: stage_direction,
..default()
},
ChildOf(card),
))
.id();
let clamped = app
.world_mut()
.spawn((
Node {
width: Val::Percent(100.0),
max_width: Val::Px(400.0),
..container
},
ChildOf(stage),
))
.id();
let below = app
.world_mut()
.spawn((
Node {
height: Val::Px(30.0),
..default()
},
ChildOf(card),
))
.id();
(stage, clamped, below)
}
fn top(app: &App, e: Entity) -> f32 {
let center = app.world().entity(e).get::<UiGlobalTransform>().unwrap();
center.translation.y - size(app, e).y / 2.0
}
#[test]
fn text_under_percent_width_and_max_width_reserves_its_wrapped_height() {
let expected = wrapped_height(400.0);
for direction in [FlexDirection::Column, FlexDirection::Row] {
let mut app = text_app();
let column = Node {
flex_direction: FlexDirection::Column,
..default()
};
let (stage, clamped, below) = clamped_page(&mut app, direction, column);
let text = app
.world_mut()
.spawn((Text::new(PROSE), Node::default(), ChildOf(clamped)))
.id();
app.update();
app.update();
assert_eq!(
size(&app, text),
Vec2::new(400.0, expected),
"{direction:?}"
);
assert_eq!(size(&app, stage).y, expected, "{direction:?}");
assert_eq!(
top(&app, below),
top(&app, stage) + expected,
"{direction:?}"
);
}
}
#[test]
fn wrap_row_under_percent_width_and_max_width_reserves_its_lines() {
for direction in [FlexDirection::Column, FlexDirection::Row] {
let mut app = layout_app();
let wrap = Node {
flex_wrap: FlexWrap::Wrap,
..default()
};
let (stage, grid, below) = clamped_page(&mut app, direction, wrap);
for _ in 0..12 {
app.world_mut().spawn((
Node {
width: Val::Px(90.0),
height: Val::Px(40.0),
flex_shrink: 0.0,
..default()
},
ChildOf(grid),
));
}
app.update();
assert_eq!(size(&app, grid), Vec2::new(400.0, 120.0), "{direction:?}");
assert_eq!(size(&app, stage).y, 120.0, "{direction:?}");
assert_eq!(top(&app, below), top(&app, stage) + 120.0, "{direction:?}");
}
}
#[test]
#[ignore = "taffy 0.14 still wraps a content-sized row on float error"]
fn content_sized_wrap_row_keeps_one_line_at_a_fractional_scale() {
let mut app = layout_app();
app.insert_resource(bevy::ui::UiScale(1.166_666_6));
let root = root(&mut app, FlexDirection::Column);
let row = Node {
flex_wrap: FlexWrap::Wrap,
column_gap: Val::Px(2.0),
..default()
};
let row = app.world_mut().spawn((row, ChildOf(root))).id();
let [first, second] = [(); 2].map(|()| {
let item = Node {
width: Val::Px(21.0),
height: Val::Px(20.0),
flex_shrink: 0.0,
..default()
};
app.world_mut().spawn((item, ChildOf(row))).id()
});
app.update();
assert_eq!(
top(&app, second),
top(&app, first),
"the second item wrapped"
);
}